
  :root {
    --bg: #0b0f14;
    --bg-wash: #17212d;
    --panel: #131a23;
    --panel-deep: #121a24;
    --panel-sunken: #121a23;
    --panel-trough: #18212c;
    --canvas: #0d1420;
    --line: #1e2836;
    --line-soft: #212c3a;
    --text: #e6edf3;
    --muted: #7d8b9a;
    --muted-dim: #7f93a8;
    --muted-log: #93a6b8;
    --accent: #46e2a0;
    --accent-ink: #06251a;
    --accent-wash: rgba(70, 226, 160, 0.05);
    --danger: #ff5d6c;
    --danger-deep: #d7263d;
    --warn: #ffd166;
    --warn-mid: #ffb347;
    --warn-deep: #ff8c42;
    --green: #7bd88f;
    --green-deep: #4fb477;
    --info: #7fd1ff;
    --violet: #c9a7ff;
    --amber: #ff9f1c;
    --ghost: #22303f;
    --track: #1c2634;
    --cell-a: #171f2a;
    --overlay: rgba(9, 13, 18, 0.86);
    --overlay-soft: rgba(8, 12, 17, 0.72);
    --overlay-light: rgba(9, 13, 18, 0.55);
    --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --font-num: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    --fs-1: 10px;
    --fs-2: 12px;
    --fs-3: 13px;
    --fs-4: 16px;
    --fs-5: 21px;
    --fs-6: 26px;
    --fs-7: 44px;
    --radius-control: 6px;
    --radius-surface: 12px;
    --radius-pill: 999px;
    --space-1: 1px;
    --space-2: 2px;
    --space-4: 4px;
    --space-6: 6px;
    --space-8: 8px;
    --space-10: 10px;
    --space-12: 12px;
    --space-14: 14px;
    --space-16: 16px;
    --space-18: 18px;
    --space-20: 20px;
    --space-22: 22px;
    --space-24: 24px;
    --space-26: 26px;
    --space-36: 36px;
    --space-40: 40px;
    --elev-stage: 0 0 0 1px var(--ghost), 0 26px 60px -22px rgba(0, 0, 0, 0.92);
    --ring-line: 0 0 0 var(--space-1) var(--line);
    --hover-bright: 1.12;
    --frame-w: 728px;
    --stage-fold-chrome: 384px;
  }
  * { box-sizing: border-box; }
  /* ---- THE ONE [hidden] GUARD (m17, VAL-DESIGN-014) ---------------------
     An author rule that sets display BEATS the user-agent [hidden] rule, so
     the hidden attribute silently fails to hide anything an author rule gives
     a display to. That is the trap this project has now hit twice: #jev-row in
     m5 (.group is display:flex) and #compare-panes here (.compare-panes is
     display:grid). This ONE rule, in the shared layer every page is served,
     closes the whole class of defect at once: !important so no author display
     rule can defeat it, and element-agnostic so it needs no per-page antidote.
     Elements a page REVEALS and HIDES are still toggled by a CLASS (below),
     never this attribute, so a rule that sets display on such an element
     cannot pair with the attribute at all — the guard is the net, the class is
     the mechanism. */
  [hidden] { display: none !important; }
  html { color-scheme: dark; }
  body {
    margin: 0; padding: 0 var(--space-18) var(--space-40);
    background: radial-gradient(circle at 50% -10%, var(--bg-wash) 0%, var(--bg) 60%);
    color: var(--text);
    font-family: var(--font-ui);
    /* THE BODY STEP (m24, VAL-DESIGN-032). The page's own text was the user
       agent's 16px because `body` declared no size at all, so the most common
       size on the site was the one size the scale did not name. --fs-4 IS the
       body step, and every element that declares no size of its own now
       inherits a declared one. */
    font-size: var(--fs-4);
    -webkit-font-smoothing: antialiased;
  }
  .wrap { width: 100%; max-width: 980px; margin: 0 auto; }
  .site-nav {
    display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-14); align-items: center;
    padding: var(--space-10) 0; border-bottom: var(--space-1) solid var(--line);
    background: linear-gradient(90deg, transparent, var(--accent), transparent) bottom / 100% var(--space-1) no-repeat;
  }
  .site-nav .brand {
    font-size: var(--fs-3); letter-spacing: 0.22em; text-transform: uppercase;
    color: var(--text); text-decoration: none; font-weight: 700; margin-right: var(--space-8);
    line-height: 0;
  }
  /* The neon logo is drawn on black, so the screen blend drops the black and
     leaves only the light on every page's ground. */
  .brand-logo { display: block; height: 2.25rem; width: auto; mix-blend-mode: screen;
    mask-image: radial-gradient(ellipse closest-side, var(--bg) 70%, transparent 100%); }
  .site-nav a { color: var(--muted); text-decoration: none; font-size: var(--fs-3); }
  .site-nav a:hover, .site-nav a:focus-visible { color: var(--text); }
  .site-nav a[aria-current="page"] { color: var(--accent); }
  /* ---- THE ONE SECTION RHYTHM (m24, VAL-DESIGN-040) ---------------------
     A heading's bottom margin is ONE declared value, on every page: the h1
     carried 4px on the shell pages, 10px inside the hero and 0 on the play
     page, so the same heading level met its content three ways. The top margin
     is the same step on both levels, so a section starts the same distance
     below whatever precedes it. */
  h1 { font-size: var(--fs-7); margin: var(--space-26) 0 var(--space-10);
    text-shadow: 0 0 0.5em color-mix(in srgb, var(--accent) 45%, transparent); }
  /* The second heading level gets a declared size. It had NO rule before, so
     the leaderboard's "Score versus generation" h2 fell back to the browser's
     own heading size and rendered near the h1's. --fs-6 is the step between
     the h1 (--fs-7) and a card title (--fs-5). */
  h2 { font-size: var(--fs-6); margin: var(--space-26) 0 var(--space-8); }
  /* THE THIRD HEADING LEVEL IS DECLARED, AND IT IS SMALLER THAN THE SECOND
     (m24, VAL-DESIGN-054). The leaderboard's board headings were h2s beside
     the page's own section heading, so the second level carried no hierarchy
     and the page read as one flat rank of headings. They are h3s under the
     boards region's h2 now, and this rule gives that level a size from the
     same scale (--fs-5 is the step between the h2's --fs-6 and the body's
     --fs-4), so a heading's rendered size is strictly greater than the size of
     the deepest level below it and no two adjacent levels share a size. A bare
     hN rule is required for every level a page renders (VAL-DESIGN-022). */
  h3 { font-size: var(--fs-5); margin: var(--space-26) 0 var(--space-8); }
  /* ---- PROSE: ONE SIZE, ONE CONTRAST ROLE (m24, VAL-DESIGN-048) ---------
     The pages' explanatory text was `--muted`, so the prose that explains a
     table was DIMMER than the table it explains, and `.lede` was 17px inside
     the hero and 16px everywhere else (it declared no size, so the hero's
     ancestor rule decided). Prose is content, not chrome: it reads at the body
     step in the text role, and a note reads one step down. */
  .lede { color: var(--text); font-size: var(--fs-4); margin: 0 0 var(--space-24); max-width: 60ch; }
  /* ---- the home hero (m17, VAL-DESIGN-007) ------------------------------
     The catalogue opens with a composed hero rather than an h1 dropped on to
     the grid: the page's h1 at the LARGEST step of the type scale (--fs-7), a
     lede, and one line of context. It is the first content block of <main> and
     precedes the grid in DOM order. Every value resolves from the token layer. */
  .hero { margin: 0 0 var(--space-26); padding: 0 0 var(--space-24);
    border-bottom: var(--space-1) solid var(--line); }
  /* The hero and the play page's header sit directly under the nav, so their
     h1's TOP margin is zero; the bottom margin is the shared h1 margin above,
     which is what makes it one value on every page. */
  .hero h1 { margin-top: 0; }
  .hero .lede { max-width: 64ch; margin: 0; }
  /* The home hero sits on the arcade hall art (the page sets --hero-art),
     shaded from the left so the headline stays readable over it. */
  .hero-sign { display: block; width: min(100%, 26rem); height: auto; margin: 0 0 var(--space-14); mix-blend-mode: screen;
    mask-image: radial-gradient(ellipse closest-side, var(--bg) 62%, transparent 100%); }
  .hero[data-art] { margin-top: var(--space-16); padding: var(--space-26);
    border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface);
    background: linear-gradient(90deg, var(--bg) 0%, var(--overlay) 38%, var(--overlay-light) 100%), var(--hero-art) center / cover; }
  /* The page title block is the slimmer sibling of the hero: the same art strip,
     a section label over the title, and less height. */
  .page-head { margin: var(--space-16) 0 var(--space-14); padding: var(--space-14) var(--space-18) var(--space-6);
    border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface);
    background: linear-gradient(90deg, var(--bg) 0%, var(--overlay) 45%, var(--overlay-light) 100%), var(--hero-art) center / cover; }
  .page-head h1, .page-head .profile-head { margin-top: 0; }
  .eyebrow { margin: 0 0 var(--space-4); color: var(--accent); font-size: var(--fs-1); font-weight: 700;
    letter-spacing: 0.22em; text-transform: uppercase;
    text-shadow: 0 0 0.8em color-mix(in srgb, var(--accent) 70%, transparent); }
  .hero-meta { color: var(--muted); font-size: var(--fs-2); letter-spacing: 0.04em;
    margin: var(--space-12) 0 0; }
  /* A sponsor's logo sits on the line's text, at the height of its capitals. */
  .sponsor-logo { display: inline-block; height: 1.15em; width: auto; vertical-align: -0.2em; margin-left: var(--space-2); }
  .games { list-style: none; margin: 0; padding: 0; display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); gap: var(--space-16); }
  /* A card is a lit poster over its text: the game's art across the top, the
     title and the actions below. The border and glow warm to the accent when the
     card is reached, so the picker answers the pointer and the keyboard alike. */
  .game-card {
    border: var(--space-1) solid var(--line); border-radius: var(--radius-surface); background: var(--panel);
    display: flex; flex-direction: column; overflow: hidden;
    box-shadow: 0 0 0 var(--space-1) var(--accent-wash);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
  }
  .game-card:hover, .game-card:focus-within {
    border-color: var(--accent); box-shadow: 0 0 var(--space-24) var(--accent-wash), 0 0 0 var(--space-1) var(--accent);
  }
  /* The text column may shrink below its content's intrinsic width, so a long
     rationale wraps inside the card instead of widening it (VAL-DESIGN-008). */
  .card-body { min-width: 0; padding: var(--space-14); }
  /* The poster is a link to the game's Watch page; the picture element that
     carries its WebP source lays out as the image's own block. */
  .card-poster, .game-card picture, .gamecard picture { display: block; }
  .game-card img { width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover; display: block;
    background: var(--canvas); border-bottom: var(--space-1) solid var(--line); }
  .game-card h2 { margin: 0 0 var(--space-6); font-size: var(--fs-5); }
  .game-card h2 a { color: var(--text); text-decoration: none; }
  .game-card h2 a:hover, .game-card h2 a:focus-visible { color: var(--accent); }
  .kv { margin: 0 0 var(--space-4); font-size: var(--fs-3); line-height: 1.45; }
  .kv .k { color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em;
    font-size: var(--fs-1); display: block; }
  /* The card's longer text waits behind a closed disclosure. */
  .card-more { margin: var(--space-8) 0 0; }
  .card-more summary { cursor: pointer; color: var(--muted); font-size: var(--fs-2); }
  .card-more .kv { margin-top: var(--space-6); }
  /* The card's action group. A hairline above it separates "what this game
     is" from "what you can do with it" (VAL-DESIGN-008); the LOOK of an
     action is the role's (below), never the group's, so the same role
     resolves identically wherever it is used. */
  .card-links { display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-8) var(--space-10); margin-top: var(--space-12);
    padding-top: var(--space-12); border-top: var(--space-1) solid var(--line); }
  .page-links { display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-8) var(--space-10); margin: var(--space-14) 0; }
  /* The current page is marked by a ring, not by restyling the action. A role's
     computed values are a token (VAL-DESIGN-006), so the marker has to use a
     property the role comparison does not read, or the current page's link
     would be the one element of its role that resolves differently. The ring is
     the same width and colour as the one focus treatment below, so "the site's
     ring" is one value pair wherever it is drawn.

     THE RING COVERS BOTH ACTION ROWS (m24, VAL-DESIGN-037). The site-chrome
     page-links row marks the play/watch/leaderboard routes; the compare
     route is deliberately not in the shared role map (VAL-CATALOG-029), so its
     page marks its own route in the page-level page-actions row instead — and
     that marker needs the same ring, or the compare page's current action
     would carry no visible marker at all. */
  .page-links a[aria-current="page"],
  .page-actions a[aria-current="page"] { outline: var(--space-2) solid var(--accent); outline-offset: var(--space-2); }
  /* A data table carries its OWN solid surface, so a cell's effective
     background resolves to a measurable colour instead of the page's gradient
     (m17, VAL-DESIGN-020). Every cell then pairs a solid foreground with a
     solid background, which is what makes the Recorded column's contrast
     computable rather than a rule axe has to skip. It also carries the surface
     radius (m24, VAL-DESIGN-033): a table has its own background, so a square
     corner made it the one square-cornered panel in a rounded page. */
  table { border-collapse: collapse; width: 100%; margin: var(--space-10) 0 var(--space-6); font-size: var(--fs-3); background: var(--panel); border-radius: var(--radius-surface); }
  caption { text-align: left; color: var(--muted); padding: var(--space-6) 0; font-size: var(--fs-2); }
  th, td { border-bottom: var(--space-1) solid var(--line); padding: var(--space-8) var(--space-10); text-align: left; }
  th[scope="col"] { color: var(--muted); font-size: var(--fs-1); letter-spacing: 0.1em;
    text-transform: uppercase; font-weight: 600; }
  tbody tr.baseline { background: var(--accent-wash); }
  tbody tr.baseline td, tbody tr.baseline th { border-left: var(--space-4) solid var(--accent); }
  /* A wide data table scrolls inside its OWN container rather than widening the
     document (m17, VAL-DESIGN-010). The leaderboard's ten-column table is
     849-1263px of intrinsic content; at a 390px viewport it used to make the
     whole document 1281px wide, so every page above it scrolled sideways. The
     container caps at its parent's width and carries the horizontal scroll, so
     the document stays at the viewport width while the table is still reachable
     by scrolling — never clipped and never scaled. The region role plus the
     aria-label (added in site.mjs) gives it an accessible name, and the one
     focus treatment below makes the keyboard-reachable scroller visible. */
  .table-scroll { max-width: 100%; overflow-x: auto; }
  .badge {
    display: inline-block; font-size: var(--fs-1); letter-spacing: 0.08em;
    text-transform: uppercase; padding: var(--space-2) var(--space-8); border-radius: var(--radius-pill);
    border: var(--space-1) solid var(--accent); color: var(--accent); font-weight: 700;
  }
  .badge-note { color: var(--muted); font-size: var(--fs-2); }
  /* `.note` is the one small-print prose class: one size, and the same contrast
     role as `.lede` (VAL-DESIGN-048). */
  .note { color: var(--text); font-size: var(--fs-2); margin: var(--space-6) 0 0; max-width: 70ch; }
  .live-dot { display: inline-block; width: 8px; height: 8px; border-radius: var(--radius-pill);
    background: var(--danger); margin-right: var(--space-6); vertical-align: baseline; }
  /* ---- the served frame's declared display size (m21, decision R1) -------
     The watch page's <img> carries the frame's declared CSS surface as its own
     numeric width/height attributes (site.mjs), and this rule declares the same
     width from the one token derived from surface.mjs's FRAME_W. Together they
     stop the browser laying the element out at the JPEG raster's INTRINSIC
     1456x1460: the frame renders at its declared 728x730 wherever the container
     allows, and max-width 100% shrinks it below that with height auto keeping
     the aspect ratio, so the declared 22px/15px type floors hold at every
     container at least as wide as the frame instead of at the one accidental
     viewport width (~764px) where the container happened to equal it. No @media
     is involved: a narrow-viewport frame variant is NOT in scope, and the play
     page (not a shrunken frame) is the mobile surface (R1). */
  .stream-frame { display: block; width: var(--frame-w); max-width: 100%; height: auto; }
  /* ---- THE BOARD AND THE BAND, DECOUPLED (m24, decision P2) -------------
     Where the board is one element among others — the watch surface's <img> and
     the compare page's two pane images — the ad is no longer the foot of the
     board's own scaled box. The board element is CROPPED to the play zone plus
     the broadcast band (`--frame-w` wide, `FRAME_W : FRAME_H - STRIP_H` tall,
     the two numbers DERIVED from surface.mjs), and the page owns a band
     element of its own, filled by the same `/api/ad/image` creative the single
     `drawAdStrip` renders server-side.

     Why a crop rather than leaving the raster's band visible: the served raster
     still carries its band (the off-site broadcast consumes the same bytes, and
     the single JPEG encode site and the single `drawAdStrip` are pinned), but a
     page that showed both would show the ad TWICE — once scaled to the board's
     display scale and once at the declared unit. The crop is page-side only;
     the served pixels are unchanged.

     The band itself is an <img> at the declared IAB unit: `width`/`height`
     carry 728x90 (from IAB_UNITS, server-side), and `width: var(--frame-w)` with
     `max-width: 100%` keeps it at exactly the declared unit wherever the
     container is at least that wide, shrinking only below it — the same
     declaration rule the frame follows (R1).

     Both elements carry the hairline the frame used to carry, through the
     DECLARED hairline token (`var(--space-1)`) rather than a fresh 1px: the
     border-1px ceiling sits at 9 (m24 lowered it from 10 when this hairline
     replaced the frame's own border), and a new literal would raise it. The hairline
     is drawn as a SHADOW, not a border — see `ring-line` in the EFFECT table:
     a border would shrink the band's rendered box below the exact IAB unit. */
  .stream-board {
    width: var(--frame-w); max-width: 100%;
    aspect-ratio: 728 / 640;
    overflow: hidden; line-height: 0;
    box-shadow: var(--ring-line);
    border-radius: var(--radius-surface); background: var(--canvas); }
  .ad-band {
    display: block; width: var(--frame-w); max-width: 100%; height: auto;
    box-shadow: var(--ring-line);
    /* The slot is a SURFACE, so it carries the surface radius like every other
       panel (`VAL-DESIGN-033` pins the site's radius set to exactly three
       values and measures it from the rendered elements: a slot with no radius
       would be a fourth, square-cornered surface). The consequence for
       `VAL-AD-016`'s occlusion sampling is documented in
       `research/m24-p2-ad-boundary-change.md`: a sample inside the rounded
       corner needs a few pixels of inset, because the corner itself belongs to
       the box's parent — that is the box's radius, not an occluder. */
    border-radius: var(--radius-surface); background: var(--canvas); }
  .after { display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-8) var(--space-10); margin-top: var(--space-14); }
  /* ---- THE PAGE-LEVEL ACTION ROW (m24, VAL-DESIGN-034/035) --------------
     The page's OWN actions, rendered directly under the page's h1 (site.mjs's
     pageActions) and repeated at the foot as .after. One
     component, two positions: the hoisted row and its foot repeat take the
     same row geometry, so they cannot look like two different things. Only the
     bottom margin differs, because the row sits between the h1 (which carries
     its own declared bottom margin) and the first content block. */
  .page-actions { display: flex; flex-wrap: wrap; align-items: center;
  .hero-meta + .page-actions { margin-top: var(--space-12); }
    gap: var(--space-8) var(--space-10); margin: 0 0 var(--space-14); }
  /* The control region: THE place a page's controls live, so a control's
     membership is a declared class rather than a per-page invention
     (VAL-DESIGN-025). Every control is styled by the one shared
     input/select/button rule. The pace control and the quality control are
     control regions too — they are declared here in the grouped selector rather
     than by adding a class to their markup, so the forms keep the one class
     their own pins read; `.board-controls` and `.compare-controls` carry the
     class in their markup and are named here so the row geometry is declared
     once for every control region the site renders. */
  .control-region, .pace-control, .quality-control, .board-controls, .compare-controls {
    display: flex; flex-wrap: wrap; gap: var(--space-8) var(--space-14);
    align-items: center; margin: var(--space-14) 0; }
  /* ---- THE ONE CONTROL-REGION LABEL TREATMENT (m24, VAL-DESIGN-038) -----
     A label inside a control region read the same on every page or it read as a
     different control: this treatment was copied per feature four times, and
     the leaderboard's `Variant` label — whose control region is
     `.board-controls` — was never given a copy at all, so the same control with
     the same label text measured 16px, full contrast and no casing on one page
     and 12px, muted and uppercase on another. ONE rule, and the pace and
     quality controls are named beside the class because their markup cannot
     carry it (see above). */
  .control-region label, .pace-control label, .variant-control label, .quality-control label,
  .group > label {
    color: var(--muted); font-size: var(--fs-2);
    text-transform: uppercase; letter-spacing: 0.08em;
  }
  /* ---- THE ONE FOCUS TREATMENT (m24, VAL-DESIGN-039) --------------------
     One width, one colour, one offset, on every focusable element the site
     renders. Before this there were three: the shared 2px accent ring, a 2px
     amber ring on the Jev key field, and a 3px amber ring on every play-page
     button (which out-specified the shared ring), so tabbing one page
     alternated green, amber and amber-thicker rings. `[tabindex]` covers the
     two keyboard-reachable scroll regions (`.table-scroll` and `#log-list`),
     which carry `tabindex="0"` in their markup. No rule removes an outline
     anywhere: focus is never invisible. */
  a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
  summary:focus-visible, [tabindex]:focus-visible {
    outline: var(--space-2) solid var(--accent); outline-offset: var(--space-2); }
  /* One control style, consumed by every page (VAL-DESIGN-005). */
  input, select, button {
    font-family: var(--font-ui); font-size: var(--fs-3);
    background: var(--panel); border: 1px solid var(--line); color: var(--text);
    border-radius: var(--radius-control); padding: var(--space-6) var(--space-8);
  }
  /* A button that is NOT one of the declared action roles takes the filled
     accent look; a role'd button's look belongs to its role (below), so the two
     never compete for the same element. The selector is scoped to the element
     rather than repeated as a bare `button`, so every selector in this sheet is
     declared exactly once. */
  button:not([data-role]) { background: var(--accent); border-color: transparent; color: var(--accent-ink);
    font-weight: 700; border-radius: var(--radius-pill); padding: var(--space-8) var(--space-16);
    cursor: pointer; }
  /* ---- the action hierarchy (m17, VAL-DESIGN-006) ------------------------
     Every site action carries ONE of three declared roles in `data-role`, and
     its look is a function of that role alone — so "Play" is a filled button,
     "Watch" and "Leaderboard" are outlined buttons, and "Compare" is a plain
     text link, instead of four identical 13px accent links.

     WHY A CLASS AND NOT THE NAV'S STYLES: the contract measures the computed
     values of each role. Two elements with the same role must resolve
     IDENTICALLY on every page, so nothing else may set one of the compared
     properties (background, colour, border, padding, radius, weight, size,
     decoration) on a role'd element — the shared `.card-links a`,
     `.page-links a` and `.after a` colour rules are gone for exactly that
     reason. The roles differ in background, colour, border, padding, radius
     and weight. */
  .cta { font-size: var(--fs-3); text-decoration: none; white-space: nowrap; }
  a.cta, button.cta { display: inline-flex; align-items: center; gap: var(--space-6); }
  a.cta-primary, button.cta-primary {
    background: var(--accent); color: var(--accent-ink);
    border: var(--space-1) solid transparent; border-radius: var(--radius-pill);
    padding: var(--space-8) var(--space-16); font-weight: 700;
  }
  a.cta-secondary, button.cta-secondary {
    background: transparent; color: var(--text);
    border: var(--space-1) solid var(--line); border-radius: var(--radius-pill);
    padding: var(--space-8) var(--space-16); font-weight: 600;
  }
  a.cta-tertiary, button.cta-tertiary {
    background: transparent; color: var(--muted);
    border: 0; padding: 0; font-weight: 400;
  }
  /* ONE hover brightness for every control the site declares (VAL-DESIGN-039):
     `--hover-bright`, so an action and the play page's button cannot brighten
     by two different amounts. */
  a.cta:hover, button.cta:hover { filter: brightness(var(--hover-bright)); }
  a.cta-tertiary:hover, button.cta-tertiary:hover { color: var(--text); }
  .compare-panes { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: var(--space-14); margin: var(--space-14) 0; }
  /* ---- the small window ---------------------------------------------------
     A replay that keeps playing while the viewer browses. The box is fixed to a
     corner of the viewport and holds the replay card; the page moves it with
     --dock-x and --dock-y when the box is dragged or moved with the arrow keys.
     It sits before the reduced-motion block so that block can switch its slide
     off, and before .is-hidden so the hidden state wins. */
  .mini-dock { position: fixed; right: var(--space-16); bottom: calc(var(--space-16) + env(safe-area-inset-bottom));
    z-index: 20; display: flex; flex-direction: column; gap: var(--space-6);
    width: min(16rem, calc(100vw - 2 * var(--space-16))); padding: var(--space-8);
    background: var(--panel-deep); border: var(--space-1) solid var(--line); border-radius: var(--radius-surface);
    box-shadow: var(--elev-stage); transform: translate(var(--dock-x, 0), var(--dock-y, 0)); }
  .mini-dock[data-state="docked"] { animation: wb-dock-in .25s ease-out; }
  .mini-dock[data-state="window"] { position: static; width: auto; padding: 0; border: 0; background: transparent;
    box-shadow: none; transform: none; }
  @keyframes wb-dock-in { from { opacity: 0; translate: 0 var(--space-16); } to { opacity: 1; translate: 0 0; } }
  button.dock-grip { width: 100%; min-height: 2.75rem; padding: var(--space-4) var(--space-8); background: transparent;
    border: 0; border-radius: var(--radius-control); color: var(--muted); font-size: var(--fs-2); font-weight: 600;
    text-align: left; cursor: grab; touch-action: none; user-select: none; }
  .mini-dock[data-dragging] button.dock-grip { cursor: grabbing; }
  .mini-dock-slot .replay { padding: 0; border: 0; background: transparent; }
  .mini-dock-slot .replay-top { flex-wrap: wrap; column-gap: var(--space-8); row-gap: 0; }
  .mini-dock-slot .replay-hud { flex-wrap: wrap; column-gap: var(--space-8); }
  .mini-dock-slot .mascot, .mini-dock-slot .reason, .mini-dock-slot .hint, .mini-dock-slot [data-popout] { display: none; }
  .mini-dock-actions { display: flex; flex-wrap: wrap; gap: var(--space-6); }
  .mini-dock-actions .dock-btn { flex: 1 1 0; }
  button.dock-btn, a.dock-btn { display: inline-flex; align-items: center; justify-content: center;
    min-height: 2.75rem; min-width: 2.75rem; padding: var(--space-8) var(--space-12); background: transparent;
    color: var(--text); border: var(--space-1) solid var(--line); border-radius: var(--radius-pill);
    font-size: var(--fs-3); font-weight: 600; text-decoration: none; cursor: pointer; }
  button.dock-btn:hover, a.dock-btn:hover { filter: brightness(var(--hover-bright)); }
  .replay-hud .dock-btn { margin-left: auto; }
  .dock-btn.is-hidden { display: none; }
  html[data-navigating] { cursor: progress; }
  /* ---- music --------------------------------------------------------------
     The Music button and its settings sit at the end of the nav. The button
     says Muted or Playing in words; the three bars beside the word move only
     while music plays, and stand still under reduced motion. The settings
     panel drops below the nav, over the page, and is as wide as the screen
     allows on a phone. Every target is at least 2.75rem (44 px) tall. */
  .music { position: relative; margin-left: auto; display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-6); }
  /* The 44 px targets reach into the nav's own padding, so the nav keeps the
     height of its one row of links. */
  .site-nav .music { margin-block: calc(-1 * var(--space-6)); }
  button.music-toggle { display: inline-flex; align-items: center; gap: var(--space-6); min-height: 2.75rem;
    padding: var(--space-6) var(--space-12); background: transparent; color: var(--text);
    border: var(--space-1) solid var(--line); border-radius: var(--radius-pill); font-size: var(--fs-3); font-weight: 600; }
  button.music-toggle[aria-pressed="true"] { border-color: var(--accent); }
  button.music-toggle:hover, summary.music-summary:hover { filter: brightness(var(--hover-bright)); }
  .music-state { color: var(--muted); font-weight: 400; }
  .music[data-playing] .music-state { color: var(--accent); }
  .music-bars { display: inline-flex; align-items: flex-end; gap: var(--space-2); height: var(--space-12); }
  .music-bars span { width: var(--space-2); height: 35%; background: var(--muted); border-radius: var(--radius-pill); }
  .music[data-playing] .music-bars span { background: var(--accent); animation: wb-music-bar 1.1s ease-in-out infinite alternate; }
  .music[data-playing] .music-bars span:nth-child(2) { animation-delay: -0.4s; }
  .music[data-playing] .music-bars span:nth-child(3) { animation-delay: -0.8s; }
  @keyframes wb-music-bar { from { height: 35%; } to { height: 100%; } }
  .music-more { position: static; }
  summary.music-summary { display: inline-flex; align-items: center; min-height: 2.75rem; padding: var(--space-6) var(--space-12);
    border: var(--space-1) solid var(--line); border-radius: var(--radius-pill); color: var(--muted); font-size: var(--fs-3);
    cursor: pointer; list-style: none; }
  summary.music-summary::-webkit-details-marker { display: none; }
  .music-more[open] summary.music-summary { color: var(--text); border-color: var(--accent); }
  .music-panel { position: absolute; right: 0; top: calc(100% + var(--space-6)); z-index: 30;
    width: min(20rem, calc(100vw - 2 * var(--space-16))); display: grid; gap: var(--space-10); padding: var(--space-14);
    background: var(--panel-deep); border: var(--space-1) solid var(--line); border-radius: var(--radius-surface);
    box-shadow: var(--elev-stage); }
  .music-volume { display: flex; align-items: center; gap: var(--space-10); color: var(--muted); font-size: var(--fs-2); }
  .music-volume input { flex: 1; min-width: 0; min-height: 2.75rem; padding: 0; background: transparent; border: 0;
    accent-color: var(--accent); }
  .music-source button { flex: 1 1 0; min-height: 2.75rem; }
  .music-now { margin: 0; color: var(--text); font-size: var(--fs-3); }
  .music-now-label { display: block; color: var(--muted); font-size: var(--fs-1); text-transform: uppercase; letter-spacing: 0.08em; }
  .music-panel .note { margin: 0; }

  /* ---- THE DECLARED REVEAL STATE (m17) ----------------------------------
     A region a page reveals and hides is toggled by THIS class, never the
     hidden attribute. The two elements below carry an author display rule
     (.compare-panes is display:grid, the export button is .cta ->
     inline-flex), so the attribute would lose to them and the hidden region
     would render — the compare page's broken-image first paint. The state is
     one declared name, so no page invents its own. */
  .is-hidden { display: none; }
  .compare-panes.is-hidden { display: none; }
  #compare-export.is-hidden { display: none; }
  .compare-pane { margin: 0; border: 1px solid var(--line); border-radius: var(--radius-surface);
    background: var(--panel); padding: var(--space-8); }
  .compare-pane img { width: 100%; display: block; border-radius: var(--radius-control);
    background: var(--canvas); min-height: 120px; }
  .compare-pane figcaption { color: var(--text); font-size: var(--fs-3); padding: var(--space-8) var(--space-4) var(--space-2);
    font-weight: 600; }
  /* The variant (m19), pace (m17) and quality (m20) controls are siblings: they
     take their row geometry from the one control-region rule above, so their
     spacing is the site's one section rhythm rather than a per-control margin. */
  .pace-status, .quality-status { color: var(--muted); font-size: var(--fs-2); }
  footer { margin-top: var(--space-36); color: var(--muted); font-size: var(--fs-2);
    border-top: var(--space-1) solid var(--line); padding-top: var(--space-12); }
  /* ---- the compare page's own presentation (m24) ------------------------
     The head-to-head surface is the newest page on the site and its regions
     rendered from the user agent: the two live sections had no box of their own,
     the variant picker had no size of its own in a row of controls, and the page
     title was the only heading on the site carrying a class with no rule.
     `h1.head-to-head` wraps rather than widening the document, and takes its
     size and colour from the shared `h1` and `body` rules. */
  h1.head-to-head { overflow-wrap: anywhere; }
  /* The pre-run state is prose, so it carries the panel surface the leaderboard's
     explanation block carries. The scoreboard section is left without a box of
     its own ON PURPOSE: the table inside it already paints that surface, and a
     second panel around it would be a box in a box. Both take the site's one
     section rhythm. */
  .compare-state {
    border: var(--space-1) solid var(--line); border-radius: var(--radius-surface);
    background: var(--panel); padding: var(--space-14); margin: var(--space-14) 0 0;
  }
  .compare-scoreboard { margin: var(--space-14) 0 0; }
  select.variant-picker { flex: 1 1 auto; min-width: 0; }
  /* ---- THE LEADERBOARD'S PRESENTATION (m24, VAL-DESIGN-030/031) ---------
     The board is the site's densest surface and it was the least styled: 25 of
     the classes it renders had no rule in the one stylesheet at all, so the
     explanation block, the per-partition sections, the reference line, the
     aggregate table's own marks, the drill-down and the empty states rendered
     from the user agent's defaults while the rest of the site rendered from the
     design system. Every class the page renders is styled here, from tokens.

     `section.rsi` is scoped to the element on purpose. The play page carries a
     DIFFERENT `.rsi` idea — `#rsi-panel`, its telemetry readout — and an
     unscoped `.rsi` rule would leak this page's section treatment onto it; the
     name is shared, the region is not (VAL-DESIGN-030's name-collision clause).
     The class name itself stays, because the leaderboard's own pins read
     `section.rsi` to exclude the policy table from the ranked-board
     population. */
  /* THE EXPLANATION IS A DISCLOSURE (m24, VAL-DESIGN-042). The block shipped as
     an always-open 16px panel — the largest prose block on the page, taller
     than the first data table, so a reader met a wall of explanation before a
     single number. It is now a closed <details> element: the same text stays in
     the DOM (VAL-BOARD-012's ranking-metric requirement still reads) but the
     reader opens it on request and the first table reaches the fold. */
  .how-to-read {
    border: 1px solid var(--line); border-radius: var(--radius-surface);
    background: var(--panel); padding: var(--space-10) var(--space-14); margin: var(--space-14) 0;
  }
  .how-to-read summary { cursor: pointer; color: var(--text); font-size: var(--fs-3); font-weight: 600; }
  .how-to-read ul { margin: var(--space-8) 0 0; padding-left: var(--space-18); }
  .how-to-read li { color: var(--text); font-size: var(--fs-2); margin: var(--space-4) 0; }
  .partition, section.rsi { margin: var(--space-26) 0 0; }
  .partition h2, section.rsi h2, .partition-empty h2 { margin-top: 0; }
  .partition-empty {
    border: 1px dashed var(--line-soft); border-radius: var(--radius-surface);
    background: var(--panel-deep); padding: var(--space-14); margin: var(--space-26) 0 0;
  }
  .partition-empty p { color: var(--text); }
  /* The per-partition reference is a MARKER, not a paragraph (m24,
     VAL-DESIGN-043). It shipped as a 16px full-contrast paragraph repeated once
     per partition — four on snake, each 72px tall — so a parenthetical about the
     baseline was the most prominent text in every partition, larger and brighter
     than the `.lede` and the `.note` that explain it. It is now an inline badge
     at the micro-label step (`--fs-1`), carrying the reference number and its
     meaning; the page-level baseline explanation below the data states the scale
     once. The element and its `data-kind` attribute stay, so VAL-BOARD-006/007/016
     still read it. */
  p.reference {
    display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-4) var(--space-8);
    color: var(--text); font-size: var(--fs-1); margin: var(--space-6) 0 0; max-width: 100%;
    padding: var(--space-2) var(--space-10); border: var(--space-1) solid var(--line-soft);
    border-radius: var(--radius-pill); background: var(--panel-deep);
  }
  /* NUMERIC COLUMNS ARE RIGHT-ALIGNED AND USE TABULAR FIGURES (m24,
     VAL-DESIGN-041). Every numeric cell computed `text-align: left` and only the
     aggregate rows carried tabular figures, so a column of digits could not be
     scanned down: the eye had no right edge to follow and the digits' widths
     varied with the glyph. A right edge plus `tabular-nums` (equal advance widths)
     is what makes a column of numbers line up. Text columns keep the table's own
     left alignment. */
  .num { text-align: right; font-variant-numeric: tabular-nums; }
  /* The aggregate table is the page's primary data: the surface radius (every
     table carries it), tabular figures so its numeric columns line up, and a
     hover tint so one row can be followed across ten columns. */
  table.aggregate { border-radius: var(--radius-surface); }
  tbody.aggregate { font-variant-numeric: tabular-nums; }
  .aggregate-row:hover { background: var(--panel-deep); }
  /* The competitor cell and the ranked score are the row's two anchors; the
     signed gap is a number and is set in the numeric face so its sign cannot
     shift the column. */
  td.competitor { color: var(--text); font-weight: 600; }
  .headline { color: var(--text); font-weight: 600; }
  .gap { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
  /* The drill link is IN TEXT (the competitor cell also carries the kind tag),
     so it must be distinguishable from that text without relying on colour: the
     underline is the treatment, not a colour change (VAL-DESIGN-019's
     link-in-text-block rule). */
  .drill-link { color: var(--text); text-decoration: underline; text-underline-offset: var(--space-2); }
  .drill-link:hover, .drill-link:focus-visible { color: var(--accent); }
  /* Supporting columns recede: the variant, the episode id and the timestamp
     are reference material, not the measurement. */
  .last-played, .recorded { color: var(--muted); white-space: nowrap; }
  .episode-id { font-family: var(--font-num); font-size: var(--fs-2); }
  .episode-link { color: var(--accent); text-decoration: none; }
  .episode-link:hover, .episode-link:focus-visible { text-decoration: underline; }
  /* One drill-down per competitor, reachable from its aggregate row. */
  .episode-detail { margin: var(--space-10) 0 0; }
  .episode-detail summary { color: var(--muted); font-size: var(--fs-2); cursor: pointer; }
  .ranked { color: var(--muted); }
  /* =======================================================================
     THE PLAY PAGE'S OWN RULES (m17).

     These are the game-local rules the play page always had: the board, the
     HUD strip, the log, the panic overlay, the RSI panel, the JEV row. They
     live HERE, in the one stylesheet, rather than in index.html's own style
     block, for two reasons the contract measures:

       - VAL-DESIGN-001: a token declared in the one token block must be
         referenced by a rule. Forty-two of the tokens below are consumed only
         by these rules, so a stylesheet split in two would leave them declared
         and unreferenced on every shell page — a dead token, which is what
         --gold was.
       - VAL-DESIGN-003: the play page's CSS must be the shell's stylesheet or
         a superset of it. It is now literally the same bytes.

     THE ELEMENT SELECTORS ARE SCOPED UNDER body.play. A bare body, main,
     header, h1, button or canvas rule here would restyle the catalogue, the
     watch page, the leaderboard and the compare page, because this stylesheet
     is served to all of them; body.play matches only the play page, whose
     <body> carries class="play". The class and id selectors below (.scores,
     .overlay, #log-list, #rsi-panel, #jev-row, #manifest-report, ...) are NOT
     scoped: no shell page uses any of those names, and the
     html[data-page-mode="manifest"] rule that hides the snake-only chrome is
     already gated on an attribute only this page sets. A new rule here that
     names a class the shell also uses must be scoped.
     ======================================================================= */
  body.play {
    margin: 0;
    min-height: 100%;
    overscroll-behavior: none;
    touch-action: manipulation;
    display: flex;
    justify-content: center;
    padding: var(--space-22) var(--space-18) calc(var(--space-22) + env(safe-area-inset-bottom));
  }

  body.play main {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-12);
    /* The play page uses the site's MEASURE, not a narrow column (m17,
       VAL-DESIGN-011): main fills the shell's 980px wrap, so the nav sits on
       one row and the header, controls and panels use the width a desktop
       actually has. Before this, main was min(88vw, 620px, 100vh - 232px) =
       568px at 1280x800, so ~56% of the viewport was empty and the injected
       nav wrapped into a vertical stack. The BOARD is sized separately below,
       so widening main does not stretch the canvas. */
    width: 100%;
    margin: 0 auto;
  }

  /* THE PAGE HEAD IS A DIV, NOT A LANDMARK (m24, VAL-DESIGN-052). It used to be
     a `<header>`, which put a second `HEADER` inside `MAIN` beside the injected
     site nav — a landmark tree no shell route has. The rule is unchanged; only
     the selector moves from the element to the declared class, so the title
     row keeps its layout without claiming a landmark role.

     `#site-header` shares this row (m24, VAL-DESIGN-052). The injected block
     (the game links, the brief, the pace control, the variant control) used to
     be a `<header>` too, so `body.play header` styled BOTH rows. Now that it
     is a `<div>`, it must name itself here or it falls back to block layout
     and stacks its children — which measured the board 55px lower, off the
     fold the m24 fold budget was calibrated to. The nav is no longer one of its
     children: the nav is the shell's, outside `<main>`. */
  .play-head, #site-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-16);
    width: 100%;
  }

  /* The h1 is the shared h1 --fs-7 (44px): the hero's title and the play
     page's title must resolve the same size (VAL-DESIGN-005), and the home
     hero's h1 must use the largest step of the type scale (VAL-DESIGN-007).
     The tracking is tightened because the shared size grew: 0.34em was chosen
     for a 15px wordmark and is far too loose at 44px, where it is the widest
     thing in the header row. Only the TOP margin is zeroed: the header row sits
     directly under the nav, while the BOTTOM margin is the shared h1 margin, so
     the h1 meets its content the same distance down on every page. */
  body.play h1 {
    margin-top: 0;
  }

  /* The scores sit on a solid plate, not on the title strip's art, so their
     contrast is measurable. */
  .scores { display: flex; gap: var(--space-18); margin-left: auto;
    background: var(--panel); padding: var(--space-6) var(--space-12); border-radius: var(--radius-control); }
  /* The hunger clock. Sits between the title and the scores, and is deliberately
     compact so it reads as a resource gauge rather than a second score. */
  .hunger {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    margin-left: auto;
    min-width: 150px;
  }
  /* The gauge's two labels carry one treatment (10px, uppercase, muted), with
     the count in the numeric face so its digits do not jitter as it falls. */
  .hunger-label {
    font-size: var(--fs-1);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--muted);
  }
  .hunger-count {
    font-family: var(--font-num);
    font-size: var(--fs-1);
    letter-spacing: 0;
    text-transform: uppercase;
    color: var(--muted);
  }
  .hunger-track {
    flex: 1;
    height: 6px;
    min-width: 60px;
    border-radius: var(--radius-pill);
    background: var(--track);
    overflow: hidden;
  }
  .hunger-fill {
    height: 100%;
    width: 100%;
    border-radius: var(--radius-pill);
    background: linear-gradient(90deg, var(--green), var(--green-deep));
    transition: width 0.16s linear, background 0.3s;
  }
  /* Warning states. Colour alone is not enough, so the label changes too. */
  .hunger.low .hunger-fill { background: linear-gradient(90deg, var(--warn-mid), var(--warn-deep)); }
  .hunger.low .hunger-label { color: var(--warn-mid); }
  .hunger.critical .hunger-fill { background: linear-gradient(90deg, var(--danger), var(--danger-deep)); }
  .hunger.critical .hunger-label { color: var(--danger); }
  .hunger.critical { animation: hunger-flash 0.7s steps(2, end) infinite; }
  @keyframes hunger-flash { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }

  .score { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-1); }

  .score span {
    font-size: var(--fs-1);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--muted);
  }

  .score strong {
    font-family: var(--font-num);
    font-size: var(--fs-5);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
  }

  #best.hot { color: var(--accent); }
  /* ---- telemetry panels ---------------------------------------------------- */
  .panel-title {
    font-size: var(--fs-1);
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 0 0 var(--space-8);
    font-weight: 600;
    display: flex;
    align-items: baseline;
    gap: var(--space-10);
  }
  .log-stats {
    margin-left: auto;
    font-family: var(--font-num);
    letter-spacing: 0;
    text-transform: none;
    /* --muted, not a dimmer token (m17, VAL-DESIGN-027): this line is populated
       only DURING a run, so axe sees an empty element at first paint and cannot
       flag it. The old --muted-faint #5f7285 measured 3.53:1 on --panel-deep,
       below the 4.5:1 AA floor for this 11px text. --muted measures 5.03:1. */
    color: var(--muted);
  }
  #legend, #log-panel {
    width: 100%;
    margin-top: var(--space-10);
    padding: var(--space-12) var(--space-14);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-surface);
    background: var(--panel-deep);
  }
  /* Side by side on a wide screen, so the board keeps its place and the panels
     read as supporting detail rather than pushing the game off the page.
     THE LEGEND TAKES THE WIDER SHARE (m24, decision P2). The panels share the
     fold with the board, so their height is a fold budget: at an equal split
     each section measured 271px at 1280x800, and the legend's list — a
     `minmax(132px, 1fr)` grid — could not fit two of its own columns in the
     243px that leaves, so five pellet rows stacked into 247px and the panels
     ended 118px below the fold. A 1.4 : 1 split gives the legend 322px, its
     list fits two columns, and the legend ends up 246px tall — inside the fold
     with room to spare. The log's share (230px) is above its own minimum: the
     list is a fixed-height scroller, so narrowing it changes no measurement. */
  #panels {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: var(--space-10);
    width: 100%;
    align-items: start;
  }
  #panels > section { margin-top: 0; }
  #legend-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
    gap: var(--space-6) var(--space-10);
  }
  #legend-list li {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    font-size: var(--fs-2);
    line-height: 1.35;
    color: var(--muted);
  }
  /* The swatch must opt out of the global `canvas { width: 100% }` rule, which is
     meant for the board. Without an explicit width each 26px swatch stretches to
     fill its grid column and the legend becomes enormous. */
  #legend-list canvas {
    flex: 0 0 auto;
    border-radius: var(--radius-control);
    background: var(--cell-a);
    width: 26px;
    height: 26px;
  }
  #legend-list b { color: var(--text); font-weight: 600; }
  /* The scoring rule that is not per-pellet. Without it the numbers on screen
     look inconsistent with the legend even when both are correct. */
  #legend-note {
    margin: var(--space-8) 0 0;
    font-size: var(--fs-2);
    /* --muted, not a dimmer token (m17, VAL-DESIGN-019): the note sits on
       --panel-deep, where the old #6d8093 measured 4.30:1 — below the 4.5:1
       AA floor for small text. --muted measures 5.03:1 there. */
    color: var(--muted);
    line-height: 1.4;
  }
  /* Fixed two-line rows: name + score, then the effect. Uniform height is what
     keeps the legend from turning into a ragged block of mismatched cells. */
  .lg-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.25;
  }
  .lg-pts { color: var(--muted-dim); font-size: var(--fs-2); }
  .lg-effect {
    /* --muted, for the same AA reason as #legend-note above (the effect text
       is the part that explains the trade-off and must be readable). */
    color: var(--muted);
    font-size: var(--fs-2);
    /* Wraps rather than truncating: the effects now carry the pellet's real
       numbers (growth, expiry, clock cost), and an ellipsis was cutting off
       exactly the part that explains the trade-off. */
    white-space: normal;
    line-height: 1.3;
  }
  #log-list {
    list-style: none;
    margin: 0;
    padding: 0;
    /* Tall enough to actually read. At 104px the panel showed about four lines,
       so the log scrolled past faster than it could be read and every message
       was clipped by the single-line rule below. */
    height: 188px;
    overflow-y: auto;
    font-family: var(--font-num);
    font-size: var(--fs-2);
    line-height: 1.5;
    scrollbar-width: thin;
    color: var(--muted-log);
  }
  /* The log scrolls once a run fills it, so the list is keyboard-focusable
     (tabindex lives on the element in index.html; VAL-DESIGN-029). The focus
     ring it needs is the ONE shared treatment (the `[tabindex]` selector
     above), so this rule adds nothing: the log's box, colours and type sizes
     belong to the m17 run-text-contrast work. */
  #log-list li {
    display: flex;
    gap: var(--space-8);
    padding: var(--space-2) 0;
    /* Messages wrap instead of being truncated. The old rule forced one line and
       hid the overflow, which is why the reasoning was cut off mid-sentence —
       the part explaining WHY the AI moved was exactly what got dropped. */
    align-items: baseline;
  }
  /* The move stamp is populated only DURING a run (it is written by logAdd as
     the AI plays), so axe sees an empty list at first paint. The old
     --muted-time #55697d measured 3.09:1 on --panel-deep, below the 4.5:1 AA
     floor. --muted (5.03:1) clears it and stays dimmer than the message text,
     which is --muted-log #93a6b8, so the column hierarchy is preserved. */
  #log-list .t { color: var(--muted); flex: 0 0 44px; }
  /* Wider, and allowed to shrink, so "CYCLE-FALLBACK" is not clipped to "RESCUE".
     The label is also shortened at the source now (see branchLabel). */
  #log-list .k { flex: 0 0 auto; min-width: 52px; font-weight: 600; letter-spacing: 0.04em; }
  /* Branch colours, so the answering layer is readable at a glance. The level
     tag was the one kind with no colour of its own: `logAdd('level', …)` renders
     `k k-level` (index.html), so a level announcement was the only log line whose
     tag inherited the list's own colour. It takes the accent, the same colour
     the between-levels banner states a level in. */
  #log-list .k-plan { color: var(--info); }
  #log-list .k-advisor { color: var(--warn); }
  #log-list .k-cycle { color: var(--amber); }
  #log-list .k-repair { color: var(--violet); }
  #log-list .k-dead { color: var(--danger); }
  #log-list .k-eat { color: var(--green); }
  #log-list .k-level { color: var(--accent); }
  #log-list .m { flex: 1 1 auto; min-width: 0; white-space: normal; overflow-wrap: anywhere; }

  #mult {
    font-family: var(--font-num);
    font-size: var(--fs-2);
    color: var(--warn);
    min-height: 1em;
    letter-spacing: 0.06em;
  }
  /* The multiplier's expiring state, which `index.html` has always toggled
     (`multEl.classList.toggle('expiring', left <= 15)`) with no rule to render
     it. The count in the readout is the information; the colour says "read it
     now". */
  #mult.expiring { color: var(--danger); }

  /* The slow-pellet brake readout. Uses the slow pellet's own colour so the line
     is visibly tied to the pellet that caused it. */
  #slow {
    font-family: var(--font-num);
    font-size: var(--fs-1);
    color: var(--violet);
    letter-spacing: 0.06em;
    min-height: 1em;
  }
  #slow.hidden { display: none; }
  /* The Level readout is shown only in endless mode (levelWrap toggled at run
     start). It is a .score, which is display:flex, so the [hidden] ATTRIBUTE
     lost to that author rule and "Level 1" stayed visible on every non-endless
     board — the same trap, audited and fixed in m17: toggled by the class. */
  #level-wrap.hidden { display: none; }

  .stage {
    position: relative;
    /* The board keeps a watchable, square size instead of stretching to fill
       the measure main now owns (m17): min(88vw, 620px, ...) is the expression
       main used to carry, relocated onto the element it was really sizing. It
       is wider than a phone and capped on a desktop, and it never exceeds the
       viewport.

       THE HEIGHT TERM IS THE FOLD (m24, decision P2). It used to be
       `calc(100vh - 232px)`, and 232px was smaller than the chrome above the
       board actually is (344px at 1280x800), so the board overflowed the fold
       and pushed the ad band — the canvas's own bottom band — off screen. The
       budget is now the declared `--stage-fold-chrome`, which is the whole
       vertical chrome outside the board plus the body's own bottom padding, so
       the board and BOTH bands land inside the viewport. The literal this
       replaces is freed from the ratchet's `container-measure` category (the
       value moved into the token layer). */
    width: min(88vw, 620px, calc(100vh - var(--stage-fold-chrome)));
    max-width: 100%;
    border-radius: var(--radius-surface);
    overflow: hidden;
    box-shadow: var(--elev-stage);
    background: var(--panel);
    line-height: 0;
    touch-action: none;
  }

  /* ---- THE BOARD AND ITS SUPPORTING PANELS SHARE THE FOLD (m24, P2) -----
     Before this, `main` stacked everything: the board, then the controls, then
     the RSI panel, the hint and the panels — a 1,392px page at 1280x800, with
     the controls at y 926 and the panels at y 1,110, both below an 800px fold.
     The board column and the side column now start on the SAME row, so the
     board, its controls and its supporting panels are visible together without
     scrolling: the board is one element among others and the panels take the
     space the board does not occupy.

     The board column is `auto` — it is exactly the `.stage`'s own declared
     width, so the panel column gets everything the board leaves. The grid is
     one column in the two contexts where there is no space beside the board: a
     phone (`max-width: 45em`, which a narrow rotated phone matches too) and a
     manifest page (see those rules). A WIDE rotated phone keeps two columns —
     the board is 366px of an 844px viewport, and the pre-change layout put the
     158px control column in that room. */
  .play-grid {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: var(--space-12);
    width: 100%;
  }
  .play-side {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    min-width: 0;
  }

  body.play canvas { display: block; width: 100%; height: auto; touch-action: none; }

  .overlay {
    position: absolute;
    /* THE OVERLAY PAINTS OVER THE BOARD, BUT DOES NOT OWN THE POINTS ON IT
       (m24, decision P2). The scrim is the game's own state screen, and the
       board's bottom band is the IAB unit the frame is composed with — the play
       page keeps it inside the canvas (P2 option (a), VAL-AD-017), so the
       overlay cannot simply stop above it: the scrim covers the board, and a
       scrim cut short of the band would leave a lit strip of playfield under
       the game-over screen (and the cut would need a token for the band's own
       share, with no other reader). What the overlay must NOT do is sit between
       a viewer and the slot: `VAL-AD-016` samples the element at the band's
       centre and corners, and with the scrim hit-testable those points resolved
       to the overlay rather than to the board that carries the band. Measured:
       the overlay is the board's own box (417.1px tall at 1280x800, 344.1px at
       390x844) and its content is a ~141px block centred inside it, so nothing
       is clipped by keeping the scrim's full height. This is the same treatment
       the between-levels banner has always had (`pointer-events: none`): paint,
       but do not intercept — and the overlay's own controls opt back in below,
       so the Play button still takes its own clicks. */
    inset: 0;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    padding: var(--space-20);
    text-align: center;
    background: var(--overlay);
    backdrop-filter: blur(var(--space-4));
    line-height: 1.45;
    transition: opacity 0.22s cubic-bezier(.2,.9,.25,1);
  }

  /* Opacity alone is not enough to hide an overlay that blurs what is behind it.
     `backdrop-filter` keeps sampling the canvas even at zero opacity on some
     engines, and the result is a permanent blur over the whole board. Removing
     the filter and the panel outright when hidden is what actually hides it. */
  .overlay.hidden {
    opacity: 0;
    pointer-events: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* The overlay's own controls opt back into hit testing: the scrim above
     passes pointer events through to the board (so the band's points belong to
     the board), but the Play / Restart controls are still the page's one
     primary action and must take their own clicks. */
  .overlay button { pointer-events: auto; }

  /* The between-levels report. Same stage overlay as game over, but it never
     intercepts input and it is centred as a single readable block. It is a
     report, not a screen, so it must NOT blur the playfield behind it — the
     board stays visible while the next level is being set up, and blurring here
     read as the whole game going soft. ONE block: the two rules this replaces
     sat three lines apart and both matched `.overlay.level-banner`, so a reader
     could not tell which one owned the banner's look. */
  .overlay.level-banner {
    pointer-events: none;
    background: var(--overlay-soft);
    gap: var(--space-6);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .overlay.level-banner b {
    font-size: var(--fs-5);
    color: var(--accent);
    letter-spacing: 0.02em;
  }
  .overlay.level-banner span {
    font-family: var(--font-num);
    font-size: var(--fs-2);
    color: var(--muted);
  }
  .overlay.attract { background: var(--overlay-light); }

  .overlay h2 {
    margin: 0;
    font-size: var(--fs-5);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent);
    font-weight: 600;
  }

  .overlay p { margin: 0; font-size: var(--fs-3); color: var(--muted); max-width: 36ch; }

  #ov-score {
    font-family: var(--font-num);
    font-size: var(--fs-7);
    font-weight: 600;
    line-height: 1;
    color: var(--text);
    font-variant-numeric: tabular-nums;
  }

  #ov-score.best { color: var(--warn); }
  #ov-newbest { font-size: var(--fs-3); letter-spacing: 0.14em; text-transform: uppercase; color: var(--warn); font-weight: 600; }

  table.scores-table {
    border-collapse: collapse;
    font-family: var(--font-num);
    color: var(--muted);
    margin: var(--space-2) 0;
  }

  table.scores-table tr.you td { color: var(--text); }
  table.scores-table tr.you td:first-child { color: var(--accent); }

  .actions { display: flex; gap: var(--space-8); flex-wrap: wrap; justify-content: center; margin-top: var(--space-4); }

  body.play button {
    letter-spacing: 0.04em;
    min-height: 44px;
    cursor: pointer;
    transition: transform 0.12s ease, filter 0.12s ease;
  }

  /* ONE hover brightness, shared with the actions above (VAL-DESIGN-039): the
     play page's buttons used to brighten by 1.08 while an action brightened by
     1.12. The focus ring is the ONE shared treatment (the `button` selector
     above), so no rule here restates it. */
  body.play button:hover { filter: brightness(var(--hover-bright)); }
  body.play button:active { transform: scale(0.96); }
  body.play button.ghost { background: var(--ghost); color: var(--text); }

  .hint { font-size: var(--fs-2); color: var(--muted); margin: 0; text-align: center; }
  .hint kbd {
    font-family: var(--font-num);
    font-size: var(--fs-1);
    background: var(--ghost);
    border-radius: var(--radius-control);
    padding: var(--space-1) var(--space-6);
    color: var(--text);
  }

  .controls {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8) var(--space-12);
    align-items: center;
    justify-content: center;
    width: 100%;
  }

  .group { display: flex; align-items: center; gap: var(--space-6); }

  .seg { display: flex; flex-wrap: wrap; background: var(--panel-trough); border-radius: var(--radius-control); padding: var(--space-2); gap: var(--space-2); }

  /* ---- THE SEGMENTED CONTROL IS ITS OWN DECLARED COMPONENT (m24, VAL-DESIGN-036)
     The .seg buttons used to be styled by the bare button rule plus the
     play page's own body.play button rule, so every one of the 15 on /g/snake
     measured byte-identical to .cta-primary — accent fill, accent-ink text, the
     pill radius, the action padding, weight 700 — i.e. 15 primary-looking
     controls competing with the page's own action. And NOTHING said which
     option was selected: the only difference between the selected and the
     unselected option was aria-pressed, and no [aria-pressed] rule existed
     anywhere on the site.

     Two things change, and they are the two halves of the assertion. The
     segmented control declares its OWN values (a secondary control: its own
     surface, radius, padding and type step — not the shared select's, which
     VAL-DESIGN-005 compares between pages, and not a primary action's). And
     the selected state differs by MORE THAN COLOUR: the pressed option is
     BOLD and carries an inset accent ring, so "which option is selected"
     survives a greyscale render instead of being carried by a fill alone. */
  .seg button {
    background: transparent; color: var(--muted);
    border: var(--space-1) solid transparent;
    border-radius: var(--radius-control);
    padding: var(--space-6) var(--space-10);
    font-size: var(--fs-2);
    font-weight: 400;
  }
  .seg button[aria-pressed="false"] {
    background: transparent; color: var(--muted); font-weight: 400;
  }
  .seg button[aria-pressed="true"] {
    background: var(--panel); color: var(--text); font-weight: 700;
    box-shadow: inset 0 0 0 var(--space-1) var(--accent);
  }


  #rsi-panel {
    width: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-10);
    font-family: var(--font-num);
    font-size: var(--fs-1);
    color: var(--muted);
    background: var(--panel-sunken);
    border: 1px solid var(--line);
    border-radius: var(--radius-surface);
    padding: var(--space-6) var(--space-10);
  }

  #rsi-panel b { color: var(--accent); font-weight: 600; }
  #rsi-spark { flex: 1; height: 16px; min-width: 40px; display: block; }
  #rsi-panel .rsi-label { letter-spacing: 0.12em; text-transform: uppercase; }

  .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: calc(-1 * var(--space-1));
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }

  /* ---- the site header (the shared nav and page-link rules own the nav
     and the game links now; only the brief and the pace control are local) ----
     Its row geometry is declared once with `.play-head` above, so the injected
     block and the title row share ONE layout rule rather than two copies. */
  #site-brief { margin: 0 0 var(--space-8); font-size: var(--fs-2); color: var(--muted); max-width: 72ch; }
  #site-brief strong { color: var(--text); }

  /* The shared pace control (m15, N9) is styled by the shared stylesheet;
     this page adds no second copy (m17 folded it into the token layer). */

  /* ---- the episode deep link (/g/:game?episode=<id>) ----
     A shared clip links to a specific game AND episode, so the page it lands on
     must name that episode and expose its seed (so it can be replayed) and its
     recorded score. Server-rendered, so a crawler or a no-script viewer reads
     the same facts the browser shows. */
  #episode-card {
    width: 100%; border: 1px solid var(--line); border-radius: var(--radius-surface);
    background: var(--panel); padding: var(--space-8) var(--space-12); margin: 0 0 var(--space-8);
  }
  /* The unknown-episode form. `#episode-card` alone made an empty card
     indistinguishable from a recorded one (VAL-DESIGN-044): the dashed edge and
     the recessed surface say "nothing is recorded here" before the sentence
     does. */
  #episode-card.empty { border-style: dashed; background: var(--panel-deep); }
  #episode-card.empty p { color: var(--text); }
  #episode-card h2 { margin: 0 0 var(--space-4); font-size: var(--fs-3); color: var(--text); font-weight: 700; }
  #episode-card code { font-family: var(--font-num); font-size: var(--fs-2); color: var(--accent); }
  #episode-card p { margin: var(--space-2) 0; font-size: var(--fs-2); color: var(--muted); }
  #episode-card .episode-facts strong { color: var(--text); }

  /* ---- manifest mode (the page hosting a registry game it does not
     drive interactively) ----
     The snake-only chrome — mode/speed/grid controls, the RSI panel, the
     pellet legend, the Jev row — is hidden by a data attribute the server
     sets BEFORE the page runs, so none of it flashes for a frame. The
     manifest player owns the board and reports its outcomes in the
     #manifest-report region. */
  html[data-page-mode="manifest"] .play-side,
  html[data-page-mode="manifest"] .controls,
  html[data-page-mode="manifest"] #rsi-panel,
  html[data-page-mode="manifest"] #panels,
  html[data-page-mode="manifest"] #jev-row,
  html[data-page-mode="manifest"] .hunger,
  /* .scores/#mult are the ENGINE game's own live counters (Score/Best). In
     manifest mode nothing updates them — the hosted game reports through
     #manifest-hud — so leaving them visible showed a static wrong-game
     "Score 0 / Best 0" header above the hosted board. */
  html[data-page-mode="manifest"] .scores,
  html[data-page-mode="manifest"] #mult,
  html[data-page-mode="manifest"] .hint {
    display: none !important;
  }
  /* A manifest page has no side column (every element in it is hidden above),
     so the grid is ONE column and the board is centred in the full measure —
     the layout it had before the board and its panels shared a row. */
  html[data-page-mode="manifest"] .play-grid {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
  }
  #manifest-report { width: 100%; min-height: 1.2em; }
  #manifest-report:empty { display: none; }
  #manifest-report p { margin: var(--space-2) 0; font-size: var(--fs-2); color: var(--muted); }
  #manifest-report .terminal { color: var(--text); font-weight: 600; }
  /* The HUD mirror states the target/status facts the canvas HUD colours, so
     a viewer who cannot see the colours still reads them. */
  #manifest-hud { margin: var(--space-2) 0 0; font-size: var(--fs-2); color: var(--muted); }
  #manifest-hud b { color: var(--text); }

  #jev-row { width: 100%; justify-content: center; }
  /* The row is hidden via the `hidden` CLASS, never the [hidden] attribute:
     .group sets display:flex, an author rule that beats the UA's
     [hidden]{display:none}, so `jevRow.hidden = true` left the key field
     VISIBLE while Jev was off. #slow and .overlay hide the same way. */
  #jev-row.hidden { display: none; }
  #jev-row input { flex: 1; min-width: 0; min-height: 32px; }
  #jev-status { font-size: var(--fs-1); white-space: nowrap; }

  /* ---- real breakpoints (m17, VAL-DESIGN-009) ---------------------------
     The shell had ZERO @media rules; two of its rules overflowed a 390px
     viewport as a result — the catalogue grid's 430px track floor (a 448px
     document) and the play page's non-wrapping segmented controls (a 411px
     document). These are the breakpoints that fix each. They are expressed in
     em (relative to the 16px root) so a breakpoint scales with the reader's
     font size instead of assuming one pixel width; em in a media query is
     always relative to the initial font size, never a parent element's. */
  @media (max-width: 45em) {
    /* The arena rules below are declared later in this sheet, so the narrow
       overrides of their grid tracks are scoped under main to outrank them. */
    main .wall { grid-template-columns: minmax(0, 1fr); }
    /* The small window is smaller on a phone and sits close to the bottom edge;
       the page keeps room under its last line so nothing ends up behind it. */
    .mini-dock { right: var(--space-12); bottom: calc(var(--space-12) + env(safe-area-inset-bottom));
      width: min(8rem, 40vw); padding: var(--space-4); gap: var(--space-4); }
    button.dock-grip { padding: var(--space-2) var(--space-4); font-size: var(--fs-1); line-height: 1.2; }
    .mini-dock-slot .replay-top, .mini-dock-slot .replay-hud { font-size: var(--fs-2); }
    button.dock-btn, a.dock-btn { padding: var(--space-8) var(--space-4); font-size: var(--fs-2); }
    body[data-mini] { padding-bottom: 20rem; }
    .hero[data-art] { padding: var(--space-26) var(--space-14) var(--space-18);
      background: linear-gradient(180deg, var(--overlay-light) 0%, var(--overlay) 60%, var(--bg) 100%), var(--hero-art) 30% center / cover; }
    .page-head { padding: var(--space-14) var(--space-14) var(--space-6);
      background: linear-gradient(180deg, var(--overlay-light) 0%, var(--overlay) 70%, var(--bg) 100%), var(--hero-art) 30% center / cover; }
    .wall .side { grid-auto-flow: column; grid-auto-columns: minmax(78%, 1fr); overflow-x: auto; scroll-snap-type: x mandatory;
      padding-bottom: var(--space-6); }
    .wall .side .replay { scroll-snap-align: start; }
    main .home-cols { grid-template-columns: minmax(0, 1fr); }
    /* The studio's closing card stacks its character over its words. */
    .studio-outro { flex-direction: column; gap: var(--space-8); padding: var(--space-12); }
    /* The thought stream drops below the stage and sizes to its rows. */
    main .wall > .thought-stream { contain: none; }
    main .thought-stream .ts-list { max-height: 60vh; }
    .arena-board td.who { position: sticky; left: 0; z-index: 1; background: var(--panel); }
    .arena-board th.l { position: sticky; left: 0; z-index: 1; background: var(--bg); }
    main .bars li { grid-template-columns: 5rem minmax(0, 1fr) 5.6rem; }
    .bars .val { font-size: var(--fs-2); }

    /* One column at a phone width, whatever the card minimum. */
    .games { grid-template-columns: 1fr; }
    /* The board and its panels cannot sit beside each other on a phone: the
       play grid is one column, so the board keeps the full measure and the
       controls and panels stack below it (m24, P2 — the desktop grid is the
       two-column case). */
    .play-grid { grid-template-columns: minmax(0, 1fr); justify-items: center; }
    /* On a phone the board comes straight after the title and the links, the brief
       and the controls follow it, so the game is on screen without scrolling. */
    body.play main { display: flex; flex-direction: column; }
    body.play .play-grid { order: 1; }
    body.play #site-header { order: 2; }
    /* The segmented controls (mode, speed, grid, the action buttons) are one
       non-wrapping row measuring 433px — wider than the 354px a 390px viewport
       leaves the play page — so they wrap instead of pushing the document
       sideways. */
    .seg { flex-wrap: wrap; }
    /* No leaderboard cell is crushed on a phone (VAL-DESIGN-024). A table
       shrinks only to its min-content width, so a column whose content is a
       single em dash collapsed to 33px at 390px and a hyphenated agent name
       such as hamiltonian-cycle-walker stacked into a vertical sliver. The
       floor is a MEASURE, not spacing (a container-measure, per
       tools/design-tokens.test.mjs's allowlist), so it declares no new literal
       — var(--space-40) is already referenced by the body's bottom padding. It
       only raises a column's minimum: a wide table still scrolls inside its
       own .table-scroll, and the document stays at the viewport width. */
    body.board th, body.board td { min-width: var(--space-40); }
  }

  @media (orientation: landscape) and (max-height: 560px) {
    body.play { align-items: center; padding: var(--space-12) var(--space-18); }
    body.play main { flex-direction: row; flex-wrap: wrap; width: auto; gap: var(--space-16); align-items: center; }
    /* Size the board from the viewport HEIGHT here (m17, VAL-DESIGN-028).
       This query matches only when the viewport is short (max-height: 560px),
       so the height term is the binding one and the 620px desktop cap can
       never apply. Without this rule the stacked layout's expression on
       .stage — min(88vw, 620px, calc(100vh - var(--stage-fold-chrome))) —
       resolves to its height term, and that term is 6px against the 384px
       budget (it was 158px against the 232px literal the budget replaces): the
       board would collapse to a sliver instead of filling the height, so the
       "body.play main { width: auto }" above only neutralized that term while
       it lived on main, and e4e8270 moved it onto .stage. 2 *
       --space-12 is this query's own vertical padding (top + bottom), so the
       square board fills the height exactly; .stage's max-width: 100% keeps it
       inside a narrow viewport. The play grid stays TWO columns here (m24, P2):
       the board is 366px of an 844px viewport, so there IS room beside it, and
       the pre-change layout put the 158px control column in that room — the
       one-column grid belongs to the phone query, which matches a rotated phone
       too narrow for a side column (667x375, where it puts the controls below
       the board, as before). */
    .stage { width: calc(100vh - 2 * var(--space-12)); }
    body.play .play-head, body.play #site-header { width: auto; flex-direction: column; align-items: flex-start; gap: var(--space-10); }
    .scores { margin-left: 0; }
    .controls { width: 158px; flex-direction: column; align-items: stretch; }
    /* A control group is a non-wrapping label + segmented-button row measuring
       433px, sitting in this 158px column. It used to overflow its column by
       ~275px without widening the DOCUMENT, because the stacked board beside
       it was so narrow. With the board sized from the height (above) the column
       sits further right and the group's right edge leaves the viewport, so the
       group and its segmented rows wrap inside the column (VAL-DESIGN-009's
       second clause: no element's right edge beyond the viewport). Scoped to
       .controls because #jev-row also carries the .group class. */
    .controls .group { flex-wrap: wrap; }
    .controls .seg { flex-wrap: wrap; }
    /* NO ELEMENT IS HIDDEN BY THIS QUERY (m24, VAL-DESIGN-053). It used to set
       `display: none` on #rsi-panel, .hint, #ov-hint, #legend-note and
       #jev-status — small print it judged to have no room in the 158px control
       column. But none of those elements is IN that column: they are children
       of `main`, which is `flex-wrap: wrap` here, so they wrap to their own row
       and take `main`'s width. Hiding them took the RSI readout, the controls
       hint, the legend's scoring note, the overlay's key hint and the Jev
       status away on a landscape phone with no equivalent in any other query.
       MEASURED with them shown: the RSI panel is 808px of an 844px viewport and
       631px of a 667px one, the hint 388px, and `document.scrollWidth` equals
       `innerWidth` at both — so the page still scrolls neither sideways nor
       inside a box, and every one of them is reachable in every orientation.
       Do NOT reintroduce a display:none here; a media query may hide an element
       only when an equivalent carries its information in that context, and
       none of these has one. */
  }

  /* REDUCED MOTION IS HONOURED END TO END (m24, VAL-DESIGN-049/VAL-CROSS-020).
     The block covered the overlay fade and the play buttons' hover/active
     transition, but NOT the hunger warning: `.hunger.critical` runs an infinite
     0.7s `steps(2, end)` opacity flash and `.hunger-fill` transitions its width
     and background, so a viewer who asked for reduced motion still got a
     flashing bar. Both are collapsed here.
     THE STATE IS NOT LOST WITHOUT THE FLASH. Suppressing the motion removes no
     information: `.hunger.critical .hunger-fill` still changes the fill's
     colour and `.hunger.critical .hunger-label` still changes the label text
     from "Fed" to "Hungry" to "Starving" (the page writes it, and the meter's
     `aria-valuenow` tracks the count), so hunger reads through its text and
     shape carriers with no motion at all. */
  @media (prefers-reduced-motion: reduce) {
    [data-pop] { animation: none; opacity: 0; }
    .overlay { transition: none; }
    body.play button { transition: none; }
    .hunger.critical { animation: none; }
    .hunger-fill { transition: none; }
    .game-card, .gamecard { transition: none; }
    .mini-dock[data-state="docked"] { animation: none; }
    .ts-row { transition: none; }
    .music[data-playing] .music-bars span { animation: none; height: 70%; }
  }


  /* ---- arena: board, player, head to head, home wall ---------------------- */
  .arena-lede { color: var(--muted); max-width: 46rem; margin: 0 0 var(--space-16); }
  .arena-chips { display: flex; gap: var(--space-6); flex-wrap: wrap; margin: 0 0 var(--space-16); }
  .arena-chips a, .arena-chips span { padding: var(--space-4) var(--space-12); border-radius: var(--radius-pill); border: var(--space-1) solid var(--line);
    color: var(--text); text-decoration: none; font-size: var(--fs-2); }
  .arena-chips a[aria-current="page"], .arena-chips span[aria-current="true"] { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
  .board-scroll { overflow-x: auto; }
  table.arena-board { width: 100%; border-collapse: separate; border-spacing: 0 var(--space-4); font-variant-numeric: tabular-nums; }
  .arena-board th { font-size: var(--fs-1); color: var(--muted); text-transform: uppercase; letter-spacing: .06em;
    font-weight: 600; text-align: right; padding: 0 var(--space-6); white-space: nowrap; }
  .arena-board th.l, .arena-board td.l, table.plain .l { text-align: left; }
  .arena-board th a { color: inherit; text-decoration: none; }
  .arena-board th a[aria-current="true"] { color: var(--accent); }
  .arena-board td { background: var(--panel); padding: var(--space-8) var(--space-6); text-align: right; white-space: nowrap; }
  .arena-board .rank { color: var(--muted); width: 2.5rem; }
  .arena-board .who a { color: var(--text); font-weight: 600; text-decoration: none; }
  .arena-board .idx { font-size: var(--fs-5); font-weight: 700; }
  .kind { display: inline-block; margin-left: var(--space-6); font-size: var(--fs-1); color: var(--muted);
    border: var(--space-1) solid var(--line); border-radius: var(--radius-pill); padding: 0 var(--space-6); vertical-align: middle; }
  .heat { display: inline-block; min-width: 2.6rem; padding: var(--space-2) var(--space-4); border-radius: var(--radius-control);
    font-weight: 600; text-align: center; color: var(--text);
    background: color-mix(in oklab, var(--accent) calc(var(--p) * 0.85%), var(--panel-trough)); }
  .heat.none { background: transparent; color: var(--muted-dim); }
  .heat.top { outline: var(--space-2) solid var(--accent); outline-offset: var(--space-2); }
  .arena-board tr.divider td { background: transparent; color: var(--muted); text-align: left; font-size: var(--fs-2);
    padding-top: var(--space-16); }
  .legend-line { color: var(--muted); font-size: var(--fs-2); margin: var(--space-12) 0 0; }
  .profile-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-12); }
  .profile-head .big { font-size: var(--fs-7); font-weight: 800; font-variant-numeric: tabular-nums; }
  .stats { display: flex; flex-wrap: wrap; gap: var(--space-16); color: var(--muted); margin: var(--space-8) 0 var(--space-20); }
  .stats b { color: var(--text); font-variant-numeric: tabular-nums; }
  .bars { list-style: none; padding: 0; margin: 0 0 var(--space-24); max-width: 40rem; }
  .bars li { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr) 5.6rem; align-items: center; gap: var(--space-8);
    margin-bottom: var(--space-6); }
  .bars .track { display: block; height: var(--space-12); border-radius: var(--radius-control); background: var(--panel-trough); overflow: hidden; }
  .bars .fill { display: block; height: 100%; background: var(--accent); width: calc(var(--p) * 1%); }
  .bars .val { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
  table.plain { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
  table.plain th, table.plain td { padding: var(--space-6) var(--space-8); border-bottom: var(--space-1) solid var(--line-soft); text-align: right; }
  table.plain thead th { color: var(--muted); font-size: var(--fs-1); text-transform: uppercase; letter-spacing: .06em; }
  .versus { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-16); margin: var(--space-12) 0 var(--space-20); }
  .versus .score { font-size: var(--fs-7); font-weight: 800; font-variant-numeric: tabular-nums; }
  .win { color: var(--green); }
  .loss { color: var(--danger); }
  .draw { color: var(--muted); }
  .wall { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: var(--space-16); align-items: start; }
  .wall .side { display: grid; gap: var(--space-12); }
  .replay { background: var(--panel); border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface);
    padding: var(--space-12); position: relative; }
  .replay-top { display: flex; align-items: baseline; gap: var(--space-8); margin-bottom: var(--space-8); min-width: 0; }
  .mascot { width: 2.25rem; height: 2.25rem; flex: none; align-self: center; border-radius: var(--radius-pill); background: var(--bg); }
  .profile-head .mascot { width: 4.5rem; height: 4.5rem; }
  .replay-top .who { color: var(--text); font-weight: 700; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .replay-top .game { color: var(--muted); font-size: var(--fs-2); }
  .replay-top .pts { margin-left: auto; font-weight: 800; color: var(--accent); font-variant-numeric: tabular-nums; white-space: nowrap; }
  .replay-stage { position: relative; aspect-ratio: 1; border-radius: var(--radius-control); overflow: hidden; background: var(--bg); }
  .replay-stage canvas { display: block; width: 100%; height: 100%; }
  .replay-stage .missed { position: absolute; top: var(--space-8); left: var(--space-8); background: var(--danger); color: var(--text);
    font-size: var(--fs-1); font-weight: 700; padding: var(--space-2) var(--space-8); border-radius: var(--radius-pill); }
  .replay-stage .done { position: absolute; inset: auto 0 0 0; padding: var(--space-12); text-align: center; font-weight: 800;
    font-size: var(--fs-4); background: linear-gradient(transparent, var(--overlay)); }
  .replay-hud { display: flex; gap: var(--space-16); margin-top: var(--space-8); color: var(--muted); font-variant-numeric: tabular-nums; }
  .replay-hud b { color: var(--text); }
  .replay-hud .scorebox { position: relative; }
  /* A card's reason never changes the card's height: the line and the log
     each keep a fixed height and scroll inside it. */
  .replay .reason { block-size: 2.8em; overflow-y: auto; overscroll-behavior: contain; margin: var(--space-6) 0 0;
    color: var(--text); font-style: italic; font-size: var(--fs-2); }
  .replay .reason-log { position: relative; block-size: 6.4em; list-style: none; padding: 0; font-style: normal; scrollbar-width: thin; }
  .replay.small .reason-log { block-size: 6.4em; }
  .reason-log li { margin: 0; }
  .reason-log button { display: block; width: 100%; padding: var(--space-2) var(--space-6); text-align: left; background: transparent;
    border: 0; border-radius: var(--radius-control); color: var(--muted); font: inherit; line-height: 1.35; cursor: pointer; }
  .reason-log button:hover { color: var(--text); }
  .reason-log li.is-current button { color: var(--text); background: var(--panel); font-style: italic; }
  .reason-log:focus-visible { outline: var(--space-2) solid var(--accent); outline-offset: var(--space-2); }
  .stage-resume { position: absolute; right: var(--space-8); bottom: var(--space-8); min-height: 2.75rem;
    padding: var(--space-6) var(--space-14); border: var(--space-1) solid var(--accent); border-radius: var(--radius-pill);
    background: var(--panel-deep); color: var(--text); font: inherit; font-weight: 700; cursor: pointer; }
  .stage-resume:hover { filter: brightness(var(--hover-bright)); }
  .replay.is-paused .replay-stage { box-shadow: inset 0 0 0 var(--space-2) var(--accent); }
  .replay.small { display: grid; grid-template-columns: 8.75rem minmax(0, 1fr); gap: var(--space-10); align-items: start; }
  .replay.small .replay-top { grid-column: 1 / -1; margin: 0; }
  .replay.small .replay-stage { aspect-ratio: 1; }
  .replay.small .replay-hud { flex-direction: column; gap: var(--space-2); margin-top: 0; }
  [data-pop] { position: absolute; left: 100%; top: -0.2em; margin-left: var(--space-4); color: var(--accent); font-weight: 800;
    animation: wb-pop .9s ease-out forwards; pointer-events: none; }
  @keyframes wb-pop { from { opacity: 1; transform: translateY(0) scale(1.2); } to { opacity: 0; transform: translateY(-1.4em) scale(1); } }
  .ticker { display: flex; gap: var(--space-8); overflow-x: auto; padding: var(--space-4) 0 var(--space-12);
    margin: var(--space-20) 0 var(--space-8); scrollbar-width: none; }
  .ticker li { list-style: none; flex: 0 0 auto; background: var(--panel); border: var(--space-1) solid var(--line-soft);
    border-radius: var(--radius-pill); padding: var(--space-4) var(--space-12); font-size: var(--fs-2); white-space: nowrap; }
  .ticker li b { color: var(--accent); }
  .ticker a { color: var(--text); text-decoration: none; font-weight: 600; }
  .home-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap: var(--space-24); margin-top: var(--space-16); }
  .top10 { list-style: none; padding: 0; margin: 0; }
  .top10 li { display: grid; grid-template-columns: 1.6rem minmax(0, 1fr) 7rem 3rem; align-items: center; gap: var(--space-8);
    padding: var(--space-6) 0; border-bottom: var(--space-1) solid var(--line-soft); }
  .top10 .n { color: var(--muted); }
  .top10 a { color: var(--text); text-decoration: none; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .top10 .bar { height: var(--space-8); border-radius: var(--radius-control); background: var(--panel-trough); overflow: hidden; display: block; }
  .top10 .bar span { display: block; height: 100%; background: var(--accent); width: calc(var(--p) * 1%); }
  .top10 .v { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
  .gamegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: var(--space-12); }
  /* A home game card is one link to the game's Watch page: the card's Watch
     link stretches over the card, and the Play link sits above it. The card
     warms to the accent when it is pointed at or holds the focus. */
  .gamecard { background: var(--panel); border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface);
    overflow: hidden; color: var(--text); display: flex; flex-direction: column; position: relative;
    transition: border-color 0.2s ease, box-shadow 0.2s ease; }
  .gamecard:hover, .gamecard:focus-within {
    border-color: var(--accent); box-shadow: 0 0 var(--space-24) var(--accent-wash), 0 0 0 var(--space-1) var(--accent);
  }
  .gamecard .card-watch::after { content: ""; position: absolute; inset: 0; }
  .gamecard .card-play { position: relative; z-index: 1; }
  .gamecard .acts { display: flex; gap: var(--space-10); margin-top: var(--space-6); font-size: var(--fs-2); }
  .gamecard img { display: block; width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; background: var(--bg); }
  .gamecard .body { display: flex; flex-direction: column; padding: var(--space-8) var(--space-10) var(--space-10); }
  .gamecard .t { display: block; font-weight: 700; color: var(--text); text-decoration: none; }
  .gamecard .lead { display: block; color: var(--muted); font-size: var(--fs-1); margin-top: var(--space-2);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-8); }
  .section-head a { font-size: var(--fs-2); }

  /* ---- the vote box: choose the next game --------------------------------
     Each game is one row: its name (with the radio when the viewer may still
     vote) and its count on one line, the bar under them. The bar is the game's
     share of today's votes. */
  .vote-box { background: var(--panel); border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface);
    padding: var(--space-16); margin: var(--space-24) 0; max-width: 46rem; }
  .vote-close { color: var(--muted); font-size: var(--fs-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
  .vote-options { border: 0; margin: var(--space-12) 0 0; padding: 0; min-width: 0; }
  .vote-list { list-style: none; margin: 0; padding: 0; }
  .vote-option { margin: 0; }
  .vote-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name count" "bar bar";
    align-items: center; gap: var(--space-4) var(--space-8); padding: var(--space-6) 0; }
  label.vote-row { cursor: pointer; }
  .vote-name { grid-area: name; display: flex; align-items: center; gap: var(--space-8); font-weight: 600; }
  .vote-name input { margin: 0; accent-color: var(--accent); }
  .vote-count { grid-area: count; color: var(--muted); font-size: var(--fs-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
  .vote-track { grid-area: bar; display: block; height: var(--space-8); border-radius: var(--radius-control);
    background: var(--panel-trough); overflow: hidden; }
  .vote-fill { display: block; height: 100%; background: var(--accent); width: calc(var(--p) * 1%); }
  .vote-actions { margin: var(--space-12) 0 0; }
  /* ---- characters: the picker and the character sheet -------------------- */
  .avatar-picker { margin: 0 0 var(--space-16); }
  .avatar-label { margin: 0 0 var(--space-6); font-size: var(--fs-1); color: var(--muted); text-transform: uppercase; letter-spacing: .08em; font-weight: 600; }
  .avatar-chips { display: flex; flex-wrap: wrap; gap: var(--space-8); margin: 0; padding: 0; list-style: none; }
  .avatar-chips li { flex: 0 0 auto; }
  .avatar-chip { display: flex; align-items: center; gap: var(--space-8); min-height: 2.75rem; padding: var(--space-4) var(--space-14) var(--space-4) var(--space-4);
    border: var(--space-1) solid var(--line); border-radius: var(--radius-pill); background: var(--panel); color: var(--text); text-decoration: none; font-size: var(--fs-3); }
  .avatar-chip-own { padding-left: var(--space-14); }
  .avatar-chip[aria-current="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 var(--space-1) var(--accent); font-weight: 700; }
  .avatar-face { width: 2.25rem; height: 2.25rem; flex: none; border-radius: var(--radius-pill); background: var(--bg); }
  .avatar-text { display: flex; flex-direction: column; line-height: 1.2; }
  .avatar-name { white-space: nowrap; }
  .character-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: var(--space-16); list-style: none; padding: 0; margin: var(--space-16) 0; }
  .character-card { display: flex; flex-direction: column; gap: var(--space-8); background: var(--panel); border: var(--space-1) solid var(--line-soft);
    border-radius: var(--radius-surface); padding: var(--space-12); }
  .character-card h2 { margin: 0; font-size: var(--fs-5); }
  .character-card p { margin: 0; }
  .character-card .kind { align-self: flex-start; margin-left: 0; }
  .character-stage { position: relative; aspect-ratio: 1; border-radius: var(--radius-control); overflow: hidden; background: var(--bg); }
  .character-stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
  .character-portrait { position: absolute; inset: 0; width: 100%; height: 100%; display: block; object-fit: cover; }
  .character-state { margin: 0; min-height: 1.4em; color: var(--accent); font-size: var(--fs-2); font-weight: 700; }
  .character-facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-2) var(--space-8); margin: 0; font-size: var(--fs-2); }
  .character-facts dt { color: var(--muted); }
  .character-facts dd { margin: 0; }
  .character-swatch { display: inline-block; width: var(--space-10); height: var(--space-10); margin-right: var(--space-4); border-radius: var(--radius-pill); vertical-align: middle; }
  /* ---- learning: the session curve and the session list ------------------- */
  .session-list { list-style: none; padding: 0; margin: 0 0 var(--space-24); display: grid; gap: var(--space-12);
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); }
  .session-card { background: var(--panel); border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface);
    padding: var(--space-12); display: grid; gap: var(--space-8); }
  .curve { display: block; width: 100%; height: auto; }
  .curve-axis { stroke: var(--line); stroke-width: var(--space-1); }
  .curve-mark { stroke: var(--muted); stroke-width: var(--space-1); stroke-dasharray: var(--space-4) var(--space-4); }
  .curve-line { stroke: var(--accent); stroke-width: var(--space-2); fill: none; stroke-linejoin: round; }
  .curve-dot { fill: var(--accent); }
  .curve-text { fill: var(--muted); font-size: var(--fs-1); font-variant-numeric: tabular-nums; }

  /* ---- the thought stream: each move beside the replay --------------------
     A column to the right of the stage on a wide screen, as tall as the replay
     card (size containment keeps its rows from stretching the grid row), with
     its own scrolling list. Below the stage on a phone (narrow block above). */
  .wall > .thought-stream { contain: size; align-self: stretch; }
  .thought-stream { display: flex; flex-direction: column; min-width: 0; background: var(--panel);
    border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface); padding: var(--space-12); }
  .thought-stream h2 { margin: 0; font-size: var(--fs-5); }
  .ts-foot { display: flex; flex-wrap: wrap; gap: var(--space-8); margin-top: var(--space-8); }
  .ts-btn { min-height: 2.75rem; }
  .ts-foot .is-hidden { display: none; }
  .ts-note { margin: var(--space-6) 0 var(--space-8); color: var(--muted); font-size: var(--fs-2); }
  .ts-empty { margin: 0; color: var(--muted); font-size: var(--fs-2); }
  .ts-list { list-style: none; margin: 0; padding: 0; position: relative; flex: 1 1 auto; min-height: 0; overflow-y: auto;
    overscroll-behavior: contain; }
  .ts-row { padding: var(--space-8) var(--space-10); border-left: var(--space-4) solid transparent;
    border-bottom: var(--space-1) solid var(--line-soft); transition: background-color .2s ease-out; }
  .ts-row.is-current { background: var(--panel-deep); border-left-color: var(--accent); }
  .ts-list .ts-row { cursor: pointer; }
  .ts-list:focus-visible { outline: var(--space-2) solid var(--accent); outline-offset: var(--space-2); }
  .ts-line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-6); font-size: var(--fs-2); }
  .ts-move { color: var(--muted); font-variant-numeric: tabular-nums; }
  .ts-chip { background: var(--accent-wash); color: var(--text); border: var(--space-1) solid var(--accent);
    border-radius: var(--radius-pill); padding: 0 var(--space-8); font-weight: 700; }
  .ts-default { color: var(--muted); }
  .ts-badge { color: var(--muted); border: var(--space-1) solid var(--line); border-radius: var(--radius-pill);
    padding: 0 var(--space-6); font-size: var(--fs-1); font-variant-numeric: tabular-nums; }
  .ts-reason { margin: var(--space-4) 0 0; color: var(--text); font-size: var(--fs-2); font-style: italic; }
  .ts-reason.ts-missed, .ts-reason.ts-quiet { font-style: normal; }
  .ts-quiet { color: var(--muted); }
  .ts-flag { background: var(--danger); color: var(--text); font-weight: 700; font-size: var(--fs-1);
    border-radius: var(--radius-pill); padding: 0 var(--space-6); }
  .ts-moments { margin: var(--space-4) 0 0; display: flex; flex-wrap: wrap; gap: var(--space-6); font-size: var(--fs-2); font-weight: 700; }
  .ts-moment[data-kind="up"] { color: var(--green); }
  .ts-moment[data-kind="down"], .ts-moment[data-kind="end"] { color: var(--danger); }
  .ts-summary { margin: var(--space-4) 0 0; color: var(--text); font-size: var(--fs-2); }
  .ts-summary-label { display: block; color: var(--muted); font-size: var(--fs-1); }

  /* ---- the replay studio: a grid of replays on one stage ------------------
     One canvas sits under the grid and the player draws every cell's game into
     its square. Each cell's words sit on a solid plate beside or under the
     square. Without script the cells flow as a plain grid; the player places
     them (.is-laid) and sizes each square from the grid's own measure. */
  .studio { margin-bottom: var(--space-24); }
  .studio-board { position: relative; background: var(--bg); border-radius: var(--radius-surface); overflow: hidden; }
  .studio-canvas { position: absolute; left: 0; top: 0; display: block; pointer-events: none; }
  .studio-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-8);
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); }
  .studio-screen.is-laid .studio-grid { display: block; position: relative; height: 100%; }
  .studio-cell { display: flex; flex-direction: column; align-items: center; gap: var(--space-8); min-width: 0; }
  .studio-screen.is-laid .studio-cell { position: absolute; }
  .studio-screen .is-hidden { display: none; }
  .studio-screen.is-wide .studio-cell { flex-direction: row; }
  .studio-stage { position: relative; flex: none; width: 100%; aspect-ratio: 1; }
  .studio-screen.is-laid .studio-stage { width: auto; }
  .studio-info { display: flex; flex-direction: column; gap: var(--space-4); align-self: stretch; flex: 1 1 auto; min-width: 0;
    overflow: hidden; background: var(--panel); border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-control);
    padding: var(--space-8) var(--space-10); }
  .studio-who { display: flex; align-items: center; gap: var(--space-8); margin: 0; font-weight: 700; min-width: 0; }
  .studio-face { width: 2.25rem; height: 2.25rem; flex: none; border-radius: var(--radius-pill); background: var(--bg); }
  .studio-name { min-width: 0; overflow-wrap: anywhere; line-height: 1.2; }
  .studio-nums { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-12); margin: 0; color: var(--muted);
    font-size: var(--fs-3); font-variant-numeric: tabular-nums; }
  .studio-nums b { color: var(--text); }
  .studio-reason { margin: 0; font-size: var(--fs-2); overflow: hidden; display: -webkit-box;
    -webkit-box-orient: vertical; -webkit-line-clamp: 3; color: var(--text); font-style: italic; }
  .studio-thought { margin: 0; font-size: var(--fs-2); overflow: hidden; display: -webkit-box;
    -webkit-box-orient: vertical; -webkit-line-clamp: 2; color: var(--muted); }
  .studio-rules { margin: 0; color: var(--warn); font-size: var(--fs-2); }
  .studio-end { position: absolute; left: 0; right: 0; bottom: 0; margin: 0; padding: var(--space-8); text-align: center;
    font-weight: 800; font-size: var(--fs-3); color: var(--text); background: var(--overlay); }
  .studio-outro { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    gap: var(--space-24); padding: var(--space-24); }
  .studio-outro-stage { flex: none; width: 28%; max-width: 16rem; aspect-ratio: 1; }
  .studio-outro-text { max-width: 34rem; background: var(--panel); border: var(--space-1) solid var(--accent);
    border-radius: var(--radius-surface); padding: var(--space-20) var(--space-24); }
  .studio-outro-kicker { margin: 0; color: var(--muted); font-size: var(--fs-1); text-transform: uppercase; letter-spacing: .06em; }
  .studio-outro-title { margin: var(--space-6) 0; color: var(--accent); font-size: var(--fs-6); font-weight: 800; line-height: 1.15; }
  .studio-outro-line { margin: 0 0 var(--space-12); color: var(--text); }
  .studio-ranks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-4); font-variant-numeric: tabular-nums; }
  .studio-ranks li { display: flex; gap: var(--space-8); align-items: baseline; }
  .studio-rank-place { min-width: 1.5rem; color: var(--muted); }
  .studio-rank-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .studio-rank-value { font-weight: 700; }
  .studio-form { margin-top: var(--space-36); }
  .studio-fields { display: flex; flex-wrap: wrap; gap: var(--space-12); }
  .studio-select { display: flex; flex-direction: column; gap: var(--space-4); color: var(--muted); font-size: var(--fs-3); }
  .studio-select select { min-height: 2.75rem; }
  .studio-field { margin: var(--space-12) 0; padding: var(--space-12); border: var(--space-1) solid var(--line);
    border-radius: var(--radius-surface); }
  .studio-field legend { padding: 0 var(--space-6); color: var(--muted); font-size: var(--fs-3); }
  .studio-choices { display: flex; flex-wrap: wrap; gap: var(--space-8); }
  .studio-choice { display: inline-flex; align-items: center; gap: var(--space-6); min-height: 2.75rem; padding: 0 var(--space-12);
    border: var(--space-1) solid var(--line); border-radius: var(--radius-pill); font-size: var(--fs-3); }
  .studio-choice-note { color: var(--muted); font-size: var(--fs-2); }
  .studio-video { margin: var(--space-12) 0; }
  .studio-player-video { display: block; width: 100%; max-width: 48rem; margin: var(--space-12) 0; border-radius: var(--radius-surface);
    background: var(--bg); }
  .studio-videos { margin: 0; padding-left: var(--space-20); display: grid; gap: var(--space-6); }
  /* The video page: the view alone, filling the frame, with a title strip. */
  body.studio-render { padding: 0; overflow: hidden; background: var(--bg); }
  .studio-screen[data-mode="video"] { box-sizing: border-box; width: 100vw; height: 100vh; display: flex; flex-direction: column;
    gap: var(--space-8); padding: var(--space-12); }
  .studio-screen[data-mode="video"] .studio-board { flex: 1 1 auto; }
  .studio-bar { display: flex; align-items: baseline; gap: var(--space-12); }
  .studio-bar-title { margin: 0; font-size: var(--fs-5); font-weight: 800; }
  .studio-bar-sub { margin: 0; color: var(--muted); font-size: var(--fs-3); }
  .studio-bar-brand { margin-left: auto; align-self: center; color: var(--muted); font-size: var(--fs-3); }
  .studio-bar-brand img { display: block; height: 1.75rem; width: auto; mix-blend-mode: screen; }



  .holding .hero[data-art="arcade"] { --hero-art: url(/art/arcade-hall.webp); }
  .holding-signup { max-width: 40rem; margin: var(--space-26) 0; padding: var(--space-18);
    background: var(--panel); border: var(--space-1) solid var(--line-soft); border-radius: var(--radius-surface); }
  .holding-signup h2 { margin-top: 0; }
  .holding-label { display: block; margin: var(--space-14) 0 var(--space-6); color: var(--text); }
  .holding-signup input[type="email"] { width: 100%; min-height: 2.75rem; }
  .holding-topics { margin: var(--space-14) 0; padding: 0; border: 0; }
  .holding-topics legend { margin-bottom: var(--space-6); color: var(--text); }
  .holding-opt { display: flex; gap: var(--space-8); align-items: center; min-height: 2.75rem; color: var(--muted); }
  .holding-consent { display: flex; gap: var(--space-8); align-items: flex-start; margin: 0 0 var(--space-14);
    color: var(--muted); font-size: var(--fs-2); }
  .holding-signup input[type="checkbox"] { width: 1.25rem; height: 1.25rem; flex: none; accent-color: var(--accent); }
  .holding-alert { color: var(--text); font-weight: 700; }
  .holding-trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
