/* ============================================================================
   site.css — layout and components. Structure and numbers follow SPEC.md
   (§3, §5, §6, §9); every colour, length and easing comes from tokens.css.

   Layers (z): #gl 0 · #ui 1 (#header 52 inside it) · #scroll-indicator 60 ·
   #transition 100 · #preloader 200. The GL canvas sits between the page
   ground (body background) and the DOM, so any DOM box a WebGL plane is
   synced to must stay transparent while JS runs — see "GL-synced boxes".

   html.js gates every animation initial state; html.no-js is a plain,
   scrollable, fully readable document.
   ========================================================================= */

/* ---------------------------------------------------------------- base --- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: auto;
  /* `body { overflow-x: hidden }` below does not reach this: the viewport takes
     its overflow from <html>, and measured in a real browser the page could be
     dragged 15px sideways. The cause is the full-bleed trick on #footer-bg —
     100vw counts the scrollbar and the viewport does not — and a headless run
     never sees it, because headless has no classic scrollbar. `clip` rather
     than `hidden`: hidden would make the root a scroll container and change
     how the fixed header and the virtual scroll behave. */
  overflow-x: clip;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.4;
  background: var(--bg);
  color: var(--fg);
  overflow-x: hidden;
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The black phase now comes through the variable: hero.js publishes
   --ground-tint, tokens.css feeds it into --bg for the light theme, and
   html.is-black-bg overrides --bg outright. No element-specific pin needed. */

h1, h2, h3, p, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
canvas { display: block; }
svg { display: block; }

.mono { font-family: var(--font-mono); }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
:focus:not(:focus-visible) { outline: 0; }

::selection { background: var(--accent); color: var(--off-white); }

/* -------------------------------------------------------------- layers --- */
#gl {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

#ui {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 1;
  pointer-events: none;
}

#page-container {
  position: relative;
  z-index: 1;
  width: 100%;
  pointer-events: auto;
  will-change: transform;
}

/* scroll.js sets its height to the content height so the native scrollbar,
   keyboard and accessibility scrolling all keep working. */
#spacer { width: 100%; height: 0; pointer-events: none; }

/* ---------------------------------------------------------- section grid --- */
.section {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  padding: var(--pad-y) var(--pad-x);
  position: relative;
  width: 100%;
}

/* --------------------------------------------------- split-text primitives --- */
/* text.js wraps into .line / .word / .char; lines mask, words and chars move.
   The .12em padding keeps Geist descenders inside the mask at tight leading;
   the negative margin gives the space back so layout is unchanged. */
.line { display: block; overflow: hidden; padding-bottom: .12em; margin-bottom: -.12em; }
.word, .char { display: inline-block; transform-origin: 0% 100%; }
#hero-title .word { position: relative; top: -.1em; }

/* Initial states, matched to the JS formulas that drive them: the page is
   coherent before the first frame and no-JS users see the final state. */
html.js #hero-title .word,
html.js #footer-contact .word { transform: translate3d(0, 1.5em, 0) rotate(15deg); }
html.js #featured-title .char { transform: translate3d(0, 1em, 0) rotate(10deg); }
html.js .cross { transform: scale(0) rotate(0deg); }
html.js .header-btn { transform: scale(0); }
html.js .end-deco { transform: scaleX(0); }
html.js #featured-cta { transform: translate3d(0, 150%, 0) rotate(10deg); opacity: 0; }
html.js #goal-tunnel-title { opacity: 0; }

/* -------------------------------------------------------------- crosses --- */
.cross-row { position: absolute; inset: 0; pointer-events: none; }

.cross {
  position: absolute;
  top: 50%;
  width: var(--cross);
  height: var(--cross);
  margin-top: calc(var(--cross) / -2);
  will-change: transform;
}
.cross::before, .cross::after {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: calc(var(--cross) * .125);
  margin-top: calc(var(--cross) * -.0625);
  background: currentColor;
}
.cross::after { transform: rotate(90deg); }


.cross-row--ends .cross:nth-child(1) { left: 0; }
.cross-row--ends .cross:nth-child(2) { right: 0; }

.cross-row--corners .cross { margin-top: 0; }
.cross-row--corners .cross:nth-child(1) { left: 0; top: 0; }
.cross-row--corners .cross:nth-child(2) { right: 0; top: 0; }
.cross-row--corners .cross:nth-child(3) { left: 0; top: auto; bottom: 0; }
.cross-row--corners .cross:nth-child(4) { right: 0; top: auto; bottom: 0; }

/* ---------------------------------------------------------------- pills --- */
.pill {
  font-size: .875em;
  height: 3.2em;
  padding: 0 1.125em 0 1.625em;
  border-radius: 6.25em;
  text-transform: uppercase;
  font-weight: 500;
  letter-spacing: .02em;
  white-space: nowrap;
  background: var(--pill-bg);
  color: var(--pill-fg);
  display: inline-flex;
  align-items: center;
  gap: 1em;
  overflow: hidden;
  position: relative;
  isolation: isolate;
  line-height: 1;
  transition: background-color .25s, color .25s;
}

.pill-dot {
  flex: none;
  width: .5em;
  height: .5em;
  border-radius: 50%;
  background: var(--accent-live, var(--accent));
  transition: transform var(--d-3) var(--e-lusion);
}

.pill-text {
  position: relative;
  z-index: 1;
  transition: transform var(--d-3) var(--e-lusion), color var(--d-1) var(--e-std);
}

@media (hover: hover) {
  /* The dot swells to fill the pill behind the text (lusion #home-featured-cta). */
  .pill:hover .pill-dot { transform: translate3d(4em, 0, 0) scale(32); }
  .pill:hover .pill-text { transform: translate3d(-1.5em, 0, 0); color: var(--off-white); }
}

/* --------------------------------------------------------------- header --- */
/* --- header scrim -----------------------------------------------------------
   Measured, not guessed: at the featured section the 8vw title ran straight
   under the wordmark, because the fixed header had no background at all. A
   gradient keeps the header legible without drawing a hard bar across the page,
   and it follows the ground when the corridor turns the page black. */
#header::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: calc(100% + 2.4em);
  /* Opaque until the header's content ends, then a short fade. Starting the
     fade at the top let the section title read through as a grey ghost. */
  background: linear-gradient(to bottom,
    var(--bg) 0%,
    var(--bg) 62%,
    color-mix(in srgb, var(--bg) 55%, transparent) 82%,
    color-mix(in srgb, var(--bg) 0%, transparent) 100%);
  pointer-events: none;
  z-index: -1;
}

#header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 52;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--pad-y) var(--pad-x);
  font-size: var(--fs-header);
  color: var(--fg);
  transition: color .25s;
  pointer-events: none;
}
#header > * { pointer-events: auto; }

#header-bg {
  position: fixed;
  top: 0;
  right: 0;
  width: 50vw;
  height: calc(var(--vh) * 100);
  background: linear-gradient(270deg, color-mix(in srgb, var(--black) 50%, transparent), transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--d-3) var(--e-std);
}

#header-logo {
  display: flex;
  flex-direction: column;
  gap: .3em;
  line-height: 1;
}
.logo-wordmark { font-weight: 500; letter-spacing: -.01em; }
.logo-sub {
  font-size: .625em;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--fg-muted);
  transition: color .25s;
}

#header-nav {
  display: flex;
  align-items: center;
  gap: .5em;
}

/* The header could not fit on a phone, and did not say so.
   Nothing shrinks: --fs-header is clamp(1rem, 1vw, 1.5rem), so the font floors
   at 16px, and the logo stops at its 77px min-content. The four controls
   measure 407px, which with the 40px gutter and the logo gives a hard 524px
   minimum -- and html is overflow-x: clip, so below 525px the nav simply left
   the screen with no way to pan to it. Measured at 390px: #menu-btn sat at
   x 423..524, entirely outside the viewport and unreachable by touch, on the
   home page's only global navigation.

   The talk pill is the widest control at 185px and the only one that is
   repeated elsewhere -- "Start a project" is in the menu it sits beside, and
   again in the closing section. It stands down on small screens so the three
   that have nowhere else to be can stay. */
@media (max-width: 600px) {
  #talk-btn { display: none; }
}
/* At 320px even the three remaining controls plus the wordmark exceed the
   40px gutter by 11px. The gutter gives way before the navigation does. */
@media (max-width: 380px) {
  #header { padding-inline: 20px; }
}

.header-btn { will-change: transform; }

#sound-btn {
  font-size: .875em;
  width: 3.2em;
  height: 3.2em;
  border-radius: 50%;
  background: var(--panel);
  color: var(--panel-fg);
  border: 1px solid var(--rule);
  overflow: hidden;
  transition: background-color .25s, color .25s, border-color .25s;
}
html.is-black-bg #sound-btn { background: var(--ink); color: var(--off-white); }

/* --- chrome steps aside for the corridor ---------------------------------
   A station card is 0.6 to 0.72 of a half-viewport tall in NDC, so it fills
   most of the frame as it passes. Measured, nine of the eighteen reached
   their readable moment with the wordmark or the button group printed over
   them, and no amount of lift or offset avoids it -- any card centred above
   ndc.y 0.07 reaches the header. So the header withdraws for the immersive
   passage and returns at the exit. tools/check_corridor.py asserts this and
   treats chrome under 0.05 opacity as covering nothing. */
#header-logo, #header-nav { transition: opacity .45s var(--e-std); }
html.is-black-bg #header-logo,
html.is-black-bg #header-nav {
  opacity: 0;
  pointer-events: none;
}
/* sound.js draws the five bars on this canvas in the button's computed color. */
.sound-canvas { width: 100%; height: 100%; }

/* ----------------------------------------------------------------- menu --- */
#menu {
  position: absolute;
  top: calc(var(--pad-y) + var(--fs-header) * 3.1 + .5em);
  right: var(--pad-x);
  width: min(19.38em, calc(100vw - var(--pad-x) * 2));
  display: grid;
  gap: .625em;
  color: var(--fg);
  visibility: hidden;
  pointer-events: none;
  transition: visibility 0s var(--d-3);
}
#menu.is-open {
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0s;
}
#header:has(#menu.is-open) #header-bg { opacity: 1; }

.menu-card {
  background: var(--panel);
  color: var(--panel-fg);
  border-radius: .625em;
  padding: 1.25em 1.5em;
  opacity: 0;
  transition: transform var(--d-3) var(--open-delay) var(--e-std), opacity var(--d-3) var(--open-delay) var(--e-std);
  will-change: transform, opacity;
}
.menu-card--links   { --open-delay: .08s; transform: translate3d(0, 5.5em, 0) rotate(3.5deg); }
.menu-card--contact { --open-delay: 0s;   transform: translate3d(0, 7.75em, 0) rotate(-3.5deg); }
/* Opening: links first, contact .08s later (lusion). Closing reverses the order. */
#menu.is-open .menu-card--links   { --open-delay: 0s; }
#menu.is-open .menu-card--contact { --open-delay: .08s; }
#menu.is-open .menu-card { transform: none; opacity: 1; }

.menu-links { display: grid; gap: .5em; }
.menu-link {
  display: inline-block;
  font-size: 1.25em;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -.01em;
  position: relative;
}
.menu-link::after,
.footer-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -.08em;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scale3d(0, 1, 1);
  transform-origin: right;
  transition: transform var(--d-2) var(--e-std);
}
@media (hover: hover) {
  .menu-link:hover::after,
  .footer-link:hover::after { transform: scale3d(1, 1, 1); transform-origin: left; }
}

.menu-row {
  display: flex;
  flex-direction: column;
  gap: .25em;
  padding: .625em 0;
  border-top: 1px solid var(--line);
}
.menu-row:first-child { border-top: 0; padding-top: 0; }
.menu-row:last-child { padding-bottom: 0; }
.menu-label {
  font-size: .625em;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--fg-muted);
}
.menu-contact-link { font-weight: 500; }

/* ----------------------------------------------------------------- hero --- */
#hero {
  height: calc(var(--vh) * 100);
  grid-template-rows: auto auto minmax(0, 1fr);
  /* Measured, not assumed: this used to be ~107px, which cleared the header's
     CONTENT but not the header itself (145px) or the scrim it paints (183px),
     so the eyebrow and the first line of the title were the two things on the
     page you could not read. --header-h is published by ui/header.js. */
  padding-top: calc(var(--header-h, 150px) + var(--pad-y) * .8);
}

/* --- the hero was the smallest type on its own page ----------------------
   Measured at 1366: the h1 was 34px (2.5vw) and started at x=382, indented to
   column 4, while #featured below it was 109px starting at x=61 and #end was
   137px. The first promise the site makes was set smaller and pushed further
   in than everything that follows it, which is why the page read flat however
   good the words were. It now opens at the page margin and at the largest size
   on the page, and the sections after it step down rather than up. */
#hero-eyebrow {
  grid-column: 1 / span 12;
  grid-row: 1;
  font-size: .75em;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--fg-muted);
  margin-bottom: 1.1em;
}

#hero-title {
  grid-column: 1 / span 11;
  grid-row: 2;
  font-size: clamp(38px, 6.6vw, 96px);
  line-height: .96;
  font-weight: 400;
  letter-spacing: -.028em;
  margin-left: -.04em;          /* optical: pull the cap edge onto the margin */
  /* At 90px the old headline broke as 27 / 24 / 4 / 9 characters, leaving
     "them" and "running." alone on two lines of their own. balance spreads the
     words evenly instead; browsers without it fall back to normal wrapping. */
  text-wrap: balance;
}

#hero-visual {
  grid-column: 1 / span 12;
  grid-row: 3;
  margin-top: calc(var(--pad-y) * 3 / 5);
  min-height: 0;
  border-radius: var(--radius);
  cursor: pointer;
}


/* ----------------------------------------------------------------- reel --- */
#reel { padding-top: calc(var(--pad-y) * 2); }

#reel-title {
  grid-column: 1 / span 8;
  font-size: 4.2vw;
  line-height: 1.05;
  font-weight: 400;
  letter-spacing: -.02em;
}

#reel-desc {
  grid-column: 8 / span 5;
  margin-top: calc(var(--pad-y) * 1.5);
  font-size: 1.125em;
  line-height: 1.45;
}

#reel-cta {
  grid-column: 8 / span 5;
  justify-self: start;
  margin-top: 2em;
}

#reel-frame-outer {
  grid-column: 1 / 13;
  margin-top: calc(var(--pad-y) * 2);
  /* The tail used to be 100 + 10 --vh units of nothing — 946px against an 860px
     viewport, so a whole screen could land inside it with nothing in view. It is
     stretch F in GAPS.md. #robotic now occupies that space; what is left here is
     breathing room after the frame rather than a hole. Nothing clocks off this
     element — reel.js times its choreography from #reel-title, #reel-desc,
     #reel-cta and #reel-frame — so shortening it changes layout, not motion. */
  padding-bottom: calc(var(--vh) * 18);
  margin-bottom: calc(var(--vh) * 8);
}

#reel-frame-top,
#reel-frame-bottom {
  position: relative;
  inset: auto;
  height: var(--cross);
  overflow: hidden;
}
#reel-frame-top { margin-bottom: calc(var(--cross) * .75); }
#reel-frame-bottom { margin-top: calc(var(--cross) * .75); }

#reel-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  background: var(--ink);
  color: var(--off-white);
  overflow: hidden;
}

#reel-terminal {
  position: absolute;
  inset: 0;
  padding: calc(var(--pad-y) * .9) calc(var(--pad-x) * .5);
  font-size: .95em;
  line-height: 1.85;
  opacity: .42;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
#reel-terminal-head {
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: .875em;
  margin-bottom: 1.5em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* flex: none, because overflow: hidden switches off this item's automatic
     minimum size. Inside the column flex above, that let it shrink past its
     own line box: measured 270x0 at 390px, so the line naming the engines --
     the page's provenance -- was not rendered at all on a phone. */
  flex: none;
}
#reel-terminal-log {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: calc(var(--grid-gap) * 1.6);
  max-width: none;
}
.reel-log-line {
  display: grid;
  grid-template-columns: 2.5em minmax(0, 1fr) auto;
  gap: 1em;
  white-space: nowrap;
}
.reel-log-status { color: var(--accent-bright); }

#reel-frame-title {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0 .3em;
  font-size: 7.5em;
  line-height: 1;
  font-weight: 500;
  letter-spacing: -.02em;
  text-transform: uppercase;
  color: var(--off-white);
  pointer-events: none;
}
.reel-frame-word { display: inline-block; white-space: nowrap; }

#reel-watch {
  font-size: var(--fs-body);
  width: 9.4em;
  height: 6em;
  border-radius: 22.5em;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--off-white);
  color: var(--fg);
  pointer-events: auto;
  transition: color var(--d-2) var(--e-std);
}
.reel-watch-bg {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--accent-live, var(--accent));
  transform: scale(0);
  transition: transform var(--d-3) var(--e-lusion);
  z-index: -1;
}
.reel-watch-arrow { font-size: 2.2em; }
@media (hover: hover) {
  #reel-watch:hover { color: var(--off-white); }
  #reel-watch:hover .reel-watch-bg { transform: scale(1); }
}

/* ------------------------------------------------------------- featured --- */
#featured { padding-top: calc(var(--pad-y) * 3); }

#featured-head {
  grid-column: 1 / 13;
  position: relative;
}

/* The pill is a grid item of #featured's twelve-column grid and had no column of
   its own, so it was auto-placed into a single 1fr track: 77px wide against the
   167px "All 200 services" needs. .pill carries overflow:hidden, so the page's
   one route to the whole catalogue read "ALL 20". Measured, not guessed —
   clientWidth 77 against scrollWidth 167. */
#featured-cta {
  grid-column: 1 / 13;
  justify-self: start;
  margin-top: calc(var(--pad-y) * 0.6);
}

#featured-title {
  font-size: 8vw;
  line-height: .9;
  letter-spacing: -.02em;
  font-weight: 400;
  overflow: hidden;
  margin-left: -.07em;
  /* the title is its own char mask; room for the "g" at .9 leading */
  padding-bottom: .12em;
}

/* It was absolutely positioned against the title block’s right edge — which is
   exactly where an 8vw headline puts its last word, so the two printed on top
   of each other. In normal flow it cannot collide with anything. */
#featured-disclaimer {
  position: static;
  max-width: 62ch;
  margin-top: .9em;
  font-size: clamp(13px, 1.05vw, 17px);
  line-height: 1.5;
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg-muted);
}

/* --- the six practices ------------------------------------------------------
   These were decorative WebGL panels with generated patterns inside them and
   the name printed underneath. The pattern meant nothing and the name read
   backwards, so both went. A card now carries what a visitor actually needs:
   the name first, then what the practice is, then every category inside it with
   its count, then how many of them the studio delivers itself. */

.practice-list {
  grid-column: 1 / 13;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
  gap: var(--grid-gap);
  margin-top: calc(var(--pad-y) * 1.6);
}

.practice { position: relative; }

/* The six practice cards carry the page's only dark surface above the corridor.
   On --surface they were cream on cream: the cards and the ground behind them
   were within a couple of levels of each other, so six separate objects read as
   one flat field and nothing on the screen had an edge.

   Rather than recolour eight children one at a time, the card redefines the
   tokens its children already read — the same move #refuse makes for a whole
   section. A custom property set on an element is visible to that element's own
   declarations too, so `border-top: 2px solid var(--accent)` below picks up the
   bright gold from the line above it, and .practice-n / .practice-go inherit it. */
.practice-link {
  --fg: var(--off-white);
  --fg-muted: #9A948A;
  --rule: var(--line-dark);
  --accent: var(--accent-bright);

  display: flex;
  flex-direction: column;
  height: 100%;
  padding: clamp(22px, 2vw, 32px);
  background: var(--ink);
  border: 1px solid var(--rule);
  border-top: 2px solid var(--accent);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--off-white);
  opacity: 0;
  transform: translate3d(0, 1.6em, 0);
  transition:
    opacity .5s var(--e-lusion),
    transform .5s var(--e-lusion),
    background-color var(--d-2) var(--e-std),
    border-color var(--d-2) var(--e-std),
    box-shadow var(--d-2) var(--e-std);
}
.practice.is-in .practice-link { opacity: 1; transform: none; }
/* A shadow does almost nothing under a dark card on a cream ground, so the hover
   is carried by the surface lifting off the black instead. */
.practice-link:hover,
.practice-link:focus-visible {
  background: #1B1712;
  border-color: var(--accent-bright);
  box-shadow: var(--shadow);
}

.practice-n {
  font-size: clamp(11px, .85vw, 13px);
  letter-spacing: .16em;
  color: var(--accent);
}
.practice-name {
  font-size: clamp(23px, 2.3vw, 34px);
  line-height: 1.06;
  letter-spacing: -.02em;
  font-weight: 500;
  margin: .5em 0 0;
}
.practice-lede {
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.55;
  color: var(--fg-muted);
  margin-top: .75em;
  max-width: 46ch;
}

.practice-cats {
  margin: 1.6em 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--rule);
}
.practice-cats li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: .62em 0;
  border-bottom: 1px solid var(--rule);
  font-size: clamp(13.5px, 1vw, 15.5px);
}
.practice-cats li span { flex: 1 1 auto; }
.practice-cats li b {
  font-weight: 400;
  font-size: .85em;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.practice-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
  margin-top: auto;
  padding-top: 1.5em;
  font-size: clamp(11px, .85vw, 13px);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
/* .practice-inhouse is gone: it coloured the retired in-house/partner badge,
   which is never rendered, and --good (#3F6B33) would have been 2.5:1 on the
   card's new dark ground. */
.practice-go {
  margin-left: auto;
  color: var(--accent);
  white-space: nowrap;
}
.practice-go span {
  display: inline-block;
  transition: transform var(--d-2) var(--e-lusion);
}
.practice-link:hover .practice-go span { transform: translateX(.45em); }

@media (max-width: 640px) {
  .practice-list { margin-top: calc(var(--pad-y) * 1.1); }
}

@media (prefers-reduced-motion: reduce) {
  .practice-link { opacity: 1; transform: none; }
}

/* ============================================================================
   REBUILT 2026-09-06. Removing the decorative tile rules, the delete ran from
   `.tile-list` to the END OF THE FILE instead of to the end of that block, and
   took the goal, end, footer, scroll-nav and preloader rules with it. The page
   collapsed from ~31,000px to 6,667px. Rewritten from SPEC.md §5.4–§5.7 and
   §6.1, which carry the geometry measured off lusion.co.
   ========================================================================= */

/* --- the goal section: 2400vh of scroll for the corridor ------------------ */

#goal {
  font-size: clamp(.875rem, 1vw, 2rem);
  padding-top: calc(var(--pad-y) * 3);
  /* The corridor's scroll length, and it is a share of the page as much as a
     number. Measured at 2400 it was 24 viewports with a blank tail; at 2100 the
     tail was gone but the corridor was still 69% of a 33-viewport page, so the
     services — the thing this studio actually sells — had 5% of the scroll and
     the page read as an advert for the audit with a long 3D interlude.
     1850 brings it under half once the services sections are on the page,
     without speeding the cards past the point where they can be read;
     tools/check_corridor.py and tools/check_density.py both have to stay green. */
  /* 1850 and staying there. Cutting it to 1750 did close the dead viewport at
     27.50-28.00, but weighted is derived from this height, so every range
     re-timed by about five per cent and a NEW 1.5-viewport gap opened at
     24.00-25.00. The tail is covered by #pipeline growing into it instead. */
  padding-bottom: calc(var(--vh) * 1850);
  color: var(--fg);
}
html.is-black-bg #goal { color: var(--off-white); }

#goal-context {
  grid-column: 1 / 13;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
}
#goal-title {
  grid-column: 1 / 8;
  font-size: 8vw;
  line-height: 1;
  letter-spacing: -.01em;
  font-weight: 400;
  margin: 0;
  overflow: hidden;
  padding-bottom: .12em;
}
#goal-texts {
  grid-column: 8 / 13;
  align-self: end;
  font-size: 1.5em;
  line-height: 1.4;
  display: grid;
  gap: 1em;
  color: var(--fg-muted);
}
#goal-texts p { overflow: hidden; margin: 0; }

#goal-image-in-outer { grid-column: 1 / 8; margin-top: calc(var(--pad-y) * 2); }
#goal-image-in {
  position: relative;
  aspect-ratio: 1496 / 1080;
  border-radius: var(--radius);
}
#goal-image-in-screen { position: absolute; left: 3.9%; top: 5.4%; width: 92.5%; height: 89.5%; }

#goal-image-out-outer { grid-column: 2 / 12; margin-top: calc(var(--pad-y) * 2); }
#goal-image-out {
  position: relative;
  aspect-ratio: 2000 / 1537;
  border-radius: var(--radius);
}
#goal-image-out-screen { position: absolute; left: 2.2%; top: 2.8%; width: 95.7%; height: 70.2%; }

/* The tagline that justifies and then flies past the camera. Height 0 so it
   takes no space in flow; goal.js pins and scales it. */
#goal-tunnel-title {
  position: relative;
  grid-column: 1 / 13;
  height: 0;
  width: 100%;
  text-align: center;
  font-size: 6vw;
  line-height: 1.05;
  text-transform: uppercase;
  color: var(--off-white);
  visibility: hidden;
  will-change: transform;
  pointer-events: none;
}
#goal-tunnel-title .line span { display: inline-block; position: relative; margin: 0 .25ch; }

/* --- the close: 350vh, pinned through the middle -------------------------- */

#end {
  position: relative;
  /* (1 + PIN_VIEWS) viewports: one for the panel, one for how long it is held.
     This was 350 -- three and a half viewports for a close that has finished
     animating after about a second, so two and a half of them were a static
     screen the reader had to scroll through. PIN_VIEWS in js/sections/end.js
     is the other half of this number; change them together. */
  height: calc(var(--vh) * 350);
  font-size: clamp(.875rem, 1vw, 2rem);
  color: var(--fg);
}
#end-outer {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: calc(var(--vh) * 100);
  will-change: transform;
}
#end-content {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  padding-inline: var(--pad-x);
  text-align: center;
}
/* --fg, not --fg-muted. These two do not sit on the page ground: three
   report sheets are drawn in WebGL directly behind them and shade from near
   white to #D6CFC2 at the rim, so a muted ink measured 2.24:1 across 88% of
   the subtitle's pixels in the light theme. The headline beside them is 137px
   and these are 13-14px -- the size carries the hierarchy without the text
   also having to be faint. */
#end-subtitle {
  margin: 0;
  overflow: hidden;
  font-size: 1em;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--fg);
}
#end-title {
  --font-size: 10vw;
  position: relative;
  width: fit-content;
  margin: .4em auto 0;
  line-height: 1;
  font-size: var(--font-size);
  font-weight: 400;
  letter-spacing: -.03em;
}
#end-title-link { text-decoration: none; color: inherit; cursor: pointer; }
#end-title-link:hover { color: var(--accent); }

.end-deco {
  position: absolute;
  height: .07em;
  background: currentColor;
  transform-origin: left center;
  transform: scaleX(0);
  pointer-events: none;
}
.end-deco--top { top: .85em; left: .05em; width: calc(100% - .05em); }
.end-deco--bl  { bottom: .1em; left: .25em; width: .82em; }
.end-deco--br  { bottom: .1em; left: 1.78em; width: 2.45em; }

#end-tiers {
  margin: 1.6em 0 0;
  font-size: clamp(11px, .95vw, 15px);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--fg);
  opacity: 0;
}
.end-tier { white-space: nowrap; }
.end-tier-sep { color: var(--accent); }

#end-crosses {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 60vw;
  height: 44vh;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
#end-crosses .cross:nth-child(1) { left: 0; top: 0; }
#end-crosses .cross:nth-child(2) { right: 0; top: 0; left: auto; }
#end-crosses .cross:nth-child(3) { left: 0; bottom: 0; top: auto; }
#end-crosses .cross:nth-child(4) { right: 0; bottom: 0; left: auto; top: auto; }

/* --- the footer: the one white ground ------------------------------------ */

#footer {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  align-content: start;
  min-height: calc(var(--vh) * 100);
  color: var(--fg);
  font-size: clamp(.875rem, 1vw, 2rem);
  padding-block: calc(var(--pad-y) * 1.5);
}
#footer-bg {
  position: absolute;
  left: 50%;
  top: -2px;
  width: 100vw;
  height: calc(100% + 4px);
  margin-left: -50vw;
  /* --bg, not --white: this band is the page ground behind the footer, and
     a named colour here was the one element that did not follow the theme. */
  background: var(--bg);
}
#footer-top { position: relative; z-index: 1; grid-column: 1 / 13; }
#footer-statement {
  font-size: 5vw;
  line-height: 1.05;
  letter-spacing: -.022em;
  margin: 0;
  max-width: 18ch;
}
#footer-statement .line { overflow: hidden; }

#footer-middle {
  position: relative;
  z-index: 1;
  grid-column: 1 / 13;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  row-gap: calc(var(--pad-y) * 1.2);
  margin-top: calc(var(--pad-y) * 2);
}
#footer-contact {
  grid-column: 1 / 7;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: calc(var(--pad-y) * .8) var(--grid-gap);
}
.footer-block { min-width: 0; }
.footer-label {
  font-size: .72em;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--fg-muted);
  font-weight: 400;
  margin: 0 0 .5em;
}
.footer-lines { margin: 0; line-height: 1.5; }
.footer-link {
  position: relative;
  color: inherit;
  text-decoration: none;
  width: fit-content;
}
.footer-link::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: .15em;
  width: 100%;
  height: .08em;
  background: currentColor;
  transform-origin: left center;
  transform: scale3d(0, 1, 1);
  transition: transform var(--d-1) var(--e-lusion);
}
.footer-link:hover::before { transform: scale3d(1, 1, 1); }

#footer-links {
  grid-column: 7 / 13;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--grid-gap);
  align-content: start;
}
.footer-links-col { list-style: none; margin: 0; padding: 0; }
.footer-links-col li { line-height: 1.4; }
#footer-honesty {
  grid-column: 1 / -1;
  margin: 1em 0 0;
  font-size: .8em;
  line-height: 1.6;
  color: var(--fg-muted);
  max-width: 70ch;
}

#footer-bottom {
  position: relative;
  z-index: 1;
  grid-column: 1 / 13;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6em 2em;
  margin-top: calc(var(--pad-y) * 1.6);
  padding-top: calc(var(--pad-y) * .8);
  border-top: 1px solid var(--line);
  font-size: .78em;
  letter-spacing: .06em;
  color: var(--fg-muted);
}
#footer-bottom p { margin: 0; }
#footer-up { margin-left: auto; text-decoration: none; color: var(--fg); }

/* --- overscroll to the next page ----------------------------------------- */

#scrollnav {
  position: relative;
  display: block;
  background: #121416;
  color: #fff;
  font-size: clamp(.875rem, 1vw, 2rem);
  padding: calc(var(--pad-y) * 1.2) var(--pad-x);
}
#scrollnav-text {
  margin: 0;
  white-space: nowrap;
  font-size: 3.5em;
  line-height: 1;
  padding: .3em 0;
}
#scrollnav-link { color: inherit; text-decoration: none; }
#scrollnav-link:hover { color: var(--accent-bright); }
#scrollnav-right { display: flex; align-items: center; gap: .75em; margin-top: 1em; }
#scrollnav-bar { flex-grow: 1; height: 4px; background: #34393f; }
#scrollnav-bar-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: #fff;
  transform-origin: left center;
  transform: scaleX(0);
}

/* --- the preloader odometer ---------------------------------------------- */

#preloader-digits {
  position: absolute;
  left: var(--pad-x);
  bottom: 0;
  display: flex;
  font-size: clamp(7em, 8vw, 20em);
  height: .75em;
  line-height: .75em;
  color: var(--off-white);
  overflow: hidden;
  font-weight: 500;
  letter-spacing: -.03em;
}
.digit {
  position: relative;
  width: 1ch;
  text-align: center;
  transform: translateY(-.05em);
}
.num { display: block; height: .75em; line-height: .75em; }

#scroll-indicator-bar {
  position: absolute;
  inset: 0;
  background: var(--ink);
  transform-origin: 0 0;
  border-radius: 3px;
}
html.is-black-bg #scroll-indicator-bar { background: var(--off-white); }

/* --- narrow screens ------------------------------------------------------ */

@media (max-width: 768px) {
  /* 7.5em of a 14px base is 105px; the single word "report" was 402px wide
     inside a 390px viewport. The frame title has to scale with the frame. */
  #reel-frame-title { font-size: clamp(2rem, 11vw, 7.5em); }
  #reel-terminal { font-size: .82em; line-height: 1.5; }
  /* Six tool names do not fit on one phone line, and an ellipsis on a
     provenance line hides the half that names the engines. It wraps here. */
  #reel-terminal-head { white-space: normal; text-overflow: clip; }
  #reel-terminal-log { grid-template-columns: minmax(0, 1fr); }
  #reel-terminal-log .reel-log-line:nth-child(n+9) { display: none; }

  #goal-title { grid-column: 1 / 13; font-size: 13vw; }
  #goal-texts { grid-column: 1 / 13; font-size: 1.15em; margin-top: 1.4em; }
  #goal-image-in-outer,
  #goal-image-out-outer { grid-column: 1 / 13; }
  #goal-tunnel-title { font-size: 11vw; }

  #end-title { --font-size: 17vw; }
  #end-crosses { width: 84vw; }
  #end-tiers { font-size: 11px; letter-spacing: .08em; }

  #footer-statement { font-size: 9vw; max-width: none; }
  #footer-contact,
  #footer-links { grid-column: 1 / 13; }
  #footer-bottom { font-size: 11px; }
  #footer-up { margin-left: 0; }

  #scrollnav-text { font-size: 1.9em; white-space: normal; }
}

@media (prefers-reduced-motion: reduce) {
  html.js .end-deco { transform: scaleX(1); }
  html.js #end-tiers { opacity: 1; }
}

/* ---- reachable chrome ------------------------------------------------------
   Measured by tools/check_site.py, not guessed: the wordmark and the footer's
   navigation links were 16–17px tall on a phone. Text links inside a sentence
   are exempt from the 44px rule (WCAG 2.5.8 inline exception); navigation
   chrome is not. */
#header-logo { display: grid; align-content: center; min-height: 44px; }
.footer-links-col a,
#footer-up { display: inline-flex; align-items: center; min-height: 44px; }

/* The footer's contact links are the page's last real targets; at 24px they
   were the only thing on the site a thumb could still miss. They must be
   inline-BLOCK, not inline-flex: a flex container will not wrap its own text,
   so "admin@getlightening.com" grew past its column on a phone and printed
   over the block beside it. Padding, not centring, makes the 44px. */
.footer-lines a,
#footer-contact .footer-link {
  display: inline-block;
  min-height: 44px;
  padding-block: 10px;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

/* The links carry their own height now, so the surrounding lines only need
   enough room not to crowd them. */
#footer-contact .footer-lines { line-height: 1.5; }

/* ============================================================================
   HOME CONTENT SECTIONS

   Measured before these existed: the corridor was 69% of a 33-viewport home
   page, the audit had 9% and the services — the thing the studio sells — had
   5%. The page read as an advert for one product with a long 3D interlude in
   the middle, which is exactly what it was.

   One component, three arrangements, rather than five bespoke sections:

     .hsec        the section, its heading and its standfirst
     .hgrid       cards, for a set of parallel points
     .hgrid--steps  a numbered process, number in its own gutter
     .hgrid--pairs  a two-column mapping: what you say / what it usually is

   Everything is token-driven, so these read correctly in the pale phase and in
   the black phase the corridor turns the page into.
   ========================================================================= */

.hsec {
  grid-column: 1 / 13;
  padding-top: calc(var(--pad-y) * 2.2);
}
.hsec + .hsec { padding-top: calc(var(--pad-y) * 1.6); }

.hsec-eyebrow {
  font-size: clamp(10px, .78vw, 13px);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
}
.hsec-title {
  font-size: clamp(28px, 4.4vw, 68px);
  line-height: 1.02;
  letter-spacing: -.02em;
  font-weight: 400;
  max-width: 20ch;
  margin-top: .5em;
}
.hsec-lede {
  max-width: 60ch;
  margin-top: 1em;
  font-size: clamp(14px, 1.15vw, 18px);
  line-height: 1.55;
  color: var(--fg-muted);
}

/* --- cards ---------------------------------------------------------------- */
/* auto-fit rather than a fixed column count: four blocks and six blocks both
   have to look deliberate, and a widow in the last row is the tell that they
   were not thought about. */
.hgrid {
  display: grid;
  /* 340, not 268. At 268 a 1215px row fits four columns, which leaves the six
     blocks in "range" as 4+2 and the five in "rates" as 4+1 — one card alone in
     the last row with three empty columns beside it. Three columns give 3+3 and
     3+2, and the three in "refuse" fill a row exactly. Measured, not guessed. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr));
  gap: clamp(12px, 1.2vw, 20px);
  margin-top: clamp(26px, 3vw, 46px);
}
/* Dark, like the practice cards: on --surface these were cream on cream, six or
   nine objects and the ground within a couple of levels of each other, so a
   whole screen of them read as one flat field. The card redefines the tokens
   its children already read rather than recolouring each one — the same move
   #refuse and .practice-link make. A custom property set here is visible to this
   rule's own declarations, so border-top picks up the bright gold below. */
.hblock {
  display: flex;
  flex-direction: column;
  padding: clamp(20px, 1.9vw, 30px);
  border: 1px solid var(--rule);
  border-top: 2px solid var(--accent);
  border-radius: var(--radius);
}
/* The tokens belong to the CARD, not to .hblock. Two variants -- .hgrid--steps
   and .hgrid--pairs -- set `background: none` and sit straight on the cream
   ground, and when the tokens lived on .hblock they inherited off-white text
   and bright gold headings onto that cream: measured at 1.07 and 1.52 against
   a required 4.5. Which is exactly what Kumail saw. So the dark palette is
   scoped to the cards that actually have a dark ground under them. */
.hgrid:not(.hgrid--steps):not(.hgrid--pairs) > .hblock,
#refuse .hblock {
  --fg: var(--off-white);
  --fg-muted: #9A948A;
  --rule: var(--line-dark);
  --accent: var(--accent-bright);
  background: var(--ink);
  color: var(--off-white);
}

/* #refuse is already on the ink ground, so its cards separate from it rather
   than from cream. */
#refuse .hblock { background: #1B1712; }
.hblock-label {
  font-size: clamp(10px, .78vw, 12.5px);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.hblock-title {
  font-size: clamp(17px, 1.5vw, 23px);
  line-height: 1.22;
  letter-spacing: -.012em;
  margin-top: .5em;
}
.hblock-body {
  font-size: clamp(13.5px, 1.02vw, 16px);
  line-height: 1.6;
  color: var(--fg-muted);
  margin-top: .7em;
  max-width: 46ch;
}
.hblock-meta {
  font-family: var(--font-mono);
  font-size: clamp(11px, .82vw, 13px);
  letter-spacing: .06em;
  color: var(--accent);
  margin-top: auto;
  padding-top: 1.2em;
}

/* --- a numbered process --------------------------------------------------- */
/* The number sits in its own column so every step's text starts on the same
   line, however long the numbers or the titles get. */
.hgrid--steps {
  grid-template-columns: 1fr;
  gap: 0;
}
.hgrid--steps .hblock {
  display: grid;
  grid-template-columns: 3.4em 1fr;
  gap: .2em 1.4em;
  background: none;
  border: 0;
  border-top: 1px solid var(--rule);
  border-radius: 0;
  padding: clamp(18px, 1.8vw, 28px) 0;
}
.hgrid--steps .hblock:last-child { border-bottom: 1px solid var(--rule); }
.hgrid--steps .hblock-label {
  grid-row: span 3;
  font-family: var(--font-mono);
  font-size: clamp(12px, .95vw, 15px);
  color: var(--accent);
  letter-spacing: .08em;
  padding-top: .35em;
}
.hgrid--steps .hblock-body { max-width: 62ch; }
.hgrid--steps .hblock-meta { margin-top: .8em; padding-top: 0; color: var(--fg-muted); }

/* --- a mapping ------------------------------------------------------------ */
/* The left column is what a buyer actually says; the right is what it turns
   out to be. Two columns above 720px, stacked below — the pairing is the whole
   point, so it must not collapse into an undifferentiated list. */
.hgrid--pairs {
  grid-template-columns: 1fr;
  gap: 0;
}
.hgrid--pairs .hblock {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  align-items: start;
  gap: .4em clamp(18px, 2.4vw, 44px);
  background: none;
  border: 0;
  border-top: 1px solid var(--rule);
  border-radius: 0;
  padding: clamp(16px, 1.7vw, 26px) 0;
}
.hgrid--pairs .hblock:last-child { border-bottom: 1px solid var(--rule); }
.hgrid--pairs .hblock-label {
  grid-row: span 2;
  font-family: var(--font-sans);
  font-size: clamp(15px, 1.35vw, 21px);
  line-height: 1.3;
  letter-spacing: -.01em;
  text-transform: none;
  color: var(--fg);
}
.hgrid--pairs .hblock-title {
  margin-top: 0;
  font-size: clamp(13px, 1.02vw, 15.5px);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
}
.hgrid--pairs .hblock-body { margin-top: .5em; max-width: 52ch; }

/* --- the closing line a section can end on -------------------------------- */
.hsec-close {
  margin-top: clamp(22px, 2.4vw, 38px);
  max-width: 56ch;
  font-size: clamp(14px, 1.15vw, 18px);
  line-height: 1.55;
}

/* --- narrow --------------------------------------------------------------- */
@media (max-width: 720px) {
  .hgrid--pairs .hblock { grid-template-columns: 1fr; gap: .3em; }
  .hgrid--pairs .hblock-label { grid-row: auto; }
  .hgrid--steps .hblock { grid-template-columns: 1fr; }
  .hgrid--steps .hblock-label { grid-row: auto; padding-top: 0; }
}

/* --- rhythm: one section changes the ground ------------------------------
   Measured, and it was the reason the page read flat even once the words were
   right: the five written sections were identical. Every one had a 60px title,
   797px wide, at x=68, over the same cream, with the same cards. Scrolling
   through them felt like one long block, and a reader stops seeing a shape
   that never changes.

   #refuse goes onto the ink ground. It is the right one to move: it carries
   the studio's refusals, which are the most serious thing on the page, and it
   sits immediately before the closing call to action, so the page darkens once
   just as it asks for the decision. Tokens are overridden on the section
   rather than by a body class, so nothing else on the page is affected. */
/* --------------------------------------------------------------- robotic --- */
/* Fills stretch F. Two voices side by side: the left carries the argument, the
   right states the boundary once and stops. The boundary is deliberately NOT
   repeated on the left — "we do not build machines" said twice on one screen
   reads as a hedge, and the left draft's "Not the machines themselves" was cut
   for exactly that reason.

   It sits on the ink ground, so the page changes state here on purpose instead
   of doing it by accident when #refuse arrives one screen later. */
#robotic {
  --bg: var(--black);
  --fg: var(--off-white);
  --fg-muted: #9A948A;
  --rule: var(--line-dark);
  background: var(--black);
  color: var(--off-white);
  /* Matches #refuse exactly — same radius, same gutter. Full-bleed here and a
     rounded slab there put a stripe of cream between two dark sections and made
     it look like a mistake; as a pair of slabs it reads as a rhythm. */
  padding-block: calc(var(--pad-y) * 2.2);
  margin-block: calc(var(--pad-y) * 1.2);
  border-radius: var(--radius);
}

.rob {
  grid-column: 1 / 13;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
  gap: clamp(36px, 5vw, 90px);
  align-items: start;
}

.rob-eyebrow {
  margin: 0;
  font-size: clamp(11px, .85vw, 13px);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent-bright);
}

.rob-title {
  margin: .55em 0 0;
  font-size: clamp(27px, 3.4vw, 48px);
  line-height: 1.06;
  letter-spacing: -.02em;
  font-weight: 500;
  max-width: 20ch;
  text-wrap: balance;
}

.rob-body {
  margin-top: 1.1em;
  font-size: clamp(14.5px, 1.05vw, 16.5px);
  line-height: 1.6;
  color: var(--fg-muted);
  max-width: 52ch;
}

/* The right-hand statement is not a heading — it is one sentence set larger than
   the prose beside it and given nothing else to share the column with. */
.rob-aside {
  border-top: 2px solid var(--accent-bright);
  padding-top: 1.4em;
}
.rob-line {
  margin: .6em 0 0;
  font-size: clamp(22px, 2.3vw, 33px);
  line-height: 1.24;
  letter-spacing: -.015em;
  max-width: 22ch;
  text-wrap: balance;
}

#refuse {
  --bg: var(--black);
  --fg: var(--off-white);
  --fg-muted: #9A948A;
  --rule: var(--line-dark);
  --surface: #16150F;
  background: var(--black);
  color: var(--off-white);
  padding-block: calc(var(--pad-y) * 2.4);
  margin-block: calc(var(--pad-y) * 1.2);
  border-radius: var(--radius);
}
#refuse .hsec { padding-top: 0; }
#refuse .hsec-eyebrow { color: var(--accent-bright); }
#refuse .hblock { border-top-color: var(--accent-bright); }
#refuse .hblock-meta { color: var(--accent-bright); }

/* --- rhythm: the sections no longer share one size ----------------------- */
/* #problem opens the page's argument and #range answers it, so those two carry
   more weight than the three that follow. The steps in #how are a long read and
   want a quieter head. */
#problem .hsec-title { font-size: clamp(30px, 5.2vw, 76px); max-width: 17ch; }
#range .hsec-title { font-size: clamp(28px, 4.8vw, 70px); max-width: 18ch; }
#how .hsec-title { font-size: clamp(26px, 3.9vw, 54px); max-width: 24ch; }
#refuse .hsec-title { font-size: clamp(28px, 4.8vw, 70px); max-width: 18ch; }

/* -------------------------------------------------------------- pipeline --- */
/* Gap E3: the two blank cream screens between the corridor's exit and #how.
   Pinned by offset inside #goal rather than added to its flow — everything
   below 10.6 viewports in that section is one padding-bottom, so a plain child
   would land at the top of it and not here. 1790 --vh units puts it at about
   26.6 viewports, where check_empty found the run starting. */
/* A dark slab, like #robotic and #refuse, and for a reason beyond taste. This
   section sits exactly where the corridor hands the page back to the light
   ground, and setGround flips that ground a frame or two before the text
   colours follow -- so for a moment the words were off-white on cream and
   almost unreadable. Carrying its own ground and its own tokens takes it out of
   that argument entirely: ink under off-white, whatever the page is doing. */
#pipeline {
  --fg: var(--off-white);
  --fg-muted: #9A948A;
  --rule: var(--line-dark);
  --accent: var(--accent-bright);

  position: absolute;
  /* Anchored to the END of the corridor, not to a count of viewports from its
     start. top: calc(var(--vh) * 1850) was measured on a laptop; on a phone
     everything above it is taller and the slab is taller too, so it ran 266px
     past the corridor and painted its opaque black over the start of "How the
     work runs", heading included. A bottom offset cannot do that: whatever the
     slab's own height, the tail below it is the same. */
  bottom: clamp(280px, 55vh, 620px);
  left: var(--pad-x);
  right: var(--pad-x);
  z-index: 2;

  background: var(--black);
  color: var(--off-white);
  border-radius: var(--radius);
  padding: clamp(56px, 6vw, 116px) clamp(24px, 3vw, 60px);
}
.pipe-eyebrow {
  margin: 0;
  font-size: clamp(11px, .85vw, 13px);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
}
#pipeline-title {
  margin: .5em 0 0;
  font-size: clamp(26px, 3.6vw, 52px);
  line-height: 1.05;
  letter-spacing: -.022em;
  font-weight: 500;
  max-width: 20ch;
  text-wrap: balance;
}
/* Rows, not five columns. This slab has to cover the stretch between the
   corridor's exit and #how, and filling that with padding would be a void in a
   different colour. As rows each step gets room to be read, and the list reads
   as a sequence -- which is what it is. */
.pipe {
  display: grid;
  gap: 0;
  margin: clamp(28px, 3vw, 52px) 0 0;
  padding: 0;
  list-style: none;
}
.pipe > li {
  display: grid;
  /* rem, not ch. A ch on the grid track is measured against the CONTAINER'S
     font size (16px), not the h3's 27px, so 12ch was 96px while "Deployment"
     needs about 150 — and the word ran straight over its own description. */
  grid-template-columns: 3.5rem minmax(0, 11.5rem) minmax(0, 1fr);
  gap: clamp(12px, 2vw, 40px);
  align-items: baseline;
  padding: clamp(30px, 3.8vw, 82px) 0;
  border-top: 1px solid var(--rule);
}
.pipe > li:first-child { border-top: 2px solid var(--accent); }
@media (max-width: 720px) {
  .pipe > li { grid-template-columns: 3rem minmax(0, 1fr); }
  .pipe > li p:last-child { grid-column: 2; }
}
.pipe-n { margin: 0; font-size: 12.5px; letter-spacing: .14em; color: var(--accent); }
.pipe h3 {
  margin: 0;
  font-size: clamp(19px, 1.9vw, 27px);
  font-weight: 500;
  letter-spacing: -.014em;
  line-height: 1.15;
}
.pipe p:last-child {
  margin: 0;
  font-size: clamp(14.5px, 1.05vw, 16.5px);
  line-height: 1.6;
  color: var(--fg-muted);
  max-width: 52ch;
}
.pipe-close {
  margin: clamp(24px, 2.4vw, 40px) 0 0;
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.5;
  color: var(--fg-muted);
  max-width: 54ch;
}

/* ------------------------------------------------------------ hero panel --- */
/* Removed on Kumail's instruction, 8 Sep 2026 -- something else is going in its
   place. Hidden rather than deleted from the markup: hero.js syncs a WebGL
   plane to this box every frame, and it is the anchor for that whole scene, so
   taking the element out is a change to the world module rather than to CSS.
   A zero box parks the plane at zero scale and it draws nothing. */
#hero-visual { display: none; }

/* ------------------------------------------------- dark mode: the planes ---
   #refuse, #robotic and #pipeline paint themselves --black, and the cards
   inside them --ink. On a --black page that is dark on dark. They lift by one
   step each so the page still reads as three planes: ground, slab, card. */
html[data-theme="dark"] #refuse,
html[data-theme="dark"] #robotic,
html[data-theme="dark"] #pipeline { background: #16150F; }

html[data-theme="dark"] .practice-link,
html[data-theme="dark"] .hgrid:not(.hgrid--steps):not(.hgrid--pairs) > .hblock { background: #201C15; }
html[data-theme="dark"] #refuse .hblock { background: #23200E; }
html[data-theme="dark"] .practice-link:hover,
html[data-theme="dark"] .practice-link:focus-visible { background: #2A2519; }

/* The header scrim follows the ground; on a dark page it must not be a pale
   band across the top. */
html[data-theme="dark"] #header::before { --scrim: var(--black); }

/* ------------------------------------------------------------ theme button ---
   Sits with the sound button: same size, same shape, same hit area. The icon is
   drawn in CSS rather than shipped as an SVG so it inherits currentColor and
   needs no second file. */
#theme-btn {
  width: 3.2em;
  height: 3.2em;
  border-radius: 50%;
  background: var(--pill-bg);
  color: var(--pill-fg);
  display: inline-grid;
  place-items: center;
  flex: none;
  transition: background-color .25s, color .25s;
}
#theme-btn .theme-icon {
  width: 1.05em;
  height: 1.05em;
  border-radius: 50%;
  background: currentColor;
  position: relative;
  overflow: hidden;
  transition: box-shadow var(--d-2) var(--e-std);
}
/* Light: a filled disc with a bite out of it -- a crescent, meaning "go dark".
   Dark: the bite closes into a full sun. One element, one transition. */
#theme-btn .theme-icon::after {
  content: '';
  position: absolute;
  inset: -35% -35% -35% 15%;
  border-radius: 50%;
  background: var(--pill-bg);
  transition: transform var(--d-2) var(--e-lusion);
}
html[data-theme="dark"] #theme-btn .theme-icon::after { transform: translateX(115%); }

/* On the flat pages the header is not a pill bar, so the button follows that
   chrome instead. */
.pnav #theme-btn {
  width: 2.2rem;
  height: 2.2rem;
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--rule);
}
.pnav #theme-btn .theme-icon::after { background: var(--bg); }

/* --------------------------------------------------------- hero video --- */
/* Takes #hero-visual's place in the grid. #hero-visual stays in the markup and
   stays hidden: hero.js syncs a WebGL plane to it every frame and it anchors
   that whole scene, so a <video> inside it would be two things fighting for one
   rectangle. */
html.is-revealed #hero-film { opacity: 1; }

#hero-video {
  grid-column: 1 / span 12;
  grid-row: 3;
  margin-top: calc(var(--pad-y) * 2 / 5);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--ink);
  /* The frame took whatever the third grid row had left, which at a laptop
     height was 401px against 1152 wide -- a 2.9:1 letterbox for a film that is
     the first thing on the page. A floor rather than a height, so the taller
     portrait shapes on tablet and phone are left alone; it only ever grows. */
  min-height: clamp(320px, 54vh, 620px);
}
#hero-film {
  /* Held until reveal(), with the rest of the page. A moving image behind a
     fading overlay takes the whole screen's attention and the intro's line
     went unread. */
  opacity: 0;
  transition: opacity .9s var(--e-std);
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The film is warm and bright and the page is not; a touch down and a touch
     of the site's own warmth stops it reading as a stock clip dropped in. */
  filter: saturate(.92) contrast(1.04) brightness(.94);
}

/* ------------------------------------------------------ why the name --- */
#lightening { padding-block: calc(var(--pad-y) * 2.4); }
.lit { grid-column: 1 / 13; }
.lit-eyebrow {
  margin: 0;
  font-size: clamp(11px, .85vw, 13px);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
}
#lightening-title {
  margin: .45em 0 0;
  font-size: clamp(30px, 5vw, 72px);
  line-height: 1.02;
  letter-spacing: -.026em;
  font-weight: 500;
  text-wrap: balance;
}
.lit-lede {
  margin: clamp(18px, 1.8vw, 30px) 0 0;
  font-size: clamp(16px, 1.35vw, 21px);
  line-height: 1.5;
  color: var(--fg-muted);
  max-width: 60ch;
}
.lit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: clamp(20px, 2.4vw, 44px);
  margin: clamp(30px, 3.4vw, 58px) 0 0;
  padding: 0;
  list-style: none;
}
.lit-grid > li { border-top: 2px solid var(--accent); padding-top: clamp(14px, 1.4vw, 22px); }
.lit-n {
  margin: 0 0 .6em;
  font-size: clamp(12px, .95vw, 14px);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
}
.lit-body {
  margin: 0;
  font-size: clamp(14.5px, 1.05vw, 16.5px);
  line-height: 1.6;
  color: var(--fg-muted);
}

/* ============================================================ the weight ===
   Measured before this existed: 87.6% of every word on the site was set at
   weight 400, including a 144px display headline, while geist-600 was
   downloaded on every page load and used by nothing. Weight now follows role.

   These are deliberately last in the file and deliberately specific enough to
   win, because the sizes they apply to are declared in dozens of separate
   component rules that each invented their own clamp(). */

/* Display: the type that is meant to arrive. 600, and tracked in, because
   Geist at 96px with a 28px heading's tracking reads loose rather than set. */
#hero-title,
#end-title,
#goal-title,
#lightening-title,
#pipeline-title,
#footer-statement,
#reel-frame-title,
.hsec-title,
.cta-title,
.section-title,
.phero h1,
#featured-title,
#goal-tunnel-title,
#reel-title,
#scrollnav-link {
  font-weight: 600;
  letter-spacing: -.03em;
}

/* Headings everywhere else. 500 was a half-step that read as body copy at a
   larger size. */
h1, h2, h3 { font-weight: 600; }

/* There is no Geist 700, so bold was being synthesised -- the browser smears
   the 400 outlines and the result looks cheap next to real type. 600 is a
   real cut and it is already loaded. */
strong, b, .prose strong, .prose b { font-weight: 600; }

/* The mono has 400 and 500 only. Its job is labels and data, so its emphasis
   is the 500, never a synthesised bold. */
.mono strong, .mono b { font-weight: 500; }

/* ======================================================= header hierarchy ===
   Four filled capsules of identical weight gave the header no entry point:
   the primary action and the menu were the same component at the same
   contrast. Weight now follows importance. */

/* The one action. It keeps the swelling-dot signature; nothing else does. */
#talk-btn {
  font-weight: 600;
  letter-spacing: .04em;
  padding-inline: 1.25em 1.75em;
}

/* Navigation, not a second offer: bordered rather than filled. */
#menu-btn {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--rule);
  font-weight: 500;
  transition: border-color .25s, color .25s, background-color .25s;
}
#menu-btn .pill-dot { background: var(--fg-muted); }
@media (hover: hover) {
  /* The flood belongs to the action. The menu answers with its border. */
  #menu-btn:hover { border-color: var(--fg); background: transparent; }
  #menu-btn:hover .pill-dot { transform: none; background: var(--accent); }
  #menu-btn:hover .pill-text { transform: none; color: var(--fg); }
}

/* Utilities. Smaller than the two controls that carry meaning, and quiet:
   the solid light fill belongs to the action, so these sit on the panel
   colour and stop competing with it. */
#theme-btn.header-btn,
#sound-btn.header-btn {
  font-size: .8em;
  width: 2.9em;
  height: 2.9em;
  background: var(--panel);
  color: var(--panel-fg);
  border: 1px solid var(--rule);
}

/* The wordmark was 19px at weight 500 -- lighter than the sentence it
   introduces, on a page whose headline is now 600. */
.logo-wordmark {
  font-weight: 600;
  font-size: 1.06em;
  letter-spacing: -.015em;
}

/* The skip link. It lived only in pages.css, so when the home page got one it
   rendered as a plain in-flow link at the top-left of the document -- present
   in the tab order, invisible on focus, and doing none of the job. The home
   page is the one page that loads site.css and not pages.css.

   z-index 200: the header sits at 52 inside #ui, and the corridor's own
   layers go higher, so 100 was not enough to clear them here. */
.skip {
  position: fixed;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--ink);
  color: var(--off-white);
  padding: 12px 18px;
  border-radius: 0 0 8px 0;
  font-weight: 500;
}
.skip:focus { left: 0; }
