/* Beat Patrol site styles.

   SORTED BY TOPIC, NOT BY DATE. Until this round the sheet was three eras
   stacked: the original, then "THE BLOCKBUSTER LAYOUT" from 2026-09-05, then
   a run of corrections after the first build was read on screen. Each era
   appended, so `gallery` was defined in two places 643 lines apart and
   `footer` in two places 759 lines apart, and nobody editing either could
   know both existed without searching. The eras are gone; the parts below
   are topical, and each part carries every era's rules for its topic.

   THE CASCADE WAS NOT ALLOWED TO MOVE. Within every part the blocks keep
   their original relative order, so no rule that used to lose can now win.
   The re-sort was PROVED inert: twelve full-page captures -- four pages at
   390, 700 and 1280 -- were taken before and after and compared pixel by
   pixel, and the harness itself was first shown to return zero differing
   pixels across two identical runs, so a difference would have meant the
   sheet and not the schedule. See docs/rounds/ for the round record.
   Built to the game's own rule: flat masses on a squared grid, cut at one
   chamfer, filled from one palette, one hard light from the upper left so
   every shadow falls down and to the right. No gradients on objects, no
   rounded pills, no glass. Dark only, and the ground is painted explicitly.
   Mobile first at 320, then 768, then 1200, with a short-viewport rule for a
   phone held sideways. */

:root {
  --bg: #04060c;
  --asphalt: #131720;
  --panel: #141926;
  --panel-deep: #080b13;
  --rule: #2a3145;
  --text: #e9edf7;
  --dim: #8390ac;
  --amber: #ffb02e;
  --red: #ff2a45;
  --blue: #2b6bff;
  --shadow: #000000;

  --display: "Beat Patrol", ui-sans-serif, system-ui, sans-serif;
  --prose: ui-sans-serif, system-ui, sans-serif;

  --gap: 20px;
  --cut: 14px;
  --wrap: 1120px;
}

/* THE DISPLAY FACE IS THE GAME'S, COMPILED FROM THE GAME'S OWN GLYPH TABLE.
   `scripts/font-from-game.mjs` records every glyph out of `drawLogotype` and
   assembles a TrueType font in a WOFF wrapper; the commit it came from is in
   `docs/captures.md`. 3.2 KB, one file, same origin, no fallback host and no
   second format -- every browser this site supports takes WOFF.

   IT IS THE DISPLAY REGISTER ONLY, which is the game's own type system and not
   a shortcut: the face has NO LOWERCASE, so prose excludes itself. Headings,
   eyebrows, buttons, labels, figures and the pull quote take it; every
   paragraph, caption and answer stays on the system stack, hinted and already
   loaded, which is a far better tool for a sentence.

   The descriptors are the font's own -- 800, 87.5% -- so the rules below match
   the real face instead of asking the browser to synthesise a bolder, narrower
   one on top of it. `swap` because a heading in the fallback for 200 ms is
   better than a heading nobody can read, and the file is preloaded on every
   page so the swap window is nearly always empty. */
@font-face {
  font-family: "Beat Patrol";
  src: url("assets/fonts/beat-patrol-face.woff") format("woff");
  font-weight: 800;
  font-stretch: 87.5%;
  font-style: normal;
  font-display: swap;
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 16px/1.55 var(--prose);
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--amber); }
a:hover { color: var(--text); }

code {
  font-family: ui-monospace, monospace;
  font-size: 0.92em;
  color: var(--dim);
}

/* FOCUS. Two shapes, because the chamfered buttons clip an outline away and
   round 2 then scored the leftover ground-tint as a ring at 1.17:1.
   - Anything unclipped inverts: amber ground, page-dark text, plus an outline.
     That paints a change on all four sides of the box and reads at 11.6:1.
   - The chamfered .cta keeps its amber ground and takes a solid dark ring
     INSIDE the clip, 4 px on all four sides, #04060c on #ffb02e = 11.6:1. */
:focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 2px;
}

a:focus-visible {
  background: var(--amber);
  color: var(--bg);
}

.cta:focus-visible {
  outline: none;
  box-shadow:
    inset 0 0 0 4px var(--bg),
    1.86px 2.34px 0 rgba(4, 6, 11, 0.38),
    0 0 54px rgba(255, 176, 46, 0.3);
}

/* THE HEADING HAS TEXT AGAIN.
   The <h1> was an SVG and nothing else, so its accessible name was the
   MARK'S DESCRIPTION -- "heavy condensed capitals leaning forward, their
   outer corners cut at forty-five degrees..." -- which is what a crawler and
   a screen reader were handed as the page's main heading: a description of a
   shape rather than the name of the thing. The name is now real text, the
   drawing is `aria-hidden` because it is the same word drawn, and the visual
   result is identical. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--amber);
  color: var(--bg);
  padding: 12px 18px;
  font-weight: 700;
  text-decoration: none;
}
.skip:focus {
  left: 8px;
  top: 8px;
}
/* ======================================================================
   PART 1 of 11. FOUNDATIONS -- tokens, the drawn face, focus, and type
   Everything below depends on these; nothing here depends on anything below.
   ====================================================================== */

/* --- type -------------------------------------------------------------- */

h1, h2, h3, .eyebrow, .cta, .n-label, .facts dt, .steps h3, .pull figcaption {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
}

h1 { margin: 0; font-size: clamp(34px, 11vw, 92px); line-height: 0.94; }
h2 { margin: 0 0 14px; font-size: clamp(24px, 6vw, 34px); line-height: 1.05; }
h3 { margin: 0 0 6px; font-size: 17px; letter-spacing: 0.06em; }

/* THE WORDMARK IS THE GAME'S OWN FILE AND NOT TYPE ANY MORE.
   It used to be an <h1> of system-stack capitals with a 9-degree skew laid
   over them, which is a different face wearing the mark's slant. What is here
   now is site/assets/logo.svg, whose outlines come out of the game's own
   drawLogotype (see scripts/logo-from-game.mjs), so the site and the title
   screen set the same eleven glyphs. The box is capped rather than left at
   100%, because the file carries the light bar and its bloom above the caps
   and a mark that fills a desktop column would push the play button off the
   fold (L2). */
.wordmark {
  margin: 0;
  line-height: 0;
}
.wordmark .mark {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  /* CENTRED ON ITS OWN, not by the flex box around it. The heading used to
     shrink to the drawing, so the drawing looked centred for free; once the
     <h1> carried real text as well it stretched the full width and the
     block-level SVG sat against its left edge -- the mark measured 900 px
     wide at left 0 inside a 1440 px heading. A block element is not centred
     by `text-align`, so it centres itself. */
  margin-inline: auto;
}

/* THE STROBE, AND IT IS THE GAME'S AND NOT A BLINK.
   `src/ui/screens.ts` on the title screen: `flick = ((t * 5) | 0) % 2 === 0`.
   An integer floor of the clock times five, so the state flips every 200 ms,
   the cycle is 400 ms, and there is NO RAMP anywhere in it -- which is why
   every timing function here is `step-end` and not `ease`. It is also a
   HALF-BAR alternation and not a cell running along the bar: on `flick` the
   whole left five cells are `PAL.red` and the right five are
   `mix(PAL.blue, PAL.hudPanel, 0.55)`, and off `flick` the two swap. The
   blooms swap with them, 0.45 lit against 0.18 dimmed, which is the 0.4
   opacity the dimmed one carries in the markup.
   The dimmed hexes are `mix(<colour>, PAL.hudPanel, 0.55)` computed by
   scripts/logo-from-game.mjs, which prints them on every run. */
:root {
  --red-dim: #7d2033;
  --blue-dim: #1e3d87;
  --strobe: 0.4s;
}

.bp-cells-l { animation: bp-cells-l var(--strobe) step-end infinite; }
.bp-cells-r { animation: bp-cells-r var(--strobe) step-end infinite; }
.bp-bloom-l { animation: bp-bloom-l var(--strobe) step-end infinite; }
.bp-bloom-r { animation: bp-bloom-r var(--strobe) step-end infinite; }

@keyframes bp-cells-l { 0% { fill: var(--red); } 50% { fill: var(--red-dim); } }
@keyframes bp-cells-r { 0% { fill: var(--blue-dim); } 50% { fill: var(--blue); } }
@keyframes bp-bloom-l { 0% { opacity: 1; } 50% { opacity: 0.4; } }
@keyframes bp-bloom-r { 0% { opacity: 0.4; } 50% { opacity: 1; } }

/* ======================================================================
   PART 2 of 11. THE MARK, THE STROBE AND THE CAR
   The wordmark is the game's own file, the bar strobes on the game's clock, the car is recorded rather than redrawn.
   ====================================================================== */

/* --- the car over the mark ---------------------------------------------- */

/* THE CAR IS THE GAME'S, RECORDED, NOT REDRAWN. The owner, 2026-09-04: the new
   title screen stands a patrol car above the wordmark with its bar flashing,
   and asked for the same here. `scripts/car-from-game.mjs` runs the game's own
   `drawTitleHero` under a canvas that writes SVG, so what is on this page is
   that composition -- the sodium pool, the shadow, the car and the bar's two
   standing lights -- and not an illustration of it.

   IT IS DECORATIVE AND IT IS SIZED SO THE FOLD DOES NOT PAY FOR IT. The
   picture below the button takes the height the copy leaves (see the phone
   block), so a car above the wordmark comes out of the capture rather than out
   of the call to action; it is capped hard at phone widths for that reason. */
/* THE LOCKUP IS BAR AND WORDMARK, AND THE CAR IS NOT IN IT. Three positions
   were tried before this one and the owner ruled on each: above the eyebrow
   (the car, a line of type, then the bar -- three things, not one), then
   inside the lockup above the bar, which is the title screen's own stack and
   still wrong here, because a landing page's header is a place to say what
   this is and the car was saying it twice over the wordmark. It now stands in
   "What it is", beside the sentence about the siren, where a picture of the
   car is the argument the paragraph is making; the press kit shows it in the
   logo section beside the wordmark, which is where a downloadable mark
   belongs; and the 404 carries the wordmark alone. */
.lockup {
  max-width: 420px;
}

/* THE ELEMENT IS EXACTLY THE DRAWING, AND THE DRAWING IS CROPPED TO THE CAR.
   `--car-ratio` comes out of scripts/car-from-game.mjs, which crops every
   recording to the painted car plus a stated margin of lamp, so this stays
   true if the car changes shape. Nothing here overflows: an earlier cut laid
   the element out at the car's ink and let the pool spill, and L1 read 62 px
   of overflow on this box at 1,281 viewports of three pages while L3 reported
   the eyebrow UNMEASURED, the halo having come to rest over that line. Both
   rows were right, and a mark that reaches other people's type is the same
   defect the owner saw with his own eyes. */
.car-plate {
  width: 60%;
  min-width: 132px;
  max-width: 200px;
  aspect-ratio: var(--car-ratio);
  margin: 18px auto 0;
  line-height: 0;
}
.car-plate .bp-car {
  display: block;
  width: 100%;
  height: auto;
}

/* "What it is" is a paragraph, a quote and the car, and at 768 and up the car
   takes its own column beside them rather than a third row under them: the
   section is a claim about what the game is, and the thing it is a claim
   about should be visible while the claim is being read. */
.what-grid { display: grid; gap: 8px; }
.what-copy { min-width: 0; }

/* car:begin */
/* Generated by scripts/car-from-game.mjs from game commit ab8b28b. Do not edit by hand.
   6 segments of the 740 ms cycle barPhase() runs in src/art/vehicles.ts,
   sampled at each segment's centre; 4 operation(s) differ across them. */
:root {
  --car-cycle: 0.74s;
  /* The cropped box: the painted car, 26 units of lamp either side of it,
     and no overflow anywhere, so the element lies over nothing. */
  --car-ratio: 160.68 / 215.99;
}
.bp-car .bp-car-a0 { animation: bp-car-a0 var(--car-cycle) step-end infinite; }
.bp-car .bp-car-a1 { animation: bp-car-a1 var(--car-cycle) step-end infinite; }
.bp-car .bp-car-a2 { animation: bp-car-a2 var(--car-cycle) step-end infinite; }
.bp-car .bp-car-a3 { animation: bp-car-a3 var(--car-cycle) step-end infinite; }
@keyframes bp-car-a0 {
  0% { opacity: 1; fill: #ff2a45; }
  16.67% { opacity: 1; fill: #ad1c2e; }
  33.33% { opacity: 1; fill: #ff2a45; }
  50% { opacity: 1; fill: #ad1c2e; }
  66.67% { opacity: 1; fill: #ad1c2e; }
  83.33% { opacity: 1; fill: #ad1c2e; }
}
@keyframes bp-car-a1 {
  0% { opacity: 1; fill: #1d48ad; }
  16.67% { opacity: 1; fill: #1d48ad; }
  33.33% { opacity: 1; fill: #1d48ad; }
  50% { opacity: 1; fill: #2b6bff; }
  66.67% { opacity: 1; fill: #1d48ad; }
  83.33% { opacity: 1; fill: #2b6bff; }
}
@keyframes bp-car-a2 {
  0% { opacity: 0.5; }
  16.67% { opacity: 0.25; }
  33.33% { opacity: 0.5; }
  50% { opacity: 0.17; }
  66.67% { opacity: 0.17; }
  83.33% { opacity: 0.17; }
}
@keyframes bp-car-a3 {
  0% { opacity: 0.17; }
  16.67% { opacity: 0.17; }
  33.33% { opacity: 0.17; }
  50% { opacity: 0.5; }
  66.67% { opacity: 0.25; }
  83.33% { opacity: 0.5; }
}
/* car:end */

.eyebrow {
  margin: 0 0 14px;
  font-size: 11px;
  letter-spacing: 0.18em;
  color: var(--amber);
}

p { margin: 0 0 14px; }

.lede, .lead-text {
  font-size: 18px;
  color: var(--text);
}

.tagline {
  margin: 14px 0 10px;
  font-size: clamp(17px, 4.6vw, 22px);
  color: var(--dim);
}

.fine {
  font-size: 13px;
  color: var(--dim);
  margin: 12px 0 0;
}

/* --- the light bar, reusing the shipped strobe -------------------------- */

/* THE BAR IS THE WORDMARK'S BAR, NOT A SECOND ONE. The strobe keyframes
   above (`bp-cells-l` / `bp-cells-r`, 400 ms, step-end, half-bar
   alternation) came off the game's title screen, and every bar on this page
   is two <rect>s carrying those same two classes, so there is one clock and
   one set of colours on the page. The selectors were scoped to `.wordmark`
   while the mark was the only bar; they are unscoped now, and the mark is
   unaffected because its rects carry the same classes. */
.lightbar {
  display: block;
  line-height: 0;
}
.lightbar svg { display: block; width: 100%; height: 100%; }
.lightbar-sm { width: 44px; height: 6px; }
.lightbar-md { width: 220px; height: 8px; max-width: 60vw; }
.lightbar-lg { width: 320px; height: 10px; max-width: 72vw; }

/* ======================================================================
   PART 3 of 11. THE SHELL AND THE STICKY BAR
   The page container, the skip link, and the bar that follows the reader down.
   ====================================================================== */

/* --- shell ------------------------------------------------------------- */

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

.band { padding: 40px 0; }

.band-rule { position: relative; }
.band-rule::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg, var(--red) 0 50%, var(--blue) 50% 100%);
}


/* THE SKIP LINK SITS ABOVE THE STICKY BAR, which is new this round.
   `.skip` was written at z-index 10 when nothing on the page was layered;
   the blockbuster nav is sticky at z-index 40, so the focused skip link was
   painted BEHIND it and only the bottom 3 px showed. L5 read that exactly:
   tab stop 00 on the landing page, box 8,8 244x49, "no ring on 3 side(s):
   top 0px (changed at 1.12:1)", at all five viewports, while every other
   element on every page passed. The first thing a keyboard reaches was
   invisible. */
.skip { z-index: 60; }

/* The chamfered panel rule that used to cover .feature, .steps li and
   .gallery figure went with the layout those classes described: a full-bleed
   band cannot carry a clip-path, a solid panel colour and 20 px of padding.
   The one line the press page still needs is kept. */
.downloads .n-label { text-transform: none; letter-spacing: 0.08em; }

/* --- the sticky bar ----------------------------------------------------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(4, 6, 12, 0.88);
  border-bottom: 1px solid var(--rule);
  backdrop-filter: blur(6px);
}
.topbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 56px;
}
.topbar-mark {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--text);
}
.topbar-name {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 18px;
}
.topbar-links { display: flex; align-items: center; gap: 18px; }
.navlink {
  display: none;
  color: var(--dim);
  text-decoration: none;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.navlink:hover { color: var(--text); }
.chip {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  font-size: 16px;
  padding: 9px 18px;
  background: var(--amber);
  color: var(--bg);
  text-decoration: none;
}
.chip {
  position: relative;
  overflow: hidden;
  border-radius: 9px;
  background-image: linear-gradient(115deg, rgba(255, 255, 255, 0) 42%, rgba(255, 255, 255, 0.26) 50%, rgba(255, 255, 255, 0) 58%);
  background-size: 300% 100%;
  background-repeat: no-repeat;
  background-position: 160% 0;
  animation: bp-btn-sweep 1.818s linear infinite;
  box-shadow: 1.86px 2.34px 0 rgba(4, 6, 11, 0.38), 0 0 24px rgba(255, 176, 46, 0.34);
  transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, background-color 0.12s ease-out;
}
.chip::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 9px;
  border-radius: 9px 9px 0 0;
  background: rgba(255, 255, 255, 0.14);
  pointer-events: none;
}
.chip:hover {
  background-color: #ffc058;
  color: var(--bg);
  transform: translateY(-2px);
  box-shadow: 1.86px 2.34px 0 rgba(4, 6, 11, 0.38), 0 0 34px rgba(255, 176, 46, 0.55);
}
.chip:hover::before { background: rgba(255, 255, 255, 0.3); }
.chip:active { transform: translateY(3px); }
.chip:focus-visible { outline: 3px solid var(--text); outline-offset: 2px; }

/* ======================================================================
   PART 4 of 11. THE HERO, ITS BANDS AND THE TICKER
   The full-bleed band machinery lives here because the hero is the first thing that uses it.
   ====================================================================== */

/* --- full-bleed bands --------------------------------------------------- */

/* S3, AND THIS IS THE WHOLE REASON THE BAND CAPTURES EXIST.
   The image is laid out as an ordinary block at `width: 100%; height: auto`,
   so its drawn box IS its file's shape at every viewport width, and the
   band's height is whatever that shape gives. There is no `object-fit`, no
   fixed band height and no crop anywhere in this file. A band that wants a
   different shape gets a different capture file, taken at that shape through
   scripts/capture.mjs and listed in the manifest. */
.bleed { position: relative; overflow: hidden; }
.bleed-img { display: block; width: 100%; height: auto; }

/* THE BAND'S BOX DOES NOT WAIT FOR A FILE TO ARRIVE.
   With `height: auto` alone the drawn height comes from whichever file is
   CURRENTLY decoded, so swapping arm -- resizing a desktop window down past
   700, or turning a phone -- leaves the band at the old ratio until the new
   capture lands. Measured: a page opened at 1600 and resized to 320 collapsed
   each band from 328 px to about 98, and the copy of three panels stacked on
   top of one another; L8 read four colliding text pairs at 320.
   The ratio is stated per arm instead, in the SAME media query the <source>
   uses, so the box is the file's shape from the first frame and never
   depends on load order. S3 is unaffected: the ratio stated here IS the
   ratio of the file that query selects. */
.hero .bleed-img { aspect-ratio: 1440 / 880; }
.feature .bleed-img { aspect-ratio: 1440 / 440; }
@media (max-width: 700px) and (orientation: portrait) {
  .hero .bleed-img { aspect-ratio: 780 / 1228; }
  .feature .bleed-img { aspect-ratio: 780 / 800; }
}
.bleed-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.bleed-copy {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.bleed-copy:focus { outline: none; }

/* --- hero --------------------------------------------------------------- */

.hero { margin-top: -56px; background: var(--bg); }

/* THE SCRIM CARRIES L3. Text over a photograph is only legible because of
   what is laid between them, so the hero takes three stops of ground: a top
   veil under the sticky bar, a deep floor so the buttons and the fine line
   stand on something, and a soft pool centred on the stack itself. The pool
   is what lifts the eyebrow and the tagline off the brightest part of the
   frame; without it those two nodes sit on lit windows. */
.hero-scrim {
  background:
    radial-gradient(62% 48% at 50% 60%, rgba(4, 6, 12, 0.9) 0%, rgba(4, 6, 12, 0.56) 55%, rgba(4, 6, 12, 0) 100%),
    linear-gradient(180deg,
      rgba(4, 6, 12, 0.86) 0%,
      rgba(4, 6, 12, 0.42) 22%,
      rgba(4, 6, 12, 0.46) 52%,
      rgba(4, 6, 12, 0.95) 92%,
      rgba(4, 6, 12, 1) 100%);
}

/* THE SIREN WASH FROM THE TITLE SCREEN, AND IT IS THE GAME'S OWN NUMBERS.
   `lightbarWash` in `src/ui/theme.ts`:

     const phase = Math.sin(t * 2.2);
     glow(c, w * (0.5 + phase * 0.42), h * 0.06, w * 0.34, PAL.red,  0.2);
     glow(c, w * (0.5 - phase * 0.42), h * 0.06, w * 0.34, PAL.blue, 0.2);

   Two soft fields on the TOP edge, each 34 per cent of the width across, at
   alpha 0.2, sweeping counter to each other on a sine of 2.2 rad/s -- a
   period of 2.856 s. The six alpha stops below are the game's own glow
   sprite's, at 0.2 of it, so the falloff is the title screen's and not a
   guess.

   IT DOES NOT TRACK THE STROBE. The bar runs a 5 Hz integer floor and this
   runs a sine; nothing in the game ties them together, which is worth
   stating because on screen it looks as though it should.

   (The game lays a second, half-strength pair at `h * 1.02`. That is off the
   bottom of the title glass and there is no equivalent edge here, so it is
   not reproduced.)

   Written for the old flat hero, dropped when the hero became a full-bleed
   capture, and RESTORED on the owner's word, 2026-09-05: the wash belongs on
   the first section. It now lies over the capture, under the scrim and under
   the copy. */
.wash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-repeat: no-repeat, no-repeat;
  /* 0.34 of the box each way, so the field is the game's 0.34w radius on the
     width axis. It is an ELLIPSE rather than a circle on a wide box, and that
     is the one liberty taken with `lightbarWash`: a circle needs a square
     background-size, which CSS cannot express as a fraction of the width
     alone. The alphas, the sweep and the clock are the game's. */
  background-size: 68% 68%, 68% 68%;
  background-position: 50% -87.5%, 50% -87.5%;
  background-image:
    radial-gradient(closest-side,
      rgba(255, 42, 69, 0.2) 0%, rgba(255, 42, 69, 0.144) 12%,
      rgba(255, 42, 69, 0.076) 28%, rgba(255, 42, 69, 0.03) 50%,
      rgba(255, 42, 69, 0.008) 75%, rgba(255, 42, 69, 0) 100%),
    radial-gradient(closest-side,
      rgba(43, 107, 255, 0.2) 0%, rgba(43, 107, 255, 0.144) 12%,
      rgba(43, 107, 255, 0.076) 28%, rgba(43, 107, 255, 0.03) 50%,
      rgba(43, 107, 255, 0.008) 75%, rgba(43, 107, 255, 0) 100%);
  animation: bp-wash 2.856s ease-in-out infinite;
}

/* IT IS A BACKGROUND AGAIN, AND NOT TWO CHILD BOXES, FOR TWO MEASURED
   REASONS. As pseudo-elements travelling on `left` it moved a real box every
   frame and CLS came back 0.578 against a 0.1 limit, from 2,442 shift
   entries; and its boxes stick out of the section, so when L1 forces
   `overflow: visible` -- which it does deliberately -- the hero reported 44 px
   of overflow at 320. A background paints and never lays out: no shift, no
   box, nothing to stick out. The geometry stays measured off the box rather
   than the viewport, which is what made it visible on a phone.

   phase = sin(t * 2.2): the field centre rides 0.5 +/- phase * 0.42 of the
   width. A background-position percentage is a fraction of (box - image), so
   a centre at c maps to (c - 34) / 0.32: 50% in the middle, 181% at 92% and
   -81% at 8%. The y sits the centre at 6% of the height, which is -87.5%. */
@keyframes bp-wash {
  0%   { background-position: 50% -87.5%, 50% -87.5%; }
  25%  { background-position: 181% -87.5%, -81% -87.5%; }
  50%  { background-position: 50% -87.5%, 50% -87.5%; }
  75%  { background-position: -81% -87.5%, 181% -87.5%; }
  100% { background-position: 50% -87.5%, 50% -87.5%; }
}

.hero-copy {
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: 56px 16px 24px;
  gap: 0;
}
.hero-copy .eyebrow { margin: 0 0 14px; }
.hero-copy .lightbar { margin: 0 0 12px; }
.hero-copy .wordmark { margin: 0; }
.hero-copy .wordmark .mark { max-width: min(760px, 84vw); }
.hero-copy .tagline { margin: 14px 0 0; }
.hero-copy .cta-wrap { margin: 22px 0 0; }
.hero-copy .fine { margin: 12px 0 0; }

.cta-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: center;
}

/* THE BUTTON IS THE GAME'S PRIMARY, PART BY PART.
   `drawButton` for `B_PRIMARY` in `src/ui/widgets.ts`, with the constants it
   reads. Nothing below is invented; each line names where it came from.

     glow(c, cx, cy, w * 0.55, accent, (0.16 + 0.14 * hov) * lit)
     shadowUnder(...)  -> SHADOW_DX/DY (0.62, 0.78) x BTN_THROW 3 = 1.86, 2.34,
                          in PAL.ink #04060b at BTN_INK 0.38
     fill              -> P.amber, mixed 0.2 toward white while hovered,
                          which is #ffb02e -> #ffc058
     litFace(...)      -> one flat white band across the TOP, depth capped at
                          the chamfer (TOP_FACE_MIN 5 for a square-cut plate),
                          at PANEL_LIGHT 0.05 + boost (0.05 + 0.14 * hov)
     lift              -> lit * 2 up, press * 3 down

   ONE DEPARTURE, STATED RATHER THAN SMUGGLED:
   1. THE CUT IS THE GAME'S, radius.md = 9, on the owner's word 2026-09-05.
      The first cut of this kept the mockup's square corner and took
      `litFace`'s 5 px floor with it, and the owner could not see the
      treatment at all on a desktop -- correctly, because at 5 px on a square
      slab there is almost nothing to see. The game's own chamfer is 9 and
      `litFace` draws its band THE CHAMFER DEEP, so the corner and the lit
      face arrive together and the button reads as the game's mass.
   2. The game glows only a button that is `lit`. On the title screen START
      is focused from the frame the page loads, so it glows there always; on
      a web page nothing is focused at rest, and the mockup draws the button
      glowing. So a quieter glow stands at rest and the game's 0.16 -> 0.30
      pair drives hover. */
.cta {
  position: relative;
  overflow: hidden;
  display: inline-block;
  background-color: var(--amber);
  background-image: linear-gradient(115deg, rgba(255, 255, 255, 0) 42%, rgba(255, 255, 255, 0.26) 50%, rgba(255, 255, 255, 0) 58%);
  background-size: 300% 100%;
  background-repeat: no-repeat;
  background-position: 160% 0;
  animation: bp-btn-sweep 1.818s linear infinite;
  color: var(--bg);
  text-decoration: none;
  padding: 15px 26px;
  font-size: clamp(19px, 4.4vw, 30px);
  border-radius: 9px;
  box-shadow:
    1.86px 2.34px 0 rgba(4, 6, 11, 0.38),
    0 0 42px rgba(255, 176, 46, 0.38);
  transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, background-color 0.12s ease-out;
}
.cta::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 9px;
  border-radius: 9px 9px 0 0;
  background: rgba(255, 255, 255, 0.14);
  pointer-events: none;
}
.cta:hover {
  color: var(--bg);
  background-color: #ffc058;
  transform: translateY(-2px);
  box-shadow:
    1.86px 2.34px 0 rgba(4, 6, 11, 0.38),
    0 0 62px rgba(255, 176, 46, 0.58);
}
.cta:hover::before { background: rgba(255, 255, 255, 0.3); }
.cta:active { transform: translateY(3px); }

/* THE SHINE SWEEP IS A BACKGROUND, NOT A CHILD BOX.
   The game's `drawButton` sweeps a white parallelogram 0.18 of the button
   wide across it on `(t * 0.55) % 1`, one pass every 1.818 s. As an ::after
   travelling on `translateX` that box leaves the button by design, and the
   button is `overflow: hidden` to hide it -- which L1 undoes deliberately,
   so the nav bar reported overflow from 341 px up and it grew with the
   viewport. A moving gradient paints the same stripe, is clipped by the
   background box itself, has no layout box to stick out and cannot shift
   anything.

   TWO DEPARTURES FROM THE GAME, BOTH THE OWNER'S CALL, 2026-09-05: the game
   gates the sweep on FOCUS alone -- so a pointer user never sees it -- and it
   sweeps only the focused control. Here it answers to hover and focus and
   also stands at rest on every amber control, because the title screen's
   START is focused from the frame the game loads and never stops sweeping.
   It stops dead under `prefers-reduced-motion`. */
@keyframes bp-btn-sweep {
  from { background-position: 160% 0; }
  to   { background-position: -60% 0; }
}
.cta:hover::after,
.chip:hover::after,
.cta:focus-visible::after,
.chip:focus-visible::after {
  background: rgba(255, 255, 255, 0.34);
}

.ghost {
  display: inline-block;
  border: 2px solid rgba(233, 237, 247, 0.55);
  color: var(--text);
  text-decoration: none;
  padding: 13px 22px;
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  font-size: clamp(16px, 3.4vw, 22px);
}
/* The game's GHOST is a quieter slab of the same mass at the same light --
   `shadowUnder(..., 0.7)` and a fill that lifts with `lit`. The approved
   mockup draws it as an outline, so the outline stays and it takes the mass's
   throw at that same 0.7 and the same lift and press. */
.ghost {
  border-radius: 9px;
  box-shadow: 1.86px 2.34px 0 rgba(4, 6, 11, 0.27);
  transition: transform 0.12s ease-out, border-color 0.12s ease-out, background-color 0.12s ease-out;
}
.ghost:hover {
  border-color: var(--text);
  background: rgba(233, 237, 247, 0.08);
  color: var(--text);
  transform: translateY(-2px);
}
.ghost:active { transform: translateY(3px); }

/* --- the radio ticker --------------------------------------------------- */

.ticker {
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--panel-deep);
  overflow: hidden;
  padding: 10px 0;
}
/* THE TICKER DOES NOT TRAVEL, AND THAT IS A RULING THE PANEL IS OWED.
   The design's band is a marquee: a run of dispatch lines wider than the
   screen, translated leftward on a 36 s loop. L1 measures every element box
   against the viewport AFTER FORCING `overflow: visible` on everything --
   deliberately, because it once passed a 2,000 px child hidden behind a clip
   -- so a run wider than the screen fails L1 at every width, and it failed
   L7 at 720x450 and the short viewports too. It also fails L6, by a route
   worth writing down: `hoverEverything` calls `scrollIntoViewIfNeeded` on
   every element, an element inside `overflow: hidden` is still
   PROGRAMMATICALLY scrollable, so the leg scrolled the band sideways and
   left it there, and the two reduced-motion captures came back showing
   different lines. Measured: L1 2,808 px over at 320-1600, L7 2,397 px over
   at 720x450, L6 2,329 px differing at 390.
   So the band ships with its lines standing still and wrapping inside the
   page, which no row objects to. THE MOTION IS NOT SHIPPED AS A SECOND
   COMMIT the way parallax is, because parallax passes its instrument and
   raises a question of intent, whereas this fails three rows outright, and a
   builder does not commit a red gate. What the lead and the owner own: an
   L1 exception for a clipped decorative marquee, or a ticker that stands
   still. See the round report. */
.ticker-run { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 0; }
.ticker-line {
  padding: 0 12px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
}
.ticker-who { color: #9aa6c2; }
.ticker-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--red);
  flex: none;
}
.ticker-item { display: flex; align-items: center; max-width: 100%; }
.ticker-item:last-child .ticker-dot { display: none; }
.ticker-item:nth-child(3n+2) .ticker-dot { background: var(--blue); }
.ticker-item:nth-child(3n) .ticker-dot { background: var(--amber); }

/* ======================================================================
   PART 5 of 11. THE SECTIONS DOWN THE PAGE
   What-it-is, the generic section furniture, the three night bands, then the case.
   ====================================================================== */

/* --- what and the pull quotes ------------------------------------------ */

.pull {
  margin: 22px 0 0;
  border-left-color: var(--amber);
}
.pull blockquote {
  margin: 0;
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: clamp(17px, 4.4vw, 22px);
  line-height: 1.2;
}
.pull blockquote p { margin: 0; }
.pull figcaption {
  margin-top: 10px;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
}

/* --- sections ----------------------------------------------------------- */

.band { padding: 56px 0; }
.band-rule { border-top: 1px solid var(--rule); background: var(--panel-deep); }

.display-xl {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  margin: 10px 0 0;
  font-size: clamp(30px, 7.6vw, 62px);
  line-height: 0.94;
}
.display-lg {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  margin: 2px 0 0;
  font-size: clamp(26px, 6.4vw, 52px);
  line-height: 0.98;
}
.eyebrow-amber { color: var(--amber); }
.eyebrow-red { color: var(--red); }

.what-grid { display: grid; gap: 22px; }
.what-copy { min-width: 0; }
.lead-text { margin: 0; color: #c6cfe2; }

/* --- the three bands ---------------------------------------------------- */

/* THE SCRIM HAS TO CARRY THE WHOLE COPY BLOCK, not just its last line.
   Measured once L3 could see text on a picture (W21): the step index sat at
   1.13:1 at 768 and 3.71:1 at 390, and two of the three headings were under
   the large-text line -- because the index is the TOPMOST of the three
   elements and the old gradient was still nearly clear where it sits. The
   dark now starts above the block rather than under its feet. */
.band-scrim {
  background: linear-gradient(180deg,
    rgba(4, 6, 12, 0.05) 0%,
    rgba(4, 6, 12, 0.3) 34%,
    rgba(4, 6, 12, 0.9) 56%,
    rgba(4, 6, 12, 0.96) 78%,
    rgba(4, 6, 12, 0.98) 100%);
}
.band-copy { justify-content: flex-end; padding-bottom: 20px; }
.step-index {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  color: var(--amber);
  letter-spacing: 0.1em;
  margin: 0;
  font-size: 15px;
}
/* The step copy takes the stack's own white over a photograph rather than
   the dimmer prose grey: at 667x375 and 700x400 the dim grey measured 3.61:1
   and 3.34:1 against a 4.5 line. Off a picture -- the what-it-is and case
   sections -- the grey stays. */
.step-text { margin: 6px 0 0; color: var(--text); max-width: 56ch; }

/* --- the case ----------------------------------------------------------- */

.case-grid { display: grid; gap: 24px; }
.case-caption { margin: 12px 0 0; color: var(--dim); font-size: 14px; }

/* THE FRAME AND ITS TWO GLOWS: red off the left edge, blue off the right,
   which is the light bar's own pair standing still. */
.case-shot {
  margin: 0;
  border: 1px solid var(--rule);
  box-shadow: -18px 0 46px rgba(255, 42, 69, 0.14), 18px 0 46px rgba(43, 107, 255, 0.14);
  line-height: 0;
}

/* ======================================================================
   PART 6 of 11. THE GALLERY AND ITS LIGHTBOX
   Both eras of the gallery, which used to sit 643 lines apart.
   ====================================================================== */

/* --- gallery ----------------------------------------------------------- */

.gallery { display: grid; gap: var(--gap); }
.gallery figure { margin: 0; padding: 0 0 16px; }
.gallery img { width: 100%; margin-bottom: 12px; }
.gallery figcaption {
  padding: 0 18px;
  font-size: 13px;
  color: var(--dim);
}

/* --- gallery ------------------------------------------------------------ */

.gallery {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: grid;
  gap: 14px;
  grid-template-columns: 1fr;
}
.gallery figure { margin: 0; }
.gallery img { border: 1px solid var(--rule); }
.shot-link {
  display: block;
  line-height: 0;
  transition: transform 0.12s ease-out, box-shadow 0.12s ease-out;
}
.shot-link:hover { transform: translateY(-2px); }
.shot-link:hover img { border-color: var(--amber); }
.shot-link:focus-visible { background: none; }

/* THE LIGHTBOX IS `:target`, BECAUSE THE PAGE SHIPS NO SCRIPT.
   Each capture links to a panel that is `display: none` until the fragment
   names it, so nothing is fetched until somebody opens one -- the full-size
   file is not requested on load, which keeps P2 where it is. The sheet
   behind the picture is a link back to `#gallery`, so a click anywhere
   outside closes it the way a lightbox should, and the CLOSE control is a
   real link rather than a glyph nobody can tab to.
   `position: fixed` also keeps the panel out of L1's reckoning, which skips
   fixed elements -- it is not a trick, it is what fixed means: the panel is
   not part of the page's flow and cannot widen it. */
.lightbox { display: none; }
.lightbox:target {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(4, 6, 12, 0.94);
}
.lightbox-sheet {
  position: absolute;
  inset: 0;
  display: block;
}
.lightbox-figure {
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 60px 16px 24px;
  pointer-events: none;
}
/* S3: the box keeps the file's shape. Both limits are maxima on a box that
   sizes itself, so the picture scales down whole and is never cropped or
   letterboxed. */
.lightbox-figure img {
  max-width: min(96vw, 1280px);
  max-height: 78vh;
  width: auto;
  height: auto;
  border: 1px solid var(--rule);
}
.lightbox-figure figcaption {
  font-size: 13px;
  color: var(--dim);
  text-align: center;
}
.lightbox-close {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 1;
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 15px;
  padding: 10px 16px;
  background: var(--amber);
  color: var(--bg);
  text-decoration: none;
}
.lightbox-close:hover { background: #ffc058; color: var(--bg); }
.gallery figcaption { padding-top: 8px; font-size: 13px; color: var(--dim); }

/* ======================================================================
   PART 7 of 11. THE DEV LOG AND THE QUESTIONS
   The teaser, the ledger, and the questions that follow them.
   ====================================================================== */

/* --- the dev log and its teaser ----------------------------------------- */

/* The date is a LABEL and takes the drawn face; the entry itself is prose and
   does not. That is the game's own type system: the face has no lowercase, so
   a paragraph excludes itself from it. */
.log, .log-teaser {
  list-style: none;
  margin: 0;
  padding: 0;
}
.log-entry {
  padding: 0 0 22px;
  margin: 0 0 22px;
  border-bottom: 1px solid var(--rule);
  max-width: 62ch;
}
.log-entry:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.log-entry h2 { font-size: clamp(20px, 5vw, 26px); }
.log-date {
  margin: 0 0 6px;
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  font-size: 12px;
  letter-spacing: 0.16em;
  color: var(--amber);
}
.log-entry p:last-child { margin: 0; color: var(--dim); }

.log-teaser { margin: 0 0 14px; }
.log-teaser li { border-top: 1px solid var(--rule); }
.log-teaser li:last-child { border-bottom: 1px solid var(--rule); }
.log-teaser a {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  align-items: baseline;
  min-height: 44px;
  padding: 10px 0;
  text-decoration: none;
  color: var(--text);
}
.log-teaser a:hover { color: var(--amber); }
.log-teaser .log-date { margin: 0; flex: 0 0 auto; }
.log-teaser a > span:last-child {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  font-size: 17px;
}

.devlog .press-head { padding-bottom: 6px; }

/* At 768 and up the ledger's 62ch entries were pinned to the left of an
   1120 px wrap and the right half of the viewport sat empty (design review,
   2026-09-03). The wrap is narrowed to the entry width plus its own padding,
   so header, ledger and footer share one centred column. The unit is the
   wrap's own ch, and the entry's max-width is the same 62ch in the same body
   face, so the two agree by construction. */
@media (min-width: 768px) {
  .devlog .wrap { max-width: calc(62ch + 40px); }
}

/* --- questions --------------------------------------------------------- */

.qa { margin: 0; }
.qa dt {
  font-weight: 700;
  margin-top: 16px;
}
.qa dt:first-child { margin-top: 0; }
.qa dd {
  margin: 4px 0 0;
  color: var(--dim);
}

.status { border-left-color: var(--amber); align-self: start; }
.status p { margin: 0; color: var(--dim); }

/* --- dev log and questions ---------------------------------------------- */

.two-col { display: grid; gap: 34px; }
.log-teaser { list-style: none; margin: 14px 0 0; padding: 0; }
.log-teaser li { border-bottom: 1px solid var(--rule); }
.log-teaser a {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: baseline;
  padding: 12px 0;
  text-decoration: none;
  color: var(--text);
}
.log-teaser a:hover .log-title { color: var(--amber); }
.log-date {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  color: var(--amber);
  letter-spacing: 0.1em;
  font-size: 14px;
}
.log-title {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  font-size: 21px;
}
.qa { margin: 14px 0 0; }
.qa dt { font-weight: 700; margin-top: 14px; }
.qa dd { margin: 2px 0 0; color: #c6cfe2; }
.status {
  margin-top: 20px;
  border-left: 3px solid var(--amber);
  padding: 12px 16px;
  /* OPAQUE, AND THE COLOUR IS THE DESIGN'S OWN, PRE-COMPOSITED.
     The design paints this panel as 6 per cent amber over the section
     ground. L3 resolves a translucent background by compositing it over
     WHITE, not over what is actually behind it, so `rgba(255,176,46,0.06)`
     was read as light-grey ground and the callout failed at 1.51:1 against
     a 4.5 line -- on a panel that is, on screen, pale text on near-black.
     This is that same colour worked out against `--panel-deep`, which is
     what the section really paints: 6 per cent of #ffb02e over #080b13.
     The pixels are unchanged and the row can now read them. */
  background: rgb(23, 21, 21);
}
.status p { margin: 0; font-size: 14px; color: #c6cfe2; }
.pull { margin: 20px 0 0; }
.pull blockquote { margin: 0; }
.pull blockquote p { margin: 0; font-style: italic; font-weight: 600; }
.pull figcaption {
  margin-top: 4px;
  font-size: 12px;
  letter-spacing: 0.2em;
  color: var(--dim);
}

/* ======================================================================
   PART 8 of 11. THE CLOSING BAND AND THE FOOTER
   Both eras of the footer, which used to sit 759 lines apart.
   ====================================================================== */

/* --- footer ------------------------------------------------------------ */

#footer {
  border-top: 1px solid var(--rule);
  margin-top: 20px;
  padding: 28px 0 40px;
  background: var(--panel-deep);
}
.footer-links { display: flex; flex-wrap: wrap; gap: 8px 24px; margin: 0 0 14px; }
.footer-links a {
  display: inline-block;
  min-height: 44px;
  line-height: 44px;
  text-decoration: none;
  border-bottom: 2px solid var(--rule);
}
.footer-play {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  border-bottom-color: var(--amber);
}
.footer-note { margin: 0 0 6px; font-size: 13px; color: var(--dim); }

/* --- the closing band --------------------------------------------------- */

.closing { border-top: 1px solid var(--rule); padding: 64px 0 56px; }
.closing-inner { display: flex; flex-direction: column; align-items: center; text-align: center; }
.closing-title { margin: 18px 0 0; }
.closing .cta-wrap { margin: 24px 0 0; }
.closing .fine { margin: 12px 0 0; }

/* --- footer ------------------------------------------------------------- */

#footer { border-top: 1px solid var(--rule); background: var(--panel-deep); padding: 24px 0 28px; }
.footer-grid { display: grid; gap: 10px; }
.footer-links { display: flex; flex-wrap: wrap; gap: 20px; margin: 0; }
.footer-links a { font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 600; }
.footer-note { margin: 0; font-size: 13px; color: var(--dim); }

/* ======================================================================
   PART 9 of 11. THE PAGES BELOW THE LANDING PAGE
   The press plate and the 404.
   ====================================================================== */

/* --- press ------------------------------------------------------------- */

.press-head {
  background: var(--asphalt);
  border-bottom: 1px solid var(--rule);
  padding: 26px 0 20px;
}
.press h1 { font-size: clamp(34px, 10vw, 64px); }
.press h3 { margin-top: 22px; color: var(--amber); }

.facts { display: grid; gap: 0; margin: 0; }
.facts dt {
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--amber);
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}
.facts dd { margin: 4px 0 14px; }

.notes li, .plain-downloads li {
  padding: 10px 0;
  border-top: 1px solid var(--rule);
}
.plain-downloads a {
  min-height: 44px;
  display: inline-block;
  line-height: 26px;
  padding: 9px 0;
}

/* THE PRESS KIT SHOWS THE FRAME BEFORE IT HANDS IT OVER. It used to be a list
   of file names and pixel dimensions, so the only way to find out what a
   capture was of was to download it and open it. The grid below is the landing
   page's gallery shape reused: a thumbnail that opens the full frame in the
   same `:target` lightbox, and a separate Download beside it, because looking
   and taking are two different intentions and one link cannot be both.

   The two vector marks keep the old plain rows -- they have no capture row to
   describe them and the logo section above already shows both at full size, so
   a tile there would be a second copy of a picture the reader has just seen. */
.shot-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
}
.shot-grid figure { margin: 0; }
/* ONE PLATE, EVERY TILE THE SAME. The set mixes landscape frames with portrait
   phone frames, so at natural height no two tiles in a row matched and neither
   the names nor the Download links shared a baseline. The PLATE is a fixed box
   and the frame sits centred inside it at its own size. Nothing is cropped and
   nothing is stretched -- S3 measures the image's drawn box against the file's
   shape, and that is untouched; what is fixed here is the box AROUND it. */
.shot-grid .shot-link {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 240px;
  background: var(--panel-deep);
  border: 1px solid var(--rule);
  padding: 0;
  min-height: 0;
  /* THE STICKY BAR MUST NOT SIT ON THE RING. Tabbing to a tile scrolls it into
     view, and without this the bar covered the tile's top edge: L5 measured
     "no ring of 2px at 3:1 on 1 side(s): top 0px (changed at 1.02:1)" at
     390x844, where the tiles are one across and tall. 57 px of bar at 700,
     plus room to read the frame's own top. */
  scroll-margin-top: 76px;
  /* AND ROOM BELOW, the other half of the same rule. `scrollIntoView` moves as
     little as it can, so a tile already touching the bottom edge is not moved
     at all and the ring's bottom 5 px -- a 3 px outline at 2 px of offset --
     fall outside the viewport. Measured at 700x400: the tile's box ended at
     exactly 400 of a 400 px viewport, and L5 read "no ring on 2 side(s)". */
  scroll-margin-bottom: 24px;
}
/* THE TILE DOES NOT INVERT ON FOCUS, AND THE RING IS THE WHOLE RING.
   `a:focus-visible` paints an amber ground under every link on the site. Under
   a lazy thumbnail that ground is VISIBLE until the image arrives, so focusing
   a tile flashed a full amber rectangle and then the frame -- L6 caught it as
   "something on screen moves while a holds focus (62632 px over 400 ms)",
   which is one tile at 700, and it is a real flash for a keyboard reader and
   not an artefact of the instrument. The 3 px outline already gives four sides
   at 11.6:1, so the ground buys nothing here. */
.shot-grid .shot-link:focus-visible { background: var(--panel-deep); }
.shot-get { scroll-margin-top: 76px; scroll-margin-bottom: 24px; }
/* A BOUND ON HEIGHT, NOT A CROP. The set is landscape frames and PORTRAIT
   phone frames, and at 320 px of column a portrait frame came out 690 px tall
   -- taller than a 400 px viewport, so L5 could show neither the top nor the
   bottom of its focus ring at 700x400 and read "no ring on 2 side(s)". S3
   forbids fixing that the easy way: the drawn box must be the file's shape
   within 1%, so no crop and no letterbox. Bounding BOTH axes and letting the
   image take whichever bites first keeps the aspect exactly and makes the tall
   frames small instead of cropped. They then sit narrower than their column,
   so they are centred in it. */
.shot-grid img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
}
/* TWO ROWS, ALWAYS, so thirty-five tiles line up. Laid out as one flex row it
   wrapped wherever a name happened to be long, and the size and the Download
   fell to a second line on some tiles and not others: no two captions were the
   same height and nothing shared a baseline down the column. The name takes
   the first row and may wrap within it; the size and the Download take the
   second whatever the name did. */
/* The caption is a column and the meta row is pinned to its foot, so a name
   that wraps to two lines does not push its own size and Download out of step
   with the tiles beside it. */
.shot-grid li { display: flex; }
.shot-grid figure { display: flex; flex-direction: column; width: 100%; }
.shot-grid figcaption {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 9px 2px 0;
}
.shot-grid .shot-meta { margin-top: auto; }
.shot-grid .shot-name {
  display: block;
  color: var(--text);
}
.shot-grid .shot-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.shot-grid .n-label { color: var(--dim); }
.shot-get {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0 2px;
  font-family: var(--display);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-size: 14px;
}
.plain-downloads { margin-top: 26px; }

@media (min-width: 640px) {
  .shot-grid { grid-template-columns: repeat(2, 1fr); gap: 20px; }
}
@media (min-width: 1024px) {
  .shot-grid { grid-template-columns: repeat(3, 1fr); }
}
.notes li { color: var(--dim); }

.logo-plate {
  background: var(--panel);
  padding: 20px;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%);
}
.logo-plate img { width: 100%; }

/* The car gets the wordmark's own plate on this page, so the two marks are
   presented as one set rather than as a picture and a loose drawing. */
.car-bed { margin: 22px 0 14px; }
.car-bed .car-plate { margin: 0 auto; max-width: 220px; }

/* --- the 404 ----------------------------------------------------------- */

.gone {
  min-height: 100vh;
  display: grid;
  align-content: center;
  background: var(--asphalt);
}
.gone .wrap { padding-block: 40px; }
.gone h1 { margin-bottom: 18px; }
.gone p { color: var(--dim); max-width: 30em; }
.gone .eyebrow { color: var(--amber); }

/* ======================================================================
   PART 10 of 11. THE LOAD-IN AND THE SCROLL REVEALS
   One orchestrated hero reveal, four beats, and then the page is still.
   ====================================================================== */

/* --- the load-in and the scroll reveals --------------------------------- */

/* One orchestrated hero reveal, four beats, and then the page is still. */
@keyframes bp-rise { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: none; } }
.rise { animation: bp-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
.rise-1 { animation-delay: 0.05s; }
.rise-2 { animation-delay: 0.18s; }
.rise-3 { animation-delay: 0.32s; }
.rise-4 { animation-delay: 0.46s; }

/* SCROLL REVEALS ARE GUARDED, AND THE GUARD IS THE POINT. Without
   scroll-driven animations the element must stand at its FINISHED state, not
   its hidden one -- so the rule that hides it lives inside the @supports and
   nowhere else. A browser that does not know `view()` never sees `.reveal`
   mentioned. */
@supports (animation-timeline: view()) {
  /* TRANSFORM ONLY, NEVER OPACITY. A `view()` timeline does not resolve for
     an instrument that does not scroll, so a reveal that fades from
     `opacity: 0` leaves every section below the fold INVISIBLE to it: L3
     read four nodes of the case section at "effective opacity 0" and
     1:1 contrast and failed the row, and L6's full-page captures were blank
     wherever a reveal had not fired. Sliding instead of fading costs the
     design almost nothing and keeps every pixel measurable. */
  @keyframes bp-reveal { from { transform: translateY(24px); } to { transform: none; } }
  .reveal {
    animation: bp-reveal 1ms linear both;
    animation-timeline: view();
    animation-range: entry 5% entry 45%;
  }
}

/* L6: WITH THE PREFERENCE SET, NOTHING ON THIS PAGE MOVES.
   A blanket, deliberately: the targeted list this replaced had to be kept in
   step with every animation added, and the ticker, the wash, the load-in and
   the reveals were all added at once. Everything stops at its first
   keyframe, which for `both`-filled animations is the finished state. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

/* ======================================================================
   PART 11 of 11. WIDTHS -- every media query in the sheet
   ORDER IS LOAD-BEARING IN THIS PART AND NOTHING IN IT WAS MOVED. Later blocks here deliberately override earlier ones at the same breakpoint, so the part is a verbatim run of the sheet's original media-query order.
   ====================================================================== */

/* --- portrait captures at phone widths --------------------------------- */

/* The <picture> swaps to the portrait capture at 700px and below, and the box
   is pinned so the reserved space matches the candidate that actually loads.
   THE BANDS ARE NOT IN HERE ANY MORE: their portrait candidates are
   780x800 files and their boxes come from `width: 100%; height: auto` on the
   image itself, so pinning an aspect-ratio here would letterbox them and fail
   S3. The rule is left holding the one picture whose portrait file really is
   780x1688. */
@media (max-width: 700px) and (orientation: portrait) {
  .case-shot img {
    aspect-ratio: 780 / 1688;
    width: 100%;
    margin-inline: auto;
  }
}

/* --- widths ------------------------------------------------------------- */

/* TWO ACROSS FROM 640, NOT 700, AND L5 IS THE REASON.
   At 667x375 a one-across gallery draws each capture 627x354, which is taller
   than the 319 px left under the sticky bar. The leg scrolls a focused
   element to the centre, so the top of the box -- and the top of its focus
   ring -- went behind the bar: four anchors reported "no ring on 1 side, top
   0px". Two across makes each capture about 306x172, which clears the bar
   whole. */
@media (min-width: 640px) {
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .band-copy { padding-bottom: 34px; }
}

@media (min-width: 900px) {
  .topbar-inner { height: 62px; }
  .hero { margin-top: -62px; }
  .navlink { display: inline; }
  .what-grid { grid-template-columns: 1fr 1fr; gap: 56px; align-items: start; }
  .case-grid { grid-template-columns: 1fr 1.05fr; gap: 48px; align-items: center; }
  .two-col { grid-template-columns: 1fr 1fr; gap: 56px; }
  /* R3b: TWO ACROSS, NOT FOUR. At 1200 and up a four-across gallery drew each
     capture at about 280 px, and the frame detail these pictures exist to show
     -- the readout, the ticket, the ringed violator -- dies there. Two across
     doubles the drawn width. The DOM order is untouched, so the captions still
     read in career order. */
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; }
  .footer-grid { grid-template-columns: 1fr auto; align-items: center; }

  .band { padding: 84px 0; }
}

/* =======================================================================
   Corrections after the first build was put on screen and read.
   ======================================================================= */

/* THE HERO'S LIGHT BAR IS THE WORDMARK'S OWN. site/assets/logo.svg carries
   the segmented bar and its two blooms above the caps, drawn out of the
   game; the mockup's separate bar above the mark was a stand-in for that
   element, and shipping both put two bars on the fold. */

/* The eyebrow is amber everywhere else on the site; over the hero frame it
   takes the pale stack colour the design uses, which is also the readable
   one against a lit street. */
/* MEASURED, not chosen by eye. At #cfd8ec this line read 4:1 over the hero
   capture at 1440 -- under the 4.5 body line, and invisible to L3 until W21
   taught it to sample the pixels under text on a picture. It takes the
   stack's own white now, and the scrim's pool is deepened under it. */
.hero-copy .eyebrow { color: #f4f7ff; letter-spacing: 0.3em; font-weight: 700; margin-bottom: 18px; }
.hero-copy .tagline,
.closing .fine,
.hero-copy .fine {
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-weight: 600;
}
.hero-copy .tagline { color: var(--text); font-size: clamp(14px, 2.6vw, 21px); }
.hero-copy .fine, .closing .fine { color: #aab6d0; font-size: clamp(11px, 2.2vw, 14px); }

/* A commit hash is not a heading: the eyebrow's uppercase would print
   E789738 for a build called e789738. */
.eyebrow code { text-transform: none; color: var(--dim); }

/* The pull quote takes the body face, italic, as the design sets it: the
   display face has no lowercase, and a sentence from the game read in
   capitals is a different sentence. */
.pull blockquote p {
  font-family: var(--prose);
  font-style: italic;
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  font-size: 17px;
}
.pull figcaption { text-transform: uppercase; }

.gallery figure { padding: 0; }
.gallery img { margin-bottom: 0; }
.gallery figcaption { padding: 8px 0 0; }

/* --- the hero on a phone ------------------------------------------------ */

/* The portrait capture is the whole phone frame, HUD and dispatch box and
   all, so the stack cannot stand in the middle of it: it would sit on the
   readout. It stands on the floor of the band, where the design puts it,
   over the heaviest part of the scrim. */
@media (max-width: 699px) {
  .hero-copy {
    justify-content: flex-end;
    padding: 0 16px 22px;
  }
  .hero-scrim {
    background:
      radial-gradient(80% 40% at 50% 88%, rgba(4, 6, 12, 0.9) 0%, rgba(4, 6, 12, 0.5) 60%, rgba(4, 6, 12, 0) 100%),
      linear-gradient(180deg,
        rgba(4, 6, 12, 0.5) 0%,
        rgba(4, 6, 12, 0.3) 26%,
        rgba(4, 6, 12, 0.62) 58%,
        rgba(4, 6, 12, 0.97) 90%,
        rgba(4, 6, 12, 1) 100%);
  }
  /* The design gives the phone fold one button. The dev log keeps its link
     in the footer, so nothing is lost and the fold keeps the play button. */
  .hero-copy .ghost { display: none; }
  .hero-copy .eyebrow { margin-bottom: 10px; font-size: 10px; }
  .hero-copy .wordmark .mark { max-width: 94vw; }
  .hero-copy .tagline { margin-top: 10px; }
  .hero-copy .cta-wrap { margin-top: 16px; }
}

@media (min-width: 1200px) {
  .hero-copy .wordmark .mark { max-width: min(900px, 66vw); }
}


/* --- a phone held sideways, and any short viewport ---------------------- */

/* L2 asks for the play button above the fold at 375x667, 390x844 and
   1440x900, and the checker also holds the page to 700x400, 667x375 and
   844x390. The hero band's height is its capture's shape -- that is what
   makes it S3-clean -- so at 844 wide it is 516 px tall and the button
   landed 82 px below a 390 px fold. The band stops being an overlay when
   the viewport is short: the copy takes its own column and the picture
   takes the other, at its own shape, which is the shape the old site used
   for the same row and the same reason. */
@media (max-height: 560px) and (min-width: 560px) {
  .hero.bleed {
    /* The hero tucks under the sticky bar only while it is a full-bleed
       overlay. Here it is a two-column block with the scrim off, so the pull
       just drove the eyebrow into the wordmark -- L8 read 111x7 px shared at
       700x400, 667x375 and 844x390. */
    margin-top: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: 24px;
    padding: 12px 20px 16px;
    overflow: visible;
  }
  .hero picture { grid-area: 1 / 2; min-width: 0; }
  .hero .bleed-copy {
    position: static;
    grid-area: 1 / 1;
    padding: 0;
    justify-content: center;
    align-items: flex-start;
    text-align: left;
  }
  .hero .hero-scrim, .hero .wash { display: none; }
  /* THE FOLD IS ABOUT 328 PX HERE, so the stack is cut to fit it. Dropping the
     hero's tuck under the bar -- which L8 required -- pushed the play button
     to 438 px down at 844x390 and 398 at 667x375, against folds of 390 and
     375. The wordmark is the tall element, so it gives up the most. */
  .hero-copy .wordmark .mark { max-width: 66%; margin-inline: 0; }
  .hero-copy .cta-wrap { justify-content: flex-start; margin-top: 8px; }
  .hero-copy .ghost { display: none; }
  .hero-copy .eyebrow { margin-bottom: 3px; font-size: 9px; letter-spacing: 0.2em; }
  .hero-copy .tagline { margin-top: 4px; font-size: 11px; letter-spacing: 0.1em; }
  .hero-copy .cta { padding: 9px 16px; font-size: 16px; }
  .hero-copy .fine { margin-top: 4px; font-size: 9px; }
}

/* Under 560 px wide AND short, there is no room for two columns; the copy
   stands under the picture rather than on it. */
@media (max-height: 560px) and (max-width: 559px) {
  .hero.bleed { overflow: visible; margin-top: 0; }
  .hero .bleed-copy { position: static; padding: 12px 16px 16px; }
  .hero .hero-scrim, .hero .wash { display: none; }
  .hero-copy .ghost { display: none; }
}


/* --- the head every page below the landing page shares ------------------ */

/* ALL FOUR PAGES WEAR THE SAME BAR, on the owner's word 2026-09-05. The press
   kit used to open with the wordmark at full size and no bar at all, and the
   dev log with a bare title; each page introduced the site again in its own
   way. The bar now carries the mark on every page, so the page head carries
   only what that page is.

   AND IT FIXES A REAL COLLISION. With the landing page's centred `.cta-wrap`
   applying site-wide, the press kit's play button was centred under a
   left-aligned wordmark and the two overlapped: measured at 1440, the mark
   ran 180-600 and the button 524-916, so the button sat across the mark's
   last two glyphs. The head is one left-aligned column now and the button
   sits in it. */
/* THE WASH RUNS ON EVERY PAGE, on the owner's word 2026-09-05: the landing
   hero is not the only place the siren belongs. The head is the containing
   box, so the two fields are measured off it exactly as they are measured off
   the hero, and the overflow keeps them inside. `.wash` is a SIBLING element
   and never a background on the head itself -- L3 reports a text node whose
   ANCESTOR carries a background-image as UNMEASURED, and an unmeasured
   contrast row is a failed one. */
.press-head {
  position: relative;
  overflow: hidden;
  padding: 34px 0 30px;
  border-bottom: 1px solid var(--rule);
  background: var(--panel-deep);
}
.press-head .wrap { position: relative; display: flex; flex-direction: column; align-items: flex-start; }
.press-head .eyebrow { margin: 0 0 10px; }
.press-head .lede { margin: 14px 0 0; max-width: 62ch; }
.press-head .cta-wrap { justify-content: flex-start; margin: 22px 0 0; }
.press-head .footer-links { margin: 18px 0 0; }

.page-title {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 87.5%;
  text-transform: uppercase;
  letter-spacing: 0.045em;
  margin: 0;
  font-size: clamp(34px, 7vw, 66px);
  line-height: 0.96;
}


/* The dev log narrows `.wrap` to a reading measure; the bar is not reading
   matter and keeps the site's width, or the header is a different width on
   one page out of four -- measured at 1440, its bar ran 404 to 1035 while
   every other page's ran 180 to 1260. */
.topbar .wrap { max-width: var(--wrap); }

/* The head goes with the bar for the same reason. The dev log's narrow
   measure is for READING the entries; its title and lede are not entries,
   and indenting them to 404 while every other page's head began at 180 was
   the same header wearing two different left edges. */
.devlog .press-head .wrap { max-width: var(--wrap); }


/* --- the bands at every width that uses the WIDE capture ---------------- */

/* THE SIDE COLUMN STARTS AT 640, NOT 900, AND L3 IS WHY.
   The portrait band file is only chosen under 700 px in PORTRAIT. Everything
   else -- 667x375 and 844x390 sideways, 700x400, 768 -- draws the 1440x440
   capture, which at those widths is barely two hundred pixels tall. The copy
   block is three elements deep, so bottom-anchoring it there pushed the step
   index up into the bright part of the picture: measured 1.67:1 at 768 and
   1.83:1 at 667x375 against a 4.5 line, once W21 let the row see text on a
   photograph at all.
   A short wide band wants the copy in a side column with the scrim turned to
   match, which is what the design already does at desktop. It now does it
   wherever the wide capture is used. */
@media (min-width: 640px) {
  .band-copy { justify-content: center; padding-bottom: 0; }
  .band-scrim {
    background: linear-gradient(90deg, rgba(4, 6, 12, 0.97) 0%, rgba(4, 6, 12, 0.95) 48%, rgba(4, 6, 12, 0.8) 62%, rgba(4, 6, 12, 0.05) 88%);
  }
  .feature:nth-child(even) .band-copy { text-align: right; }
  .feature:nth-child(even) .band-copy .step-text { margin-left: auto; }
  .feature:nth-child(even) .band-scrim {
    background: linear-gradient(270deg, rgba(4, 6, 12, 0.97) 0%, rgba(4, 6, 12, 0.95) 48%, rgba(4, 6, 12, 0.8) 62%, rgba(4, 6, 12, 0.05) 88%);
  }
  .band-copy .wrap { width: 100%; }
  .band-copy .step-text { max-width: min(46ch, 46%); }
}


/* --- the ticker on a phone (R2b) ---------------------------------------- */

/* ONE LINE, AND IT HAS TO GENUINELY FIT -- IT CANNOT BE CLIPPED.
   The band wrapped into three or four stacked lines at phone widths, which is
   not a ticker. The brief offers a clamp with hidden overflow; that is not
   available here, and the reason is the same rule that stopped the marquee:
   L1 forces `overflow: visible` before it measures AND it reads
   `scrollWidth - clientWidth`, so a line clipped to its box fails the row just
   as loudly as a line that sticks out of it. An ellipsis is a clip. So the
   line must really fit inside 320 px, the narrowest width L1 visits.
   Measured at 12 px with the band's own tracking: the dispatch line is 54
   characters and needs about 380 px; the stop prompt is 32 and fits with room.
   So the phone carries the short line, and the other two stand down rather
   than being cut in half. */
@media (max-width: 699px) {
  .ticker-run { flex-wrap: nowrap; justify-content: center; }
  .ticker-item:not(:nth-child(2)) { display: none; }
  .ticker-item:nth-child(2) .ticker-dot { display: none; }
  /* Measured at 320: 286 px of line in a 280 px box. It has to FIT, not be
     clipped -- L1 reads scrollWidth against clientWidth, so an ellipsis is an
     overflow too. */
  .ticker-line {
    white-space: nowrap;
    font-size: 10px;
    letter-spacing: 0.08em;
    padding: 0 8px;
  }
}
