/* ═══ THE FACE — DECLARED HERE, BECAUSE THIS PAGE LOADS NOTHING ELSE ══════
   index.html links landing.css and footer.css. It does NOT link site.css,
   and site.css is where the four @font-face rules live. So the landing has
   been asking for 'Manrope', 'DM Sans', system-ui … while declaring NO FACE
   AT ALL, and falling through to system-ui on every machine.

   MEASURED, S8: document.fonts.size === 0 on this page. The h1 stack measured
   1653.2px for a fixed string — identical to system-ui alone — while 'DM Sans'
   measured 1547.9px, the same as a family invented to be absent. The face was
   never installed here either, so the landing has never once rendered DM Sans.

   That makes every metric in DOCS/07-THE-PERFORM-LOCK.md a measurement of
   system-ui, not of the substitute it says it measured. The sizes and the
   leading are still right; the LETTERFORMS were never what the document says.

   The <link rel=preload as=font> in the head made this harder to see, not
   easier: the file was fetched on every visit and used by nothing.

   Copied byte-for-byte from site.css rather than rewritten, including the
   unicode-range split, so the two design systems cannot drift on the face
   while both are alive. Fraunces is not copied — the landing does not use it.
   ═══════════════════════════════════════════════════════════════════════════ */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/dm-sans-normal-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/dm-sans-normal-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ═══════════════════════════════════════════════════════════════════════════
   CORVEXAL — THE LANDING, THREE WAYS

   One stylesheet, three postures. Everything shared lives at the top; each
   version overrides only what makes it itself, keyed off <body data-v>.

       A · THE INSTRUMENT   white, clinical, data-forward. A survey document.
       B · THE FRONTIER     near-black, cinematic, monumental. One point of heat.
       C · THE COMMONS      warm paper, editorial, human. A great institution.

   ═══ THE STRUCTURE IS PERFORM'S, AND THAT IS THE POINT ══════════════════

   MEASURED off references/WebsiteTemplatesFramer/06-perform-stack-tabs.html
   at 1440x900. Its rhythm is the reason it was chosen: a full-bleed opener,
   then a LIGHT, mostly-typographic section that does not hit you with a
   second photograph, then half-and-half, then the stacked cards. Image,
   breath, image, breath. Nothing is shouted twice in a row.

   ── THE NUMBERS BELOW WERE WRONG UNTIL S2. ────────────────────────────────
   The table that used to sit here claimed the hero was 52/67.6 ls -1.56 with
   a 32/41.6 lead. Re-measured 2026-08-22 by mounting the capture and reading
   getComputedStyle off Perform's own elements, THE HERO IS:

       eyebrow   Manrope  16 / 24    w400  ls normal   #fff   sentence case
                 preceded by a 48 x 1px white rule, 12px gap
       H1        Manrope  72 / 86.4  w500  ls -3.6px   #fff   (lh ratio 1.20)
       sub       Manrope  16 / 24    w400  ls normal   #fff
       button    156.8 x 48, radius 24px, white, pad 0 4px 0 24px,
                 label Manrope 16 / 24 w500 #000
       column    544px wide, left inset 152.5px (10.6% of 1440)
       stack     16px between eyebrow-row, H1 and sub

   Three of the five old rows were wrong, and the h1 in landing-c.html
   overrides all of it inline anyway with 74px / 1.08 / -1.9px. The tokens
   below are therefore NOT yet Perform's — closing that gap is S3's type lock,
   and the full delta table is in DOCS/07-THE-PERFORM-LOCK.md.

   MANROPE IS NOT ON THIS MACHINE, and this was also verified rather than
   assumed. document.fonts.check('500 74px Manrope') returns TRUE for a face
   that is absent — it answers "can something render this?", not "do you have
   it". Measuring a string proves it: 1520.9px under Manrope alone, identical
   to a deliberately nonexistent family, against 1629px for DM Sans. The page
   renders DM SANS. Every metric above the swap is a substitute's.
   Manrope is SIL OFL and free, and it is the first item on the fetch list.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Perform's measured scale, face-independent */
  --l-h1: 52px;    --l-h1-lh: 67.6px;  --l-h1-ls: -1.56px;
  --l-lead: 32px;  --l-lead-lh: 41.6px; --l-lead-ls: -0.96px;
  --l-body: 20px;  --l-body-lh: 28px;   --l-body-ls: -0.4px;
  --l-ui: 16px;    --l-ui-lh: 24px;
  --l-eye: 13px;   --l-eye-ls: 1.7px;
  --l-num: 52px;   --l-num-lh: 52px;

  --l-w-display: 500;
  --l-w-ui: 400;
  --l-w-med: 500;

  /* the face: Manrope's metrics, DM Sans's outlines, until Manrope lands */
  --l-face: 'Manrope', 'DM Sans', system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;

  --l-band: 128px;      /* Perform's section rhythm, generous */
  --l-band-s: 88px;
  --l-col: 1240px;
  --l-gut: 40px;
  --l-r-lg: 26px;
  --l-r-md: 16px;

  /* TYPE OVER FOOTAGE IS WHITE, ALWAYS — L4, in Kevin's words: "It needs to
     be white always." Named rather than written as #fff six times, because
     the six were about to become six places to forget. */
  --on-media: #FFFFFF;

  --l-reveal: 1200ms;
  --l-node: 260ms;
  /* the chart reveal stagger — one token per step so the rhythm is
     readable in one place instead of three magic numbers in three rules */
  --l-stagger-0: 0ms;
  --l-stagger-1: 140ms;
  --l-stagger-2: 240ms;
  --l-stagger-3: 320ms;
  --veil-ink: #000000;
  --l-lift: 900ms;
  --l-reel-run: 54s;
  --l-globe-run: 120s;
  --l-instant: 1ms;
  --l-dur: 640ms;
  --l-quick: 200ms;
  --l-ease: cubic-bezier(.44, 0, .56, 1);
  --l-snap: cubic-bezier(.2, 0, 0, 1);
  --l-out: cubic-bezier(.16, 1, .3, 1);
}

/* ── THE THREE GROUNDS ──────────────────────────────────────────────────── */

/* --accent-text is the accent when it is TYPE, and --fg2 is darker than the
   measured value for the same reason: bronze letterforms are 3.09:1 on white
   and #6E7681 is 4.11:1 on --g2. Both were measured on the rendered page, and
   both are the kind of failure a token audit cannot see, because the token is
   fine and the pairing is not. */
[data-v="c"] {
  --g: #F7F4ED; --g2: #FFFFFF; --fg: #121820; --fg2: #5F6670;
  --line: rgba(182,138,94,.28); --accent: #B68A5E; --accent-text: #8A6034; --heat: #E8A87C;
  --card: #FFFFFF; --card-line: rgba(182,138,94,.22);

  /* THE VERTICAL GRADE — restored 2026-08-22, thirteenth casualty of the
     greedy [data-v] regex. .l-hero__media::after reads var(--grade) and the
     declaration lived in a [data-v] rule, so the wash silently stopped
     painting: the rule survived, its input did not, and var() with no
     fallback resolves to nothing rather than to an error.

     The bottom stop is not decoration. It lands on the page's own cream so
     the hero dissolves into the section under it instead of ending on a hard
     horizontal edge. The top two stops stay light on purpose — L2, vivid —
     and the type is protected by the SIDE scrim above, not by this.

     THE FOURTH STOP, 84%. With three stops the cream ramp began at the
     halfway mark, so by 80% down — where the command bar's hint line sits —
     the ground had already washed pale and 13px white type measured 3.21:1
     against it. Not against the token, against the composited pixels: video,
     then the side scrim, then this, painted into a canvas and read back. The
     token itself looked perfectly reasonable the whole time.

     Holding ink to 84% and ramping cream over the last sixth puts the hint
     at 10.09:1 and costs 0.023 of luminance at the very bottom edge — 0.828
     where the page it meets is 0.906. No visible band; measured, not
     assumed. Every other white element in the hero improved or held:
     reel 4.17 -> 4.49, eyebrow 5.51 -> 5.67, sub 6.69 -> 9.71. */
  /* THE THIRD AND FOURTH GROUNDS. Kevin: "try to give us a few more colored
     sections". Sand sits between the cream and the white — far enough to read
     as a different room, close enough that the curve between them is a fold
     and not a border. Ink is the same inversion /aria uses.
     Every pairing below was measured on its own ground before it was written:
     --fg2 is 4.85:1 on sand, --accent-text 4.62:1, and on ink the BRIGHT
     bronze is 5.76:1 where the text bronze is only 3.23:1. */
  --fold-shadow: rgba(28,19,12,.42);
  --fold-shadow-up: rgba(28,19,12,.30);
  /* THE GAP. A soft, very faded pastel — the ground the plates float on.
     Warm rather than neutral, because L1 is "I want to foster warmth", and
     desaturated far enough that it reads as a surface behind the page rather
     than as a colour on it. No type ever sits on this. */
  /* the ground behind the planet — deep enough that the limb glow reads,
     warm enough that it is not a blue-black hole in a cream page */
  --g-space: #0A0D14;
  /* the eyebrow on space: the bright bronze is 4.1:1 there, the heat is 8.9 */
  --heat-lift: #E8A87C;
  --gap: #EFE6DF;
  --plate-r: 30px;
  --plate-r-hero: 42px;
  --plate-gap: 16px;
  --plate-x: 16px;
  --g-ink: #121820;
  --g3: #F1EADD;
  --card-sand: #FAF6EE;
  --line-sand: rgba(182,138,94,.22);
  --on-ink: #F4F1EA;
  --on-ink-mut: #A9A29A;
  --card-ink: rgba(244,241,234,.05);
  --line-ink: rgba(244,241,234,.16);
  /* THE THIRD STOP MOVED 84% -> 90%, AND IT IS A CONTRAST FIX.

     tools/hero-contrast.js at a 1280 viewport put .l-cmd__hint at 3.28:1
     against a 4.5 bar on hero-pen. The hint is already FULL white — there is
     nothing left to give on the type side. The ground was the problem: from
     84% this gradient interpolates toward the page cream, and the hint sits
     at roughly 82% of the hero, so on a shorter viewport it lands inside the
     cream ramp. White on cream is the worst pairing on this page.

     IT PASSED AT 1676 AND FAILED AT 1280, which is why it survived earlier
     sessions: the hero is taller at a wide viewport and the same copy sits
     higher up the gradient. Contrast here is a function of viewport width
     and has to be measured across the range, not once.

     Moving the stop to 90% compresses the cream dissolve from the last 16%
     of the hero to the last 10% and takes the hint to 5.11:1. The darkness
     value is untouched at .40 — this changes WHERE the hero starts becoming
     the page, not how dark it is anywhere above that point. */
  --grade: linear-gradient(180deg,
           rgba(18,24,32,.42) 0%, rgba(18,24,32,.34) 50%,
           rgba(18,24,32,.40) 90%, rgba(247,244,237,.97) 100%);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: auto; }
body {
  margin: 0; background: var(--g); color: var(--fg);
  font-family: var(--l-face);
  font-size: var(--l-ui); line-height: var(--l-ui-lh); font-weight: var(--l-w-ui);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}
h1,h2,h3,h4,p,figure,blockquote { margin: 0; }
ul,ol { margin: 0; padding: 0; list-style: none; }
/* THE height:auto IS THE WHOLE POINT OF THIS LINE.
   max-width:100% clamps an oversized image WIDTH and does nothing to its
   HEIGHT, so the intrinsic height survives and the picture stretches. Found
   in the Reservoir, S8: notebook.webp is 1400x2100 and was rendering
   584x2100 — aspect 0.28 against a natural 0.667, a 58% distortion, in the
   one section Kevin has praised by name. It also made the section 2356px
   tall and left a 420px void beside it.
   It survived because nothing can catch it: the file loads, the layout is
   valid, no check fails, and a photograph you have not seen undistorted
   looks like a photograph. Every other image on the page escaped only
   because its own class sets width and height together. */
img,video,svg { max-width: 100%; display: block; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

.skip {
  position: absolute; left: 16px; top: -80px; z-index: 200;
  background: var(--card); color: var(--fg); padding: 12px 20px;
  border-radius: 100px; font-size: 14px;
  transition: top var(--l-quick) var(--l-snap);
}
.skip:focus { top: 16px; }
.vh { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }

/* ── TYPE ───────────────────────────────────────────────────────────────── */

.l-h1 { font-size: var(--l-h1); line-height: var(--l-h1-lh); letter-spacing: var(--l-h1-ls);
        font-weight: var(--l-w-display); text-wrap: balance; }
.l-lead { font-size: var(--l-lead); line-height: var(--l-lead-lh); letter-spacing: var(--l-lead-ls);
        font-weight: var(--l-w-display); text-wrap: pretty; }
.l-body { font-size: var(--l-body); line-height: var(--l-body-lh); letter-spacing: var(--l-body-ls);
        font-weight: var(--l-w-med); max-width: 60ch; }
.l-small { font-size: var(--l-ui); line-height: var(--l-ui-lh); color: var(--fg2); }
.l-eye { font-size: var(--l-eye); letter-spacing: var(--l-eye-ls); text-transform: uppercase;
        font-weight: var(--l-w-med); color: var(--accent-text, var(--accent)); }
.l-muted { color: var(--fg2); }
.l-num { font-size: var(--l-num); line-height: var(--l-num-lh); font-weight: var(--l-w-display);
        letter-spacing: -1.2px; }

/* ── STRUCTURE ──────────────────────────────────────────────────────────── */

.l-band { padding: var(--l-band) var(--l-gut); position: relative; }
.l-band--s { padding-block: var(--l-band-s); }
.l-col { max-width: var(--l-col); margin-inline: auto; width: 100%; }
.l-split { display: grid; grid-template-columns: 1fr 1fr; gap: 72px; align-items: center; }
.l-rule { height: 1px; background: var(--line); border: 0; margin: 0; }

/* ═══ THE WORDMARK ═════════════════════════════════════════════════════════
   Drawn, not typed. The X is the one warm letter and the only place heat
   appears in the mark.                                                    */
.l-mark {
  display: inline-block;
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 3.4px;
  line-height: 1;
  white-space: nowrap;
  font-feature-settings: 'ss01';
}
/* THE ORANGE X IS A LOGOTYPE, AND THAT IS THE WHOLE ANSWER.
   Measured at 2.03:1 on white and 1.85:1 on cream, which would be a
   comfortable AA failure for any other 17px letterform on this page.
   WCAG 1.4.3 exempts it by name: text that is part of a logo or brand name
   has no contrast requirement.
   This is written down because a future audit WILL flag it, and the right
   response is a citation rather than a rediscovery. The exemption covers the
   X in CORVEXAL and nothing else — the moment --heat is used for a sentence,
   a label or a link, it is type again and it has to clear 4.5:1.
   The accessible name comes from the link's aria-label, so the word is never
   read as CORVE AL regardless of what the X looks like. */
/* THE MARK IS DRAWN NOW, NOT RECOLOURED.  L3: "It just can't be just an
   orange X... it has to do something artistic. It doesn't scream authority."
   He was describing exactly what this was: the letter X from the body face,
   given a different colour. A colour swap is not a logo.

   Five directions were drawn as SVG paths and compared at 132 / 32 / 20px, in
   1-bit black-on-white, reversed on ink, and set inside the wordmark at two
   sizes — tools/_scratch/logo.html renders all five side by side. Two failed
   on the only test that is not negotiable, which is whether the word still
   reads CORVEXAL: "the router" broke into four floating triangles, and the
   first pass at "the vault" read as an arch rather than a crossing.

   This is E, "the handover, drawn". Two strokes cross and leave heavier than
   they arrived — thin at the top, swelling to mass at the base — because what
   passes through Corvexal is meant to come out bigger than it went in. The
   curve is cut the way a broad nib cuts, which is where the artistic flair
   sits; the mass at the base is where the authority sits. L3 asks for both in
   one sentence and neither a purely geometric nor a purely calligraphic mark
   delivered both.

   Sized in em so it tracks the wordmark from 17px in the nav to 132px in the
   big lockup, with no second asset and no second decision.

   THE WCAG 1.4.3 LOGOTYPE EXEMPTION STILL APPLIES AND STILL NEEDS SAYING.
   The mark is brand-orange on cream and would fail as type. 1.4.3 exempts
   text that is part of a logo by name. The accessible name comes from the
   link's aria-label, so this is never read as CORVE AL — and it is now a
   <svg aria-hidden> rather than a letter, so no screen reader can try. */
/* ══════ BEGIN LOGOTYPE — NOT AN AMBER USE ═══════════════════════════════
   The X in CORVEXAL. Exempt from the budget and from the never-share-a-
   viewport rule, on the record and narrowly: the budget is on SURFACES —
   warm areas competing with the content — and a logotype is what the content
   belongs to. Same reasoning, same marker, as chrome.css. */
.l-mark__x {
  display: inline-block;
  width: .94em; height: .94em;
  vertical-align: -.16em;
  margin-right: .04em;
  overflow: visible;
}
/* The two bars are the letter. The sweep is the mark. Kevin picked this
   construction off a reference and asked for it verbatim with the colour
   changed, so the geometry is the reference's and only the fill is ours. */
.l-mark__x .mk-bar { fill: var(--fg); }
.l-mark__x .mk-sweep { fill: var(--heat); }
/* On the footage-backed nav the bars have to read against video, not against
   the page — the sweep already does, being the warm one. */
.l-nav:not(.is-pill) .l-mark__x .mk-bar { fill: var(--on-media); }
/* ══════ END LOGOTYPE ════════════════════════════════════════════════════ */
.l-mark--big {
  font-size: clamp(44px, 9.4vw, 132px);
  letter-spacing: clamp(4px, 1.1vw, 15px);
}

/* ═══ THE PILL — morphs from a bar ═════════════════════════════════════════ */

/* ══ THE BAR — 08-vantara-hub, measured ═══════════════════════════════════
   Its geometry off the capture, not off a description:

     bar        1280x66 · rgba(255,255,255,.40) · radius 80 · blur(8px)
                padding 10px 14px 10px 24px — asymmetric because the CTA
                pill on the right needs less room than the brand on the left
     brand      45px mark, then a TWO-LINE lockup:
                  name     28px / 400
                  tagline  11px / 400 in rgb(182,138,94)
                That bronze is already our --accent. Same value, no change.
     links      13px / 500, centred
     CTA        filled pill, 15px / 500, white type

   THE LOPSIDED SCROLL. The old bar centred ONE group inside a box with
   asymmetric padding, so the links drifted off-centre by half the difference
   between the brand and whatever sat opposite it — and by a different amount
   once the bar shrank into its pill. A 1fr auto 1fr grid puts the links dead
   centre of the BAR regardless of what flanks them, at every width and in
   both states, without a single magic number.                             */

.l-nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  height: 84px; padding: 0 var(--l-gut);
  --nav-fg: var(--on-media);
}
.l-nav__group {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  width: 100%; max-width: 1280px;
  /* Vantara pads 24 left / 14 right, and with the links in the centre
     column that asymmetry put them 5px off the middle of the bar — small,
     but it is exactly the lopsidedness Kevin reported. Averaged to 19px:
     symmetric, dead centre, and within 5px of the source on both sides. */
  padding: 10px 19px;
  border-radius: 80px;
  transition: background var(--l-reveal) var(--l-ease),
              box-shadow var(--l-reveal) var(--l-ease),
              max-width var(--l-reveal) var(--l-out);
}

/* ── the brand lockup ── */
.l-nav__brand {
  display: flex; align-items: center; gap: 11px;
  text-decoration: none; color: inherit; justify-self: start;
}
.l-nav__lock { display: flex; flex-direction: column; gap: 3px; }
.l-nav__tag {
  font-size: 11px; letter-spacing: .12em; line-height: 1;
  color: var(--accent-text); white-space: nowrap;
}
/* The .72 that used to be declared here is gone, not overridden. It lost to
   the .88 further down either way, but a dead declaration holding a value
   measured at 1.85:1 is a landmine: reorder the file and the failing one
   wins. See the note beside .88. */

/* ── the links ── */
.l-nav__links { display: flex; align-items: center; gap: 4px; justify-self: center; }
.l-nav__a {
  font-size: 13px; font-weight: 500; padding: 9px 16px; border-radius: 100px;
  color: inherit; opacity: .82; text-decoration: none; white-space: nowrap;
  transition: opacity var(--l-quick) var(--l-ease), background var(--l-quick) var(--l-ease);
}
.l-nav__a:hover, .l-nav__a.is-open {
  opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent);
}

/* ── the CTA ── */
.l-nav__cta {
  display: inline-flex; align-items: center; gap: 7px; justify-self: end;
  padding: 12px 20px; border-radius: 100px;
  font-size: 14px; font-weight: 500; text-decoration: none; white-space: nowrap;
  background: var(--g-ink); color: var(--on-ink);
  transition: background var(--l-quick) var(--l-ease);
}
.l-nav__cta svg { width: 16px; height: 16px; transition: transform var(--l-quick) var(--l-out); }
.l-nav__cta:hover { background: var(--accent-text); }
.l-nav__cta:hover svg { transform: translate3d(3px, 0, 0); }
.l-nav:not(.is-pill) .l-nav__cta { background: rgba(255,255,255,.94); color: var(--g-ink); }

/* THE NET. Nine points and their crossings — the site's first sentence as a
   glyph, and deliberately not a second X, so the bar carries two marks
   rather than the same one twice. */
.l-net { width: 26px; height: 26px; flex: none; overflow: visible; }
.l-net__w { fill: none; stroke: currentColor; stroke-width: 1; opacity: .34; }
.l-net__p { fill: currentColor; opacity: .55; }
/* ══════ BEGIN LOGOTYPE — NOT AN AMBER USE ═══════════════════════════════
   The knot at the centre of the net. It sits inside the nav lockup beside
   the wordmark — part of the mark, not a surface. Same exemption and same
   marker as the X itself. */
.l-net__c { fill: var(--heat); }
/* ══════ END LOGOTYPE ════════════════════════════════════════════════════ */

/* Over the video the bar is bare and its type is white. On scroll it becomes
   Vantara's translucent pill — the transition Kevin asked to keep. */
.l-nav.is-pill { --nav-fg: var(--fg); }
.l-nav.is-pill .l-nav__group {
  max-width: 1180px;
  background: color-mix(in srgb, var(--g2) 62%, transparent);
  box-shadow: rgba(0,0,0,.13) 0 22px 46px -24px, rgba(255,255,255,.5) 0 1px 0 0 inset;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}

.l-nav__burger {
  display: none;                       /* shown only under 900px */
  align-items: center; justify-content: center;
  /* 44, NOT 38. This is the ONLY way to navigate on a phone — the links are
     hidden below 900px and the rail is display:none — so it is the single
     most important target on the site, and it was six pixels under the bar
     the rubric sets (a control under 44px is a FRICTION zero). The glyph
     inside is unchanged; only the hit area grew, so nothing moves visually
     except 6px of extra reach. */
  width: 44px; height: 44px; justify-self: end;
  border: 0; border-radius: 50%; background: transparent;
  cursor: pointer; position: relative; flex: none;
}

/* ══ THE WEB ══════════════════════════════════════════════════════════════
   Bible Part I §2.1: "Each opens a web on hover — the web stays open when
   the pointer leaves the trigger and closes only when the pointer leaves the
   web. On open, the page behind darkens ~8% and the web's nodes stagger in
   at 40ms intervals."

   All four of those are load-bearing and all four are implemented. The
   forgiving close is the one that decides whether this feels engineered:
   a menu that vanishes the instant the pointer crosses the gap between the
   trigger and the panel is the commonest way a hover nav is got wrong.

   The tree is DATA, not markup — Bible Part II: "The nav web is data-driven,
   a JSON tree. Adding EMBER K-8 in 2027 should be one object, not a nav
   rebuild."                                                               */

.l-web {
  position: absolute; top: calc(100% - 6px); z-index: 2;
  min-width: 272px; padding: 14px;
  border-radius: 22px;
  background: color-mix(in srgb, var(--g2) 94%, transparent);
  box-shadow: rgba(0,0,0,.20) 0 30px 60px -28px, rgba(255,255,255,.6) 0 1px 0 0 inset;
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  transition: left var(--l-quick) var(--l-out);
}
.l-web[hidden] { display: none; }
.l-web__h {
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg2); padding: 3px 12px 11px;
}
.l-web__n {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 12px; border-radius: 13px;
  font-size: 14px; color: var(--fg); text-decoration: none;
  opacity: 0; transform: translate3d(0, -5px, 0);
  animation: webin var(--l-node) var(--l-out) forwards;
  transition: background var(--l-quick) var(--l-ease);
}
.l-web__n:hover { background: var(--g); }
.l-web__n[aria-disabled="true"] { color: var(--fg2); pointer-events: none; }
.l-web__yr {
  margin-left: auto; font-family: var(--l-mono); font-size: 10.5px;
  padding: 2px 7px; border-radius: 100px;
  background: var(--g3); color: var(--fg2);
}
.l-web__n .l-ico { width: 16px; height: 16px; flex: none; opacity: .6; color: var(--accent-text); }
@keyframes webin { to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .l-web__n { animation: none; opacity: 1; transform: none; }
}

/* The ~8% darken. A fixed sheet under the bar and over everything else, so
   the web reads as lifted off the page rather than printed on it. */
.l-veil {
  position: fixed; inset: 0; z-index: 99;
  background: var(--veil-ink); opacity: 0; pointer-events: none;
  transition: opacity var(--l-quick) var(--l-ease);
}
.l-veil.is-on { opacity: .08; }



/* ═══ THE HERO ═════════════════════════════════════════════════════════════ */

/* ═══ THE HERO IS PERFORM'S, MEASURED ══════════════════════════════════════
   S3. Every number in this block was read off
   references/WebsiteTemplatesFramer/06-perform-stack-tabs.html live at
   1440x900 with getComputedStyle and getBoundingClientRect. The full table,
   with ours beside it, is DOCS/07-THE-PERFORM-LOCK.md.

   Kevin, L4: "It should be the exact same as the Perform website... the text
   is too close to the bottom of the page. Copy it verbatim."

   THE PLACEMENT WAS THE COMPLAINT, AND IT WAS NOT A FONT PROBLEM.
   Perform's hero block sits 198.6px from the top and 198.6px from the bottom
   of a 900px viewport — symmetric to one decimal place. Ours was 387 / 72:
   5.4x closer to the floor than the ceiling, because this rule said
   `align-items: end` with 72px of bottom padding. It is centred now, and the
   symmetry is a consequence of centring rather than a pair of magic numbers
   that stop being right at another viewport height.                       */
.l-hero { position: relative; min-height: 100svh; display: grid; align-items: center;
          overflow: hidden; padding: 0 var(--l-gut); }
.l-hero__media { position: absolute; inset: 0; z-index: 0; }
.l-hero__v {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity var(--l-reveal) var(--l-ease);
}
.l-hero__v.is-on { opacity: 1; }

/* ══ THE DAMPENER ═════════════════════════════════════════════════════════
   Kevin: "add a little dampening filter to the initial video — a filter
   that's dimming it slightly. Not actually dimming the video, but just a
   filter over it that allows for the contrast and every scene to have a
   uniform type of exposure. You don't have to go each one and tailor it."

   So it is not `filter` on the <video>, which would be dimming the source
   and would need per-clip tuning. It is a `backdrop-filter` on a layer ABOVE
   the video: the same normalisation applied to whatever is behind it, which
   is exactly "uniform exposure without going hyper-specific".

     brightness(.90)   pulls the hot clips down; the dark ones barely move
     contrast(.94)     compresses toward the middle, so the range across the
                       four clips narrows instead of each one keeping its own
     saturate(.94)     takes the edge off hero-crayon's oranges, which read a
                       stop hotter than the other three

   Four clips shot by four different people in four different rooms now sit
   in the same exposure envelope, and the type over them stops fighting a
   different picture every eight seconds.

   THE Z-ORDER IS EXPLICIT, ALL FIVE LAYERS. It used to be two declared and
   three implied, which is fine until something is inserted between them —
   and this is exactly that insertion. Declared: video, damp, side scrim,
   vertical grade, copy.                                                   */

.l-hero__v    { z-index: 0; }
.l-hero__damp {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  backdrop-filter: brightness(.90) contrast(.94) saturate(.94);
  -webkit-backdrop-filter: brightness(.90) contrast(.94) saturate(.94);
}
[data-v="c"] .l-hero__media::before { z-index: 2; }
.l-hero__media::after { z-index: 3; }
.l-hero__inner { z-index: 4; }

/* A browser without backdrop-filter gets a flat scrim instead. Not the same
   normalisation, but the same direction, and far better than the layer
   silently doing nothing — which is what an unsupported backdrop-filter
   does: the element renders, transparent, and nobody notices. */
@supports not (backdrop-filter: brightness(.9)) {
  .l-hero__damp { background: color-mix(in srgb, var(--g-space) 14%, transparent); }
}

/* the grade: every version darkens the footage differently, and that grade is
   most of what makes the three feel like different companies */
.l-hero__media::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: var(--grade);
}
/* THE SIDE SCRIM — S3, and it is what made the type lock survivable.
   Centring the copy moved it from y 0.43-0.92 to y 0.18-0.82, straight into
   the part of --grade deliberately kept lightest so the picture stays vivid.
   The obvious response is to darken the vertical ramp, but that flattens the
   whole frame and L2 exists to stop exactly that.

   Perform does not do it vertically either — look at the capture: its left
   third is markedly darker and the cyclist on the right is fully lit. Now
   that our copy is a 544px LEFT column rather than a full-width band, the
   same move is available: darken where the words are, leave the photograph
   alone. The gradient is clear by 64%, so the right two thirds of every clip
   are untouched, and the measured saturation of the picture does not move.
   Tuned by measurement, not by eye — the numbers are in
   DOCS/07-THE-PERFORM-LOCK.md. */
[data-v="c"] .l-hero__media::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: linear-gradient(90deg,
              rgba(28,19,12,.90) 0%, rgba(28,19,12,.78) 38%, rgba(28,19,12,0) 74%);
}
/* A is a photograph printed on paper, not a photograph you look through. The
   first grade let the footage stay dark and the ink headline fought it; this
   one washes it to a plate, which is both readable and the concept.      */

/* ── THE HERO'S TEXT COLUMN ────────────────────────────────────────────────
   RESTORED 2026-08-22 from 07-THE-PERFORM-LOCK.md §7.2 after a greedy regex
   of mine deleted it along with the dead A/B versions. Every number here is
   the measured Perform value that document recorded:
     column 544px · left inset 152.5px · eyebrow 16/24 w400 ls normal, white
     eyebrow rule 48 x 1px currentColor at a 12px gap
   The inset is what clears the left rail as well as matching Perform — the
   rail's widest label reaches x 144.2, and a column at 92.5 ran through it. */
.l-hero__inner {
  position: relative; z-index: 2; width: 100%;
  /* Perform's measured inset is 152.5px from the section edge, and .l-hero
     already pads by --l-gut — so the inner adds only the remainder. Setting
     152.5 here produced 192.5, which the measurement caught. max() keeps it
     from going negative if the gutter ever grows past the inset. */
  padding: 0 var(--l-gut) 0 max(0px, calc(152.5px - var(--l-gut)));
}
.l-hero__col { max-width: 544px; }
.l-hero__eye {
  display: flex; align-items: center; gap: 12px;
  font-size: 16px; line-height: 24px; font-weight: var(--l-w-ui);
  letter-spacing: normal; text-transform: none; color: var(--on-media);
  margin: 0 0 20px;
}

/* THE ROTATOR. All five lines occupy one grid cell so the block does not jump
   as they swap, and only the live one is painted. */
.l-roll { display: grid; }
.l-roll__line {
  grid-area: 1 / 1; opacity: 0; transform: translateY(14px);
  transition: opacity var(--l-lift) var(--l-ease), transform var(--l-lift) var(--l-out);
}
.l-hero__rule { flex: none; width: 48px; height: 1px; background: currentColor; }
.l-roll__line.is-on { opacity: 1; transform: none; }
/* THE HEADLINE. Perform: 72 / 86.4 (a 1.20 ratio) w500, ls -3.6px, white.
   Ours was 74 / 79.92 (1.08), ls -1.9px — and set INLINE on the <h1>, so every
   token in this file was decorative. The inline style is gone.

   74 vs 72 was never the difference anyone could see. 1.08 against 1.20 is a
   different typographic voice: ours was tight and stacked, Perform's is open.
   The tracking is expressed in em rather than the measured -3.6px so it stays
   proportional as the clamp scales the size down — -3.6px at 72px IS -0.05em,
   and a fixed -3.6px would tear a 38px headline apart on a phone. */
.l-hero .l-h1 {
  font-size: clamp(38px, 5vw, 72px);
  line-height: 1.2;
  letter-spacing: -0.05em;
  /* WHITE, ALWAYS — L4, in Kevin's words. This was carried by a rule that
     also named [data-v="b"], so it went with the dead versions and the
     headline rendered in ink on video until the measurement caught it.
     It belongs on the element that needs it, not in a version scope. */
  color: var(--on-media);
}

/* The hero's action, same casualty: a white pill on the footage. */
.l-hero .l-cmd__btn { background: var(--on-media); color: var(--fg); }

/* THE SUB. Perform: 16 / 24 w400, plain white. Ours was 32 / 41.6 w500 at 82%
   white — twice the size and a fifth of the contrast. Perform's hierarchy is
   a 4.5:1 ratio between headline and deck; ours was 2.3:1, which is why the
   two competed. The 82% alpha goes too: it was costing contrast over the
   footage for no typographic gain, and L4 says the hero type is white. */
.l-hero__sub {
  margin: 16px 0 0; max-width: none;
  font-size: 16px; line-height: 24px; font-weight: var(--l-w-ui);
  letter-spacing: normal; color: var(--on-media);
}

/* THE BUTTON. Perform: 48px tall, radius 24 (fully round at that height),
   white, padding 0 4px 0 24px — asymmetric because the arrow sits in a circle
   flush to the right edge. Ours was 56px with even 30px padding. */
.l-hero .l-cmd__btn { height: 48px; padding: 0 4px 0 24px; gap: 12px; }

/* ── THE COMMAND BAR — "Start now" becomes the way in ────────────────────── */

.l-cmd { margin-top: 40px; position: relative; max-width: 620px; }
.l-cmd__btn {
  display: inline-flex; align-items: center; gap: 10px;
  height: 56px; padding: 0 30px; border: 0; border-radius: 100px;
  background: var(--fg); color: var(--g);
  font-family: inherit; font-size: 16px; font-weight: 500; cursor: pointer;
  /* nowrap, and it is not cosmetic. Found by screenshot: the "Meet Aria"
     button at the end of the engine section was rendering its label on TWO
     lines inside a 56px pill — "Meet" over "Aria" — which reads as broken.
     That instance sits outside .l-cmd, so its width is shrink-to-fit against
     a 1240px block and the label collapsed to its min-content (the longest
     single word) rather than its max-content. The pill kept its height, so
     the second line simply overflowed the type out of the middle.
     Every label on this button is two short words at most, so nowrap cannot
     cause an overflow here — and if a long one is ever added, it should get
     its own rule rather than silently wrapping inside a pill. */
  white-space: nowrap;
  transition: transform var(--l-quick) var(--l-snap), opacity var(--l-dur) var(--l-ease);
}
/* THE ARROW HAD NO SIZE, SO TWO BUTTONS HAD NO ARROW.
   Every other button on the page writes `<svg width="17" height="17">`. The
   "Meet Aria" and "Meet EMBER" links write `<svg viewBox="0 0 18 18">` with no
   width and no height — and the reset at the top of this file gives every svg
   `max-width: 100%`, which against no intrinsic width resolves to ZERO. Both
   measured 0x0. The arrow was in the markup, in the DOM, and not on the page.

   Sized here rather than in the markup so it cannot happen again to the next
   one somebody adds; the two buttons that already carry explicit attributes
   are set to the same 17px, so nothing of theirs moves. `flex: none` because
   an unsized flex item is exactly what let this collapse quietly. */
.l-cmd__btn > svg { flex: none; width: 17px; height: 17px; }
/* RESTORED — casualty 16. The shell was rendering position:static at opacity
   1, so the search field sat BELOW the button permanently instead of growing
   out of it. Only the .is-open rule survived, and an .is-open rule with no
   base is a state with nothing to be a state OF.

   landing.js's own note is the specification: "Start now does not navigate.
   It OPENS — the button scales away and a command bar grows out of the same
   footprint, which is why the two share a height and a radius." So the shell
   is absolutely positioned over the button's box, at the button's height and
   radius, and the two cross-fade. */
.l-cmd__shell {
  position: absolute; top: 0; left: 0;
  display: flex; align-items: center; gap: 12px;
  width: 100%; height: 56px; padding: 0 7px 0 22px;
  border-radius: 100px;
  background: var(--card); border: 1px solid var(--card-line);
  box-shadow: rgba(0,0,0,.14) 0 18px 40px -20px;
  opacity: 0; transform: scale(.97); transform-origin: left center;
  pointer-events: none;
  transition: opacity var(--l-quick) var(--l-ease), transform var(--l-quick) var(--l-out);
}
/* the hero's button is Perform's 48px, so the shell matches it there */
.l-hero .l-cmd__shell { height: 48px; padding: 0 5px 0 20px; }
.l-cmd.is-open .l-cmd__btn { opacity: 0; transform: scale(.94); pointer-events: none; }
.l-cmd.is-open .l-cmd__shell { opacity: 1; transform: none; pointer-events: auto; }
.l-cmd__in {
  flex: 1; border: 0; background: transparent; color: var(--fg);
  font-family: inherit; font-size: 17px; min-width: 0; height: 100%;
}
.l-cmd__in::placeholder { color: var(--fg2); }
.l-cmd__in:focus { outline: none; }
.l-cmd__go {
  height: 42px; padding: 0 20px; border: 0; border-radius: 100px;
  background: var(--fg); color: var(--g); font-family: inherit; font-size: 14px;
  font-weight: 500; cursor: pointer; flex: none;
}
/* RESTORED — casualty 17. Same shape as the command shell: only the .is-on
   rule survived, so the answer panel had no resting state and would have
   rendered as a static block in the flow the first time it was asked a
   question. The 100vw that check 9 flagged was on THIS rule, which is how I
   know it existed and what it said. */
.l-ans {
  position: fixed; right: 22px; bottom: 22px; z-index: 150;
  width: min(400px, calc(100% - 44px));
  padding: 18px 20px 20px; border-radius: var(--l-r-md);
  background: var(--card); border: 1px solid var(--card-line);
  box-shadow: rgba(0,0,0,.20) 0 26px 60px -24px;
  opacity: 0; transform: translateY(10px); pointer-events: none;
  transition: opacity var(--l-quick) var(--l-ease), transform var(--l-quick) var(--l-out);
}
.l-ans.is-on { opacity: 1; transform: none; pointer-events: auto; }
.l-ans__q { font-size: 13px; color: var(--fg2); margin-bottom: 8px; }
.l-ans__a { font-size: 16px; line-height: 24px; }
.l-ans__x { position: absolute; top: 10px; right: 12px; border: 0; background: transparent;
            color: var(--fg2); cursor: pointer; font-size: 17px; padding: 4px 7px; border-radius: 6px; }
.l-ans__x:hover { color: var(--fg); }

/* ═══ THE RAIL — five lines, not dots ══════════════════════════════════════
   C23's keeping-tab: the live section's line is solid and long, the rest are
   short and nearly transparent. It is a position readout, not a menu.    */

.l-rail { position: fixed; left: 26px; top: 50%; transform: translateY(-50%);
          z-index: 90; display: flex; flex-direction: column; gap: 7px; }
/* The item is exactly as wide as its line. The label is absolutely
   positioned OUT of that box, because a hidden label still occupies layout:
   at opacity 0 the button measured 175px wide and 80 of those sat on top of
   the body copy, swallowing clicks and text selection over a strip of every
   paragraph on the page. Invisible and interactive is the worst of the two
   states — the visual overlap at least announced itself. */
.l-rail__i {
  border: 0; padding: 0; background: transparent; cursor: pointer;
  display: flex; align-items: center;
  position: relative; width: 22px;
}
/* SMALLER — Kevin: "make that a little bit smaller so it can be
   aesthetically pleasing and not so huge. It's not modern or aesthetic."
   The line was 18px resting and 40px current; it is 11 and 22 now, and the
   gap between items came down with it so the whole rail reads as one
   instrument rather than as a stack of dashes. */
.l-rail__b {
  display: block; width: 11px; height: 1px; border-radius: 2px;
  background: var(--rail-c, var(--fg)); opacity: .22;
  transition: width var(--l-dur) var(--l-out), opacity var(--l-dur) var(--l-ease);
}
.l-rail__i[aria-current="true"] .l-rail__b { width: 22px; opacity: 1; }
/* THE LABEL IS HOVER-ONLY, AND THAT IS A COLLISION FIX, NOT A TASTE CALL.
   It used to also show on [aria-current], so the section you were reading
   had its name printed permanently at x 26–161 — while the content column
   starts at x 95. Sixty-six pixels of overlap on every single section, and
   on the Reservoir the word HOW IT sat directly on top of the H1's T.

   At rest the rail is lines and nothing else, which is also the smaller,
   quieter thing that was asked for. The current section is still marked:
   its line is twice as long and at full opacity.

   When a label does appear it carries its own ground, because it is being
   painted over body copy and 9px type at .72 on top of a sentence is
   unreadable no matter what the contrast number says. */
.l-rail__t {
  font-size: 9px; letter-spacing: 1.3px; text-transform: uppercase;
  color: var(--rail-c, var(--fg)); opacity: 0; white-space: nowrap;
  transform: translateX(-6px);
  position: absolute; left: calc(100% + 10px); top: 50%;
  margin-top: -9px; pointer-events: none;
  padding: 3px 7px; border-radius: 4px;
  background: var(--rail-pill, var(--g));
  transition: opacity var(--l-quick) var(--l-ease), transform var(--l-quick) var(--l-out);
}
.l-rail__i:hover .l-rail__t,
.l-rail__i:focus-visible .l-rail__t { opacity: 1; transform: none; }
.l-rail.on-dark .l-rail__t { --rail-pill: var(--g-space); }
.l-rail.on-dark { --rail-c: #fff; }

/* ═══ CARDS, STATS, THE STACK ══════════════════════════════════════════════ */

.l-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; }
.l-stat { display: flex; flex-direction: column; gap: 6px; padding-top: 22px; border-top: 1px solid var(--line); }

.l-cards { display: grid; gap: 20px; }
/* THREE CARDS, AND THEY MUST COLLAPSE.  This was `style="grid-template-
   columns:repeat(3,1fr)"` inline on the section, which no media query can
   reach — so at 375 it stayed three columns. Two things then went wrong at
   once: `1fr` is `minmax(auto,1fr)`, so the columns refused to go below their
   min-content width and measured 195.9 / 154.1 / 185.1 = 575px inside a 331px
   box; and `body { overflow-x: hidden }` clipped the result rather than
   scrolling it. The page did NOT scroll sideways — it silently ate two of the
   three cards. That is the worse failure of the two, because a missing card
   looks like a card that was never written.
   minmax(0,1fr) removes the min-content floor. Measured at 1440 the columns
   are 400/400/400 before and after, so the desktop layout is untouched. */
.l-cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.l-card {
  background: var(--card); border: 1px solid var(--card-line); border-radius: var(--l-r-lg);
  padding: 34px; display: flex; flex-direction: column; gap: 14px;
  transition: transform var(--l-dur) var(--l-out), box-shadow var(--l-dur) var(--l-ease);
}
.l-card:hover { transform: translateY(-4px); box-shadow: rgba(0,0,0,.16) 0 26px 50px -26px; }
.l-card__t { font-size: 24px; font-weight: 500; letter-spacing: -.5px; }
.l-card__k { font-size: 12px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--accent-text, var(--accent)); }

/* THE STACK — Perform's folding plans, re-purposed for the subsidiaries.
   Each card sticks, and the next rides up over it. position: sticky does the
   whole thing; there is no scroll listener anywhere on this page. */
.l-stack { display: flex; flex-direction: column; gap: 26px; }
.l-stack__i { position: sticky; top: 110px; }
.l-stack__c {
  border-radius: var(--l-r-lg); padding: 44px; border: 1px solid var(--card-line);
  background: var(--card); display: grid; grid-template-columns: 1fr auto; gap: 32px; align-items: center;
  box-shadow: rgba(0,0,0,.14) 0 30px 60px -34px;
}
.l-stack__n { font-size: 13px; letter-spacing: 1.5px; color: var(--accent-text, var(--accent)); text-transform: uppercase; }
.l-stack__h { font-size: 34px; font-weight: 500; letter-spacing: -.9px; margin: 10px 0 12px; }
.l-stack__tag { font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase;
  border: 1px solid var(--card-line); border-radius: 100px; padding: 7px 14px; color: var(--fg2); white-space: nowrap; }

/* ═══ THE CAROUSEL ═════════════════════════════════════════════════════════ */

.l-reel { position: relative; overflow: hidden; }
.l-reel__t { display: flex; gap: 18px; width: max-content; animation: reel var(--l-reel-run) linear infinite; }
.l-reel:hover .l-reel__t { animation-play-state: paused; }
@keyframes reel { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.l-reel__f {
  width: 330px; aspect-ratio: 4/3; object-fit: cover; border-radius: var(--l-r-md);
  flex: none; filter: var(--reel-f, none);
  transition: filter var(--l-dur) var(--l-ease), transform var(--l-dur) var(--l-out);
}
.l-reel::before { left: 0; background: linear-gradient(90deg, var(--g), transparent); }
.l-reel::after { right: 0; background: linear-gradient(270deg, var(--g), transparent); }

/* ═══ THE GLOBE ════════════════════════════════════════════════════════════
   A graticule drawn in SVG, with the network's real places on it. It is a
   survey plot, not a claim of territory — and it is the honest placeholder
   until globe.gl and the Blue Marble textures land.                      */
.l-globe { position: relative; aspect-ratio: 1; max-width: 560px; margin-inline: auto; width: 100%; }
.l-globe svg { width: 100%; height: 100%; overflow: visible; }
.l-globe__sphere { fill: var(--globe-fill, rgba(14,17,22,.03)); stroke: var(--line); }
.l-globe__grat { fill: none; stroke: var(--line); }

/* THE PINS. RESTORED with .l-hero__* above — same deletion.
   The label is hidden until the pin is hovered or focused, so five city names
   do not crowd a plot whose whole point is that none of them are built. */
.l-pin { cursor: pointer; }
/* ══════ BEGIN HEAT USE 1 OF 3 — the Beacon pins ══════════════════════════
   Five warm points on a survey plot of cities that are chosen and not built.
   Ring and dot are two declarations and one use, the same way the arrival
   point in site.css is a dot plus the glow behind it.
   This is the only warm thing in the lower two thirds of the page, which is
   what makes five small marks read as five places rather than as decoration. */
.l-pin__r { fill: none; stroke: var(--heat); opacity: .4; }
.l-pin__d { fill: var(--heat); }
/* ══════ END HEAT USE 1 OF 3 ══════════════════════════════════════════════ */
.l-pin__t {
  font-size: 10px; letter-spacing: 1.2px; text-transform: uppercase;
  fill: var(--fg); opacity: 0;
  transition: opacity var(--l-quick) var(--l-ease);
}
.l-pin:hover .l-pin__t, .l-pin:focus-visible .l-pin__t { opacity: 1; }


/* ═══ FOOTER ═══════════════════════════════════════════════════════════════ */
.l-foot { border-top: 1px solid var(--line); padding: 64px var(--l-gut) 48px; }
.l-foot__g { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 40px; max-width: var(--l-col); margin-inline: auto; }
.l-foot__l { display: flex; flex-direction: column; gap: 10px; }
.l-foot a:hover { color: var(--accent-text, var(--accent)); }

/* ═══ RESPONSIVE ═══════════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
  :root { --l-h1: 40px; --l-h1-lh: 48px; --l-h1-ls: -1.1px;
          --l-lead: 25px; --l-lead-lh: 34px; --l-lead-ls: -.6px;
          --l-body: 18px; --l-body-lh: 27px; --l-num: 40px; --l-num-lh: 40px;
          --l-band: 84px; --l-band-s: 62px; --l-gut: 22px; }
  .l-split { grid-template-columns: 1fr; gap: 34px; }
  .l-stats { grid-template-columns: repeat(2, 1fr); }
  .l-rail { display: none; }
  .l-stack__c { grid-template-columns: 1fr; }
  .l-foot__g { grid-template-columns: 1fr; }
  .l-cards--3 { grid-template-columns: 1fr; }
  /* THE MOBILE MENU.
     This line used to be the whole story: the links were hidden at 900px
     and nothing replaced them, so a phone got a header with one link in it
     — the logo. The rail is display:none here too and the command bar's
     only trigger is ⌘K, so the site's entire navigation was unreachable on
     the device most people would open it on. */
  .l-nav__links { display: none; }
  .l-nav__burger { display: inline-flex; }

  /* TWO COLUMNS AT MOBILE, NOT THREE. The bar is a 1fr auto 1fr grid, and
     with the links hidden the CTA still claimed the third column while the
     burger claimed a fourth — measured at 375px the CTA ran to x400 and the
     burger to x438, both off a 375 screen. The page did not scroll sideways
     only because the nav is fixed and clipped, so the buttons were simply
     GONE with nothing to show they had ever been there.

     The CTA is not lost: Open EMBER is the last item in the menu panel. */
  .l-nav__group { grid-template-columns: 1fr auto; padding: 10px 14px; }
  .l-nav__cta { display: none; }

  /* THE SIDE SCRIM HAS TO CROSS THE WHOLE FRAME HERE.
     Its rule above says, correctly, that the copy is "a 544px LEFT column"
     and so the gradient is clear by 64% and the right two thirds of the
     picture are untouched. That premise is true at 1440px and false at 375,
     where the copy is the full width of the screen.

     Measured on the composited pixels — every clip, six frames each, worst
     value per box:

                       eye    sub    hint
       hero-avenue     2.76   2.31   2.62
       hero-crayon     3.91   3.36   2.87
       hero-desk       4.58   4.74  11.52
       hero-pen        5.08   4.47   2.61

     Nine of twelve under the bar, on the first screen of the site. One clip
     and one frame had said 8.49 and passed, which is how it survived: the
     hero rotates four clips and the sampler had only ever seen one.

     Carrying the scrim to the far edge at .50 puts the worst cell at 7.13.
     Desktop is untouched — it keeps the 64% clear point and its vivid right
     two thirds. */
  [data-v="c"] .l-hero__media::before {
    background: linear-gradient(90deg,
                rgba(28,19,12,.72) 0%, rgba(28,19,12,.62) 50%, rgba(28,19,12,.50) 100%);
  }
}
@media (max-width: 560px) {
  :root { --l-h1: 32px; --l-h1-lh: 39px; --l-lead: 21px; --l-lead-lh: 29px; }
  .l-stats { grid-template-columns: 1fr; }
  .l-reel__f { width: 230px; }
}
@media (prefers-reduced-motion: reduce) {
  .l-reel__t, .l-globe__spin { animation: none; }
  * { transition-duration: var(--l-instant) !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE CONTEXTUAL BLOCKS
   Added 2026-08-22. Nothing above this line was changed.

   The three landings had six or seven sections each; the Site Bible Part I §4
   has twelve cards. These are the components the missing six need, written in
   the l- language rather than borrowed from blocks.css — the two systems have
   different scales, different faces and different grounds, and pulling a card
   across would have made a landing that is two designs stapled together.

   Every one of these reads --g/--g2/--fg/--fg2/--line/--accent/--card, so it
   takes version A's white, B's near-black and C's cream without a per-version
   rule. That is the whole reason the versions are a token swap.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --l-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
  --l-mono-sz: 13px;
  --l-mono-lh: 22px;
}

/* ── THE FACT RAIL ──────────────────────────────────────────────────────────
   Part I §1: if you cannot say what an element reports, delete it. This one
   reports. It is the survey-document move and it appears on most cards.    */

.l-facts { border-top: 1px solid var(--line); margin-top: 34px; }
.l-facts__r {
  display: grid; grid-template-columns: 150px 1fr; gap: 16px;
  padding: 12px 0; border-bottom: 1px solid var(--line);
  font-family: var(--l-mono); font-size: 12px; line-height: 19px;
}
.l-facts__k { color: var(--fg2); text-transform: uppercase; letter-spacing: 1px; }
.l-facts__v { color: var(--fg); }

/* ── THE LIMIT ──────────────────────────────────────────────────────────────
   A claim's boundary, always immediately after the claim.

   The Bible calls this the site's signature move and asks for it at least six
   times across the site. Nobody else publishes where their own claim stops,
   which is exactly why it generates more trust than the claim did. It carries
   data-limit so it can be COUNTED rather than merely written.             */

.l-limit {
  border-left: 2px solid var(--fg);
  padding: 2px 0 2px 24px; margin-top: 32px; max-width: 62ch;
}
.l-limit__k {
  display: block; margin-bottom: 10px;
  font-family: var(--l-mono); font-size: 11px; letter-spacing: 1.4px;
  text-transform: uppercase; color: var(--fg2);
}
.l-limit p { font-size: 15px; line-height: 24px; color: var(--fg); }
.l-limit p + p { margin-top: 13px; }

/* ── THE SPEC TABLE ─────────────────────────────────────────────────────────
   Monospace, hairline rules, NO FILLS. A spec table that is striped is a
   marketing table.                                                        */

.l-spec { width: 100%; border-collapse: collapse; font-family: var(--l-mono); }
.l-spec caption { text-align: left; padding-bottom: 14px; color: var(--fg2); font-size: 13px; }
.l-spec th, .l-spec td {
  text-align: left; padding: 13px 20px 13px 0; vertical-align: top;
  border-bottom: 1px solid var(--line);
  font-size: var(--l-mono-sz); line-height: var(--l-mono-lh); font-weight: 400;
}
.l-spec th { color: var(--fg2); text-transform: uppercase; letter-spacing: 1px; font-size: 11px; }
.l-spec td { color: var(--fg2); }
.l-spec td:first-child { color: var(--fg); width: 38%; }
.l-spec tr:last-child td { border-bottom: 0; }
/* The four routed inboxes in the door section are links inside table cells,
   and a bare inline <a> is only as tall as its line box — measured 14px at
   375, under WCAG 2.5.8's 24px minimum. These sit OUTSIDE .foot2, so the
   footer's target rule never reached them; they were the last four on the
   landing. inline-block plus 5px takes them to 24 without moving the text,
   and the row already carries its own cell padding so the table rhythm is
   unchanged. */
.l-spec td a { display: inline-block; padding-block: 5px; }

/* ── THE CONTROL PLANE ──────────────────────────────────────────────────────
   Scroll-bound and POSITION-locked, so scrolling back runs it backward. That
   one property is what makes it read as an instrument rather than a GIF, and
   it is why it is a view() timeline and not a scroll listener.            */

.l-diag { margin-top: 56px; }
.l-diag svg { width: 100%; height: auto; overflow: visible; }
.l-diag__box { fill: var(--card); stroke: var(--line); }
.l-diag__t { font-family: var(--l-mono); font-size: 11px; fill: var(--fg); letter-spacing: .6px; }
.l-diag__k { font-family: var(--l-mono); font-size: 9.5px; fill: var(--fg2); letter-spacing: .8px; }
.l-diag__l { stroke: var(--line); fill: none; }
.l-diag__l--heavy { stroke: var(--fg); stroke-width: 1.5; }
/* ══════ BEGIN HEAT USE 2 OF 3 — the task descending the control plane ═════
   One moving warm dot in a diagram drawn entirely in hairlines. It is the
   only thing on that diagram that is not structure, which is the point: the
   boxes are the system and the warm dot is the question travelling through
   it. Bound to a view() timeline, so it runs backward when you scroll back. */
.l-diag__tok { fill: var(--heat); }
/* ══════ END HEAT USE 2 OF 3 ══════════════════════════════════════════════ */
.l-diag__n { cursor: pointer; }
.l-diag__n:hover .l-diag__box, .l-diag__n:focus-visible .l-diag__box { stroke: var(--accent); }
.l-diag__out { margin-top: 16px; font-size: 14px; line-height: 22px; color: var(--fg2); min-height: 44px; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .l-diag__tok {
      animation: l-descend linear both;
      animation-timeline: view();
      animation-range: entry 40% exit 55%;
    }
    @keyframes l-descend {
      from { transform: translate3d(0, 0, 0); }
      to   { transform: translate3d(0, var(--l-drop, 340px), 0); }
    }
  }
}

/* ── THE PEOPLE ─────────────────────────────────────────────────────────────
   Three columns. Not customers.                                           */

.l-three { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 52px; }
.l-three__c {
  border: 1px solid var(--card-line); border-radius: var(--l-r-lg);
  background: var(--card); padding: 30px;
  display: flex; flex-direction: column; gap: 12px;
}
.l-three__h { font-size: 24px; line-height: 30px; letter-spacing: -.5px; font-weight: var(--l-w-med); }
.l-three__d { font-size: 15px; line-height: 24px; font-weight: 500; color: var(--fg); }
.l-three__b { font-size: 15px; line-height: 24px; color: var(--fg2); }

/* ── THE FOUNDER ────────────────────────────────────────────────────────────
   Four sentences. Do not write five. Black and white, actual light, looking
   into the lens — a photograph that could be in a newspaper.              */

.l-fnd { display: grid; grid-template-columns: 5fr 7fr; gap: 64px; align-items: center; }
.l-fnd__frame {
  aspect-ratio: 4/5; border: 1px solid var(--card-line); border-radius: var(--l-r-lg);
  background: var(--card); display: grid; place-items: center;
  text-align: center; padding: 30px;
}
.l-fnd__frame img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--l-r-lg); filter: grayscale(1); }
.l-fnd p + p { margin-top: 18px; }

/* ── THE WORDMARK ───────────────────────────────────────────────────────────
   Kevin asked for this twice: big, bold, prominent, clean, modern, X in
   orange. .l-mark--big was styled in the first landing pass and then placed
   on none of the three — the CSS arrived and the markup never did. This is
   the band it lives in.

   The X is var(--heat), which is landing.css's own token. It is NOT
   var(--amber), so it does not touch the three-use amber budget that
   tools/colour-budget.mjs counts across site.css and blocks.css.         */

.l-word { text-align: center; overflow: hidden; }
.l-word .l-mark--big { display: block; }
.l-word__sub {
  margin: 26px auto 0; max-width: 46ch;
  font-size: 15px; line-height: 24px; color: var(--fg2);
}

/* ── THE DOOR ───────────────────────────────────────────────────────────────
   One action, one line of context. Never two buttons where the second is a
   ghost.                                                                   */

.l-door { display: grid; grid-template-columns: 7fr 5fr; gap: 64px; align-items: start; }
.l-door__c { font-size: 15px; line-height: 24px; color: var(--fg2); max-width: 52ch; }

/* ── RESPONSIVE ─────────────────────────────────────────────────────────── */

@media (max-width: 1080px) {
  .l-three { grid-template-columns: 1fr; gap: 14px; margin-top: 36px; }
  .l-fnd, .l-door { grid-template-columns: 1fr; gap: 32px; }
  .l-fnd__frame { max-width: 300px; }
  .l-facts__r { grid-template-columns: 1fr; gap: 2px; }
  .l-limit { padding-left: 18px; margin-top: 26px; }
}
@media (max-width: 560px) {
  .l-spec th, .l-spec td { padding-right: 12px; font-size: 12px; }
  .l-spec td:first-child { width: 46%; }
}

/* ── THE SUGGESTION CHIPS ───────────────────────────────────────────────────
   The hint line under the command bar used to be a sentence. It is now four
   buttons, because a sentence telling you that you may ask anything is not
   the same as four things you can click and see answered.                 */
.l-cmd__sug {
  appearance: none; cursor: pointer;
  border: 1px solid var(--card-line); background: var(--card);
  color: var(--fg); font: inherit; font-size: 13px; line-height: 1;
  padding: 8px 13px; border-radius: 100px; margin: 0 2px 6px 0;
  transition: border-color var(--l-quick) var(--l-ease), transform var(--l-quick) var(--l-out);
}
.l-cmd__sug:hover { border-color: var(--accent); transform: translateY(-1px); }



/* ── RESTORED WITH THE ABOVE ───────────────────────────────────────────────

   THE HINT. 07-THE-PERFORM-LOCK.md §7.3: it went from 62% white to 86%,
   because 62% measured 3.69:1 on the crayon clip and 86% measures 5.42:1 at
   worst across all four. It was buying de-emphasis with contrast it did not
   have to spend; size does the same job for free. */
.l-cmd__hint {
  margin-top: 14px; font-size: 13px; line-height: 20px;
  /* FULL WHITE, not 86%. S3 raised this from 62% to 86% and measured
     5.42-7.28 against the grade it had. Measured again after the grade was
     restored, the pen clip puts it at 4.28 against a 4.5 bar — because low in
     the hero the vertical grade is interpolating toward the page's cream, and
     white on cream is the worst pairing on the page. The de-emphasis this
     colour was buying is already carried by 13px against a 72px headline, so
     it was being paid for twice and only one of the two was free. */
  color: var(--on-media);
}

/* THE LIT SENTENCE. Lit is the BASE and the dim state exists only inside the
   query where an animation will lift it — the other way round left every
   browser without a view() timeline, and every reader with reduced motion,
   reading the sentence at 26% forever. 47% is the first floor that clears
   3:1 on all three grounds. */
/* 24px IS A CONTRAST FLOOR HERE, NOT A TYPE CHOICE. The unlit state below is
   solved to 3:1, and 3:1 is the bar for LARGE text — which at weight 500
   means 24px. At the narrow breakpoint --l-lead drops to 21px, the bar
   becomes 4.5:1, and the same 3.08 that is fine at 768, 1024 and 1280 fails
   at 375. The floor is put on this element rather than on --l-lead because
   it is this sentence contrast requires it, not every lead on the page. */
.l-lit { color: var(--fg); font-size: max(var(--l-lead), 24px);
         line-height: max(var(--l-lead-lh), 33px); }
.l-lit b { font-weight: inherit; color: var(--fg); }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* OPACITY, NOT COLOR — and the two are the same picture here.
       Text at color-mix(--fg 47%, transparent) and text at color:--fg with
       opacity:.47 composite to identical pixels, because both are the same
       source colour at the same alpha over the same ground. They are not the
       same to the compositor: color animates on the main thread and repaints
       every affected glyph on every frame of the scroll, while opacity is
       promoted and runs off it.

       This was here before the landing was inside the gate and the gate could
       not see it — parallax-budget reads the stylesheets the PAGE loads, and
       until C became index.html this file was not one of them. Bringing the
       landing in surfaced it on the first run. */
    .l-lit span {
      color: var(--fg);
      /* .48, not .47. #thesis moved onto the new SAND ground, which is darker
         than the cream the 47% was solved against, and the unlit state fell to
         2.99 against a 3.00 bar. 48% clears on all three grounds this sentence
         can now sit on: cream 3.14, sand 3.08, sheet 3.20. A floor solved for
         one ground stops being a floor the moment a section is re-toned. */
      opacity: .48;
      animation: lit linear both;
      animation-timeline: view();
      animation-range: cover 24% cover 62%;
    }
    @keyframes lit { to { opacity: 1; } }
  }
}

@media (max-width: 1080px) {
  /* No inset below the rail's breakpoint — the rail is hidden there, so the
     152.5px existed only to clear something that is no longer on screen.
     ZERO, not var(--l-gut): .l-hero already pads by the gutter, and setting
     it again here put the column at 44px on a 375 screen instead of 22. */
  .l-hero__inner { padding-left: 0; }
}

/* ═══ ADOPTED FROM site.css ════════════════════════════════════════════════
   index.html used to load the whole of site.css to get these two classes and
   inherited a second, competing .skip rule along the way. They live here now
   and the landing loads one stylesheet.                                    */

/* .vh is declared once, at the top of this file with the other resets. */

/* An unresolved fact, marked so gate.sh can keep the ledger. */
/* .foot2 .tk USED TO BE IN THIS SELECTOR and has moved to footer.css,
   which all fifteen pages link — here it only ever reached index.html while
   the other fourteen footers rendered at 3.08:1. What is left is the case
   footer.css cannot see: an ink-TONED BAND on this page, which is a landing
   construct and belongs to this sheet. */
/* [data-tone="ink"] .tk USED TO BE HERE. The shared .tk in footer.css is
   built on currentColor, so an ink-toned band gives it the right colour
   without being told. */

/* ═══ THE FOOTER ═══════════════════════════════════════════════════════════
   Bible Part I, Card 12 specifies this shape, and the other thirteen pages
   carry the same markup byte-for-byte so gate.sh check 8c can compare them.
   C had its own .l-foot, which predates the Bible.

   The markup is shared; the dressing is this page's own. That is the whole
   trick to unifying a site that is mid-migration — one structure, styled per
   design system, rather than one structure forced into one look.          */

/* THE FOOTER now lives in footer.css — see the note at the top of that
   file. It is shared by all fifteen pages and only one of them loads this
   stylesheet. */

.foot2 a { transition: color var(--l-quick) var(--l-ease); }
.foot2 a:hover { color: var(--accent-text, var(--accent)); }
.foot2__b { display: flex; flex-wrap: wrap; gap: 8px 30px;
            margin-top: 52px; padding-top: 24px; border-top: 1px solid var(--line); }

/* The noscript nav — the only navigation a reader without scripting gets,
   because both the bar and the rail are rendered by landing.js. */
.ns { display: flex; flex-wrap: wrap; gap: 20px; padding: 18px var(--l-gut);
      border-bottom: 1px solid var(--line); font-size: 14px; }
.ns a { color: var(--fg); text-decoration: none; }

@media (max-width: 900px) {
  .foot2__g { grid-template-columns: 1fr 1fr; gap: 30px; }
}
@media (max-width: 560px) {
  .foot2__g { grid-template-columns: 1fr; }
}


/* ═══ THE SCROLLBAR ════════════════════════════════════════════════════════
   Kevin: "the bar on the right side of the page to scroll, this needs to be
   overhauled into something ten times more aesthetic, probably something
   that's translucent, semi-translucent."

   Translucent, so the page shows through it, and inset from the edge by its
   own border so it reads as a thumb on a track rather than as a slab welded
   to the viewport. The track itself is left transparent — a visible groove is
   the part that makes a default scrollbar look like machinery.

   Both syntaxes, deliberately. scrollbar-color is the standard and takes a
   flat pair; ::-webkit-scrollbar is what actually ships in Chromium and can
   take the translucency and the inset. Neither alone covers the browsers this
   site is measured in. */
html {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--fg) 26%, transparent) transparent;
}
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--fg) 22%, transparent);
  border-radius: 100px;
  border: 3px solid transparent;
  background-clip: content-box;
  transition: background var(--l-quick) var(--l-ease);
}
::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--fg) 42%, transparent);
  background-clip: content-box;
}
::-webkit-scrollbar-corner { background: transparent; }


/* ═══════════════════════════════════════════════════════════════════════════
   S5 · THE FACELIFT
   Kevin: "The context is perfect. But the UI could be so much more. We have
   too many things." · "You shouldn't be recreating just about anything."
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── THE COMPANY — the Harvest numbered timeline ───────────────────────────
   A vertical rail with a rotated label, numbered nodes on a hairline, each
   reaching by a short dash to a dark image card with the copy set OVER the
   photograph.

   The numbering earns itself here, which is the test: 1, 2, 3 is the
   dependency order — EMBER is what a student holds, Aria is what makes EMBER
   possible, the Arc is where it has an address. Renumber them and the
   sentence stops being true, which is the difference between a sequence and
   three bullets wearing digits.                                            */

/* The scope. It was in the markup with no rule of its own — my own
   both-halves-or-none rule, broken in the same session I wrote it down.
   It carries the section's rhythm so the head, the steps and the foot are
   spaced by one decision rather than three margins. */
.l-tl { display: flex; flex-direction: column; }

.l-tl__head {
  display: grid; grid-template-columns: 1.1fr 1fr; gap: 56px; align-items: end;
  padding-bottom: 56px;
}
.l-tl__head .l-h1 { margin: 0; }
.l-tl__head .l-body { margin: 0; }

.l-tl__body { display: grid; grid-template-columns: 64px 1fr; }

/* the rail: one hairline, and the label rotated onto it */
.l-tl__rail { position: relative; }
.l-tl__rail::before {
  content: ''; position: absolute; left: 50%; top: 0; bottom: 0;
  width: 1px; background: var(--line);
}
.l-tl__label {
  position: absolute; left: 50%; top: 0;
  transform: translateX(-50%) rotate(180deg);
  writing-mode: vertical-rl;
  font-family: var(--l-mono); font-size: 9px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--accent-text, var(--accent));
  padding: 9px 3px; border: 1px solid var(--line); border-radius: 3px;
  background: var(--g);
}

.l-tl__steps { display: flex; flex-direction: column; gap: 22px; }
.l-tl__row { display: grid; grid-template-columns: 64px 1fr; align-items: center; }

/* the node sits ON the rail, so it needs the page's ground behind it to cut
   the hairline rather than sit on top of it */
.l-tl__node { position: relative; display: flex; align-items: center; margin-left: -64px; }
.l-tl__n {
  width: 22px; height: 22px; flex: none; margin-left: 21px;
  display: grid; place-items: center; border-radius: 50%;
  border: 1px solid var(--line); background: var(--g);
  font-family: var(--l-mono); font-size: 10px; color: var(--accent-text, var(--accent));
}
.l-tl__dash { flex: 1; height: 1px; background: var(--line); }

.l-tl__card {
  position: relative; margin: 0; overflow: hidden;
  border-radius: var(--l-r-lg); aspect-ratio: 21/7; min-height: 168px;
  background: var(--fg);
}
.l-tl__card img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: .58;
}
/* the copy sits over the photograph, so it needs its own ground — a scrim
   from the left, exactly the move the hero uses, for the same reason */
.l-tl__card figcaption {
  position: relative; z-index: 1; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-start;
  gap: 8px; padding: 26px 30px; max-width: 46ch;
  background: linear-gradient(90deg,
              rgba(10,7,4,.78) 0%, rgba(10,7,4,.52) 46%, rgba(10,7,4,0) 82%);
}
.l-tl__t {
  margin: 0; font-size: 25px; line-height: 1.15; letter-spacing: -.02em;
  font-weight: 700; color: var(--on-media);
}
.l-tl__s {
  margin: 0; font-size: 14px; line-height: 21px;
  color: color-mix(in srgb, var(--on-media) 82%, transparent);
}

.l-tl__foot {
  display: grid; grid-template-columns: 1.1fr 1fr; gap: 56px;
  margin-top: 64px; padding-top: 40px; border-top: 1px solid var(--line);
}
.l-tl__foot .l-lead { margin: 0; max-width: 18ch; }


/* ── IMAGES ON THE SUBSIDIARY STACK ────────────────────────────────────────
   Kevin: "the what we build section is missing images on those little cards.
   That would help a lot."

   The card was a two-column flex — copy on the left, status tag on the right.
   The image goes in front of both as a fixed-width plate, so the card becomes
   [ photo | copy | tag ] and the tag stays where it was.                   */

/* The card is a GRID at 1fr auto — [copy][tag] — not a flex row. Adding the
   image as a third child pushed the tag onto its own line and it rendered
   full-width under the photo. :has() lets the card know it has one and open a
   third column, so a card WITHOUT an image keeps the original two and nothing
   needs a modifier class in the markup. */
.l-stack__c:has(.l-stack__img) { grid-template-columns: auto 1fr auto; gap: 28px; }

/* The middle track is 1fr and STILL would not shrink, because a grid item's
   default min-width is auto — the width of its longest unbreakable content.
   At 375px that pushed the status tag 38px past the card on "In build" and
   64px past on "Planned", where the band's overflow:hidden cut the label in
   half. 1fr does not mean "may shrink" without this. */
.l-stack__c > div { min-width: 0; }

.l-stack__img {
  width: 148px; height: 100px; flex: none;
  object-fit: cover; border-radius: var(--l-r-md);
  align-self: center;
}

@media (max-width: 900px) {
  /* The card has to become ONE column first. width:100% inside an `auto`
     track is circular — the track sizes to the item and the item sizes to
     the track — and it resolved to 0px, so every one of these cards lost
     its image at mobile while the rule that was supposed to enlarge it sat
     right here looking correct. */
  .l-stack__c:has(.l-stack__img) { grid-template-columns: 1fr; gap: 16px; }
  .l-stack__img { width: 100%; height: 132px; }
  .l-stack__tag { justify-self: start; }
  .l-tl__head, .l-tl__foot { grid-template-columns: 1fr; gap: 24px; }
  .l-tl__body { grid-template-columns: 34px 1fr; }
  .l-tl__row { grid-template-columns: 34px 1fr; }
  .l-tl__node { margin-left: -34px; }
  .l-tl__n { margin-left: 6px; }
  .l-tl__card { aspect-ratio: auto; min-height: 0; }
  .l-tl__card figcaption { max-width: none; padding: 20px 22px;
    background: linear-gradient(180deg, rgba(10,7,4,.30) 0%, rgba(10,7,4,.80) 100%); }
  .l-tl__label { display: none; }
}


/* ── THE HAIRLINES — Interlinea ────────────────────────────────────────────
   Kevin: "you see how it has lines running throughout the page? I think
   that's very unique for specific sections, not for the entire thing."

   Four verticals on the 1240 column, painted as one repeating-linear-gradient
   on the section rather than as four elements, so it costs one paint and no
   DOM. They sit BEHIND everything and stop before the section's own padding,
   which is what keeps them reading as a drawing grid rather than as a table.

   Opt-in per section with data-lines, exactly as he asked — specific
   sections, not the entire thing.                                          */

[data-lines] { position: relative; }
[data-lines]::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  max-width: var(--l-col); margin-inline: auto;
  background-image: repeating-linear-gradient(90deg,
    var(--line) 0 1px, transparent 1px calc(100% / 4));
  background-size: 100% 100%;
  opacity: .55;
}
[data-lines] > * { position: relative; z-index: 1; }
@media (max-width: 900px) { [data-lines]::before { display: none; } }


/* ═══════════════════════════════════════════════════════════════════════════
   S6a · NO HARD EDGES, MORE GROUNDS, ICONS EVERYWHERE
   Kevin: "make sure there are no hard line transitions its a bump or wave or
   clouded transition or something with depth and shadow" · "rounded off
   (preferred)… like the reference picture from serena"
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── THE GROUNDS ───────────────────────────────────────────────────────────
   Four, not two. Declared as tokens on the section so a band can be re-toned
   by changing one attribute rather than by hunting an inline style — the six
   inline `background:var(--g2)` declarations this replaces were exactly the
   thing that made "give us a few more coloured sections" a chore.          */

/* THE SELECTOR IS DOUBLED ON PURPOSE.

   [data-tone] and .l-band are both specificity 0,1,0, and .l-band is declared
   51 lines LATER — so  won on source order and the
   ink band kept the cream ground while its TEXT tokens flipped to the ink
   set. Near-white type on cream: the headline was invisible and nothing in
   either rule looked wrong.

   Repeating the attribute takes it to 0,2,0 without changing what it matches,
   so a tone now beats the plate wherever it is written and whatever gets
   inserted between them later. Ordering would have fixed it today and broken
   again the next time a block is appended. */
[data-tone="sheet"][data-tone="sheet"] { background: var(--g2); }
[data-tone="sand"][data-tone="sand"] {
  /* between the cream and the white — warm enough to read as a different
     room, close enough that the curve between them is a fold rather than a
     border. */
  background: var(--g3);
  --card: var(--card-sand);
  --card-line: var(--line-sand);
}
[data-tone="ink"][data-tone="ink"] {
  /* var(--g-ink), NOT var(--fg).

     A custom property referenced inside the same block that REDEFINES it
     resolves to the NEW value, not the inherited one. So "background:
     var(--fg)" in a block that also sets "--fg: var(--on-ink)" painted the
     band in its own TEXT colour — near-white type on a near-white ground,
     measuring 1.00:1, while the source read as if it said the opposite.

     Found by the contrast walker reporting a GROUND of rgb(244,241,234),
     which is a text token and cannot be a ground. When a tool names a
     surprising ground, the ground is the thing to look at. */
  background: var(--g-ink);
  --fg: var(--on-ink); --fg2: var(--on-ink-mut);
  --line: var(--line-ink);
  --card: var(--card-ink); --card-line: var(--line-ink);
  /* THE BRIGHT bronze, not the text one: on ink #8A6034 is 3.23:1 and
     #B68A5E is 5.76:1. The token flips with the ground, which is the whole
     point of having a token for it. */
  --accent-text: var(--accent);
  color: var(--on-ink);
}
/* the ink band's own headline and eyebrow have to flip with it */
[data-tone="ink"] .l-h1, [data-tone="ink"] .l-lead { color: var(--on-ink); }

[data-tone="ink"] .l-small, [data-tone="ink"] .l-muted { color: var(--on-ink-mut); }

[data-tone="ink"] .l-spec td:first-child { color: var(--on-ink); }
[data-tone="ink"] .l-spec td { color: var(--on-ink-mut); }
[data-tone="ink"] .l-spec th, [data-tone="ink"] .l-spec td { border-bottom-color: var(--line-ink); }


/* ── THE PLATES ───────────────────────────────────────────────────────────
   Kevin: "make it spaced out and make the space between them a soft very
   faded pastel color".

   Every band is a rounded plate carrying its own ground, with a real gap
   beneath it, and the page behind them is a faded pastel. NO TWO SURFACES
   TOUCH — so there is no transition left to get wrong. The gap is the
   transition.

   This replaces dome/rise, which overlapped two curves with a negative
   margin and left a visible crescent of the upper band showing under the
   lower one. It read as a mistake because it was one.

   It also removes a constraint the curves imposed: a plate carries its own
   ground, so re-toning one band no longer has to negotiate with the curves
   of the two bands either side of it.                                     */

body { background: var(--gap); }

.l-band {
  border-radius: var(--plate-r);
  margin: 0 var(--plate-x) var(--plate-gap);
  background: var(--g);
  box-shadow: 0 22px 48px -34px var(--fold-shadow);
  position: relative;
  /* CLIP, NOT HIDDEN. overflow:hidden makes the band a scroll container, and
     a scroll container is the nearest scrollport for everything inside it —
     so every position:sticky descendant sticks to a box that never scrolls,
     which means it never sticks at all. The what-we-build cards have been
     declared sticky this whole time and moved -500px for every 500px of
     scroll. Kevin: "the cards don t stack into each other." They were never
     stacking; the rule was inert.

     overflow:clip clips identically and does NOT create a scroll container,
     so sticky resolves against the page again. */
  overflow: clip;
}
/* the hero is the top of the page, so it keeps a square top edge and a
   larger curve at the bottom — the one place Serena's shape still applies,
   because there is nothing above it for a curve to collide with */
.l-hero {
  border-radius: 0 0 var(--plate-r-hero) var(--plate-r-hero);
  margin: 0 0 var(--plate-gap);
  overflow: hidden;
}
/* the last plate carries the footer under it, so it does not need a gap */
.l-band:last-of-type { margin-bottom: 0; }



@media (max-width: 900px) {
  .l-band { margin-inline: 10px; border-radius: 22px; }
  .foot2 { margin-inline: 10px; border-radius: 22px 22px 0 0; }
  .l-hero { border-radius: 0 0 26px 26px; }
}

/* ── THE ICON SET ──────────────────────────────────────────────────────────
   Modulars' process cards are the reference: a number, a thin line drawing,
   a title, a paragraph. One register across every icon — 1.4px stroke, no
   fill, 24-grid, round joins — so three unrelated subjects read as one
   family. Kevin: "everything should have an icon".                        */

.l-ico {
  width: 34px; height: 34px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
  color: var(--accent-text, var(--accent));
  opacity: .9;
}

.l-card--who { display: flex; flex-direction: column; gap: 0; overflow: hidden; padding: 0; }
.l-card--who .l-card__img {
  width: 100%; aspect-ratio: 16/10; object-fit: cover; display: block;
  border-radius: var(--l-r-md) var(--l-r-md) 0 0;
}
.l-card__top {
  display: flex; align-items: center; justify-content: space-between;
  padding: 22px 26px 0;
}
.l-card__no {
  font-family: var(--l-mono); font-size: 12px; letter-spacing: .14em;
  color: var(--fg2);
}
.l-card--who .l-card__k { padding: 16px 26px 0; }
.l-card--who .l-card__t { padding: 4px 26px 0; }
.l-card--who .l-body { padding: 10px 26px 26px; margin: 0; }


/* ── THE EARTH ─────────────────────────────────────────────────────────────
   Kevin: "it has to take over that entire section. That entire section."

   So the section is the globe. The copy sits ON it, bottom-left, the way the
   reference does — not beside it in a column. The stage is bigger than the
   plate and the plate crops it, which is what makes the planet read as
   continuing past the frame rather than sitting inside it.                */

.l-earth {
  padding: 0;
  background: var(--g-space);
  /* MORE REAL ESTATE. Kevin: "make the window just a tad bit bigger so it
     can take up more real estate." 92vh/900 -> 100vh/1120, which on a
     laptop is the whole screen and on a desktop is a full stop rather than
     a panel you scroll past. The globe is sized off H, so the sphere grows
     with it and the limb crossings stay inside the frame. */
  min-height: min(100vh, 1120px);
  display: grid;
  isolation: isolate;
}
.l-earth__stage { position: absolute; inset: 0; z-index: 0; }
/* THE COPY SCRIM. White type over a satellite composite meets whatever the
   planet happens to be showing — and the Sahara at noon is the brightest
   thing in the texture. The same move the hero uses: darken where the words
   are, leave the picture alone. Clear by 62%, so two thirds of the planet is
   untouched. */
.l-earth::before {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    /* weighted to the BOTTOM-left where the copy is, and light across the
       top so the limb reads instead of being swallowed by its own scrim */
    linear-gradient(105deg, rgba(6,9,16,.80) 0%, rgba(6,9,16,.46) 30%, rgba(6,9,16,0) 56%),
    linear-gradient(0deg, rgba(6,9,16,.72) 0%, rgba(6,9,16,.16) 42%, rgba(6,9,16,0) 66%);
}
.l-earth__stage canvas { display: block; width: 100%; height: 100%; }

/* if the texture ever fails to load, the section says so rather than
   rendering an empty black band nobody can explain */
.l-earth__stage[data-earth-failed]::after {
  content: 'The Earth texture did not load.';
  position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  font-family: var(--l-mono); font-size: 12px; color: var(--on-ink-mut);
}

.l-earth__copy {
  position: relative; z-index: 1; align-self: end;
  max-width: 560px;
  margin: 0 auto 0 clamp(24px, 7vw, 96px);
  padding: 0 0 clamp(48px, 8vh, 92px);
  color: var(--on-ink);
}
.l-earth__copy .l-h1 { color: var(--on-ink); margin-top: 14px; }
.l-earth__copy .l-body { color: rgba(240,244,250,.82); margin-top: 20px; }
/* ══════ BEGIN HEAT USE 3 OF 3 — the Arc, on space ═══════════════════════
   The eyebrow over the planet, and the Chicago pin drawn on the canvas
   beside it. One use, two surfaces: the first Beacon and the label that
   names what it is.

   Raised from two uses to three on 2026-08-22 with the Earth section, which
   is the only near-black ground on the landing and the one place a warm mark
   has real work to do — on deep space the bright bronze is 4.1:1 and the
   heat is 8.9:1.

   THE CANVAS HALF IS NOT VISIBLE TO THIS TOOL. landing.js paints the Chicago
   pin rgba(232,168,124) directly, and a stylesheet scanner cannot see a
   colour inside a script. It is recorded here so the count is honest even
   though only half of it can be enforced. */
.l-earth__copy .l-eye { color: var(--heat-lift); }
/* ══════ END HEAT USE 3 OF 3 ═════════════════════════════════════════════ */
.l-earth__copy .l-limit { border-left-color: rgba(240,244,250,.42); margin-top: 30px; }
.l-earth__copy .l-limit p { color: rgba(240,244,250,.86); }
.l-earth__copy .l-limit__k { color: rgba(240,244,250,.62); }

.l-earth__facts { display: flex; gap: 44px; margin-top: 32px; flex-wrap: wrap; }
.l-earth__facts > div { display: flex; flex-direction: column; gap: 3px; }
.l-earth__n {
  font-family: var(--l-mono); font-size: 30px; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--on-ink);
}
.l-earth__facts .l-small { color: rgba(240,244,250,.68); }

@media (max-width: 900px) {
  .l-earth { min-height: 78vh; }
  .l-earth__copy { margin-inline: 22px; padding-bottom: 40px; max-width: none; }
  .l-earth__facts { gap: 26px; }
}


/* ── THE STAT ROW — Vantara ────────────────────────────────────────────────
   08-vantara-hub's location strip: an icon, a figure, a label. Ours adds one
   line under each saying HOW the figure is known, because a number without a
   method is the thing this whole section exists to argue against.

   Four columns that collapse to two, not to one — a stat row that becomes a
   list stops being a row and the comparison goes with it.                 */

.l-stats2 {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px;
  margin-top: 56px;
  border: 1px solid var(--line); border-radius: var(--l-r-lg);
  overflow: hidden; background: var(--line);
}
.l-stat2 {
  display: flex; flex-direction: column; gap: 8px;
  padding: 30px 28px 32px;
  background: var(--g-ink);
}
.l-ico--stat { width: 26px; height: 26px; color: var(--accent); margin-bottom: 6px; }
.l-stat2__n {
  font-family: var(--l-mono); font-size: 42px; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--on-ink);
}
.l-stat2__l { font-size: 15px; font-weight: 500; color: var(--on-ink); }
.l-stat2__s { font-size: 13px; line-height: 19px; color: var(--on-ink-mut); }

@media (max-width: 900px) {
  .l-stats2 { grid-template-columns: repeat(2, 1fr); }
  .l-stat2 { padding: 22px 20px 24px; }
  .l-stat2__n { font-size: 32px; }
}


/* ── THE TIERS — Vantara's floor-plan switcher ─────────────────────────────
   08-vantara-hub: a list on the left, one row selected as a filled pill, and
   the full spec in a card on the right. Villa A/B/C is Beacon/Arc/Node with
   different nouns and the same job — three variants of one thing, compared
   on numbers.                                                             */

.l-tier {
  display: grid; grid-template-columns: 1fr 1.15fr; gap: 28px;
  margin-top: 52px; align-items: start;
}
.l-tier__tabs { display: flex; flex-direction: column; gap: 10px; }

.l-tier__tab {
  display: grid; grid-template-columns: 1fr auto auto; align-items: center;
  gap: 8px 18px; width: 100%; text-align: left; cursor: pointer;
  padding: 18px 20px; border-radius: 100px;
  border: 1px solid var(--card-line); background: transparent;
  font-family: inherit; color: var(--fg);
  transition: background var(--l-quick) var(--l-ease),
              border-color var(--l-quick) var(--l-ease),
              color var(--l-quick) var(--l-ease);
}
.l-tier__nm { font-size: 16px; font-weight: 500; }
.l-tier__sz { font-family: var(--l-mono); font-size: 13px; color: var(--fg2); }
/* Two rows, three columns, and the chevron spans both rows in the third.
   Left to itself the arrow fell to a row of its own under the sub-line,
   because grid-column 1 / -1 on the sub had already consumed the row it would
   have sat in. Placement is explicit for every child — an auto-placed grid
   with one spanning child will move something the next time a child is
   added. */
.l-tier__nm  { grid-column: 1; grid-row: 1; }
.l-tier__sz  { grid-column: 2; grid-row: 1; }
.l-tier__sub { grid-column: 1 / 3; grid-row: 2; font-size: 12.5px; color: var(--fg2); }
.l-tier__tab svg {
  grid-column: 3; grid-row: 1 / 3; align-self: center;
  width: 18px; height: 18px; opacity: .45;
  transition: transform var(--l-quick) var(--l-ease), opacity var(--l-quick) var(--l-ease);
}
.l-tier__tab:hover svg { transform: translate3d(3px, 0, 0); opacity: .8; }

.l-tier__tab:hover { border-color: var(--accent); }
.l-tier__tab[aria-selected="true"] {
  background: var(--g-ink); border-color: transparent; color: var(--on-ink);
}
.l-tier__tab[aria-selected="true"] .l-tier__sz,
.l-tier__tab[aria-selected="true"] .l-tier__sub { color: var(--on-ink-mut); }
.l-tier__tab[aria-selected="true"] svg { opacity: 1; }

.l-tier__panel {
  border: 1px solid var(--card-line); border-radius: var(--l-r-lg);
  background: var(--card); padding: 34px 36px 30px;
}
.l-tier__h {
  font-size: 30px; line-height: 1.15; letter-spacing: -.02em;
  font-weight: 500; margin: 12px 0 14px;
}
.l-tier__panel .l-body { margin: 0 0 26px; }
.l-tier__panel .l-spec td:first-child { width: 42%; }

@media (max-width: 900px) {
  .l-tier { grid-template-columns: 1fr; }
  .l-tier__tab { border-radius: var(--l-r-md); padding: 15px 18px; }
  .l-tier__panel { padding: 24px 22px; }
}


/* ── THE MENU BUTTON AND PANEL ─────────────────────────────────────────────
   Two bars, not three: the wordmark beside it is already doing the work of
   saying where you are, and three bars at this size reads as a control panel
   rather than a menu. They cross into an X when open, which is the one piece
   of motion here that carries information rather than decorating.        */

.l-nav__bl {
  position: absolute; left: 11px; width: 16px; height: 1.5px;
  border-radius: 2px; background: var(--fg);
  transition: transform var(--l-quick) var(--l-out);
}
.l-nav__bl:nth-child(1) { transform: translateY(-3.5px); }
.l-nav__bl:nth-child(2) { transform: translateY(3.5px); }
.l-nav__burger[aria-expanded="true"] .l-nav__bl:nth-child(1) { transform: rotate(45deg); }
.l-nav__burger[aria-expanded="true"] .l-nav__bl:nth-child(2) { transform: rotate(-45deg); }

/* Absolute, anchored to the fixed .l-nav — NOT a flex child of it.
   .l-nav is display:flex, flex-direction:row, justify-content:center, so as
   a sibling of the pill the panel was laid out BESIDE it: a 113px column
   pinned right, 252px tall, starting at y -84 with its first link cut off
   above the top of the screen. It looked like a positioning tweak was
   needed; it was the wrong formatting context entirely. */
.l-nav__panel {
  position: absolute; top: 100%; left: 10px; right: 10px;
  display: flex; flex-direction: column;
  padding: 10px;
  border-radius: 20px; background: var(--g2);
  box-shadow: 0 18px 40px -28px var(--fold-shadow);
}
.l-nav__panel[hidden] { display: none; }
.l-nav__panel a {
  padding: 14px 16px; border-radius: 12px;
  font-size: 16px; color: var(--fg); text-decoration: none;
}
.l-nav__panel a:hover, .l-nav__panel a:focus-visible { background: var(--g); }

@media (min-width: 901px) {
  /* Belt and braces. The panel is only ever opened by a button that is
     display:none above 900px, but a viewport resized while it is open would
     otherwise leave it hanging under the pill. */
  .l-nav__panel { display: none; }
}


/* The shaped seams moved to edges.css, which every page can load. */

/* ══ THE OVERLAP ══════════════════════════════════════════════════════════
   Kevin, on five seams: "those need to actually touch ... there should not be
   any space between ... those should overlap, but with the same design", and
   three separate times: "overlap slightly, white on top".

   The pastel gap between plates is the default event on this page and it
   stays the default — it is what makes the page read as a stack of cards.
   These five are the exception. The lower plate climbs OVER the one above it,
   so the seam is a single curve instead of two curves with a ribbon of pastel
   trapped between them.

   WHY THE UPPER PLATE HAS TO GO FLAT. Two shaped edges facing each other do
   not tessellate. --w-top and --w-bot are near mirror images of one curve,
   not the same curve offset — where one bulges down the other bulges up. Slide
   them together and the seam crosses itself, leaking pastel through every
   place both are cut away. Only one of the pair can carry a shape. The LOWER
   one carries it: it is the one that paints on top (DOM order decides, both
   are position:relative with z-index auto), and "white on top" is the effect
   that was asked for.

   THE ARITHMETIC. The lower plate rises by exactly --edge, the height of the
   shaped strip. Its shape therefore lands wholly inside the upper plate's
   last 64px and can never ride above it — at its highest the wave crest sits
   60px up, at its lowest 4px, and both are ink either way. Never pastel.
   The upper plate's own bottom margin goes to zero so the two numbers do not
   collapse into something approximate.

   AND THE UPPER PLATE GETS ITS SPACE BACK. Rising 64px would otherwise park
   the new seam directly on top of whatever the upper band ends with — Kevin:
   "grow the Reservoir's cream block downward first so the overlap doesn't
   cover the photograph", and "extend the cream down so it doesn't cover the
   wording". Both are the same fix: the plate underneath an overlap grows at
   the bottom by --edge, which is precisely what the overlap took from it. */

[data-overlap] { margin-top: calc(var(--seam) * -1); }

.l-band:has(+ [data-overlap]) {
  margin-bottom: 0;
  padding-bottom: calc(var(--band-pad, 96px) + var(--seam) * 1.5);
}


/* ══ THE STACK RECEDES ════════════════════════════════════════════════════
   Sticky alone gets five cards pinned at the same top, perfectly overlapping
   — a stack you cannot see, because the front card hides the four behind it
   completely. What makes it read is the cards BEHIND shrinking and dimming
   as the next one arrives over them.

   THE TIMELINE IS ON THE CONTAINER, NOT THE CARD, and that is forced rather
   than chosen. `animation-timeline: view()` measures the element's own box
   against the scrollport — and a pinned element's box stops moving, so its
   progress freezes the instant the effect should start. The container is not
   sticky, so its progress keeps advancing the whole way down; each card then
   takes its own slice of that one timeline.

   Slices are 14% wide and butt up against each other, so card n finishes
   receding exactly as card n+1 starts. The fifth never recedes — it is the
   one left on top, and a stack whose last card also shrinks reads as a bug.

   Behind the same @supports + reduced-motion guard as the other four
   scroll-driven animations here. Without the timeline the cards still pin;
   they simply stack flat, which is the correct degraded state.           */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .l-stack { view-timeline-name: --l-stack; view-timeline-axis: block; }

    .l-stack__c {
      animation: l-recede linear both;
      animation-timeline: --l-stack;
      transform-origin: 50% 0%;
    }
    /* transform and opacity only — both composited, so a five-card stack
       costs the compositor five layers and the main thread nothing. That is
       the difference between this holding 60fps on a phone and not. */
    @keyframes l-recede {
      to {
        transform: scale(.93) translate3d(0, -12px, 0);
        opacity: .5;
      }
    }

    .l-stack__i:nth-child(1) .l-stack__c { animation-range: cover 30% cover 44%; }
    .l-stack__i:nth-child(2) .l-stack__c { animation-range: cover 44% cover 58%; }
    .l-stack__i:nth-child(3) .l-stack__c { animation-range: cover 58% cover 72%; }
    .l-stack__i:nth-child(4) .l-stack__c { animation-range: cover 72% cover 86%; }
    .l-stack__i:last-child   .l-stack__c { animation: none; }
  }
}

/* ══ THE ENGINE PANEL ═════════════════════════════════════════════════════
   The flow sits in its OWN dark inset, not on the sand ground of the band
   around it. That is the single biggest reason the old diagram read as
   cheap: hairlines on cream look like a whiteboard, and the same graph on a
   dark field looks like an instrument. It is the difference between a
   drawing of a control plane and a picture of one running.

   The canvas carries no text for a screen reader — everything it draws is
   also written out in the .vh paragraph beside it, and the caption under it
   changes as you move between lanes.                                      */

/* .l-flow moved to flow.css, which /aria loads too. index.html loads both. */


/* ══ THE BUILD-OUT CHART ══════════════════════════════════════════════════
   Kevin: "we need a graph for this section, for the numbers section... get
   very creative. I don't want no sloppy work. But go crazy."

   The honest problem the section already had: its benchmark table is empty,
   and a graph of nothing is worse than a table of nothing — it invents a
   shape where there is no data. So this graphs the one dataset that IS
   real and IS ours: Project Oracle §6, the twenty-year deployment.

   TWO LINE WEIGHTS, AND THE DIFFERENCE IS THE POINT.
   Solid runs only between figures Oracle states outright — 9 facilities at
   year 4, 43 at year 7. Everything after that is dashed, because Oracle
   gives a twenty-year TARGET and not a curve, and drawing a confident line
   through years 8 to 19 would be inventing eleven numbers. The endpoint is
   a wedge rather than a point for the same reason: the document says
   "1,800–3,200", so the chart says 1,800–3,200.

   That restraint is the section's whole argument — "we publish the
   methodology with the result" — made visible instead of claimed. An
   investor who checks this against the document finds it matches.

   Logarithmic, and the caption says why: on a linear axis the first four
   years are a flat line on the floor, and those are the only years anyone
   can hold us to yet.                                                     */

.l-chart {
  margin: 76px 0 0; padding: 0;
  border: 1px solid var(--line-ink); border-radius: var(--l-r-lg);
  background: var(--card-ink);
  overflow: clip;
}
.l-chart__cap { padding: 34px 36px 6px; }
.l-chart__h {
  font-size: clamp(24px, 2.2vw, 30px); line-height: 1.16; font-weight: 500;
  letter-spacing: -.02em; color: var(--on-ink); margin: 12px 0 12px;
  text-wrap: balance;
}
.l-chart__cap .l-body { margin: 0; max-width: 62ch; }
.l-chart__svg { display: block; width: 100%; height: auto; padding: 10px 24px 0; }

.l-chart__phase rect { fill: var(--on-ink); opacity: .022; }
.l-chart__phase rect:nth-child(even) { opacity: .045; }
.l-chart__grid line { stroke: var(--line-ink); stroke-width: 1; }

.l-chart__ytick text,
.l-chart__xtick text,
.l-chart__xyr text,
.l-chart__note text,
.l-chart__target text {
  font-family: var(--l-mono); fill: var(--on-ink-mut);
}
/* ── AND THE CHART’S OWN SIZES CAME UP TOO ─────────────────────────────
   Panning at 1:1 fixed the catastrophic case — 2.6px — but the axis was
   AUTHORED at 10.5 and 11, so 1:1 lands it at 10.7 and 11.2 and it is still
   under. A unit or two in a 1240-unit viewBox cannot collide with anything;
   these labels sit in tens of units of clearance. The tick keeps the label
   floor at 11.5 because it is tracked caps (PHASE I, II, III); everything
   that is read as a number or a phrase goes to 12. */
.l-chart__ytick text { font-size: 12px; text-anchor: end; }
.l-chart__xtick text { font-size: 11.5px; letter-spacing: 1.6px; text-anchor: middle; fill: var(--on-ink); }
.l-chart__xyr  text { font-size: 12px; text-anchor: middle; }
.l-chart__note text { font-size: 12px; text-anchor: middle; fill: var(--on-ink); }
.l-chart__target text { font-size: 15px; fill: var(--accent); }
.l-chart__target .l-chart__sub { font-size: 12px; fill: var(--on-ink-mut); }

.l-chart__band { fill: var(--accent); opacity: .16; }
.l-chart__dot circle { fill: var(--on-ink); }

.l-chart__line, .l-chart__proj {
  fill: none; stroke: var(--accent); stroke-width: 2.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.l-chart__proj { stroke-width: 2; stroke-dasharray: 3 9; opacity: .72; }

.l-chart__src {
  margin: 0; padding: 14px 36px 30px;
  font-size: 12.5px; line-height: 20px; color: var(--on-ink-mut);
}
.l-chart__src span { font-family: var(--l-mono); color: var(--on-ink); }

/* ONE-SHOT ON ARRIVAL, NOT SCROLL-BOUND — and both halves of that were
   forced by the budget, correctly.

   The first version drew the line by animating stroke-dashoffset against
   `animation-timeline: view()`. parallax-budget.mjs rejected it twice over:

     1  stroke-dashoffset is neither transform nor opacity, so it repaints
        the path on EVERY frame of every scroll. Kevin asked for scrolling
        that stays fluid on a 2fps phone; a per-frame SVG repaint is the
        exact opposite of that.
     2  #numbers is data-kind="read", and read sections hold still. Only
        showcases carry scroll-driven depth. That rule is what stops the
        page turning into fourteen competing parallax effects.

   So the chart reveals ONCE when it arrives, on opacity and transform
   only — both composited, both free — and then never moves again. The
   draw-on was a nice idea that cost more than it was worth.             */
.l-chart [data-in] { }
.l-chart__line, .l-chart__proj, .l-chart__band,
.l-chart__dot, .l-chart__note, .l-chart__target {
  opacity: 0;
  transform: translate3d(0, 10px, 0);
  transition: opacity var(--l-dur) var(--l-ease), transform var(--l-dur) var(--l-out);
}
.l-chart.is-in .l-chart__line   { opacity: 1;   transform: none; transition-delay: var(--l-stagger-0); }
.l-chart.is-in .l-chart__proj   { opacity: .72; transform: none; transition-delay: var(--l-stagger-1); }
.l-chart.is-in .l-chart__band   { opacity: .16; transform: none; transition-delay: var(--l-stagger-2); }
.l-chart.is-in .l-chart__dot,
.l-chart.is-in .l-chart__note,
.l-chart.is-in .l-chart__target { opacity: 1;   transform: none; transition-delay: var(--l-stagger-3); }

@media (prefers-reduced-motion: reduce) {
  /* no movement, and no waiting for an observer either — the chart is
     simply there. An element left at opacity:0 because its reveal never
     ran is the commonest way a reduced-motion page ends up blank. */
  .l-chart__line, .l-chart__proj, .l-chart__band,
  .l-chart__dot, .l-chart__note, .l-chart__target {
    opacity: 1; transform: none; transition: none;
  }
  .l-chart__proj { opacity: .72; }
  .l-chart__band { opacity: .16; }
}

@media (max-width: 900px) {
  .l-chart { margin-top: 48px; }
  .l-chart__cap { padding: 24px 20px 4px; }
  .l-chart__svg { padding: 6px 10px 0; }
  .l-chart__src { padding: 12px 20px 24px; }
  /* the phase sub-labels are the first thing to go — four lines of 10px
     mono under a 375px chart is a smudge, and the phase names above them
     already carry the structure */
  .l-chart__xyr { display: none; }
}


/* ══ HARMONIX 2.0 — the services layout ═══════════════════════════════════
   Kevin: "I would like you to import verbatim probably the HARMONIX 2.0,
   the services that advance your goals with professional guidance. I like
   the way that the pictures are laid out. Not the HUD bar."

   Measured off his screenshot rather than described:

     card      380 x 500 — a 19:25 portrait, not a square and not a 16:9
     gap       28px
     stagger   each card sits ~29px lower than the one before it, so the
               row reads as three steps down rather than a row of three
     text      title then a two-line body, bottom-left, white, over a
               gradient that only darkens the lower half of the picture
     radius    ~16px

   THE MARKUP IS UNTOUCHED. Same three cards, same images, same numbers,
   same icons, same copy — this is a layout swap and nothing else, which is
   the instruction: "do not rewrite the UI/UX, just change the context."
   The icons Kevin asked for on this section were already there; what was
   wrong was the arrangement, so only the arrangement changed.

   The stagger is the whole trick and it is worth naming why it works: three
   equal cards in a row is a table, and a table of people reads as a
   comparison. Stepped, they read as a sequence — student, then teacher,
   then institution — which is the order the argument is in.               */

.l-cards--harm {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px;
  align-items: start;
  --harm-step: 30px;
}
.l-cards--harm .l-card {
  position: relative; display: flex; flex-direction: column; justify-content: flex-end;
  aspect-ratio: 19 / 25; padding: 0 26px 28px;
  border: 0; border-radius: 18px; overflow: clip;
  background: var(--g-space);
  box-shadow: rgba(0,0,0,.26) 0 34px 66px -38px;
}
.l-cards--harm .l-card:nth-child(2) { margin-top: var(--harm-step); }
.l-cards--harm .l-card:nth-child(3) { margin-top: calc(var(--harm-step) * 2); }

.l-cards--harm .l-card__img {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
  margin: 0; border-radius: 0;
  transition: transform var(--l-dur) var(--l-out);
}
/* A slow push-in on hover. transform only, so it is composited, and slight
   enough that it reads as the picture breathing rather than a zoom effect. */
.l-cards--harm .l-card:hover .l-card__img { transform: scale(1.045); }

/* The scrim darkens the bottom and leaves the top of the photograph alone —
   Harmonix's gradient does the same, and it is why the pictures stay
   pictures instead of becoming textured backgrounds. */
.l-cards--harm .l-card::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  /* Ramped EARLIER and harder than Harmonix s, because our cards carry a
     label, a title AND two lines of body where its cards carry a title and
     one. The text block starts at about 62% of the card height, so the
     scrim has to be doing real work by then — at .52/68% the eyebrow sat
     on 3.2 to one against the brightest pixel behind it. The top of the
     photograph is still untouched, which is the part that matters. */
  background: linear-gradient(180deg,
              rgba(10,13,20,0) 28%, rgba(10,13,20,.30) 48%,
              rgba(10,13,20,.74) 66%, rgba(10,13,20,.94) 100%);
}

.l-cards--harm .l-card__top {
  position: absolute; top: 20px; left: 22px; right: 22px; z-index: 2;
  display: flex; align-items: center; justify-content: space-between;
}
.l-cards--harm .l-card__no { color: var(--on-media); opacity: .82; }
.l-cards--harm .l-card__top .l-ico { color: var(--on-media); opacity: .82; }

.l-cards--harm .l-card__k,
.l-cards--harm .l-card__t,
.l-cards--harm .l-card .l-body { position: relative; z-index: 2; }
/* WHITE, NOT BRONZE — measured, not preferred.
   var(--accent) is #B68A5E, and its luminance sits almost exactly on top of
   a mid-brown photograph. Composited over the real image pixels under each
   label the three eyebrows read 1.11, 1.03 and 1.12 to one. Not low
   contrast — invisible. Bronze earns its place on the cream and sand
   grounds where it is a warm mark against a flat field; over photography it
   is camouflage, and no amount of scrim fixes a hue that matches the
   subject.

   Harmonix does not have a bronze eyebrow on these cards either — its
   labels are white. This is the source layout being right about something
   that was ours to get wrong.                                          */
.l-cards--harm .l-card__k {
  /* FULL WHITE. At .74 it measured 2.88 to one over the darkest of the
     three photographs — better than bronze s 1.03, still under the 4.5 a
     12px label needs. Opacity is the cheapest thing in this rule to spend. */
  color: var(--on-media);
  font-size: 12px; letter-spacing: 1.4px; text-transform: uppercase;
}

/* A top scrim as well as a bottom one. The number and the icon sit above
   the gradient, and on the atrium photograph — bright glass roof, straight
   under the number — card three read 1.82 to one. The bottom scrim cannot
   help text that is nowhere near it. */
.l-cards--harm .l-card::before {
  content: ''; position: absolute; inset: 0 0 auto; height: 34%; z-index: 1;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(10,13,20,.84) 0%, rgba(10,13,20,0) 100%);
}
.l-cards--harm .l-card__t {
  color: var(--on-media); font-size: 23px; line-height: 1.2; font-weight: 500;
  letter-spacing: -.01em; margin: 8px 0 10px; display: block;
}
.l-cards--harm .l-card .l-body {
  color: var(--on-media); font-size: 14px; line-height: 21px; margin: 0;
  opacity: .88;
}

@media (max-width: 900px) {
  /* The stagger is a desktop idea. At one column it becomes three cards with
     odd gaps between them, so it goes — and the portrait ratio goes with it,
     because a 19:25 card on a 375px screen is 493px tall and pushes the two
     below it off the fold entirely. */
  .l-cards--harm { grid-template-columns: 1fr; gap: 18px; }
  /* AND THE RATIO CANNOT BE A FIXED BOX EITHER. 4:3 at 311px is a 233px card,
     and the copy in it needs 258 — so with justify-content: flex-end the
     content had nowhere to go but UP, into the absolutely-positioned number
     and icon row. MEASURED: 76px of overlap, the eyebrow and the heading
     printed straight through "01" and the graduation cap.

     The card grows to its content instead, with the top row’s own box
     (20px offset + 56px tall) reserved as padding so nothing can ever land
     on it. The photograph is inset:0 with object-fit: cover, so it follows
     whatever height the card ends up. */
  .l-cards--harm .l-card {
    aspect-ratio: auto;
    padding-top: 76px;
    margin-top: 0 !important;
  }
  .l-cards--harm .l-card__t { font-size: 20px; }
}

/* Each tier gets a photograph, because "140,000–190,000 sq ft" is a number
   nobody can picture and a glass-roofed hall is the same fact at a glance.
   Sized to a wide letterbox so it sits above the spec table without pushing
   it below the fold — the numbers are still the point, the picture is the
   thing that makes you believe them. */
.l-tier__fig {
  margin: 0 0 24px; border-radius: var(--l-r-md); overflow: clip;
  aspect-ratio: 16 / 7; background: var(--g3);
}
.l-tier__fig img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 900px) { .l-tier__fig { aspect-ratio: 16 / 9; margin-bottom: 18px; } }


/* ── AND THE SECOND SEAM AROUND CONTACT ───────────────────────────────────
   "The two card transitions between contact" — Contact has two neighbours,
   Corvexal above and the footer below, and both were pastel gaps.

   The footer is not a .l-band and it is not even a sibling of one: it lives
   outside <main>. It is also shared with fourteen other pages through
   footer.css, so none of this can go there. It goes here instead, and here is
   already the right scope: landing.css is linked by index.html and by nothing
   else, so a bare .foot2 rule in this file cannot reach another page.

   The first attempt hung a data-overlap attribute on index's footer to make
   the intent visible in the markup. Gate check 8c immediately failed with
   fourteen lines of "differs from index.html" — that check compares every
   footer to index's byte for byte, and it was right to. The attribute came
   back out; nothing in the markup changed at all.

   main + .foot2, not .foot2 alone, because footer.css loads AFTER this sheet
   and sets `margin` as a shorthand. At equal specificity the later sheet
   wins and the overlap would silently not happen. The combinator costs one
   point, which is all it takes, and it states a fact that is true: this is
   the footer that directly follows main.

   Only the top strip is shaped. The footer has no plate below it to meet, so
   the second mask layer runs flat all the way down.                       */

/* the footer overlap moved to edges.css so every page shares the seam. */
#door { padding-bottom: calc(var(--band-pad, 96px) + var(--seam) * 1.5); }

@media (max-width: 640px) {
  main + .foot2 { margin-inline: 10px; }
}


/* ══ THE MEASURES AND THE RHYTHM ══════════════════════════════════════════
   THIRTY-SEVEN OF THESE WERE INLINE style ATTRIBUTES AND ALL THIRTY-SEVEN
   WERE DEAD IN PRODUCTION.

   netlify.toml ships Content-Security-Policy with `style-src 'self'` and
   deliberately WITHOUT 'unsafe-inline' — its own comment says "There is not
   one inline style attribute in the site; the orbit's angles live in the
   stylesheet precisely so this line can stay strict." That was true when it
   was written. This page then accumulated thirty-seven of them.

   A style-src without 'unsafe-inline' does not merely discourage the style
   attribute; the browser refuses to parse it. Loaded with the production CSP
   applied as a meta tag, this page reported element.style.length === 0 on
   every one of them: the headline in §2 computed max-width: none instead of
   23ch, and .l-facts computed the stylesheet's 34px instead of its intended
   26px. Every measure and every rhythm value on the landing page was being
   silently discarded by the host and by nothing else — which is why it looked
   perfect on localhost, where no CSP header is sent at all.

   WHY THESE ARE RULES AND NOT UTILITY CLASSES. Seven of the thirty-seven
   said the same thing: margin-top:16px on the headline directly under an
   eyebrow. That is not thirty-seven decisions, it is one rule and a set of
   editorial measures. So the repeated case became a single adjacency rule,
   and what genuinely varies — how wide each headline is allowed to be, which
   is a judgement per section — is scoped to the section that made it.       */

/* THE NEARLY-ONE RULE. Eight of the nine headlines that follow an eyebrow
   carried margin-top:16px. The ninth, in #network, carried nothing and took
   the 14px it inherits — and writing this as a bare .l-eye + .l-h1 gave it
   16px, which the geometry diff caught as a two-pixel shift in a section I
   had not touched.

   So it is a list, not a generalisation. A rule that is true eight times out
   of nine is not a rule; it is eight facts and a bug. */
#who .l-h1, #engine .l-h1, #reservoir .l-h1, #subsidiaries .l-h1,
#numbers .l-h1, #tiers .l-h1, #museum .l-h1, #door .l-h1 { margin-top: 16px; }

/* THE MEASURES. How many characters each headline is allowed before it wraps
   — a per-section editorial decision, which is why these are not one value. */
#who   .l-h1 { max-width: 20ch; }
#engine .l-h1 { max-width: 20ch; }
#subsidiaries .l-h1 { max-width: 18ch; }
#numbers .l-h1 { max-width: 17ch; }
#tiers .l-h1 { max-width: 20ch; }
#museum .l-h1 { max-width: 20ch; }
#door  .l-h1 { max-width: 19ch; }

/* THE RHYTHM, band by band.

   AND SCOPED BY ADJACENCY, NOT BY SECTION. #numbers .l-body.l-muted looked
   like a faithful translation of the one attribute it replaced; the section
   also contains a chart caption built from the same class, which duly took a
   58ch measure it never had and lost 56px of width. The attribute applied to
   ONE element, so the rule has to as well — and what actually identifies that
   element is its position: the paragraph directly under the headline.

   THE BUTTON WRAPPERS ARE SELECTED STRUCTURALLY AND THAT IS NOT STYLE.
   The first attempt gave each of those four paragraphs class="l-cmd".
   .l-cmd already exists at line 691 — it is the command palette block, and
   it carries max-width: 620px. All four wrappers duly halved from 1240 to
   620 and the geometry diff caught it. A new class name can always collide
   with an old one; p:has(> .l-cmd__btn) introduces no name at all and says
   exactly what it means: the paragraph that holds the button. */
#open .l-cmd__btn > svg[aria-hidden] { flex: none; opacity: .5; }
/* The two Museum/About buttons carried text-decoration:none inline and the
   class never declared it, so removing the attribute would have put an
   underline under two pill buttons. It belongs on the class. */
.l-cmd__btn { text-decoration: none; }
#company .l-facts { margin-top: 26px; }

#thesis .l-lead { max-width: 23ch; margin-top: 18px; }
#thesis .l-lead + .l-body { margin-top: 40px; max-width: 64ch; }

#who .l-cards { margin-top: 52px; }

#engine .l-split { margin-top: 34px; }
#engine p:has(> .l-cmd__btn) { margin-top: 34px; }

#reservoir .l-h1 + .l-body { margin-top: 22px; }
#reservoir p:has(> .l-cmd__btn) { margin-top: 34px; }

#subsidiaries .l-stack { margin-top: 56px; }

#numbers .l-h1 + .l-body { margin-top: 20px; max-width: 58ch; }
#numbers .l-eye + .l-spec { margin-top: 48px; }
#numbers .l-restate { margin-top: 76px; }
#numbers .l-spec + .l-body { margin-top: 28px; max-width: 62ch; }

#tiers .l-h1 + .l-body { margin-top: 20px; max-width: 58ch; }

#museum .l-split { margin-top: 34px; }
#museum .l-split .l-body + .l-body { margin-top: 20px; }
#museum p:has(> .l-cmd__btn) { margin-top: 26px; }
#museum .l-card + .l-card { margin-top: 14px; }

#founder .l-fnd__frame { margin: 0; }
#founder .l-lead { margin: 14px 0 24px; }
#founder p:has(> .l-cmd__btn) { margin-top: 28px; }

#world .l-col { margin-bottom: 40px; }
#world .l-lead { margin-top: 14px; max-width: 30ch; }

#door .l-spec { margin-top: 30px; }


/* ══ THE NAV OVER THE VIDEO ═══════════════════════════════════════════════
   Kevin: "the text at the top of the screen gets washed out by the
   background video."

   MEASURED, PER CLIP, AT FOUR TIMECODES EACH. The nav band was sampled out
   of every hero clip and composited with the text colour actually rendering
   there. The links came back between 1.80:1 and 7.18:1 and the tag between
   1.85:1 and 2.95:1, against a 4.5 requirement for 11px and 13px type. Three
   of the four clips failed on both runs of text. This is the most-seen text
   on the site.

   THE CAUSE WAS NOT THE VIDEO. It was a token that was declared and never
   spent. .l-nav sets --nav-fg: var(--on-media) at the top and .l-nav.is-pill
   overrides it to var(--fg) when the pill arrives — a complete two-state
   colour system, written correctly, and referenced by NOTHING. grep counts
   two declarations of --nav-fg and zero uses of var(--nav-fg). The links say
   color: inherit, so with nothing setting the nav's colour they inherited
   ink from <body> and have been rendering dark-on-bright-video the whole
   time, while the tag beside them was explicitly white — half the nav one
   colour, half the other, and the design's own intent applied to neither.

   Wiring the token up is necessary and NOT sufficient: pure white on the
   brightest frame of hero-desk is 2.28:1. Video is not a background you can
   put text on, whatever the colour. So there is a scrim.

   WHY 46%. With links at their existing .82 and the tag raised to .88, a
   46% black scrim puts the worst pair on the worst frame at 5.10:1. 38% is
   the first value that clears 4.5 at all and leaves no margin for a clip
   that gets recut; 54% clears 6.17 and is visibly a black bar. 46% is the
   lightest value with real headroom, and it is a gradient rather than a band
   so it reads as the top of a photograph rather than as a UI chrome strip.

   IT IS ONLY THERE OVER THE VIDEO. Once the pill arrives it has its own
   light ground, --nav-fg flips to ink, and the scrim fades out on the same
   duration as the pill so the two states cross over as one movement.      */

.l-nav { color: var(--nav-fg); }

.l-nav::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 140px;
  pointer-events: none; z-index: -1;
  background: linear-gradient(to bottom,
    rgba(0,0,0,.54) 0%, rgba(0,0,0,.52) 50%,
    rgba(0,0,0,.30) 76%, rgba(0,0,0,0) 100%);
  opacity: 1;
  transition: opacity var(--l-reveal) var(--l-ease);
}
.l-nav.is-pill::before { opacity: 0; }

/* .72 measured 1.85:1 on hero-desk. .88 under the scrim measures 5.64:1. */
.l-nav:not(.is-pill) .l-nav__tag { color: rgba(255,255,255,.88); }

/* AND THE LINKS GO TO FULL STRENGTH OVER THE VIDEO. The brightest region
   under them across all four clips is rgb(222,226,214) — very nearly white.
   At the resting .82 that needs a 55% scrim to clear AA; at full white it
   needs 48%, so the scrim above is 52% and seven points lighter than it
   would otherwise have to be. The hover affordance is unaffected: :hover
   also paints a currentColor-10% pill, so opacity was never carrying it
   alone. On the pill, where the ground is known and light, .82 returns. */
.l-nav:not(.is-pill) .l-nav__a { opacity: 1; }


/* ══ THE ACTION BAR ═══════════════════════════════════════════════════════
   Transposed from references/action-search-bar (kokonutui), which Kevin
   pointed at more than once: *"I told you explicitly where to grab that. I
   shouldn't be repeating myself over and over again."*

   WHAT THE REFERENCE SUPPLIES, AND IT IS TAKEN VERBATIM: a results panel
   that grows from zero height under the field, rows that arrive staggered
   rather than all at once, a right-hand icon that swaps between search and
   send depending on whether anything has been typed, and a footer rule
   carrying the two keyboard hints. The numbers below are its numbers —
   400ms for the panel, 300ms for a row, 100ms of stagger between rows,
   200ms for the icon swap and 200ms of debounce.

   WHAT IS OURS: the actions. The palette already holds seventeen routes, each
   with the words that reach it and the place it goes, and until now they were
   only reachable by typing a question and pressing Ask. The reference's whole
   point is that you see what is available WHILE you type, so the same
   seventeen now appear as a filtered list. No new content, no new data — the
   list is the routing table, shown.

   WHAT THE REFERENCE DOES NOT SUPPLY, AND HAD TO BE ADDED: keyboard. It is
   click-only — no roving selection, no Enter, no listbox semantics. That is
   fine in a demo and not fine in a product, so the rows are a real listbox
   with aria-activedescendant and arrow keys. Adding that is not inventing
   design; it is finishing it.

   THE PANEL GROWS WITH grid-template-rows, NOT max-height. A max-height
   animation has to guess a number larger than the content, which makes the
   easing wrong for every list that is shorter than the guess — and this list
   changes length on every keystroke. 0fr to 1fr animates to the content's
   real height, so a two-row result and a seventeen-row result open at the
   same speed and neither one snaps.                                       */

:root {
  --act-open:    400ms;
  --act-row:     300ms;
  --act-stagger: 100ms;
  --act-swap:    200ms;
  --act-lift:    20px;
}

.l-act {
  display: grid; grid-template-rows: 0fr;
  margin-top: 8px;
  opacity: 0;
  transition: grid-template-rows var(--act-open) var(--l-ease),
              opacity var(--act-swap) var(--l-ease);
}
.l-act > * { overflow: hidden; }
.l-cmd.is-acting .l-act { grid-template-rows: 1fr; opacity: 1; }

.l-act__panel {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--g2);
  box-shadow: rgba(0,0,0,.10) 0 10px 30px -18px;
}

.l-act__list { margin: 0; padding: 6px; list-style: none; }

.l-act__row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 9px 12px; border-radius: 9px; cursor: pointer;
  opacity: 0; transform: translate3d(0, var(--act-lift), 0);
  transition: opacity var(--act-row) var(--l-ease),
              transform var(--act-row) var(--l-ease),
              background var(--l-quick) var(--l-ease);
}
.l-cmd.is-acting .l-act__row { opacity: 1; transform: translate3d(0, 0, 0); }
.l-act__row:hover, .l-act__row[data-on] { background: var(--line-soft, rgba(18,24,32,.06)); }

.l-act__l { display: flex; align-items: center; gap: 9px; min-width: 0; }
.l-act__i { display: inline-flex; flex: none; color: var(--fg2); }
.l-act__t { font-size: 14px; font-weight: 500; white-space: nowrap; }
.l-act__d { font-size: 12px; color: var(--fg2); overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }
.l-act__r { display: flex; align-items: center; gap: 10px; flex: none; }
.l-act__s, .l-act__e { font-size: 12px; color: var(--fg2); white-space: nowrap; }

.l-act__foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: 9px 14px; border-top: 1px solid var(--line);
  font-size: 12px; color: var(--fg2);
}
.l-act__none { padding: 14px; font-size: 13px; color: var(--fg2); }

/* The icon at the right of the field swaps as soon as anything is typed.
   Both live in the same 18px box and slide through it. */
.l-cmd__swap { position: relative; width: 18px; height: 18px; flex: none; }
.l-cmd__swap > svg {
  position: absolute; inset: 0;
  transition: opacity var(--act-swap) var(--l-ease),
              transform var(--act-swap) var(--l-ease);
}
.l-cmd__swap [data-send] { opacity: 0; transform: translate3d(0, -20px, 0); }
.l-cmd.has-q .l-cmd__swap [data-find] { opacity: 0; transform: translate3d(0, 20px, 0); }
.l-cmd.has-q .l-cmd__swap [data-send] { opacity: 1; transform: translate3d(0, 0, 0); }

@media (max-width: 560px) {
  .l-act__d, .l-act__s { display: none; }
}
/* NO prefers-reduced-motion BLOCK HERE, and that is deliberate. Line 993
   already carries * { transition-duration: var(--l-instant) !important } inside
   one, which reaches every transition in this file including all three above.
   A second block would only restate it — with a raw 1ms that gate check 2
   correctly refused, because the duration token exists precisely so a value
   like that is written once.

   What matters for reduced motion is that the panel still OPENS: .is-acting
   sets opacity 1 and translate 0 outright, so with instant transitions the
   rows are simply present. An element left at opacity 0 because its reveal
   never ran is the commonest way a reduced-motion page ends up blank. */

/* The five What-we-build headings are links now — all five destinations exist,
   and until this session clicking a product name did nothing at all. The
   heading keeps its own type; the anchor only has to stop looking like body
   copy and give a focus ring, because a 34px display heading is already the
   affordance. */
.l-stack__a {
  color: inherit; text-decoration: none;
  transition: color var(--l-quick) var(--l-ease);
}
.l-stack__a:hover { color: var(--accent-text, var(--accent)); }
.l-stack__a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 3px; }

/* A PILL ON AN INK BAND HAS TO INVERT.
   .l-cmd__btn is background: var(--fg) / color: var(--g) — ink pill, cream
   type. On [data-tone="ink"] those two tokens swap, so the pill became cream
   on cream and measured 1.03:1. Invisible, on a button I had just added to
   #numbers to give that section a destination.

   The hero already solves this exact case with .l-hero .l-cmd__btn; this is
   the same inversion for the ink-toned bands. */
[data-tone="ink"] .l-cmd__btn {
  background: var(--on-media);
  color: var(--g-ink);
}
[data-tone="ink"] .l-cmd__btn:hover { background: var(--on-ink); }

/* ── THE TWO CONTROLS ON THE LANDING PAGE THAT MISSED 44 ───────────────────
   Measured at 375px: the brand lockup is 31px tall and the Ask button is 41.
   The button is the one that matters — it is the primary action in the
   command bar and it missed the minimum by three pixels, which is exactly the
   kind of miss that never gets noticed and always gets felt. */
@media (pointer: coarse) {
  .l-nav__brand { min-height: 44px; align-items: center; }
  .l-cmd__go { min-height: 44px; }
}

/* ── THE LAST THREE, ALL MEASURED AT 375 ───────────────────────────────────
   .l-cmd__go came back at 43px after min-height: 44px, because the button
   sits in a subpixel grid row and the rect rounds down. 46 clears it with a
   pixel to spare rather than landing exactly on the number and hoping.

   .l-ans__x is the control that DISMISSES the answer panel — 24×28, the
   smallest target on the front door and the one somebody reaches for when
   they want the page back.

   .l-nav__tag is the line "EDUCATION IS NOT A PRODUCT" at 11px. It is a
   sentence, not a chip, and a sentence has to be readable. */
@media (pointer: coarse) {
  .l-cmd__go { min-height: 46px; }
  .l-ans__x { min-width: 44px; min-height: 44px; }
  .l-nav__tag { font-size: 12px; }
}

/* ── THE GROWTH CHART WAS 2.6px TALL ON A PHONE ────────────────────────────
   MEASURED at 375px: .l-chart__svg is authored in a 1240-unit viewBox and was
   fitted to a 309px column — a scale of 0.233. Its axis labels are 11px, its
   annotations 12, its headline figure 15. On the device those paint at 2.6px,
   2.8px and 3.5px. Not small: illegible. A grey smear where the build-out
   plan is, on the page an investor opens first.

   Nothing was broken, which is why it survived: the figure has correct
   markup, a full aria-label, and looks right in every desktop screenshot.
   Only the multiplication gives it away.

   Same answer as the control plane, and for the same reason — it PANS at 1:1
   rather than shrinking to fit. The scroll is on a wrapper rather than on
   .l-chart, so the caption above still wraps to the column and only the
   drawing moves. The aria-label stays the real answer for anyone who would
   rather read the numbers than pan a graph. */
.l-chart__pan { min-width: 0; }
@media (max-width: 1280px) {
  .l-chart__pan {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }
  /* 1240 IS THE viewBox, NOT THE BOX. .l-chart__svg carries padding: 10px
     24px, so a 1240px element draws its 1240 units into 1192 of content — a
     0.961 scale that put the axis labels back at 10.8px, just under. The
     floor has to be the viewBox PLUS its own gutters. */
  .l-chart__pan .l-chart__svg { min-width: 1288px; }
}

/* The timeline spine label is set vertically at 9px — a typographic device
   rather than a sentence, and at a desk it reads as one. On a tablet in
   landscape it is the only thing on the page under the floor, and the strip
   it sits in widens by two pixels to fix it. Phones never see it: it is
   display: none below the breakpoint. */
@media (pointer: coarse) {
  .l-tl__label { font-size: 11px; }
}

/* ── A KEYBOARD SHORTCUT ON A DEVICE WITH NO KEYBOARD ──────────────────────
   "⌘K from anywhere" is a real affordance at a desk and noise on a phone:
   there is no ⌘, and the sentence sends somebody looking for a key that is
   not there. It is the clause that goes, not the whole hint — "Ask anything:
   the network, the model, why it is free" is still true everywhere.

   AND IT WAS OVERLAPPING. <kbd> renders 15px tall inside a 20px line, and at
   375px the hint wraps so that box landed on the descenders of the line above.
   Two lines of type sitting on top of each other on the first screen of the
   front door. Giving the line box room fixes it at every width, including the
   desktop one where it happened not to wrap. */
.l-cmd__hint kbd {
  line-height: 1;
  vertical-align: baseline;
}
@media (max-width: 760px), (pointer: coarse) {
  .l-cmd__kb { display: none; }
  .l-cmd__hint { line-height: 22px; }
}
