/* ============================================================================
   LDX — marketing site, DRAFT
   ----------------------------------------------------------------------------
   Design intent: this is a PRECISION ENGINEERING firm, not a SaaS product.
   Every choice below is aimed away from the templated-landing-page look:
     · hairline rules and a visible grid instead of soft cards and blob shapes
     · monospace for technical metadata (section numbers, labels, specs) — the
       typographic register of drawings and datasheets
     · ONE restrained industrial accent, used sparingly, never as a gradient
     · a line-work SVG schematic instead of stock hardware photography, which
       would either be fake or someone else's robot
   No web fonts: a refined system stack renders instantly, needs no CDN, and
   leaks nothing. Swap in a licensed face when Daniel picks one.

   🔴 CONTRAST IS COMPUTED, NOT EYEBALLED (WCAG 2.1, measured before shipping):
        ink    on paper  16.78:1     paper on deep    17.35:1
        steel  on paper   5.84:1     muted on deep     4.93:1
        accent on paper   5.36:1     accent-lift on deep 5.66:1
   The first muted-on-deep candidate measured 2.97:1 — under even the 3:1
   large-text floor — so it was replaced rather than shipped. Dark sections use
   --muted / --accent-lift, which clear 4.5:1 and are safe for body text.
   ========================================================================= */

:root {
  --paper:  #F6F5F2;
  --ink:    #12151A;
  --steel:  #5A6065;   /* body text on paper  — 5.84:1 */
  --hair:   #D9D6D0;   /* hairline rules, non-text */
  /* 🔴 ACCENT IS A THREE-STEP SYSTEM, not one colour, because "the Signal blue"
     cannot legally do every job. MEASURED: the real app blue #2C6BED is 4.35:1 on
     this paper — it FAILS the 4.5:1 floor for body/small text. So the hue family
     splits by ROLE, each step measured for the job it does:
       --accent        text-bearing (links, section numbers, chips)  6.10:1
       --accent-brand  THE Signal app blue: wordmark, schematic, rules  4.35:1
                       (non-text and large-bold floor is 3:1 — clears with margin)
       --accent-lift   accent on the deep section                     5.06:1
     Art predicted a saturated blue would not hold the 5.36:1 the orange had; it
     does not, and this is the shape that keeps the brand colour where it is
     legible while never putting small text on an under-floor value. */
  --accent:       #1B4FD8;  /* small text on paper — 6.10:1 */
  --accent-brand: #2C6BED;  /* Signal app blue, graphics/large — 4.35:1 on paper */
  --deep:   #0E1116;
  --muted:  #7C838A;   /* body text on deep  — 4.93:1 */
  --accent-lift: #5B79FF; /* accent on deep — 5.06:1 */

  --measure: 62ch;
  --gut: clamp(20px, 5vw, 64px);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* 🔴 Impossible to mistake this file for final. Art's requirement is that
   nothing fake can ship by accident; a banner that cannot be missed is the
   cheapest guarantee, and it is removed in one deletion when copy is real. */

/* Every substance-bearing line carries this. Dotted underline = provisional;
   the chip names it outright so a screenshot is self-documenting. */

/* --- skeleton ------------------------------------------------------------ */
.skip {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--paper); padding: 10px 14px; z-index: 200;
}
.skip:focus { left: var(--gut); top: 44px; }

:where(a, button):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.wrap { max-width: 1180px; margin: 0 auto; padding: 0 var(--gut); }

header.site {
  border-bottom: 1px solid var(--hair);
  padding: 22px 0;
}
.bar { display: flex; align-items: baseline; justify-content: space-between; gap: 24px; }

/* Wordmark: neutral "LDX" only — the legal entity is Daniel's to supply, so
   nothing here invents one. */
.wordmark {
  font-family: var(--mono); font-size: 20px; font-weight: 600;
  /* Tracking 0.22em -> 0.06em (Daniel, 08-18: letters too far apart). 0.06em is
     the SAME tracking nav.site links use, so the wordmark no longer carries a
     spacing value of its own; its presence comes from size + weight. Not 0:
     monospace uppercase at 0 reads cramped and the X stops reading as a mark.
     Note for any future re-alignment — letter-spacing also adds a TRAILING
     advance after the last glyph, so the X sits that much left of flush
     (4.4px at 0.22em, 1.2px now). Immaterial under .bar's space-between;
     it would need a negative margin if the mark were ever centred. */
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink);
  text-decoration: none;
}
.wordmark span { color: var(--accent-brand); } /* the brand mark carries THE Signal blue */

nav.site { display: flex; gap: 26px; flex-wrap: wrap; }
nav.site a {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--steel); text-decoration: none;
  padding-bottom: 2px; border-bottom: 1px solid transparent;
}
nav.site a:hover { color: var(--ink); border-bottom-color: var(--accent); }

/* --- section furniture --------------------------------------------------- */
section { border-bottom: 1px solid var(--hair); padding: clamp(56px, 9vw, 112px) 0; }
.eyebrow {
  display: flex; align-items: center; gap: 14px;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--steel); margin: 0 0 28px;
}
.eyebrow::after { content: ""; flex: 1; height: 1px; background: var(--hair); }
.eyebrow b { color: var(--accent); font-weight: 500; }

h1, h2, h3 { font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; margin: 0; }
h1 { font-size: clamp(38px, 6.2vw, 68px); }
h2 { font-size: clamp(28px, 3.6vw, 40px); }
h3 { font-size: 18px; letter-spacing: -0.01em; }
p  { max-width: var(--measure); }
.lede { font-size: clamp(18px, 2.1vw, 22px); color: var(--steel); line-height: 1.5; }

/* --- hero ---------------------------------------------------------------- */
.hero { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(32px, 6vw, 72px); align-items: center; }
@media (max-width: 900px) { .hero { grid-template-columns: 1fr; } }
.hero-spec {
  margin: 32px 0 0; padding: 0; list-style: none;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1px;
  background: var(--hair); border: 1px solid var(--hair);
}
.hero-spec li { background: var(--paper); padding: 14px 16px; }
.hero-spec dt, .hero-spec .k {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--steel); display: block; margin-bottom: 4px;
}
.hero-spec .v { font-family: var(--mono); font-size: 15px; color: var(--ink); }

.schematic { width: 100%; height: auto; display: block; }
.schematic .rule { stroke: var(--hair); }
.schematic .line { stroke: var(--ink); }
.schematic .hot  { stroke: var(--accent-brand); } /* graphic: 3:1 floor, 4.35:1 actual */
.schematic text  { font-family: var(--mono); font-size: 9px; fill: var(--steel); letter-spacing: 0.08em; }

/* --- media: illustrations + photo slots ----------------------------------- */
/* TWO IDIOMS, on purpose, each carrying what it is actually good for.
   .media--art  = line-work in the schematic language. It is OURS, in-palette by
                  construction, and needs no licence. It carries the ABSTRACT
                  sections (capabilities, about) where a photo would be decoration.
   .media--photo = licensed stock. It carries SELECTED WORK — the one place a
                  photograph adds evidence rather than mood, and also the place the
                  "this is not LDX's machine" problem bites hardest.
   🔴 NO PHOTO FILE IS COMMITTED. Every photo slot renders as a PENDING PLATE: a
   drawing-sheet void, deliberately legible as unfinished. A draft that shows an
   honest empty frame is worth more at review than one showing a borrowed robot. */
.media { margin: 0; border: 1px solid var(--hair); background: var(--paper); isolation: isolate; }
.media__frame { position: relative; overflow: hidden; display: block; }
.media__frame > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.media__frame > svg { display: block; width: 100%; height: auto; }
.media__cap {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--steel);
  padding: 9px 12px; border-top: 1px solid var(--hair);
  display: flex; gap: 12px; justify-content: space-between; align-items: baseline;
  flex-wrap: wrap;
}
.ratio-3x2 { aspect-ratio: 3 / 2; }
.ratio-21x9 { aspect-ratio: 21 / 9; }
.ratio-4x3 { aspect-ratio: 4 / 3; }

/* The pending plate. Diagonals are the drawing convention for a deliberately
   void field — this reads as "nothing goes here yet", not as a broken <img>. */
.media__frame.plate { display: grid; place-items: center; }
.plate::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to top right,    transparent calc(50% - 0.5px), var(--hair) 50%, transparent calc(50% + 0.5px)),
    linear-gradient(to bottom right, transparent calc(50% - 0.5px), var(--hair) 50%, transparent calc(50% + 0.5px));
}
.plate span {
  position: relative; font-family: var(--mono); font-size: 10px; line-height: 1.5;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--steel);
  text-align: center; background: var(--paper); padding: 9px 13px;
  border: 1px solid var(--hair); max-width: 80%;
}

.media-band { margin-top: clamp(30px, 4vw, 46px); }
.media-3up { display: grid; grid-template-columns: repeat(auto-fit, minmax(228px, 1fr)); gap: clamp(14px, 2vw, 22px); }
/* In the 3-up, the caption STACKS rather than sitting on one line. Not cosmetic:
   side-by-side, a caption long enough to wrap makes one card taller and the row
   goes ragged. Real project titles vary in length, so shortening the copy would
   only move the fragility — a fixed two-line caption is the same height whatever
   the title is. */
.media-3up .media__cap { flex-direction: column; align-items: flex-start; gap: 6px; }

/* Deep-section variants: the line-art inverts to paper-on-ink and the accent
   steps up to --accent-lift, which is the value measured for the deep ground. */
.deep .media { border-color: #2A2F36; background: transparent; }
.deep .media__cap { border-top-color: #2A2F36; color: var(--muted); }
.deep .plate::before { background:
    linear-gradient(to top right,    transparent calc(50% - 0.5px), #2A2F36 50%, transparent calc(50% + 0.5px)),
    linear-gradient(to bottom right, transparent calc(50% - 0.5px), #2A2F36 50%, transparent calc(50% + 0.5px)); }
.deep .plate span { background: var(--deep); border-color: #2A2F36; color: var(--muted); }
.deep .schematic .rule { stroke: #2A2F36; }
.deep .schematic .line { stroke: var(--paper); }
.deep .schematic .hot  { stroke: var(--accent-lift); }
.deep .schematic text  { fill: var(--muted); }

/* DUOTONE TREATMENT — opt-in via `.tone-duo` on <html>.
   Greyscale the photograph, then tint through a blend layer so hue+saturation
   come from the brand ramp while luminance stays the image's. Photos ONLY;
   .media--art is already in-palette and must never be filtered.
   🔴 THE TRADEOFF THIS MAKES VISIBLE: it works by destroying the photograph's own
   colour — which on industrial-robot stock is exactly the yellow Daniel said he
   liked. Cohering with this design system and keeping that yellow are in direct
   tension; that is Daniel's call, not the stylesheet's. Default is OFF (full
   colour) because full colour is what was actually asked for.
   🔴 UNVERIFIED AGAINST A PHOTOGRAPH. No licensed image exists in this repo, so
   what is proven is that the filter and blend layer APPLY — not that they look
   right over real hardware. Judge it on the first real image. */
.tone-duo .media--photo .media__frame > img { filter: grayscale(1) contrast(1.05); }
.tone-duo .media--photo .media__frame::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(160deg, var(--ink), var(--accent-brand));
  mix-blend-mode: color;
}

/* --- capabilities -------------------------------------------------------- */
/* 🔴 The 320px floor is load-bearing arithmetic, not taste. The container is 1140px
   usable, so a 260px floor auto-fits FOUR columns — and six cells across four
   columns leaves TWO empty grid areas, which show the container's hairline
   background as grey voids. At a 320px floor the column count can only ever be
   1, 2 or 3, and six divides evenly by all three, so the void is impossible at
   every width rather than merely absent at the one we looked at. (Also what the
   name `grid3` always claimed.) Adding a 7th capability would reintroduce it —
   six is what divides. */
.grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1px; background: var(--hair); border: 1px solid var(--hair); }
.cell { background: var(--paper); padding: clamp(22px, 3vw, 34px); }
.cell .n { font-family: var(--mono); font-size: 11px; color: var(--accent); letter-spacing: 0.1em; display: block; margin-bottom: 14px; }
.cell h3 { margin-bottom: 10px; }
.cell p { color: var(--steel); font-size: 15.5px; margin: 0; }

/* --- about (deep) -------------------------------------------------------- */
.deep { background: var(--deep); color: var(--paper); border-bottom-color: #1B2029; }
.deep .eyebrow { color: var(--muted); }
.deep .eyebrow::after { background: #262C36; }
.deep .eyebrow b { color: var(--accent-lift); }
.deep p { color: var(--muted); }
.deep .deep .two { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 5vw, 64px); }
/* 07 — the featured capability. Photo and text sit as equals; the text cell
   drops the grid3 hairline treatment so it reads as prose beside a plate. */
.ai-feature { margin-top: clamp(28px, 4vw, 44px); align-items: center; }
.cell--flat { border: 0; padding: 0; background: none; }
@media (max-width: 820px) { .two { grid-template-columns: 1fr; } }

/* --- selected work ------------------------------------------------------- */
.work { list-style: none; margin: 0; padding: 0; }
.work li { border-top: 1px solid var(--hair); padding: 26px 0; display: grid; grid-template-columns: 90px 1fr auto; gap: 24px; align-items: baseline; }
.work li:last-child { border-bottom: 1px solid var(--hair); }
@media (max-width: 760px) { .work li { grid-template-columns: 60px 1fr; } .work .meta { grid-column: 2; } }
.work .idx { font-family: var(--mono); font-size: 12px; color: var(--accent); letter-spacing: 0.1em; }
.work h3 { margin-bottom: 6px; }
.work p { color: var(--steel); font-size: 15.5px; margin: 0; }
.work .meta { font-family: var(--mono); font-size: 11px; color: var(--steel); letter-spacing: 0.08em; text-transform: uppercase; text-align: right; }

/* --- contact ------------------------------------------------------------- */
.contact-grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(28px, 5vw, 64px); }
@media (max-width: 820px) { .contact-grid { grid-template-columns: 1fr; } }
dl.kv { margin: 0; display: grid; grid-template-columns: max-content 1fr; gap: 10px 20px; }
dl.kv dt { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--steel); }
dl.kv dd { margin: 0; }
dl.kv a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--accent); }

footer.site { padding: 40px 0 64px; }
footer.site .row { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--steel); }

/* --- motion: an ENTRANCE, never a gate -----------------------------------
   🔴 THIS IS THE SECOND REWRITE OF THIS BLOCK, AND THE REASON IS THE POINT.
   v1: `.reveal { opacity: 0 }` + reveal-on-observe. Measured in preview — the
       observer fired for ONE block and not two others, leaving the hero spec
       table and the entire project list at opacity 0. Real content, withheld by
       a decoration whose trigger did not run.
   v2: scoped that to `.js-anim` + a watchdog. Better, but STILL WRONG IN KIND:
       the base state was the HIDDEN state, so every failure mode — no JS, a
       thrown observer, a paused transition in a backgrounded tab — still
       resolved to "no content". I could not even measure it in a hidden pane,
       because a paused transition reports its from-value.
   v3 (this): the base state is VISIBLE. Elements animate only when JS adds
       `.in`, via @keyframes rather than a transition-from-hidden. If the
       animation never runs, is paused, or JS never loads, the element renders
       its NORMAL state — which is readable.
   🔑 A transition FROM a hidden base makes hiding the default and revealing the
   exception. An entrance animation makes visible the default and motion the
   exception. Only the second one is safe for content. */
@media (prefers-reduced-motion: no-preference) {
  .js-anim .reveal.in { animation: ldx-rise 0.5s ease both; }
}
@keyframes ldx-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
