/* ============================================================
   olive.is — alternate front page, v2. The scrollytelling-essay
   register, ported from first-use-case/css/story.css.

   TYPE SCALE AND SPACING ARE UNCHANGED FROM THE REFERENCE — every
   clamp(), every rem, every breakpoint below is copied verbatim, with
   ONE exception, recorded rather than silent: `.masthead h1`'s clamp
   MAXIMUM is 4.375rem (70px), not the reference's 5rem (80px), from the
   founder's own edit of the rendered page (2026-09-03, made at a 1299px
   viewport). Only the maximum moved. The 2.5rem floor and the 7vw
   preferred term are untouched, so every viewport at or below 1000px --
   where 7vw is already under 70px -- renders exactly what it did before.
   That is the entire fix for the founder's complaint about v1
   ("everything is microscopic"): v1 hand-placed text on canvas and
   shrank the font to fit a box (measured floor 7-8px on phone); this
   file has a hard floor of 0.8125rem (13px) for any real prose,
   enforced by clamp() rather than by a media-query font shrink, and
   never crossed at any width. See DEVIATIONS in index.html's own
   comment header for the one addition (the .masthead-title reveal
   rule) this port needed.

   ONLY the color token VALUES are adapted below, and only to fit
   olive.is's own accent rather than the reference's Miami-vice
   palette — the token NAMES are identical to the reference (and,
   conveniently, identical to olive.is's own design-system names in
   assets/css/site.css, loaded before this file for the header/
   footer chrome). Every hex here is lifted from that file's own
   :root / :root[data-theme="dark"] blocks, not invented:
     light  ink #000000 / paper #f9f9f9 / accent #00682e (site.css's
            own green) / accent-2 #1ba5ff (site.css's own blue)
     dark   ink #EEECE1 / paper #16150F (site.css's own dark ground)
            / accent #95bb6f (site.css's --pill-policy dark rung —
            the one legible light-green already in the palette,
            since site.css's own dark accent collapses to blue) /
            accent-2 #7dadd9 (site.css's own dark accent-2)
   Series colors reuse accent/accent-2 rather than adding a third and
   fourth hue outside the validated palette.
   ============================================================ */

/* BUGFIX (found while building the roles/capture scenes, verified by
   getComputedStyle in a live page: --accent and --accent-2 both read
   back as #7dadd9 in dark mode, silently erasing every scene's two-
   color encoding): assets/css/site.css guards ITS dark palette with
   the attribute selector `:root[data-theme="dark"]` (specificity
   0,1,1,0), which beats a plain `:root {}` (0,0,1,0) regardless of
   which stylesheet loads second — "loads later" only wins a tie.
   Repeating the same attribute selector here matches that specificity,
   and story.css's later position in <head> then wins the tie, same as
   the light block below already does correctly via
   `:root[data-theme="light"]`. The plain `:root` selector stays too,
   as the no-JS/no-attribute fallback (the inline theme script in
   index.html always sets the attribute before paint, so this branch is
   belt-and-suspenders, not the live path). */
:root, :root[data-theme="dark"] {
  /* dark is the default, matching the reference and this essay's
     register — recolored to olive.is's own validated tokens */
  --ink: #EEECE1;
  --paper: #16150F;
  --ink-muted: color-mix(in oklab, var(--ink) 64%, var(--paper) 36%);
  --ink-faint: color-mix(in oklab, var(--ink) 40%, var(--paper) 60%);
  --accent: #95bb6f;           /* the one earned accent: olive.is's dark-mode green */
  --olive-gold: var(--accent); /* legacy alias used across the page */
  --accent-2: #7dadd9;         /* olive.is's dark-mode accent-2 — charts + hero only */
  --hairline: rgba(238, 236, 225, 0.12);
  --paper-wash: rgba(22, 21, 15, 0.86);

  /* chart series — reuse the two accents rather than inventing a third hue */
  --series-human: var(--accent-2);
  --series-machine: var(--accent);
  --chart-grid: rgba(238, 236, 225, 0.09);
  --chart-axis: rgba(238, 236, 225, 0.32);

  /* the masthead's grid paper — 10px squares behind the title, lit on
     touch. Founder-specified, exactly: #1C1C15 on this paper is 1.068:1,
     which is the point. A LITERAL hex and not a color-mix, because
     viz.js's hexA() parses colours by SLICING '#rrggbb' — anything else
     yields rgba(NaN,…), and an invalid fillStyle is silently IGNORED, so
     the grid would paint in whatever colour was set last. */
  --grid-line: #1C1C15;

  --hero-dim: color-mix(in oklab, var(--accent) 30%, var(--paper) 70%);
  --hero-mid: var(--accent-2);
  --hero-hot: var(--accent);
  --hero-top: var(--ink);

  --font-serif: "Fraunces", "Iowan Old Style", Georgia, serif;
  --font-sans: "Inter", -apple-system, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;

  --measure: 34rem;
  --measure-wide: 44rem;
}

:root[data-theme="light"] {
  --ink: #000000;
  /* #f9f9f9 by founder ruling (3 Sep), replacing the pink #fcf1f2 — the same
     move tokens.css made for the built site. This file is hand-written and
     served with no build step, so it did not follow automatically, and because
     DARK IS THE DEFAULT the stale pink was invisible until someone switched
     themes. That is the whole reason it survived the sweep. */
  --paper: #f9f9f9;
  --accent: #00682e;
  --accent-2: #1ba5ff;
  --hairline: rgba(0, 0, 0, 0.12);
  --paper-wash: rgba(249, 249, 249, 0.9);
  --series-human: var(--accent-2);
  --series-machine: var(--accent);
  --chart-grid: rgba(0, 0, 0, 0.08);
  --chart-axis: rgba(0, 0, 0, 0.32);
  /* The founder specified #1C1C15 for the dark theme. This is the same
     step on this paper, computed rather than eyeballed: #1C1C15 sits
     +0.0292 in OKLab lightness above the dark paper, and this value sits
     -0.0301 below this one — the same distance, the other way, because on
     light paper a grid line has to be darker than the page, not lighter.
     RECOMPUTED when paper moved off the pink: #f2e7e8 was that step below
     #fcf1f2, and carrying it onto #f9f9f9 unchanged would have kept a pink
     grid on a grey page AND silently changed the step to -0.0452. On
     #f9f9f9 (L=0.9821) the same -0.0301 lands on #efefef (L=0.9521,
     measured -0.0300 — one integer of rounding, not a second opinion). */
  --grid-line: #efefef;
  --hero-dim: color-mix(in oklab, var(--accent) 20%, var(--paper) 80%);
  --hero-mid: var(--accent-2);
  --hero-hot: var(--accent);
  --hero-top: var(--ink);
}

/* v2-only chrome touch-up (founder-reported: a cream line under the header).
   assets/css/site.css:1633 sets `.c-header { border-bottom: var(--rule-hair)
   solid var(--rule); }` — 1px, 100%-opacity cream in dark mode (--rule is
   #EEECE1 there, site.css:717), by design: it is the ONLY thing separating a
   sticky header from the page beneath it once content scrolls under it, and
   header/page share the exact same --paper background. On the real site
   that hairline sits right above real content (nav pills, a hero image),
   which keeps it from reading as anything but a boundary.

   Scene one here (js/main.js's masthead, per this file's own DEVIATIONS
   note) paints nothing but a flat --paper fill at scroll position zero, so
   the same rule has nothing beside it and reads as a stray opaque line
   floating in an empty field — exactly the report. This build already has a
   validated "subtle divider" token for the same job at this same opacity
   register (--hairline, used by .footer above), so the fix reuses it rather
   than removing the boundary outright: full-opacity cream becomes the
   footer's own whisper-thin rgba, keeping the sticky header's edge legible
   once real content scrolls under it without the disconnected-line look at
   rest. Only this one color channel is touched — width and style still come
   from assets/css/site.css, untouched.

   Scoped to dark, the reported theme and this build's default: in light,
   --rule already resolves to near-black ink on the light paper (site.css's
   :root default, never overridden for light), an ordinary low-contrast
   hairline that was never the complaint — leaving it alone here is the
   narrower change. */
:root[data-theme="dark"] .c-header { border-bottom-color: var(--hairline); }

* { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: auto; }

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

::selection { background: color-mix(in oklab, var(--accent) 40%, var(--paper) 60%); }

a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  transition: text-decoration-color 0.2s ease;
}
a:hover, a:focus-visible { text-decoration-color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- progress hairline ---------- */
.progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 100%;
  z-index: 50; pointer-events: none;
  background: transparent;
}
.progress > i {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
  transform-origin: 0 50%; transform: scaleX(0);
}

/* ---------- theme toggle ---------- */
/* The fixed top-right theme chip this styled is gone from the markup. It was
   z-index 60 under a z-index 100 sticky header, so the header's primary
   button lay across it and the chip could not be clicked at all — a control
   nobody could reach, in a position no other page on the site uses. The
   footer now carries the same .c-theme control every other page carries, and
   site.css already dresses it. */

/* ---------- Romie (display face, hero title only) ----------
   assets/css/site.css already registers this exact family — three files,
   Medium/MediumItalic/Bold, for the header/footer chrome's own --font-display
   — but this essay's own stylesheet had none of its own, and the task asked
   for one here so story.css stays self-contained. Declaring the same
   @font-face twice (once per stylesheet) is harmless: same family/weight/
   style/src, so the browser just resolves either.
   Per ROMIE-INTEGRATION.md: "Declare the real font-weight and font-style on
   each face, or the browser synthesises a fake bold or oblique instead of
   using the drawn one." Bold is the only upright rung the hero title needs
   (600 was already the heaviest weight Fraunces had it set to). Medium +
   MediumItalic are ALSO declared, not for the title itself but for its one
   italic word: the title's <em> would otherwise inherit 700-Bold-italic,
   which does not exist on disk (only Medium, MediumItalic and Bold ship —
   see the integration doc's face table), and the browser would synthesise a
   slanted Bold. assets/css/site.css hit this identical problem on its own
   `.t-em` rule and pins the italic to Medium for the same reason; the
   `.masthead h1 em` rule below does the same thing here. */
@font-face {
  font-family: "Romie";
  src: url("../assets/fonts/Romie-Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Romie";
  src: url("../assets/fonts/Romie-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Romie";
  src: url("../assets/fonts/Romie-MediumItalic.woff2") format("woff2");
  font-weight: 500; font-style: italic; font-display: swap;
}

/* ---------- masthead title (used inside the hero scene's own .step —
   see index.html / main.js. Ported unchanged from the reference EXCEPT
   the reveal trigger: the reference faded this in from asciiHero()'s own
   requestAnimationFrame loop, keyed to the hero canvas's progress. That
   whole system is intentionally not ported (no video, no ascii render —
   founder: "just blank background"), so the one addition below re-wires
   the identical opacity/transform tween onto the SAME .step.is-active
   mechanism every other scene already uses. Everything else in this
   block, including every pixel value, is byte-for-byte the reference. ) */
.masthead {
  position: relative;
  min-height: var(--h, 230svh);
}
.masthead-top {
  position: relative; z-index: 2;
  display: flex; justify-content: flex-start; gap: 0.75rem; align-items: baseline;
  font-family: var(--font-sans);
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--ink);
}
.masthead-top span {
  background: var(--paper-wash);
  border: 1px solid var(--hairline);
  padding: 6px 10px;
}
.masthead-title {
  display: flex; flex-direction: column; align-items: center; gap: 1.5rem;
  max-width: 44rem;
  text-align: center;
  background: var(--paper-wash);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--hairline);
  padding: 2.5rem 3rem;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s ease, transform 0.6s ease;
  will-change: opacity, transform;
  pointer-events: auto;
}
/* THE ONE ADDITION: reveal via the standard step-activation contract
   instead of the reference's bespoke asciiHero() fade. */
.step.is-active .masthead-title { opacity: 1; transform: translateY(0); }
/* THE FORBES CITATION THAT USED TO SIT HERE IS GONE, and so is the rule it
   was written under. That block styled an outlet's name as TEXT because this
   site's press convention was "an outlet's logo here is always styled text,
   never an image" — a rule the founder retired on 3 Sep by asking for the real
   marks ("render the logos, adjust them for dark and light mode"). The marks
   now ship as CSS masks painted in a token (.press-row, further down), which
   is how they invert with the theme without a second file per outlet.

   The CSS outlived the markup: `.masthead-press` matched nothing in
   index.html, so ~40 lines described a component that had already been
   replaced. Removed rather than left, because a retired rule whose
   implementation is still in the file is the version that gets copied. */
.masthead-eyebrow {
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--accent);
}
.masthead h1 {
  /* Romie, per the founder's own line: font-family + font-feature-settings
     applied exactly as given, nothing added, nothing second-guessed (ss05
     is the founder's call to make, not this build's). font-feature-settings
     REPLACES the browser's default feature set rather than adding to it —
     ROMIE-INTEGRATION.md's own warning — so "liga" is re-listed alongside
     "ss05" rather than assumed; it would otherwise silently turn off.
     font-weight moves from Fraunces' 600 to 700: Romie only ships Medium
     (500) and Bold (700), no 600, and 700 is the one that was already
     closest to "the hero's biggest headline." Georgia is Romie's own
     documented fallback (ROMIE-INTEGRATION.md) — a real bold serif — kept
     ahead of --font-serif (Fraunces) so a failed font load still reads as
     "bold serif," not a lighter face from a different family.
     font-variation-settings is dropped: that was Fraunces' optical-size
     axis, and Romie ships no variable axes for it to address. */
  font-family: "Romie", Georgia, "Times New Roman", serif;
  font-size: clamp(2.5rem, 7vw, 4.375rem);
  font-weight: 700;
  font-feature-settings: "liga" 1, "ss05" 1;
  letter-spacing: -0.02em;
  line-height: 1.05;
  max-width: 16ch;
}
/* Founder edit, 2026-09-03: one word of the hero title, "WITH", set a rung
   down from the rest — 60px against the title's 70px at the 1299px viewport
   it was edited at. Written as a ratio so it tracks the clamp above rather
   than overtaking it on a phone (see the note in the rule below). */
.masthead h1 .masthead-h1-sm { font-size: 0.857em; }

/* NO CONSUMER AS OF 2026-09-03, and kept on purpose. The founder's edit
   replaced the hero headline, and the italic phrase this rule was written
   for went with it — there is no <em> under .masthead any more. The rule
   stays because it is one line and it guards a real Romie trap the moment
   an italic word returns to this heading; the reasoning is below, unchanged
   and still true. Its original subject:
   The title's one italic word ("before the offer.") inherits the 700 above,
   but Romie has no Bold Italic on disk — only Medium, MediumItalic and Bold
   ship (ROMIE-INTEGRATION.md's face table; assets/css/site.css's own `.t-em`
   hit this identical problem and pins its italic to Medium for the same
   reason). Left at 700 the browser would synthesise a slanted Bold — the
   "synthesised mush" the integration guide warns about — so the em is
   pinned to the real, drawn Medium italic instead. */
.masthead h1 em { font-weight: 500; }
/* .masthead-dek retired 2026-09-04 with the paragraph it styled: the founder
   replaced the hero standfirst with a search field. Its enforcement goes in the
   same commit as the thing, which is this file's own house rule. */
.masthead-byline {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--ink-muted);
  letter-spacing: 0.02em;
}
.masthead-byline strong { color: var(--ink); font-weight: 600; }
.masthead-ctas {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 0.9rem;
}
.scroll-cue {
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.3em;
  color: var(--ink);
  background: var(--paper-wash);
  border: 1px solid var(--hairline);
  padding: 8px 14px 8px 18px;
  animation: cue 2.4s ease-in-out infinite;
}
@keyframes cue { 0%,100% { transform: translateY(0); opacity: .75; } 50% { transform: translateY(6px); opacity: 1; } }

/* ---------- HERO: the top gap, the field, the cue ----------------------------
   THE RULE BEING CHANGED IS `.step { align-items: center }` above (and the
   `@media (max-width: 899px)` override near the foot of this file, which flips
   `.step` to `align-items: flex-end; padding-bottom: 6svh`). Both are right for a
   chart scene, where the card is placed against a figure. Neither is right for
   #scene-hero, which has no figure and one step. Scoped by id (1,0,1,0) so it beats
   the base rule AND the media-query rule on specificity at any source position, and
   every other scene keeps both untouched.

   WHY THE OLD GAP WAS WHAT IT WAS: nothing set it. `.step` centres its card in a
   100svh box, and `.c-header` is sticky-but-in-flow, so #scene-hero starts at page-y
   73 (--header-h 72 + its 1px --rule border) and its last 73px are already below the
   fold. Measured at 1400x900: the box's top edge sat 255.56px below the header's
   bottom edge, with the same 255.56px beneath it inside the step.

   14svh IS HALF, COMPUTED: 0.14 x 900 = 126.0px = 49.3% of 255.56. In svh rather
   than px so it stays half at every viewport height instead of only at the one it
   was measured at — the centred rule got WORSE the taller the screen (405.6px at
   1400x1200). The 2rem floor keeps a short landscape window off the header; the
   12rem ceiling stops a very tall monitor re-creating the band this removes. Not px,
   and not `padding-bottom` on a still-centred step: the value that halves the gap is
   the card's own HEIGHT, which CSS cannot read, so any literal there drifts the
   moment the card's contents change — which is exactly what removing the dek and
   adding the field does.

   THE BOTTOM RESERVE IS NOT SYMMETRY. It is the cue's floor. Because the header
   holds 73px of flow, anything bottom-anchored in this step is off-screen unless it
   clears that first; --header-h is the token that already names the number, so this
   tracks the header rather than restating it. Clearance falls out
   viewport-INDEPENDENT: 33px above the fold at 844, 900, 1080 and 1200 alike.

   MEASURED AFTER (dpr 1, dark): 1400x900 card 199.0..573.5, gap-from-header 126.0;
   390x844 card 191.2..489.2, 354.8px clear of the fold — where the SHIPPED page put
   it at 866.38, i.e. 22.38px BELOW an 844 fold. Fixing that phone overflow is a side
   effect of this rule, not a separate change. */
#scene-hero .step {
  /* EVEN AIR ABOVE AND BELOW, which supersedes the top-anchoring that was here.
     One round ago the founder asked to halve the space above the card and this
     rule top-anchored it with a 14svh padding. They have now asked for evenness
     top and bottom, and the two cannot both be true — the later ask wins, and
     it is only affordable because the card grew: it now carries the headline,
     the field AND the press row, so a centred box no longer leaves the lake of
     empty canvas that the halving was for. Measured before: 126px above, 277px
     below. Both asks came from looking at the page; this one came from looking
     at the page as it is now.

     THE PADDING-BOTTOM IS THE HEADER, and that is the whole trick. `.c-header`
     is sticky but still occupies 73px of FLOW, so #scene-hero spans page-y
     73..973 while the visible region is 73..900 — its last 73px are already
     under the fold. Centring in the raw box therefore sits the card ~36px too
     low. Reserving exactly --header-h at the bottom makes the centred region
     the VISIBLE one, so `justify-content: center` means what it looks like.

     The cue is out of flow (below) so it cannot pull the centring off. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  padding-top: 0;
  padding-bottom: var(--header-h);
}
/* Holds the card's measured geometry exactly once the step turns into a column:
   704px at 1400 (its own max-width: 44rem), 342px at 390 — identical to before. */
#scene-hero .masthead-title { width: 100%; }

/* THE SEARCH FIELD IS NOW ONE BORDERED BOX WITH THE CONTROL INSIDE IT.

   It used to wear `.c-capture`, which is a flex ROW of a bordered input and a
   separate button — two boxes side by side. The founder asked for a SEARCH
   button "on the right of the search box" with "equal spacing on the lead of
   the box to the following after the SEARCH button", and that second sentence
   is a measurement: the inset before the text and the inset after the button
   have to be the same number. Two separate boxes cannot have it, because the
   gap between them is a third value. So the border moves to the wrapper, the
   input goes transparent inside it, and one padding value serves both ends.

   --field-inset is that number, declared once and used four times: the box's
   own left and right padding, the ghost overlay's left offset, and nothing
   else. Change it in one place and both ends stay equal by construction rather
   than by two edits that have to agree. */
/* THE CARD'S INTERNAL RHYTHM. .masthead-title is a flex column with one uniform
   1.5rem gap, and three children of very different weight do not want one gap:
   the field is the thing the headline is handing off to, so it sits closer to
   it than the credential line does to either. Founder: "space inside the HERO
   box for text, search, press carousel." */
.masthead-title > h1 { margin-bottom: 0.25rem; }
.masthead-title .press-row { margin-top: 0.75rem; }

.masthead-search {
  --field-inset: var(--space-4);
  display: flex;
  align-items: stretch;
  width: 100%;
  max-width: 460px;
  margin: 0 auto;
  padding-left: var(--field-inset);
  padding-right: var(--field-inset);
  min-height: 48px;
  border: var(--frame-border) solid var(--rule);
  background: var(--lift);
  position: relative;
  isolation: isolate;
}
.masthead-search:focus-within {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
}
.masthead-search input {
  flex: 1 1 auto;
  min-width: 0;
  /* every edge now belongs to the wrapper */
  border: 0; background: transparent; box-shadow: none; outline: none;
  padding: var(--space-3) 0;
  font-family: var(--font-sans);
  font-size: var(--text-ui);
  color: var(--ink);
  appearance: none; -webkit-appearance: none;
}
.masthead-search input::placeholder { color: var(--ink-muted); }
.masthead-search input::-webkit-search-decoration,
.masthead-search input::-webkit-search-results-button,
.masthead-search input::-webkit-search-results-decoration { -webkit-appearance: none; }
/* The control. Type, not a filled slab: a solid button inside the field would
   read as the page's primary action and outrank "Evaluate candidates" in the
   header, which is the actual conversion. It sits at the same optical weight as
   the eyebrows elsewhere on this page. */
.masthead-search_go {
  /* OPAQUE AND FULL HEIGHT, which is not decoration. The ghost completion is a
     ::before on this wrapper at `inset: 0`, so it spans the whole box and would
     paint its grey tail straight across a transparent button. Stretching the
     control and giving it the wrapper's own --lift ground makes it a mask: the
     ghost is clipped at the button's leading edge and the seam is invisible in
     both themes because it is the same token the box is painted with.

     The negative margin lets that ground reach the frame while the padding
     keeps the last VISIBLE glyph at --field-inset — so the equal-insets promise
     is still measured to the letter, not to the box. */
  flex: 0 0 auto;
  align-self: stretch;
  display: inline-flex; align-items: center;
  margin-left: var(--space-3);
  margin-right: calc(var(--field-inset) * -1);
  padding: 0 var(--field-inset) 0 var(--space-3);
  border: 0; background: var(--lift); cursor: pointer;
  font-family: var(--font-sans);
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease);
}
.masthead-search_go:hover { color: var(--ink); }
.masthead-search_go:focus-visible {
  outline: var(--focus-width) solid var(--focus);
  /* inset, because the box now touches the frame and a positive offset would
     draw the ring outside it */
  outline-offset: -2px;
  color: var(--ink);
}

/* Legacy note: the hero no longer wears `.c-capture`.
   NOT SET, DELIBERATELY: `pointer-events: auto` — measured, a click at the field's
   centre already reaches the input, because .masthead-title re-enables pointer
   events for its whole subtree. */
.masthead-search { position: relative; }
.masthead-search input {
  /* One family in the card rather than a third: every other word here is Romie or
     the sans, so the field takes the sans too. */
  font-family: var(--font-sans);
  /* `type="search"` is the honest semantic, and Safari draws a searchfield with its
     own rounded chrome unless told otherwise — without this the founder's
     "rectangular" is true in Chrome and false in Safari. The cancel button is left
     alone on purpose: it is the clear affordance. */
  appearance: none;
  -webkit-appearance: none;
}
.masthead-search input::-webkit-search-decoration,
.masthead-search input::-webkit-search-results-button,
.masthead-search input::-webkit-search-results-decoration { -webkit-appearance: none; }

/* The scroll cue. `.scroll-cue` and `@keyframes cue` are above and were ported with
   this file — until now with no consumer anywhere in the build. `margin-top: auto`
   parks the pill on the step's bottom padding edge when there is free space and
   COLLAPSES TO ZERO when there is not, so on a viewport shorter than the layout
   expects the cue tucks under the card instead of pushing it down — the title
   verdict survives the case nobody designed for. */
.masthead-cue {
  /* OUT OF FLOW, so it cannot skew the card's centring. As a flex child with
     margin-top:auto it ate the space below the card and made "even" impossible
     to state, let alone hit. Pinned to the step's bottom padding edge instead,
     which is the fold, because --header-h is already reserved above it. */
  position: absolute;
  /* --header-h first, then the gap. The step is 100svh but STARTS 73px down
     (the sticky header holds that much flow), so its bottom edge is 73px BELOW
     the fold — measured: a bare `bottom: 1.75rem` put the cue 45px off-screen.
     The same reservation the step's padding-bottom makes has to be repeated
     here, because `bottom` resolves against the padding box, not the content
     box. */
  left: 0; right: 0; bottom: calc(var(--header-h) + 1.75rem);
  display: flex;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s ease 0.25s;
}
.scroll-cue { display: inline-flex; align-items: center; gap: 0.5em; line-height: 1; }
.scroll-cue svg { width: 0.9375rem; height: 0.9375rem; flex: 0 0 auto; }
/* Same activation contract as the card, so the cue arrives WITH the title rather
   than popping in against a fading box. */
.step.is-active .masthead-cue { opacity: 1; }
/* It retires the moment the reader does the thing it asks for. Five simple selectors
   so it outranks the rule above without depending on source order. */
:root[data-scrolled] .step.is-active .masthead-cue {
  opacity: 0;
  transition: opacity 0.3s ease 0s;
}
/* AND IT GETS OUT OF THE WAY OF THE SEARCH. The results panel is absolutely
   positioned and can be taller than the space under the card, so with the cue
   still painted the two overlap and "SCROLL" reads through the fourth row —
   caught in a screenshot, not in a rule. Hiding it while the listbox is open is
   also the honest instruction: a reader who is typing a question is not being
   asked to scroll. The attribute is set by js/search.js on the same element
   that already carries aria-expanded, so there is one source of truth for
   "the panel is open". */
.masthead-title:has(.masthead-search [aria-expanded="true"]) ~ .masthead-cue,
:root[data-search-open] .masthead-cue {
  opacity: 0;
  transition: opacity 0.15s ease 0s;
}

/* THE PRESS ROW GOES WITH IT, AND LAYERING ALONE STOPPED BEING ENOUGH ON 5 SEP.
   The founder's original report was "look ahead search should cover the press
   logos, now they collide", and the fix was a stacking one: lift the search
   subtree over the row (see .masthead-search's z-index, further down). That
   works only while the row is NARROWER than the panel, and the panel is as wide
   as the field — 458px measured. Four marks came to 499px, so the overhang was
   20px a side and landed in the card's own margin. Adding WSJ took the row to
   571.5px against the same 458px panel, and the outermost mark at each end —
   WSJ on the left, Fast Company on the right — sat clear of the panel and back
   in view. Measured, both themes, before and after: the four-mark row hit-tests
   as fully covered and the five-mark one does not.

   So the row now leaves rather than hides behind. It is the same instruction the
   cue above follows, off the same one flag, and it is the honest one: a reader
   reading five answers is not reading a credential line. `opacity` keeps the
   row's box, so nothing under it reflows as the panel opens and closes;
   pointer-events go with the ink, because a link that cannot be seen must not
   be clickable — the two ends stick out past the panel, so without this a click
   beside the results would leave for wsj.com. */
:root[data-search-open] .press-row {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease 0s;
}

/* SHORT-VIEWPORT GUARDS, and the honest limit. 844x390 landscape is the one case
   that does not fit: the h1 is three lines of 59px Romie and the card overflows the
   fold by 47.1px. It overflowed by 49.6px BEFORE this change too — these two rules
   are what keep it from getting worse (without them the top-anchor pushes it to
   +79.7px). The only real fix is a smaller headline at short heights, which the
   founder's "everything is microscopic" verdict on v1 rules out. */
@media (max-height: 600px) {
  #scene-hero .step {
    padding-top: 1.5rem;
    padding-bottom: calc(var(--header-h) + 1rem);
  }
}
@media (max-height: 480px) { #scene-hero .masthead-cue { display: none; } }

/* ---------- the hero search panel ----------
   Absolutely positioned so opening it CANNOT change the card's height — the
   hero is a 100svh single-step scene and a panel that pushed the box down
   would move the headline the founder's verdict pins to the first frame.
   `.masthead-title` has no overflow:hidden (checked), so the panel escapes the
   card cleanly, and `.masthead-search` is the positioned ancestor it hangs off.

   z-index sits above the scene canvas but below `.c-header` (100), so the
   sticky nav still wins — a results list drawn over the menu is the classic
   version of this bug.

   Colour is the card's own: --lift ground, --rule edge, --ink and --ink-muted
   type, so it inverts with the theme without a value written here. The row's
   hover/active ground is --row-tint, the token that already exists for a
   quiet selected row. */
.search-panel {
  position: absolute; z-index: 60;
  top: calc(100% + 0.5rem); left: 0; right: 0;
  /* a floor only; js/search.js measures the real room on every open */
  max-height: 26rem; overflow-y: auto; overscroll-behavior: contain;
  background: var(--lift);
  border: var(--frame-border, 1px) solid var(--rule);
  text-align: left;
}
.search-list { list-style: none; margin: 0; padding: 0; }
.search-row {
  display: block; padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--hairline);
  text-decoration: none; color: var(--ink);
}
.search-list li:last-child .search-row { border-bottom: 0; }
.search-row:hover,
.search-row[aria-selected="true"] { background: var(--row-tint); }
.search-row_q {
  display: block;
  font-family: var(--font-sans); font-size: 0.9375rem; line-height: 1.35;
  color: var(--ink);
}
.search-row_q mark { background: none; color: var(--accent); font-weight: 600; }
.search-row_h {
  display: block; margin-top: 0.15rem;
  font-family: var(--font-sans); font-size: 0.8125rem; line-height: 1.35;
  color: var(--ink-muted);
}
.search-row_c {
  display: inline-block; margin-top: 0.35rem;
  font-family: var(--font-sans); font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--ink-faint);
}
/* THE GHOST COMPLETION is painted as a sibling layer, never as the input's own
   value: writing it into `value` would mean the next keystroke appends to text
   the reader never typed, and a form submit would carry a guess. The wrapper
   holds the full "typed + completion" string in a data attribute and this
   draws it underneath, with the typed half rendered transparent so only the
   tail shows. It is aria-hidden by construction (a ::before carries no
   semantics) and pointer-events:none so it cannot eat a click. */
.masthead-search[data-ghost]:not([data-ghost=""])::before {
  content: attr(data-ghost);
  position: absolute; inset: 0;
  padding: var(--space-3) var(--field-inset);
  font-family: var(--font-sans); font-size: var(--text-ui);
  line-height: inherit; white-space: pre;
  color: var(--ink-faint); pointer-events: none;
  overflow: hidden;
}
/* `isolation: isolate` gives the ghost layer a stacking context of its own, so
   the ::before cannot slip above the input. It also CONFINES the results panel:
   once this element is a stacking context, .search-panel's z-index:60 is
   resolved inside it and no longer competes with anything outside. .press-row
   is a LATER SIBLING with no z-index of its own, so it painted over the open
   panel and the founder photographed four press logos sitting across two
   results ("Look ahead search should cover the press logos, now the collide").
   The z-index here is what the panel's own 60 could no longer do for itself:
   it lifts the whole search subtree over the press row in one move, instead of
   pushing .press-row down, which would only re-lose to the next thing added
   after it. Still below .c-header (100) — a results list drawn over the sticky
   nav is the classic version of this bug and is explicitly not traded for. */
.masthead-search { isolation: isolate; z-index: 60; }
.masthead-search input { position: relative; z-index: 1; background: transparent; }

/* ---------- press, inside the hero card ----------
   WAS a section between the hero and the prologue, set as TYPE in four faces,
   because site/lib/sections.mjs's rule said a wordmark redrawn from memory is a
   fabrication. That rule forbade DRAWING a mark we did not have, not shipping
   one we do; the founder supplied the real files and retired it in terms
   ("Change this rule permanently, render the logos"), so these are the actual
   marks now and they moved inside the hero card.

   ONE FILE PER OUTLET, PAINTED BY CSS. Every asset is single-colour art on
   transparency, so `mask-image` takes the alpha as the SHAPE and
   `background: currentColor` supplies the colour — which means the mark is the
   page's own ink and inverts with the theme for free. An <img> would need a
   black file and a white file per outlet and would still be wrong on whichever
   ground it was not made for. Measured: art means were rgb(31,24,9),
   rgb(12,11,11) and rgb(3,6,5); Fast Company ships as a black SVG path.

   OPTICALLY SIZED, NOT MECHANICALLY. Four wordmarks at one pixel height do not
   read as one size — cap height, weight and side bearings all differ. The
   heights below are per-outlet and were set by eye against each other at the
   shipped size, then checked at 390px. Widths follow from each crop's own
   aspect so nothing is stretched.

   --press-ink is --ink-muted rather than --ink: at full ink four marks compete
   with the headline directly above them, and this is a credential line rather
   than a claim. Hover lifts to full ink so the link still answers. */
.press-row {
  --press-ink: var(--ink-muted);
  /* the return trip, when the panel closes */
  transition: opacity 0.2s var(--ease) 0.05s;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 0.9rem 1.9rem;
  margin: 0; padding: 0; list-style: none;
}
/* EVERY li WAS 25.59px TALL WHATEVER IT HELD. `display: list-item` keeps a
   line box, and the row's line-height is 1.6 on a 16px root, so each item
   reserved a full text line while its mark is 11-18px. All of the surplus
   lands UNDER the ink: measured 7.59px below Vanity Fair and 14.59px below The
   Times, which is why the marks looked as though they were sitting on nothing
   and why the gap under the row read as larger than the gap above it (41px
   against an intended 40, and 29 against 40 on a phone). Making the item a
   flex box removes the strut entirely, so the li is exactly as tall as the
   mark and the row's own `gap` is the only spacing in play. */
.press-row li { display: flex; }
.press-row a {
  display: inline-flex; align-items: center;
  color: var(--press-ink);
  transition: color var(--dur-fast) var(--ease);
}
.press-row a:hover, .press-row a:focus-visible { color: var(--ink); }
/* The mark itself. `background: currentColor` + a mask is the whole trick:
   the element is a coloured rectangle and the mask cuts the wordmark out of it. */
.press-mark {
  display: block;
  background: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
}
/* WSJ IS THE ONE MARK THAT IS NOT A WORDMARK — three interlocked capitals
   rather than a run of letters, so its box is 2:1 where the others are 5:1 to
   8:1. Matching it on BOX height would have made it read as the largest thing
   in the row by a distance; matching it on CAP height is what the eye is
   actually comparing. Measured on the asset: the W's feet sit at 0.735 of the
   file's height and the J's tail owns the rest, so a 21px box puts ~15px of cap
   against Vanity Fair's 18 and Semafor's 15 — which is where it stopped
   competing with the headline above it. Width follows the crop's own aspect
   (2.015), as everywhere else here, so nothing is stretched. */
.press-mark--wsj {
  width: 42px; height: 21px;
  -webkit-mask-image: url("../assets/press/wsj.png");
  mask-image: url("../assets/press/wsj.png");
}
.press-mark--vanity-fair {
  width: 90px; height: 18px;
  -webkit-mask-image: url("../assets/press/vanity-fair.png");
  mask-image: url("../assets/press/vanity-fair.png");
}
.press-mark--the-times {
  width: 118px; height: 14px;
  -webkit-mask-image: url("../assets/press/the-times.png");
  mask-image: url("../assets/press/the-times.png");
}
.press-mark--semafor {
  width: 96px; height: 15px;
  -webkit-mask-image: url("../assets/press/semafor.png");
  mask-image: url("../assets/press/semafor.png");
}
.press-mark--fast-company {
  width: 104px; height: 16px;
  -webkit-mask-image: url("../assets/press/fast-company.svg");
  mask-image: url("../assets/press/fast-company.svg");
}
@media (max-width: 640px) {
  .press-row { gap: 0.8rem 1.3rem; }
  .press-mark--wsj         { width: 35px; height: 17px; }
  .press-mark--vanity-fair { width: 74px; height: 15px; }
  .press-mark--the-times   { width: 96px;  height: 11px; }
  .press-mark--semafor     { width: 78px;  height: 12px; }
  .press-mark--fast-company{ width: 85px;  height: 13px; }
}

/* ---------- prose blocks ---------- */
.prose {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 6rem 24px;
}
.prose > * + * { margin-top: 1.5rem; }
.prose p {
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.75;
}
.prose .lede::first-letter {
  font-family: var(--font-serif);
  font-size: 3.4em;
  line-height: 0.85;
  float: left;
  padding-right: 0.12em;
  padding-top: 0.06em;
  font-weight: 600;
  color: var(--accent);
}
.prose em { font-style: normal; font-weight: 600; }
.prose .aside {
  font-family: var(--font-serif); font-style: italic;
  color: var(--ink-muted);
}

.section-head {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 5rem 24px 0;
}
.section-head .rule { border-top: 1px solid var(--hairline); padding-top: 2rem; }
.section-head .eyebrow {
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--accent);
}
.section-head h2 {
  font-family: var(--font-serif);
  font-size: clamp(1.6rem, 3.4vw, 2.25rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin-top: 0.75rem;
  max-width: 24ch;
}
.section-head .dek {
  font-family: var(--font-serif); font-style: italic;
  font-size: 1.0625rem;
  color: var(--ink-muted);
  margin-top: 0.75rem;
  max-width: 40ch;
}

/* pull quote */
.pull {
  max-width: var(--measure-wide);
  margin: 0 auto;
  padding: 4rem 24px;
  text-align: center;
}
.pull blockquote {
  font-family: var(--font-serif);
  font-size: clamp(1.5rem, 3.6vw, 2.4rem);
  font-weight: 400;
  line-height: 1.3;
  letter-spacing: -0.01em;
  max-width: 28ch;
  margin: 0 auto;
}
.pull blockquote .gold { color: var(--accent); }
.pull cite {
  display: block;
  margin-top: 1.25rem;
  font-family: var(--font-sans);
  font-style: normal;
  font-size: 0.8125rem;
  color: var(--ink-muted);
}

/* ---------- scrollytelling scenes ---------- */
.scene {
  position: relative;
  min-height: var(--h, 400vh);
}
.scene-visual {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  z-index: 1;
}
.scene-visual canvas { display: block; width: 100%; height: 100%; }
.scene-steps {
  position: relative;
  z-index: 2;
  pointer-events: none;
  margin-top: -100svh;
}
.step {
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding: 0 24px;
}
.step[data-align="left"]    { justify-content: flex-start; }
.step[data-align="center"]  { justify-content: center; }
.step[data-align="right"]   { justify-content: flex-end; }
.step[data-align="bottom"]  { align-items: flex-end; justify-content: center; padding-bottom: 10svh; }
.step-card {
  max-width: 30rem;
  background: var(--paper-wash);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid var(--hairline);
  border-left: 2px solid var(--accent);
  padding: 1.5rem 1.75rem;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s ease, transform 0.5s ease;
  pointer-events: auto;
}
.step.is-active .step-card { opacity: 1; transform: translateY(0); }
.step-card p {
  font-family: var(--font-sans);
  font-size: clamp(1rem, 1.9vw, 1.125rem);
  line-height: 1.65;
}
.step-card p + p { margin-top: 0.9rem; }
.step-card .kicker {
  display: block;
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--accent);
  margin-bottom: 0.6rem;
}
/* BUGFIX (found live via getComputedStyle while building the corpus scene:
   a headline number rendered inside a heavy 3px bordered box nobody drew):
   assets/css/site.css owns an UNRELATED `.figure` component — its own
   captioned media-plate block, complete with a 3px border, background,
   max-width and border-radius (site.css:3300). This reference idiom's
   `<span class="figure">` inline headline number collides on that bare
   class name; `.step-card .figure` (two classes) outranks site.css's bare
   `.figure` (one class) on specificity for any property BOTH set, but
   cascade is per-property — site.css's border/background/max-width/etc.
   apply unopposed for every property this rule doesn't ALSO set. Explicit
   resets below close that gap; they don't change anything the reference's
   own clamp()/weight/tracking values do. */
.step-card .figure {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(1.9rem, 4.5vw, 2.9rem);
  line-height: 1.1;
  letter-spacing: -0.015em;
  display: block;
  margin: 0.35rem 0;
  max-width: none;
  width: auto;
  margin-inline: 0;
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}
.step-card .figure.gold { color: var(--accent); }
.step-card .fine {
  font-size: 0.8125rem;
  color: var(--ink-muted);
  line-height: 1.55;
}
.step-card strong { font-weight: 600; }

/* ---------- inline per-scene citations (case / census scenes) ----------
   A compact <details>/<summary> disclosure sized to sit inside a
   step-card, rather than the page-level .receipts appendix above (whose
   own padding/margins assume a full-width section, not a 30rem card).
   Reuses the same "aff" affordance idea as .receipts summary .aff so the
   two read as one family wherever both appear on the page. */
.step-card details {
  margin-top: 0.75rem;
  border-top: 1px solid var(--hairline);
  padding-top: 0.6rem;
}
.step-card details summary {
  cursor: pointer;
  list-style: none;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
}
.step-card details summary::-webkit-details-marker { display: none; }
.step-card details[open] summary { color: var(--accent); }
.step-card details .r-body {
  margin-top: 0.5rem;
  font-size: 0.8125rem;
  color: var(--ink-muted);
  line-height: 1.55;
}

/* ---------- inline figures (article charts) ---------- */
.figure-block {
  max-width: var(--measure-wide);
  margin: 3.5rem auto;
  padding: 0 24px;
}
.figure-block .fig-title {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  font-weight: 600;
}
.figure-block .fig-dek {
  font-family: var(--font-serif); font-style: italic;
  font-size: 0.9375rem;
  color: var(--ink-muted);
  margin-top: 0.2rem;
}
.figure-block .fig-body { margin-top: 1.25rem; position: relative; }
.figure-block .fig-note {
  margin-top: 0.9rem;
  font-size: 0.8125rem;
  color: var(--ink-muted);
  line-height: 1.55;
  max-width: 56ch;
}
.figure-block .fig-note a { text-decoration-color: var(--hairline); }

.fig-body text {
  font-family: var(--font-sans);
  fill: var(--ink-muted);
  font-size: 11px;
}
.fig-body .tick-num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* tooltip */
.viz-tip {
  position: fixed;
  z-index: 60;
  pointer-events: none;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-left: 2px solid var(--accent);
  padding: 0.5rem 0.75rem;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink);
  max-width: 240px;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.viz-tip .tip-label { color: var(--ink-muted); }
.viz-tip .tip-value { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.viz-tip.on { opacity: 1; }

/* legend */
.fig-legend {
  display: flex; flex-wrap: wrap; gap: 1.25rem;
  margin-top: 0.9rem;
  font-size: 0.8125rem;
  color: var(--ink-muted);
}
.fig-legend .key { display: inline-flex; align-items: center; gap: 0.45rem; }
.fig-legend .swatch { width: 14px; height: 3px; display: inline-block; }

/* ---------- the ledger (two-column compare) ---------- */
.ledger {
  max-width: var(--measure-wide);
  margin: 3rem auto;
  padding: 0 24px;
}
.ledger table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}
.ledger th {
  font-family: var(--font-sans);
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--ink-muted);
  text-align: left;
  padding: 0.6rem 0.5rem;
  border-bottom: 1px solid var(--chart-axis);
}
.ledger td {
  padding: 0.7rem 0.5rem;
  border-bottom: 1px solid var(--hairline);
  vertical-align: baseline;
}
.ledger td.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ledger td.gold { color: var(--accent); font-weight: 500; }
.ledger .row-label { color: var(--ink-muted); }

/* ---------- coda ---------- */
.coda {
  min-height: 90svh;
  display: flex; flex-direction: column;
  justify-content: center; align-items: center;
  text-align: center;
  padding: 6rem 24px;
  gap: 2rem;
}
.coda h2 {
  font-family: var(--font-serif);
  font-size: clamp(1.9rem, 5vw, 3.2rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  max-width: 20ch;
}
.coda .coda-q {
  font-family: var(--font-serif); font-style: italic;
  font-size: clamp(1.1rem, 2.4vw, 1.4rem);
  color: var(--ink-muted);
  max-width: 40ch;
  line-height: 1.5;
}
.coda-form {
  display: flex; flex-direction: column; align-items: center; gap: 1rem;
  width: min(30rem, 100%);
}
.coda-form label { width: 100%; text-align: left; }
.coda-form label span {
  display: block;
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--ink-muted);
  margin-bottom: 0.4rem;
}
.coda-form input {
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--chart-axis);
  font-family: var(--font-serif);
  font-size: 1.4rem;
  color: var(--ink);
  padding: 0.4rem 0;
  outline: none;
  text-align: center;
}
.coda-form input:focus { border-bottom-color: var(--accent); }
.coda-answer {
  font-family: var(--font-serif);
  font-size: clamp(1.4rem, 3.2vw, 2rem);
  line-height: 1.35;
  max-width: 26ch;
  min-height: 3em;
}
.coda-answer .gold { color: var(--accent); }

/* ---------- receipts appendix ---------- */
.receipts {
  max-width: var(--measure-wide);
  margin: 0 auto;
  padding: 4rem 24px 8rem;
}
.receipts summary {
  cursor: pointer;
  list-style: none;
  font-family: var(--font-serif);
  font-size: 1.125rem;
  font-weight: 600;
  padding: 1.1rem 0;
  border-top: 1px solid var(--hairline);
  display: flex; justify-content: space-between; align-items: baseline;
}
.receipts summary::-webkit-details-marker { display: none; }
.receipts summary .aff {
  font-family: var(--font-sans);
  font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-muted);
}
.receipts details[open] summary .aff { color: var(--accent); }
.receipts .r-body {
  padding: 0 0 1.75rem;
  font-size: 0.9375rem;
  color: var(--ink);
  line-height: 1.7;
  max-width: 60ch;
}
.receipts .r-body p + p { margin-top: 0.8rem; }
.receipts .r-body .mono { font-family: var(--font-mono); font-size: 0.85em; }
.receipts .r-body ul { padding-left: 1.1rem; }
.receipts .r-body li { margin-top: 0.4rem; }

/* ---------- close: candidate stats / getting-started / final CTA ----------
   #scene-close is DOM-only — no canvas, no .scene/.step scroll-jacking
   (see index.html's own comment on that section and js/main.js's on why
   it is not registerScene'd). Eyebrow/h2/dek here repeat .section-head's
   own sizes rather than reusing that selector directly, since nesting
   .section-head (max-width:34rem, its own centered margin) inside a
   44rem-wide band would stack two independently-centered columns instead
   of one flush left-aligned one. */
.close-band {
  max-width: var(--measure-wide);
  margin: 0 auto;
  padding: 5rem 24px;
}
.close-band + .close-band { border-top: 1px solid var(--hairline); padding-top: 4rem; }
.close-band .eyebrow {
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--accent);
}
.close-band h2 {
  font-family: var(--font-serif);
  font-size: clamp(1.6rem, 3.4vw, 2.25rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin-top: 0.75rem;
  max-width: 26ch;
}
.close-band .dek {
  font-family: var(--font-serif); font-style: italic;
  font-size: 1.0625rem;
  color: var(--ink-muted);
  margin-top: 0.75rem;
  max-width: 40ch;
}
.close-band .close-body {
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.75;
  max-width: 42ch;
  margin-top: 2rem;
}
.close-stats {
  display: flex; flex-wrap: wrap; gap: 2.5rem;
  margin-top: 1.5rem;
}
.close-stat { flex: 1 1 220px; min-width: 220px; }
.close-stat .num {
  display: block;
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(2.4rem, 6vw, 3.4rem);
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--accent);
}
.close-stat p {
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.5;
  margin-top: 0.5rem;
  max-width: 26ch;
}
.close-stat .fine {
  display: block;
  font-size: 0.8125rem;
  color: var(--ink-muted);
  margin-top: 0.4rem;
  max-width: 28ch;
}
.close-steps {
  list-style: none;
  margin-top: 2rem;
}
.close-steps li {
  display: flex; gap: 1.25rem; align-items: baseline;
  border-top: 1px solid var(--hairline);
  padding: 1.5rem 0;
}
.close-steps li:first-child { border-top: none; padding-top: 0; }
.close-step-num {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--accent);
  flex: 0 0 auto;
  padding-top: 0.2rem;
}
.close-step-body h3 {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 1.125rem;
}
.close-step-body p {
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  margin-top: 0.4rem;
  max-width: 44ch;
}
.close-step-body .meta {
  display: block;
  font-size: 0.8125rem;
  color: var(--ink-muted);
  margin-top: 0.4rem;
  text-transform: uppercase; letter-spacing: 0.08em;
}
.close-ctas {
  display: flex; flex-wrap: wrap; gap: 0.9rem;
  margin-top: 1.75rem;
}

/* ══ THE CLOSING CTA — the masthead's grid paper, turned up ═══════════════
   Same idea as the hero, opposite volume. There the grid is 1.068:1 and you
   are meant to feel it rather than see it; here it is #16150F on #7DADD9 and
   it is meant to be the loudest surface on the page, because it is the last
   one and it is asking for something.

   CSS, not canvas. The coda is a plain DOM section with no <canvas> — the
   chart-density probe's own note says so — and a repeating gradient draws a
   10px grid with no script, no rAF and nothing to keep in step with the
   scroll engine. It also means the grid survives with JavaScript off, which
   the hero's does not.

   THE 1px TRAP: `repeating-linear-gradient(#16150F 0 1px, transparent 1px 10px)`
   is the obvious spelling and it renders a SOFT line — the browser
   antialiases the stop, and at 1px that is most of the line. Two hard stops
   at the same position is what makes it crisp. */
.close--grid {
  --cta-ground: #7DADD9;
  /* THE LINE WAS #16150F AND IT WAS TOO LOUD — 7.71:1 against the ground,
     which is body-text contrast spent on a background pattern. Worse, it was
     the same value as the title, so the letterforms had no edge against the
     field they sat on: the text measured 7.71:1 against the GROUND and 1:1
     against the LINES, and the second number is the one a reader experiences.
     #709BC2 is that same near-black at 22% over the blue — the same hue
     family, a quarter of the volume: 1.24:1 against the ground, and the title
     now reads 6.23:1 against the lines themselves. */
  --cta-line: #709BC2;
  /* The touch. It DARKENS, where the masthead's glows, because on this ground
     nothing light survives: the olive accent is 1.09:1 here, cream 2.00:1 and
     white 2.37:1. Near-black centre at 7.71:1, deep-olive halo at 5.10:1. */
  --cta-hot: #16150F;
  --cta-warm: #2C3A22;
  position: relative;
  background-color: var(--cta-ground);
  /* The no-JS fallback, and nothing else: js/main.js paints an opaque canvas
     over this, so only one grid is ever on screen. Kept because the section is
     content, not decoration — with scripting off it should still be paper. */
  background-image:
    repeating-linear-gradient(to right,
      var(--cta-line) 0, var(--cta-line) 1px, transparent 1px, transparent 10px),
    repeating-linear-gradient(to bottom,
      var(--cta-line) 0, var(--cta-line) 1px, transparent 1px, transparent 10px);
  background-position: 0 0;
}
.close--grid .close-grid {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;   /* the section listens, not the canvas — see main.js */
}
.close--grid .close-band { position: relative; z-index: 1; }

/* The title's colour is a TOKEN so it tracks the theme, and it lands dark in
   both — which is a measurement, not a compromise. On #7DADD9: near-black
   #16150F is 7.71:1 and pure black 8.85:1, while the dark theme's own cream
   ink is 2.00:1 and white is 2.37:1. Both light options fail AA outright, so
   "adaptive" here means each theme contributes its own darkest value rather
   than one of them inverting into an unreadable headline. */
.close--grid { --cta-ink: #16150F; }
:root[data-theme="light"] .close--grid { --cta-ink: #000000; }

.close--grid .close-band { text-align: center; }
/* THE HERO'S TYPE, not the section's own. Copied from .masthead h1 rather
   than referenced, because the two are different sizes on purpose: this is
   the closing statement, not the masthead, and it takes the same face, the
   same weight, the same feature stack and the same tracking at its own scale.
   "liga" is re-listed beside "ss05" for the reason .masthead h1 states —
   font-feature-settings REPLACES the default set, so an unlisted liga is a
   silently disabled one. */
.close--grid .close-band h2 {
  color: var(--cta-ink);
  font-family: "Romie", Georgia, "Times New Roman", serif;
  font-weight: 700;
  font-feature-settings: "liga" 1, "ss05" 1;
  letter-spacing: -0.02em;
  line-height: 1.05;
  font-size: clamp(2rem, 5vw, 3.25rem);
  max-width: 18ch;
  margin: 0 auto;
}
.close--grid .close-ctas { justify-content: center; }
/* The fine print takes the FULL ink, not a muted one, and that is a change
   from how this line is set everywhere else on the page.
   Measured: the obvious `color-mix(… --cta-ink 78%, --cta-ground)` resolves to
   #40586e, which is 3.12:1 on the blue — fine as body text on a plain ground
   and not fine here, because it is 13px sitting on a grid whose lines are
   1px of near-black every 10px. The pattern competes with the glyphs, so the
   usual muting trick spends contrast this line cannot afford. Hierarchy comes
   from size and weight instead. */
.close--grid .close-band .source-line {
  color: var(--cta-ink);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 0.9375rem;
  font-weight: 500;
  margin-left: auto; margin-right: auto;
}

/* The slab button — the shape from the founder's reference: a square-cornered
   rectangle, a hairline border lighter than its own fill, and the label
   letter-spaced across it. Scoped to this band so nothing else on the page
   inherits a treatment designed for one blue ground.

   It sets every property .btn could have given it rather than layering on top
   of site.css's --primary: a button that is half one system and half another
   is the thing that drifts the first time either system moves. */
/* The fill is --cta-hot, the ink, NOT --cta-line.
   It was --cta-line, and that was fine only for as long as the lines happened
   to be near-black: toning the grid down to #709BC2 turned the button into a
   pale blue box with white text on a pale blue ground, and the shape
   assertions all still passed because they measured the radius and the border
   and never the contrast. A control's fill has no business being defined by
   the background pattern's token. */
.btn.btn--slab {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--cta-hot);
  color: #EEECE1;
  border: 1px solid color-mix(in oklab, #EEECE1 55%, var(--cta-hot));
  border-radius: 0;
  padding: 1rem 2.25rem;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  box-shadow: none;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.btn.btn--slab:hover,
.btn.btn--slab:focus-visible {
  background: #EEECE1;
  color: var(--cta-hot);
  border-color: var(--cta-hot);
}
.btn.btn--slab:focus-visible { outline: 2px solid var(--cta-hot); outline-offset: 3px; }

/* ══ the wordmark ════════════════════════════════════════════════════════
   Founder-specified: OLIVE, Romie, "liga" 1 and "ss15" 1.

   kern and calt are added to that list and the reason is the single most
   important rule in this build's typography: font-feature-settings REPLACES
   the browser's default feature set rather than adding to it. Writing only
   liga and ss15 would silently switch OFF kerning and contextual alternates
   for the one word on the page that is set in the brand face — the failure
   is invisible in code review and visible in the spacing of five letters.
   The two the founder named are on; nothing they did not name is. */
.c-logo_word {
  font-family: "Romie", var(--font-serif);
  font-feature-settings: "liga" 1, "ss15" 1, "kern" 1, "calt" 1;
  font-weight: 500;   /* Romie-Medium is a drawn cut; 400 would be synthesised */
  font-size: 1.05rem;
  /* NO letter-spacing, and that is the point rather than an omission.
     ss15 is "capital pairs, raised second letter" — in OLIVE it fires on LI
     and VE, lifting the I and the E. Those substitutions are pair-based, so
     tracking the letters apart works directly against the feature that was
     asked for: at 0.14em the raised I read as a stray exclamation mark beside
     an L rather than as the second half of a pair. The pairs have to touch. */
}
.close-band .source-line {
  font-size: 0.8125rem;
  color: var(--ink-muted);
  margin-top: 1.5rem;
  max-width: 52ch;
}

.footer {
  border-top: 1px solid var(--hairline);
  padding: 2.5rem 24px 4rem;
  text-align: center;
  font-size: 0.8125rem;
  color: var(--ink-muted);
}

/* ---------- responsive ---------- */
@media (min-width: 768px) {
  .prose, .section-head { padding-left: 48px; padding-right: 48px; }
  .step { padding: 0 7vw; }
}
@media (max-width: 640px) {
  .masthead-title { padding: 1.75rem 1.5rem; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .scroll-cue { animation: none; }
  .masthead-cue { transition: none; }
  .step-card { opacity: 1; transform: none; transition: none; }
  .masthead-title { opacity: 1 !important; transform: none !important; }
  .progress { display: none; }
}

/* ============================================================================
   THE NARRATIVE BUILD (Phase 2 of narrative/PLAN.md).
   Components the acts need that the first v2 landing had no equivalent for:
   the run cards and their facsimiles, the definition lists, the sample
   report's plates, the display screen. Every one is built from the tokens
   already defined above — no new colour, no new family, no second accent.
   ========================================================================= */

/* PLAN.md's card rule, the CSS half: on a phone the canvas works in the
   upper band and the card sits BELOW it, rather than over it. The desktop
   half is js/main.js's stage(), which keeps every chart inside the left 58%.
   Change one of the two and the occlusion gate (tools/verify3.mjs) is what
   tells you. */
@media (max-width: 899px) {
  .step { align-items: flex-end; justify-content: center; padding-bottom: 6svh; }
  .step-card { max-width: 100%; }
}

/* the six dimensions, Act VI */
.dim-list {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0;
  list-style: none;
}
.dim-list li {
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  padding: 0.7rem 0 0.7rem 1rem;
  border-left: 2px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.dim-list li b { font-weight: 600; }

/* the 40-word bridge that introduces each sibling case */
.prose.bridge { padding-top: 2rem; padding-bottom: 2rem; }
.prose.bridge p {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  line-height: 1.6;
  color: var(--ink-muted);
  border-left: 2px solid var(--accent);
  padding-left: 1.1rem;
}

/* ---- run cards: the worked check, one statement at a time. Not a scene:
   no canvas is mounted here, and the facsimile is retired before the next
   canvas does mount. ---- */
.runs {
  max-width: var(--measure-wide);
  margin: 0 auto;
  padding: 2rem 24px 4rem;
  display: grid;
  gap: 1.5rem;
}
.run-card {
  border: 1px solid var(--hairline);
  border-left: 2px solid var(--accent);
  padding: 1.35rem 1.5rem;
  background: var(--paper-wash);
}
.run-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.9rem;
  align-items: baseline;
  margin-bottom: 0.85rem;
}
.run-id {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--accent);
}
.run-src {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}
.run-card > p {
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.65;
  margin: 0;
}
.run-card--punch > p {
  font-family: var(--font-serif);
  font-size: 1.35rem;
  line-height: 1.35;
}

/* ---- the facsimile: a fixed paper rectangle carrying the packet's own
   lines and nothing else. Rendered from data/verbatim.js by main.js. ---- */
.facsimile {
  border: 1px solid var(--hairline);
  background: color-mix(in oklab, var(--ink) 5%, var(--paper) 95%);
  padding: 0.9rem 1rem 0.7rem;
  margin-bottom: 1rem;
}
.fac-line {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--ink-muted);
  margin: 0 0 0.35rem;
}
.fac-line.fac-h { color: var(--ink); font-weight: 600; }
.fac-line.fac-row { color: var(--ink); }
.fac-line--mark {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.fac-cap {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0.6rem 0 0;
}

/* ---- the definition lists that close Acts VII and VIII ---- */
.deflist {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 1rem 24px 5rem;
}
.deflist p {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ink-muted);
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--hairline);
  margin: 0;
}
.deflist p b { color: var(--ink); font-weight: 600; }

/* ---- the sample report: plates in the shipped render order ---- */
.report {
  max-width: var(--measure-wide);
  margin: 0 auto;
  padding: 2rem 24px 4rem;
}
.sample-band {
  position: sticky;
  top: 73px;                     /* clears the site header */
  z-index: 3;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--accent);
  padding: 0.5rem 0.9rem;
  margin: 0 0 1.5rem;
}
.plate {
  border: 1px solid var(--hairline);
  border-top: 2px solid var(--hairline);
  padding: 1.4rem 1.5rem;
}
.plate + .plate { border-top: 0; }
.plate--head { border-top: 2px solid var(--accent); }
.plate-role {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 0.6rem;
}
.plate-dek {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  line-height: 1.45;
  margin: 0;
}
.plate-head { display: flex; align-items: baseline; gap: 0.75rem; }
.plate-head h3 {
  font-family: var(--font-serif);
  font-size: 1.25rem;
  font-weight: 600;
  margin: 0;
}
.pill {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
}
.plate-short {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 0.9375rem;
  color: var(--ink-muted);
  margin: 0.5rem 0 1rem;
}
.plate dl { margin: 0; display: grid; gap: 0.75rem; }
.plate dl div { display: grid; grid-template-columns: 9.5rem 1fr; gap: 0.9rem; }
.plate dt {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-top: 0.2rem;
}
.plate dd {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  line-height: 1.6;
}
.plate .excerpt dd {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--ink-muted);
}
.plate--note p {
  font-family: var(--font-serif);
  font-size: 1.0625rem;
  line-height: 1.5;
  margin: 0;
}
.report-footer {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  margin: 1rem 0 0;
}
@media (max-width: 599px) {
  .plate dl div { grid-template-columns: 1fr; gap: 0.25rem; }
}

/* ---- the display screen: the one sentence the product uses to say why
   there is no single figure ---- */
.display-screen {
  max-width: var(--measure-wide);
  margin: 0 auto;
  padding: 4rem 24px 5rem;
}
.display-screen p {
  font-family: var(--font-serif);
  font-size: clamp(1.35rem, 3vw, 1.9rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
  margin: 0;
  border-left: 2px solid var(--accent);
  padding-left: 1.5rem;
}

/* the coda's set-up line, above the question */
.close-setup {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ink-muted);
  max-width: 46ch;
  margin: 0 auto 1.5rem;
}
