/* STONIC — themed UI. The display font + colour vars below are the HOUSE PRINT SHOP
   defaults; themes.js overrides --disp / palette / @font-face per active theme.
   THE ONE MATERIAL RULE: every surface is INK ON PAPER. Over the film (HUD, banners,
   chevrons, tutorial) type is knockout — --white/--gold fill, ink stroke, var(--shadow)
   offset. On paper (any card) type is SOLID INK — zero text-stroke, tracking ≥ .04em.
   The only knockout on paper is the h1 STONIC marquee. */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* FIRST PAINT IS VINTAGE. The journey is pinned to the 1930s look, so the CSS
   defaults ARE that look — the loader must never flash another game's palette
   while scripts are still downloading. themes.js re-applies (a no-op for
   vintage) and dev ?theme= overrides still repaint on top. */
:root {
  /* WHERE THE INTERFACE MAY SAFELY START. On the web this is the device's own
     safe area. Inside Telegram, tg.js publishes --tg-top/--tg-bottom which
     ALREADY include the device inset plus Telegram's overlaid controls — so take
     the LARGER of the two rather than adding them, or the notch counts twice.
     Unset on the web, where max() collapses to env() and nothing changes. */
  --ui-top: max(env(safe-area-inset-top, 0px), var(--tg-top, 0px));
  --ui-bottom: max(env(safe-area-inset-bottom, 0px), var(--tg-bottom, 0px));
  --sky:    #8f8a7e;
  --sky-dk: #5a554b;
  --gold:   #e7e1d2;
  --gold-dk:#8a8377;
  --ink:    #1a1a18;          /* outline / shadow — INK, not navy */
  --white:  #f5f2ea;
  --green:  #7d786c;
  --red:    #3a352e;
  /* THE SAFE-AREA FILL. Both edge fades and the browser's own bars take these,
     so the strips above and below the picture read as more STAGE rather than a
     cream band. Both values are MEASURED off cc-stage.png's own edges: the top
     from its curtain/wings band, the bottom from its floorboards, which is why
     the fill and the painting meet without a seam. --page-bot defaults to
     --page, so the other seven themes are untouched by this. */
  --page:   #131312;          /* curtain tone, measured rgb(19,19,18) */
  --page-bot: #080808;        /* floorboard tone, measured rgb(8,8,8) */
  --card:   #f2eee4;          /* OPAQUE — a printed plate hides what is behind it */
  --card-border: 3px solid #1a1a18;
  --card-radius: 10px;
  --chip:   #f7f4ec;          /* OPAQUE — art linework must never strike through label type */
  --chip-text: #1a1a18;
  --chip-you: #1a1a18;
  --chip-you-text: #f5f2ea;
  --shadow: rgba(26,26,24,.35);
  --muted:  #4a463d;
  --body:   Georgia, "Iowan Old Style", "Times New Roman", serif;
  --btn:    linear-gradient(180deg, #efe9da, #b8b1a0);
  --btn-ghost: linear-gradient(180deg, #e6e0d1, #d5cebc);   /* flat-ish paper, not chrome */
  --overlay: radial-gradient(150% 120% at 50% 42%, rgba(245,242,234,.30), rgba(206,201,189,.72));
  --glow:   rgba(40,38,34,.26);
  --disp: "Vintage Display", "Ultra", Rockwell, Georgia, serif;
  /* print-shop tokens — mirrored into every themes.js palette */
  --accent-on-paper: #1a1a18;                 /* vintage: ink. sonic/comic/etc: their --gold */
  --stroke-card: 0px;                          /* vintage: 0. dark-card themes: 2px */
  --screen: #0b0b09;                           /* film black — endCard bg + iris (game.js reads activeStyle.screen) */
  --shadow-drop: rgba(26,26,24,.28);           /* hard offset box-shadows (cards, plates, chips) */
  --well: rgba(26,26,24,.5);                   /* meter track wells (non-vintage; vintage gauges use --card) */
  --rule-faint: rgba(26,26,24,.24);            /* ledger column rules, graduation ticks */
  --rule-half: rgba(26,26,24,.5);              /* leader crosshair */
  --dim-strong: rgba(26,26,24,.62);            /* tut spotlight */
  --dim-soft: rgba(26,26,24,.32);
  --dim-faint: rgba(26,26,24,.18);
  /* THE LOBBY MARGIN — strip, prop columns and deck all inset by this, so nothing
     sits on the frame's edge. On a phone it is a flat 18px; past 676px of width it
     grows so the furniture stays a phone-shaped column instead of stretching a
     50px-tall strip across a desktop. ONE number, so no edge can ever drift. */
  --lobby-pad: max(18px, calc((100vw - 640px) / 2));
  /* THE PROP SIZE — one box for every illustrated button, deck and side column
     alike, so the two columns can never read as different tiers by accident.
     Bounded on BOTH axes: in landscape the side column hangs above ROUTE in the
     deck, and a width-only size grew until the two collided. */
  --prop-art: clamp(56px, min(17.5vw, 11.5vh), 84px);
  --paper-grain:
    repeating-linear-gradient(0deg,  rgba(26,26,24,.035) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(26,26,24,.022) 0 1px, transparent 1px 7px);
  color-scheme: light;
}
html, body {
  height: 100%; width: 100%;
  background: var(--page, #061634);
  color: var(--white);
  font-family: var(--disp);
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
  user-select: none; -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
/* Full-bleed stage: 100dvh tracks the DYNAMIC mobile viewport (iOS URL bar / chrome),
   so the game truly covers the screen; vh is the legacy fallback. The canvas fills the
   stage box (JS reads the stage's real rendered size — not window.innerHeight, which
   excludes iOS browser chrome). UI keeps itself in the safe zones via env() paddings. */
/* THE STAGE STOPS WHERE TELEGRAM'S CHROME BEGINS. We ask Telegram for
   fullscreen, which draws its Close/menu controls OVER our content — so the
   playfield must be inset by the insets tg.js publishes, or the marquee hides
   under the header and the canvas sizes itself taller than the visible area
   (which is why the cat and the backdrop rendered too large).
   --tg-* are UNSET on the web and fall back to 0px, so the web page computes
   byte-for-byte what it always did. One rule, both doorways. */
/* THE PICTURE FILLS THE SCREEN. An earlier attempt inset the STAGE by
   Telegram's insets, which letterboxed the artwork behind black bands top and
   bottom — wrong for a full-bleed game. The canvas always runs edge to edge; it
   is the UI that steps out of the way of Telegram's controls, via --ui-top and
   --ui-bottom. */
#stage { position: fixed; inset: 0; width: 100vw; height: 100vh; height: 100dvh; height: var(--app-h, 100dvh); }
#game, #filmfx { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#filmfx { z-index: 1; pointer-events: none; }
/* Safari's status/URL bars can't be painted (no Fullscreen API on iPhone) — instead the
   game DISSOLVES into them: top/bottom fades to var(--page), the same colour the bars are
   tinted with (theme-color meta tracks --page per zone), so phone + game read as one surface. */
/* On real devices the page extends UNDER the bars into the safe areas — a fixed-height fade
   ends up submerged beneath the bar with only its weak end visible. Stay SOLID through the
   safe-area strip, then fade across the visible 80px. env() = 0 where there are no insets.
   Fades target var(--page) — the ACTIVE style's page colour, live per zone/style — so the
   game area always matches its own current look (user call, round 8). Safari's own bars keep
   the boot tint (it samples once and never re-tints); that seam is outside our control. */
#stage::before, #stage::after {
  content: ""; position: absolute; left: 0; right: 0;
  z-index: 2; pointer-events: none;
  display: none;             /* desktop: no browser bars / notch to dissolve into — no fades */
}
/* Touch devices only (phones + tablets, incl. Dynamic-Island iPhones): hover:none +
   pointer:coarse is the standard primary-input test — mouse/trackpad desktops report
   hover:hover + pointer:fine and keep the full play area edge-to-edge. */
@media (hover: none) and (pointer: coarse) {
  #stage::before, #stage::after { display: block; }
}
/* The visible fade must READ during play on every style: 80px of linear falloff let
   bright sprites stay near-full brightness until ~40px from the edge, which looks like
   no gradient at all on dark themes. 120px + an eased mid-stop (color-mix, with a plain
   3-stop fallback for engines without it) dissolves sprites clearly before the edge.
   env() lives in height only; stops use calc(100% - Npx), which equals the safe-area
   inset exactly: solid through the bar strip, fade across the visible 120px. */
#stage::before {
  top: 0; height: calc(var(--fade, 120px) + var(--ui-top));
  background: linear-gradient(to bottom,
    var(--page), var(--page) calc(100% - var(--fade, 120px)), transparent);
  background: linear-gradient(to bottom,
    var(--page), var(--page) calc(100% - var(--fade, 120px)),
    color-mix(in srgb, var(--page) 55%, transparent) calc(100% - var(--fade, 120px) * 0.46),
    transparent);
}
#stage::after {
  bottom: 0; height: calc(var(--fade, 120px) + var(--ui-bottom));
  background: linear-gradient(to top,
    var(--page-bot, var(--page)), var(--page-bot, var(--page)) calc(100% - var(--fade, 120px)), transparent);
  background: linear-gradient(to top,
    var(--page-bot, var(--page)), var(--page-bot, var(--page)) calc(100% - var(--fade, 120px)),
    color-mix(in srgb, var(--page-bot, var(--page)) 55%, transparent) calc(100% - var(--fade, 120px) * 0.46),
    transparent);
}

/* WITH A CARD UP the veil (.overlay, z-20) used to bury these fades (z-2), so the
   bar strip met the dimmed scene on a hard line — the bands looked like flat fill
   with the dissolve gone. Lift them over the veil, and shorten the falloff to a
   short lip: the seam stays dissolved while a centred card is never reached. */
body.overlay-up #stage::before,
body.overlay-up #stage::after { z-index: 21; --fade: 30px; }

/* ---------- HUD (over-film: knockout type, var(--shadow) offsets) ---------- */
.hud {
  position: absolute; inset: 0; z-index: 5; pointer-events: none;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: max(16px, var(--ui-top)) 20px max(20px, calc(var(--ui-bottom) + 14px));
}
.hud-top, .hud-bottom { display: flex; justify-content: space-between; align-items: flex-start; }
.hud-label { font-size: 12px; letter-spacing: .16em; color: var(--gold); -webkit-text-stroke: 2px var(--ink); paint-order: stroke fill; text-shadow: 0 2px 0 var(--shadow); }
.hud-num { font-size: 40px; line-height: .92; transform: skewX(-6deg); display: inline-block;
  color: var(--white); -webkit-text-stroke: 3px var(--ink); paint-order: stroke fill; text-shadow: 0 4px 0 var(--shadow); }
.hud-num.small { font-size: 22px; -webkit-text-stroke: 2px var(--ink); }
.hud-num.gold { color: var(--gold); }
.hud-num .unit { font-size: .42em; margin-left: 1px; -webkit-text-stroke: 1.5px var(--ink); }

.hud-score, .hud-best { display: flex; flex-direction: column; gap: 1px; }
.hud-rings { display: flex; align-items: center; gap: 10px; }
.hud-rings.pop { animation: ringpop .28s cubic-bezier(.2,1.6,.4,1); }
.ring-icon {
  width: 30px; height: 30px; border-radius: 50%;
  border: 8px solid var(--gold);
  box-shadow: 0 0 0 3px var(--ink), inset 0 0 0 3px #fff3b0, 0 3px 0 var(--shadow);
  background: transparent; flex: 0 0 auto;
  position: relative; overflow: hidden;
}
/* the coin's glint sweeps INSIDE the ring — the old rotation spun a radially
   symmetric shape, so the only visible motion was its drop shadow orbiting */
.ring-icon::after {
  content: ""; position: absolute; inset: -8px;
  background: conic-gradient(rgba(255,255,255,.7) 0 14deg, transparent 14deg);
  animation: spin 2.4s linear infinite; will-change: transform;
}
.hud-boost { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.boost-meter { width: 110px; height: 12px; border-radius: 8px; background: var(--well); border: 3px solid var(--ink); overflow: hidden; }
.boost-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--gold), #ff8a1e); box-shadow: 0 0 12px rgba(255,160,40,.7); transition: width .08s linear; }

.zone-label {
  font-size: 16px; letter-spacing: .16em; text-align: center; align-self: center; flex: 1;
  color: var(--white); -webkit-text-stroke: 2.5px var(--ink); paint-order: stroke fill; text-shadow: 0 3px 0 var(--shadow);
  opacity: 0; transition: opacity .3s ease; transform: skewX(-6deg);
}
.zone-label.show { opacity: .95; }

.combo {
  align-self: center; font-size: 34px; transform: skewX(-8deg);
  color: var(--gold); -webkit-text-stroke: 3px var(--ink); paint-order: stroke fill;
  text-shadow: 0 4px 0 var(--shadow);
  opacity: 0; transition: opacity .12s ease;
}
.combo.show { opacity: 1; animation: pop .22s cubic-bezier(.2,1.7,.4,1); }
[data-theme="vintage"] .combo.show { animation: pop-steps .22s steps(2); }   /* pop carries the skew every frame; vintage (skew stripped) pops on twos */

/* ---------- ring-collect "+1" popups ---------- */
.ringpop-fx {
  position: absolute; z-index: 6; pointer-events: none;
  font-family: var(--disp); font-size: 26px; color: var(--gold);
  -webkit-text-stroke: 3px var(--ink); paint-order: stroke fill; transform: skewX(-6deg);
  animation: floatup .7s ease-out forwards; will-change: transform, opacity;
}
.ringpop-fx.idle { opacity: 0; animation: none; }
.ringpop-fx.rp2 { animation-name: floatup2; }

/* ---------- banner ---------- */
.banner {
  position: absolute; top: 30%; left: 0; right: 0; text-align: center; z-index: 4;
  font-size: clamp(30px, 10vw, 60px); transform: skewX(-7deg);
  color: var(--white); -webkit-text-stroke: 4px var(--ink); paint-order: stroke fill; text-shadow: 0 6px 0 var(--shadow);
  pointer-events: none; opacity: 0;
}
.banner.play { animation: bannerPop 2.1s ease forwards; }
.banner.play2 { animation: bannerPop2 2.1s ease forwards; }
.banner .sub-banner { display: block; font-size: .36em; letter-spacing: .12em; color: var(--gold); margin-top: 8px; -webkit-text-stroke: 2px var(--ink); }

/* ---------- boss bar ---------- */
.boss-ui { margin: 8px auto 0; width: min(86%, 360px); text-align: center; }
.boss-name { font-size: 16px; letter-spacing: .14em; color: var(--red); margin-bottom: 6px; transform: skewX(-6deg);
  -webkit-text-stroke: 2.5px var(--ink); paint-order: stroke fill; text-shadow: 0 3px 0 var(--shadow); }
.boss-bar { height: 14px; border-radius: 9px; background: var(--well); border: 3px solid var(--ink); overflow: hidden; }
.boss-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--red), var(--gold)); transition: width .1s linear; }

/* ---------- overlays (title / results) ---------- */
.overlay {
  position: absolute; inset: 0; z-index: 20; display: grid; place-items: center;
  background: var(--overlay);
  animation: fadein .25s ease;
}
/* The card prints its own paper: the film shader never grades z-20 (filmfx is z-1),
   so grain, hand-cut corners, the double ink rule and the printer's quads are CSS.
   Ornaments live in background (not pseudos): the card is its own scroll container
   and backgrounds stay glued to the box while absolute pseudos scroll away. */
/* ONE PLATE WIDTH for every panel: the cards used to shrink-to-fit, so MISSIONS
   printed 40px narrower than TOP PILOTS and the ledger rows overflowed the paper
   margin. A definite width gives the whole house one measure — and one content
   column (width minus the side padding) that plates, rows and lists all share. */
.overlay-card {
  text-align: center; width: min(356px, 92vw); padding: 22px 24px 24px;
  max-height: calc(100svh - 24px); overflow-y: auto; -webkit-overflow-scrolling: touch;
  border: var(--card-border, 3px solid var(--ink));
  border-radius: 11px 9px 12px 9px / 9px 12px 9px 11px;   /* hand-cut plate corners */
  background-color: var(--card);
  background-image:
    radial-gradient(circle 3.5px at 11px 11px,                       var(--ink) 0 3px, transparent 3.5px),
    radial-gradient(circle 3.5px at calc(100% - 11px) 11px,          var(--ink) 0 3px, transparent 3.5px),
    radial-gradient(circle 3.5px at 11px calc(100% - 11px),          var(--ink) 0 3px, transparent 3.5px),
    radial-gradient(circle 3.5px at calc(100% - 11px) calc(100% - 11px), var(--ink) 0 3px, transparent 3.5px),
    var(--paper-grain);
  background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, repeat, repeat;
  box-shadow:
    inset 0 0 0 3px var(--card),      /* gap */
    inset 0 0 0 4.5px var(--ink),     /* the double ink rule — same geometry as .title-card */
    0 10px 0 var(--shadow-drop);
}

/* STONIC logo — the one knockout allowed on paper */
.overlay-card h1 {
  /* THE CARD IS A FIXED MEASURE, SO THE TITLE MUST BE TOO. 17vw overran it and
     the loader's STONIC was clipped at both ends; 14.6vw fixed that at PHONE
     widths but the 110px ceiling still overran on desktop — the card stops
     growing at 356px while vw keeps climbing, so at 1600px the title rendered
     110px / 537px wide inside a 302px column and read "STON". The ceiling has
     to be the column, not a guess: 56px is the largest that fits 302px. */
  font-family: var(--disp); font-size: clamp(38px, 14.6vw, 56px); line-height: .9;
  transform: skewX(-8deg); letter-spacing: .01em;
  color: var(--gold);
  -webkit-text-stroke: 6px var(--ink); paint-order: stroke fill;
  text-shadow: 0 8px 0 var(--shadow), 0 0 30px var(--glow);
  animation: logobounce 2.4s ease-in-out infinite;
}
/* T2 card title: solid ink on paper — no stroke, no skew, no shadow. 8vw keeps
   TODAY'S ROUTE on ONE line inside the card's measure (8.4vw broke it in two). */
.overlay-card h2 {
  font-family: var(--disp); font-size: clamp(28px, 8vw, 40px); line-height: 1.06;
  letter-spacing: .03em; color: var(--ink); text-wrap: balance;
}
/* the Oxford rule under every card title */
.overlay-card h2::after {
  content: ""; display: block; width: 72px; height: 5px;
  margin: 10px auto 0; border-top: 3px solid var(--ink); border-bottom: 1px solid var(--ink);
}
.overlay-card h2.win-title { color: var(--accent-on-paper); }
#deathScreen h2 { color: var(--red); }   /* danger red belongs to CRASHED alone */
.sub { font-size: 14px; letter-spacing: .26em; color: var(--sky); margin-top: 6px; text-transform: uppercase; -webkit-text-stroke: 1.5px var(--ink); paint-order: stroke fill; }
/* body copy: 1.9 leading floated two short lines apart with no paragraph to hold
   them; 1.5 sets them as one block, and balance stops the one-word orphan line */
.hint {
  font-size: 14px; line-height: 1.5; letter-spacing: .04em; text-wrap: balance;
  color: var(--muted); margin: 14px 0 12px;
  font-family: var(--body, system-ui, sans-serif); font-weight: 500;
}
.hint b { color: var(--accent-on-paper); font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.tiny { font-size: 12.5px; line-height: 1.45; color: var(--muted); margin-top: 12px; font-family: var(--body, system-ui, sans-serif); font-weight: 600; text-wrap: balance; }
.tiny b { color: var(--accent-on-paper); }

/* ---------- first-time tutorial freeze layer (over-film grammar) ---------- */
.tut { position: absolute; inset: 0; z-index: 8; pointer-events: none; }   /* pointer-events:none is LOAD-BEARING — canvas must keep receiving taps */
.tut-dim {   /* radial spotlight on the threat (dodge/orb) */
  position: absolute; inset: 0;
  background: radial-gradient(circle at var(--sx, 50%) var(--sy, 40%),
    transparent calc(var(--sr, 90px) * 0.8), var(--dim-soft) var(--sr, 90px), var(--dim-strong) calc(var(--sr, 90px) * 1.9));
  animation: fadein .2s ease;
}
.tut.lane .tut-dim {   /* full-height lane band (guardian tell) */
  background: linear-gradient(90deg,
    var(--dim-strong) calc(var(--lx0, 33%) - 6px), var(--dim-faint) var(--lx0, 33%),
    var(--dim-faint) var(--lx1, 66%), var(--dim-strong) calc(var(--lx1, 66%) + 6px));
}
.tut-card {
  position: absolute; top: 16%; left: 50%; transform: translateX(-50%);
  text-align: center; width: max-content; max-width: 86vw;
  animation: pop .35s cubic-bezier(.2,1.7,.4,1);
}
.tut-card h3 {
  font-family: var(--disp); font-size: clamp(28px, 8vw, 44px); color: var(--gold);
  transform: skewX(-6deg); -webkit-text-stroke: 3.5px var(--ink); paint-order: stroke fill;
  text-shadow: 0 5px 0 var(--shadow);
}
.tut-card p {
  margin-top: 8px; font-size: 15px; font-weight: 700; font-family: var(--body);
  color: var(--white); text-shadow: 0 2px 4px var(--shadow);
}
.tut-card p b { color: var(--gold); }
[data-theme="vintage"] .tut-card h3 { transform: none; }   /* vintage strips skew (flat 1930s title card) */
[data-theme="vintage"] .tut-card { animation: pop-steps-center .3s steps(2) both; }   /* pops on twos; keyframes must carry the translateX centering — fill 'both' holds the last frame forever */
.tut-hand {
  position: absolute; bottom: 22%; width: 76px; height: 76px;
  display: grid; place-items: center;
  font-family: var(--disp); font-size: 17px; color: var(--white);
  -webkit-text-stroke: 2px var(--ink); paint-order: stroke fill;
}
.tut-hand.l { left: 14%; } .tut-hand.r { right: 14%; }
.tut-ring {   /* pulsing tap ripple */
  position: absolute; inset: 0; border-radius: 50%;
  border: 4px solid var(--gold); box-shadow: 0 0 18px var(--glow);
  animation: tutring 1.1s ease-out infinite;
}
@keyframes tutring { 0% { transform: scale(.55); opacity: 1; } 70% { transform: scale(1.15); opacity: .25; } 100% { transform: scale(1.25); opacity: 0; } }

/* ---------- fading edge chevrons (early-runs tap affordance) ---------- */
.chev {
  position: absolute; top: 66%; transform: translateY(-50%); z-index: 5; pointer-events: none;
  font-family: var(--disp); font-size: 54px; line-height: 1; color: var(--white);
  -webkit-text-stroke: 3px var(--ink); paint-order: stroke fill;
  text-shadow: 0 0 14px var(--glow);
  animation: chevpulse 1.4s ease-in-out infinite;
}
.chev.l { left: calc(10px + env(safe-area-inset-left, 0px)); }
.chev.r { right: calc(10px + env(safe-area-inset-right, 0px)); }
@keyframes chevpulse { 0%, 100% { transform: translateY(-50%) scale(1); } 50% { transform: translateY(-50%) scale(1.22); } }

/* results stats — a printed table: T3 ink numerals, T7 labels, ledger rules.
   THREE EQUAL COLUMNS: as a flex row the cells were as wide as their own labels,
   so the numerals landed at uneven intervals and the rules never fell on the
   thirds. A grid rules the table properly. */
.death-stats { display: grid; grid-template-columns: repeat(3, 1fr); align-items: end; margin: 16px 0 14px; }
.death-stats div { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.death-stats div + div { border-left: 1.5px solid var(--rule-faint); }
.death-stats span { font-size: 34px; line-height: 1.05; letter-spacing: .02em; color: var(--ink); }
.death-stats small { font-family: var(--body); font-size: 12px; letter-spacing: .1em; color: var(--muted); }
.new-best { font-size: 22px; letter-spacing: .04em; color: var(--accent-on-paper);
  -webkit-text-stroke: var(--stroke-card) var(--ink); paint-order: stroke fill;
  margin-bottom: 6px; animation: pop-steps .34s steps(2); }

/* ---------- the letterpress plate — primary action ----------
   THE BILLING LADDER. Plate width used to be whatever the label measured, so
   PRACTICE FLIGHT printed wider than FLY THE ROUTE and STOKE THE FURNACE bullied
   RESTART off its own card. Now the primary owns the card's full content column
   and the ghost steps in from both edges at a smaller size — the ladder reads the
   same on every panel, whatever the words happen to be. */
.play-btn {
  /* THE PLATE IS A FIXED SIZE. It used to be min-height, so any label long
     enough to wrap grew the box — POST THE CROSSING made the win card's
     primary 85px tall while every other primary was 58px, and stacked above a
     50px ghost and 44px links it read as three mismatched buttons. Fixed
     height + nowrap means a plate is the same plate on every screen; fitPlate()
     shrinks an over-long label to fit rather than letting it wrap or spill. */
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 100%; margin-left: auto; margin-right: auto;
  height: 58px; min-height: 58px; padding: 12px 22px; white-space: nowrap;
  font-family: var(--disp); font-size: clamp(19px, 5.4vw, 24px); letter-spacing: .08em;
  line-height: 1.14; color: var(--ink);
  background-image: var(--paper-grain), var(--btn);
  border: 3px solid var(--ink); border-radius: 12px;
  cursor: pointer; pointer-events: auto;
  box-shadow: 0 5px 0 var(--ink);
  transition: none;   /* a press is a discrete state change — steps(1), no easing */
}
.play-btn::before {   /* the double ink rule — the house signature, over any gradient */
  content: ""; position: absolute; inset: 3px; border: 1.5px solid var(--ink);
  border-radius: 7px; pointer-events: none;
}
.play-btn:active { transform: translateY(4px); box-shadow: 0 1px 0 var(--ink); }
/* not set in the press — but --card IS the card, so the plate used to vanish into
   the paper. --chip keeps a readable plate with the grain still printed on it. */
.play-btn[disabled] { background-color: var(--chip); background-image: var(--paper-grain); color: var(--muted); box-shadow: none; pointer-events: none; }
/* second billing: narrower, shorter, smaller type, single rule, shallower shadow */
.play-btn.ghost {
  width: 88%; height: 50px; min-height: 50px; padding: 11px 14px;
  font-size: clamp(15px, 4.2vw, 18px);
  border-width: 2.5px; border-radius: 11px;
  background-image: var(--paper-grain), var(--btn-ghost); box-shadow: 0 3px 0 var(--ink);
}
.play-btn.ghost::before { content: none; }
.play-btn.ghost:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--ink); }
.win-buttons { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 16px; }
/* the quiet tier rides ONE row, on the win card exactly as on the death card */
.card-links { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; }

/* the one hidden utility — never re-asserted, never outranked */
.hidden { display: none !important; }

/* ---------- keyframes ---------- */
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes ringpop { 0%{transform:scale(1)} 40%{transform:scale(1.35)} 100%{transform:scale(1)} }
@keyframes pop { 0%{transform:scale(.5) skewX(-8deg)} 60%{transform:scale(1.18) skewX(-8deg)} 100%{transform:scale(1) skewX(-8deg)} }
@keyframes floatup { 0%{opacity:0; transform:skewX(-6deg) translateY(8px) scale(.7)} 25%{opacity:1; transform:skewX(-6deg) translateY(-6px) scale(1.1)} 100%{opacity:0; transform:skewX(-6deg) translateY(-56px) scale(1)} }
@keyframes floatup2 { 0%{opacity:0; transform:skewX(-6deg) translateY(8px) scale(.7)} 25%{opacity:1; transform:skewX(-6deg) translateY(-6px) scale(1.1)} 100%{opacity:0; transform:skewX(-6deg) translateY(-56px) scale(1)} }
@keyframes logobounce { 0%,100%{transform:skewX(-8deg) translateY(0) rotate(-1deg)} 50%{transform:skewX(-8deg) translateY(-12px) rotate(1deg)} }
@keyframes fadein { from{opacity:0} to{opacity:1} }
@keyframes bannerPop { 0%{opacity:0; transform:skewX(-7deg) scale(.8) translateY(10px)} 16%{opacity:1; transform:skewX(-7deg) scale(1) translateY(0)} 78%{opacity:1; transform:skewX(-7deg) scale(1) translateY(0)} 100%{opacity:0; transform:skewX(-7deg) scale(1.05) translateY(-10px)} }
@keyframes bannerPop2 { 0%{opacity:0; transform:skewX(-7deg) scale(.8) translateY(10px)} 16%{opacity:1; transform:skewX(-7deg) scale(1) translateY(0)} 78%{opacity:1; transform:skewX(-7deg) scale(1) translateY(0)} 100%{opacity:0; transform:skewX(-7deg) scale(1.05) translateY(-10px)} }

/* ---------- task rows — the burn card, printed in-system ---------- */
.tasks-list { display: flex; flex-direction: column; gap: 8px; margin: 16px 0 0; }
.task-row { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 58px;
  text-align: left; padding: 10px 12px; cursor: pointer; pointer-events: auto;
  background-color: var(--chip); background-image: var(--paper-grain);
  color: var(--chip-text); border: 2.5px solid var(--ink);
  border-radius: 9px 7px 8px 7px / 7px 9px 7px 8px;
  box-shadow: 0 3px 0 var(--shadow-drop); font-family: var(--body); transition: none; }
.task-row:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--shadow-drop); }
.hint + .tasks-list { margin-top: 8px; }   /* the paragraph already left its own air */
/* on the revive card the burn row IS the primary action, so it prints as a plate
   (ink drop, 3px rule) instead of a list chip — the ghost below it is the exit */
#revivePanel .task-row { min-height: 62px; border-width: 3px; box-shadow: 0 4px 0 var(--ink); }
#revivePanel .task-row:active { box-shadow: 0 1px 0 var(--ink); }
#revivePanel .task-body b { font-size: 16.5px; }
.task-ic     { font-size: 20px; width: 26px; text-align: center; color: var(--chip-accent, var(--accent-on-paper)); flex: 0 0 auto; }
.task-body { flex: 1; display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.task-body b { font-family: var(--disp); font-size: 15px; letter-spacing: .06em;
               text-transform: uppercase; font-weight: 400; color: var(--chip-text); }
.task-body small { font-size: 11.5px; color: var(--muted); }
.task-reward { font-family: var(--disp); font-size: 12px; letter-spacing: .08em;
               color: var(--chip-accent, var(--accent-on-paper)); flex: 0 0 auto; white-space: nowrap; }
.task-row.validating { opacity: .75; }
.task-row.validating .task-reward { animation: taskpulse .9s steps(2) infinite; }  /* stepped blink, not a fade */
.task-row.done { border-color: var(--green); }
.task-row.done .task-reward, .task-row.done .task-ic { color: var(--green); }
@keyframes taskpulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

/* ---------- Vintage Toon: flatter title-card UI ---------- */
[data-theme="vintage"] .overlay-card h1 { transform: none; animation: vintbob 2.6s ease-in-out infinite; text-shadow: 0 6px 0 var(--shadow); }
[data-theme="vintage"] .sub, [data-theme="vintage"] .hud-num,
[data-theme="vintage"] .banner, [data-theme="vintage"] .combo, [data-theme="vintage"] .zone-label { transform: none; }
[data-theme="vintage"] .chev { color: var(--ink); -webkit-text-stroke: 3px var(--white); text-shadow: none; }
@keyframes vintbob { 0%,100%{transform:translateY(0) rotate(-1deg)} 50%{transform:translateY(-8px) rotate(1deg)} }

/* ---------- Orbitals: early-90s anime OVA on VHS — episode cards / cockpit gauges / fansubs ----------
   Kills the arcade tells (skew-lean, glows, gloss shines, fat rounded stickers); replaces them with
   static italic title cards, segmented cockpit meters and fansub typography. Latin-only. */

/* — HUD: cockpit readouts (upright, flat print shadows) — */
[data-theme="orbitals"] .hud-label { color: var(--sky); -webkit-text-stroke: 0; text-shadow: 1px 1px 0 rgba(11,11,22,.9); letter-spacing: .3em; }
[data-theme="orbitals"] .hud-num { transform: none; -webkit-text-stroke: 2px var(--ink); text-shadow: 2px 2px 0 rgba(11,11,22,.75); }
[data-theme="orbitals"] .zone-label { transform: none; -webkit-text-stroke: 0; font-size: 13px; letter-spacing: .34em; text-indent: .34em; text-shadow: 1px 1px 0 rgba(11,11,22,.95); }
[data-theme="orbitals"] .ring-icon { animation: none; box-shadow: 0 0 0 2px var(--ink), 3px 3px 0 rgba(11,11,22,.5); }

/* — gauges: segmented cockpit meters (boost / boss) — */
[data-theme="orbitals"] .boost-meter, [data-theme="orbitals"] .boss-bar {
  position: relative; border-radius: 0; border: 1px solid rgba(243,234,216,.5);
  background: rgba(5,7,14,.82); box-shadow: 2px 2px 0 rgba(11,11,22,.6);
}
[data-theme="orbitals"] .boost-meter::before, [data-theme="orbitals"] .boss-bar::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: repeating-linear-gradient(90deg, transparent 0 7px, var(--ink) 7px 9px);
}
[data-theme="orbitals"] .boost-fill { background: var(--gold); box-shadow: none; }
[data-theme="orbitals"] .boss-fill { background: var(--red); }

/* targeting brackets: ONE pseudo, 8 corner gradients — on NON-clipping wrappers */
[data-theme="orbitals"] .boss-ui, [data-theme="orbitals"] .hud-boost { position: relative; }
[data-theme="orbitals"] .boss-ui::after, [data-theme="orbitals"] .hud-boost::after, [data-theme="orbitals"] .tut-card::before {
  content: ""; position: absolute; inset: -7px; pointer-events: none; background-repeat: no-repeat;
  background:
    linear-gradient(var(--gold) 0 0) left  top    / 8px 1px,
    linear-gradient(var(--gold) 0 0) left  top    / 1px 8px,
    linear-gradient(var(--gold) 0 0) right top    / 8px 1px,
    linear-gradient(var(--gold) 0 0) right top    / 1px 8px,
    linear-gradient(var(--gold) 0 0) left  bottom / 8px 1px,
    linear-gradient(var(--gold) 0 0) left  bottom / 1px 8px,
    linear-gradient(var(--gold) 0 0) right bottom / 8px 1px,
    linear-gradient(var(--gold) 0 0) right bottom / 1px 8px;
  background-repeat: no-repeat;
}
[data-theme="orbitals"] .boss-name { transform: none; -webkit-text-stroke: 0; letter-spacing: .3em; font-size: 14px; text-shadow: 1px 1px 0 rgba(11,11,22,.95); }

/* — zone banner: OVA episode title card (letterbox strip, thin rules, EPISODE numeral) — */
[data-theme="orbitals"] .banner {
  transform: none; font-style: italic; letter-spacing: .03em;
  font-size: clamp(28px, 9vw, 54px);
  -webkit-text-stroke: 0; color: var(--white);
  text-shadow: 3px 3px 0 rgba(5,7,14,.9);
  padding: 26px 0 22px;
  background: linear-gradient(180deg, transparent, rgba(5,7,14,.74) 16%, rgba(5,7,14,.74) 84%, transparent);
}
[data-theme="orbitals"] .banner.play { animation: orbBanner 2.4s ease forwards; }
@keyframes orbBanner { 0% { opacity: 0; } 9% { opacity: 1; } 85% { opacity: 1; } 100% { opacity: 0; } }
[data-theme="orbitals"] .banner::before {
  content: ""; display: block; width: min(58vw, 280px); margin: 0 auto 12px;
  border-bottom: 1px solid rgba(243,234,216,.55);
  font-family: var(--disp); font-style: normal; font-size: 13px;
  letter-spacing: .5em; text-indent: .5em; color: var(--gold);
  text-shadow: 1px 1px 0 rgba(5,7,14,.9);
}
[data-theme="orbitals"] .banner[data-ep]::before { content: attr(data-ep); padding-bottom: 8px; }
[data-theme="orbitals"] .banner::after {
  content: ""; display: block; width: min(58vw, 280px); margin: 12px auto 0;
  border-top: 1px solid rgba(243,234,216,.55);
}
[data-theme="orbitals"] .banner .sub-banner {
  font-style: normal; font-size: .26em; letter-spacing: .5em; text-indent: .5em;
  text-transform: uppercase; color: var(--sky); -webkit-text-stroke: 0; margin-top: 10px;
}

/* — overlays: static title-card logo, italic episode-end titles — */
[data-theme="orbitals"] .overlay-card h1 {
  animation: none; transform: skewX(-8deg);
  -webkit-text-stroke: 4px var(--ink);
  text-shadow: 6px 6px 0 rgba(11,11,22,.55);
}
[data-theme="orbitals"] .sub { -webkit-text-stroke: 0; color: var(--muted); letter-spacing: .38em; text-indent: .38em; }
[data-theme="orbitals"] .overlay-card h2 { transform: none; font-style: italic; -webkit-text-stroke: 0; text-shadow: 4px 4px 0 rgba(11,11,22,.65); }

/* fansub subtitles: bold sans, thick black outline (the VHS-fansub look) */
[data-theme="orbitals"] .hint, [data-theme="orbitals"] .tut-card p {
  font-family: "Helvetica Neue", Arial, sans-serif; font-weight: 700; color: #fff;
  -webkit-text-stroke: 2.5px rgba(0,0,0,.9); paint-order: stroke fill;
  text-shadow: 0 2px 0 rgba(0,0,0,.55);
}
[data-theme="orbitals"] .hint b, [data-theme="orbitals"] .tut-card p b { color: #ffe258; }
/* VCR OSD small print */
[data-theme="orbitals"] .tiny { font-family: "Courier New", monospace; font-weight: 700; text-transform: uppercase; letter-spacing: .12em; font-size: 11px; }
[data-theme="orbitals"] #loadNote::before { content: "● "; color: var(--red); animation: orbblink 1.1s steps(1) infinite; }
@keyframes orbblink { 50% { opacity: 0; } }

/* buttons: flat cel print — no shine, no pulse, hard offset shadow */
[data-theme="orbitals"] .play-btn {
  animation: none; transform: none; border-radius: 2px; border-width: 2px;
  background: var(--gold); color: var(--ink); -webkit-text-stroke: 0;
  letter-spacing: .1em; box-shadow: 4px 4px 0 var(--ink);
}
[data-theme="orbitals"] .play-btn::before { content: none; }
[data-theme="orbitals"] .play-btn:active { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--ink); }
[data-theme="orbitals"] .play-btn.ghost { background: rgba(5,7,14,.6); border-color: var(--sky); color: var(--sky); box-shadow: 4px 4px 0 rgba(11,11,22,.8); }

/* results stats + quest rows: anime-OS console list */
[data-theme="orbitals"] .death-stats span { transform: none; -webkit-text-stroke: 2px var(--ink); text-shadow: 2px 2px 0 rgba(11,11,22,.7); }
[data-theme="orbitals"] .death-stats small { color: var(--sky); -webkit-text-stroke: 0; letter-spacing: .18em; }
[data-theme="orbitals"] .new-best { transform: none; animation: none; -webkit-text-stroke: 0; letter-spacing: .2em; text-shadow: 2px 2px 0 rgba(11,11,22,.8); }
[data-theme="orbitals"] .task-row {
  border-radius: 0; border: 1px solid rgba(56,182,214,.35); border-left: 3px solid var(--gold);
  background: rgba(5,8,18,.78); box-shadow: none;
}
[data-theme="orbitals"] .task-row:active { transform: translateY(1px); box-shadow: none; }

/* tutorial: bracket-framed cockpit callout, clean cut-in */
[data-theme="orbitals"] .tut-card { animation: fadein .22s ease; padding: 14px 18px; background: rgba(5,7,14,.55); }
[data-theme="orbitals"] .tut-card::before { inset: 0; }
[data-theme="orbitals"] .tut-card h3 { transform: none; -webkit-text-stroke: 0; letter-spacing: .06em; text-shadow: 3px 3px 0 rgba(11,11,22,.8); }

/* chevrons: flat print, no glow (pulse kept — tap affordance) */
[data-theme="orbitals"] .chev { color: var(--gold); text-shadow: 2px 2px 0 var(--ink); }

/* A period production dims the house lights; it does not frost the glass —
   the blur lives ONLY off the flagship look (vintage must never pay for it). */
html:not([data-theme="vintage"]) .overlay { backdrop-filter: blur(2px); }

/* DEV style switch (local review only, ?dev=1) — exempt from the token law */
.style-switch {
  position: fixed; left: 12px; bottom: max(12px, env(safe-area-inset-bottom, 0px)); z-index: 30;
  font-family: var(--disp); font-size: 12px; letter-spacing: .12em;
  color: var(--white); background: rgba(0,0,0,.45); border: 1px solid var(--gold);
  padding: 8px 12px; border-radius: 2px; pointer-events: auto; cursor: pointer;
}

/* revive flow + death card: stacked plates, printed rhythm (16/10/8) */
#deathScreen .play-btn + .play-btn { margin-top: 10px; }
#deathScreen .rule-link.lg + .play-btn { margin-top: 10px; }
#deathScreen .revive-used + .rule-link { margin-top: 8px; }
#revivePanel .tasks-list + .play-btn { margin-top: 14px; }

/* ── quiet text control — the typeset rule-link (acts ⇒ underlined 2px/4px;
   informs ⇒ never underlined: .tiny/.fineprint/.revive-used) ── */
.rule-link { background: none; border: none; cursor: pointer; pointer-events: auto;
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
  padding: 10px 14px; font-family: var(--disp); font-size: 15px; letter-spacing: .08em;
  line-height: 1.35; text-transform: uppercase; color: var(--accent-on-paper);
  -webkit-text-stroke: var(--stroke-card) var(--ink); paint-order: stroke fill;
  text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 2px; }
/* the rank pull is a SENTENCE, not a headline: at 17px it ran three underlined
   lines and out-shouted the plate under it. A measure + balance keeps it a quiet
   two-line pull, and a 1.5px rule stops three stacked underlines reading as bars. */
.rule-link.lg { display: block; width: 100%; margin: 4px auto 0; padding: 12px 14px;
  font-size: 15.5px; text-wrap: balance; text-underline-offset: 4px;
  text-decoration-thickness: 1.5px; animation: rankpop-steps .3s steps(2); }
.rule-link.sm { font-family: var(--body); font-size: 12.5px; letter-spacing: .14em; }
.rule-link:active { transform: translateY(1px); }                   /* discrete */
.rule-link[disabled] { color: var(--muted); text-decoration: none; pointer-events: none; }
@keyframes rankpop-steps { from { transform: scale(.7); opacity: 0; } to { transform: scale(1); opacity: 1; } }

/* SHARE's WEIGHT is a function of whether the run EARNED a brag (resolveBrag in
   game.js), not a fixed rung. THREE TIERS, ONE LOUD SLOT: quiet = this bare
   rule-link, ghost = .primary-share, plate = .share-plate. RESTART is still
   FIRST and instant in the reading order — on the plate tier it steps down to a
   ghost exactly as it already does for a loud revive, and in a letterpress
   billing ladder the biggest type wins the eye wherever it sits. A loud REVIVE
   always outranks a loud SHARE: two plates on one card is the shakedown look. */
#deathShare.primary-share {   /* same metrics as .play-btn.ghost — one ghost plate in the house */
  display: block; width: 88%; margin: 10px auto 0;
  min-height: 50px; padding: 11px 14px;
  border: 2.5px solid var(--ink); border-radius: 11px;
  background-image: var(--paper-grain), var(--btn-ghost);
  color: var(--ink); -webkit-text-stroke: 0;
  font-size: clamp(15px, 4.2vw, 18px); letter-spacing: .08em;
  text-decoration: none; box-shadow: 0 3px 0 var(--ink);
}
#deathShare.primary-share:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--ink); }

/* THE PLATE TIER — the full .play-btn metrics, shaped exactly like the ghost
   block above so no fourth control family enters the house: same paper, same
   double ink rule, same 5px ink drop, same discrete press. Because both the
   plate and .play-btn are width:100%, swapping the label for an
   acknowledgement can never reflow the card. */
#deathShare.share-plate {
  position: relative; display: block; width: 100%; margin: 10px auto 0;
  min-height: 58px; padding: 12px 22px;
  font-size: clamp(19px, 5.4vw, 24px); letter-spacing: .08em; line-height: 1.14;
  color: var(--ink); -webkit-text-stroke: 0;
  background-image: var(--paper-grain), var(--btn);
  border: 3px solid var(--ink); border-radius: 12px;
  text-decoration: none; box-shadow: 0 5px 0 var(--ink);
}
#deathShare.share-plate::before {   /* the double ink rule — the house signature */
  content: ""; position: absolute; inset: 3px; border: 1.5px solid var(--ink);
  border-radius: 7px; pointer-events: none;
}
#deathShare.share-plate:active { transform: translateY(4px); box-shadow: 0 1px 0 var(--ink); }
/* .primary-share (1,1,0) outranks .rule-link[disabled] (0,2,0) on colour, so a
   spent ghost-plate stayed full ink with its shadow and looked alive. Match the
   disabled treatment on BOTH share families. */
#deathShare.primary-share[disabled], #deathShare.rule-link[disabled] {
  color: var(--muted); box-shadow: none; opacity: .75;
}
#deathShare.share-plate[disabled] {
  background-color: var(--chip); background-image: var(--paper-grain);
  color: var(--muted); box-shadow: none;
}
/* the acknowledgement re-fires the house stamp, on twos like every card motion */
#deathShare.acked, #winShare.acked, #endShare.acked { animation: pop-steps .34s steps(2); }
#deathShare.acked2, #winShare.acked2, #endShare.acked2 { animation: pop-steps2 .34s steps(2); }

/* ── TOP PILOTS leaderboard — ledger rows, paper chips ── */
.lb-title { margin-bottom: 4px; }
/* the ledger rides the card's content column: at min(320px, 84vw) it was wider
   than the paper it printed on, so auto margins collapsed and every row leaned
   left and overran the plate's own margin. Its HEIGHT is measured in whole rows
   (pitch = row + gap) — a free vh cap sliced the last row in half behind the
   sticky YOU chip. 9 pitches, less the trailing gap, = 8 clean rows + yours. */
.lb-list { display: flex; flex-direction: column; gap: 5px; overflow-y: auto;
  --lb-pitch: 37px;
  max-height: min(46vh, calc(9 * var(--lb-pitch) - 5px));
  width: 100%; max-width: 320px; margin: 12px auto 4px; }
.lb-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; min-height: 32px;
  font-variant-numeric: lining-nums tabular-nums; font-feature-settings: 'lnum' 1, 'tnum' 1;
  border-radius: 8px 6px 7px 6px / 6px 8px 6px 7px;
  background-color: var(--chip); background-image: var(--paper-grain);
  border: 2px solid var(--ink); font-family: var(--body, system-ui, sans-serif);
  font-size: 13px; color: var(--chip-text); }
.lb-row .lb-rank { width: 30px; border-right: 1.5px solid var(--rule-faint); padding-right: 6px; margin-right: 2px;
  color: var(--accent-on-paper); font-weight: 800; flex: 0 0 auto; text-align: left; }
.lb-row .lb-name { flex: 1; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-row .lb-score { font-weight: 800; }
.lb-you { margin: 10px 0 2px; }
.lb-you input { width: min(240px, 100%); min-height: 46px; padding: 11px 12px; border-radius: 8px; text-align: center;
  border: 2.5px solid var(--ink); background: var(--chip); color: var(--chip-text);
  font-family: var(--disp); font-size: 15px; letter-spacing: .08em; outline: none; }
.lb-you input::placeholder { color: var(--muted); opacity: .6; }   /* the UA grey merged into the paper */
.lb-you input:focus { box-shadow: inset 0 0 0 2px var(--chip), inset 0 0 0 3.5px var(--ink); }   /* the house double rule as a focus ring */
#lbPanel .play-btn { margin-top: 12px; }
/* the CTA used to wrap to two lines and get clipped by Safari's toolbar */
#lbConnect { font-size: clamp(15px, 4.4vw, 20px); letter-spacing: .04em; white-space: nowrap; padding-left: 18px; padding-right: 18px; }
/* your row is what the eye hunts for: invert it, pin it in view, and give the
   inverted chip its own paper pinstripe */
/* the elision row: skipped ranks, printed as a run of leaders */
.lb-gap { justify-content: center; min-height: 20px; padding: 0 10px; opacity: .5;
  letter-spacing: .3em; pointer-events: none; border: 0; }
.lb-gap .lb-name { text-align: center; }
.lb-row.you {
  background: var(--chip-you, var(--gold)); color: var(--chip-you-text, var(--ink));
  border-color: var(--ink); position: sticky; bottom: 0;
  box-shadow: inset 0 0 0 2px var(--chip-you), inset 0 0 0 3.5px var(--chip-you-text), 0 2px 0 var(--shadow-drop);
}
.lb-row.you .lb-rank { border-right-color: var(--chip-you-text); color: var(--chip-you-text); }
.challenge-line {
  font-family: var(--disp); font-size: 17px; letter-spacing: .06em; margin-top: 8px;
  color: var(--accent-on-paper); -webkit-text-stroke: var(--stroke-card) var(--ink); paint-order: stroke fill;
}
.death-cause { font-size: .34em; letter-spacing: .1em; -webkit-text-stroke: 0; color: var(--muted); vertical-align: middle; }
.revive-used { font-family: var(--body, system-ui, sans-serif); font-size: 12px; letter-spacing: .08em;
  color: var(--muted); margin: 6px 0 0; }
/* the legal truth informs, it never competes: 600-weight ink at full size read as
   loud as the plate above it */
.trust { max-width: 32ch; margin: 10px auto 0; font-size: 11.5px; font-weight: 400; line-height: 1.45; opacity: .78; }
/* …YOUR row is an inverted ink chip, so its cells must flip back or they
   render black-on-black (equal specificity — this rule has to come after) */
[data-theme="vintage"] .lb-row.you .lb-rank,
[data-theme="vintage"] .lb-row.you .lb-name,
[data-theme="vintage"] .lb-row.you .lb-score { color: var(--chip-you-text); }
/* orbitals (OVA) — square brackets look, thin cyan frame */
[data-theme="orbitals"] .lb-row { border-radius: 0; border-width: 1px; border-left: 3px solid var(--gold); }
[data-theme="orbitals"] .lb-you input { border-radius: 0; }

/* fine print — the period-honest home for technical truth (tx states, burn terms) */
.fineprint { display: inline-block; margin-top: 8px; font-size: 11.5px; line-height: 1.45;
  letter-spacing: .02em; opacity: .78; text-wrap: balance; }

/* long period labels must not shatter layouts */
.boss-name { white-space: nowrap; font-size: clamp(13px, 4.6vw, 20px); }

/* ── S2: the film's native grammar ─────────────────────────────────────── */
/* THE END — white-on-black studio ident, the only frame with no paper */
#endCard { background: var(--screen); }
.end-inner { text-align: center; padding: 0 22px; max-width: 100vw; }
/* 20vw ran THE END off both edges of the frame — a title card that doesn't fit
   the screen is not a title card. 15vw is the widest that stays inside the margins. */
.end-the { font-family: var(--disp); font-size: clamp(44px, 15vw, 120px); line-height: 1; color: var(--white); letter-spacing: .04em; }
.end-ident { font-family: var(--body, serif); font-size: 14px; letter-spacing: .3em; text-indent: .3em; color: var(--gold); margin-top: 20px; text-transform: uppercase; }
.end-tap { font-family: var(--body, serif); font-size: 12px; color: var(--gold-dk); margin-top: 38px; animation: endblink 1.6s steps(1) infinite; }
/* WIRE THE NEWS — the ident is the only card with no paper under it, so the
   quiet link prints in gold. It sits AFTER tap-to-continue and stops its own
   click, so tapping anywhere else on the card still deals the win card. */
.end-share { margin-top: 6px; color: var(--gold); -webkit-text-stroke: 0; }
@keyframes endblink { 0%,100% { opacity: .85; } 50% { opacity: .25; } }

/* Episode intertitle: a paper plate with a double ink rule, lantern-slide in */
.title-card {
  position: absolute; left: 50%; top: 26%; transform: translateX(-50%);
  z-index: 6; pointer-events: none; text-align: center;
  min-width: min(320px, 78vw); padding: 20px 30px 22px;
  background: var(--card, #f2eee4);
  border: var(--card-border, 3px solid var(--ink));
  border-radius: var(--card-radius, 10px);
  box-shadow: inset 0 0 0 3px var(--card, #f2eee4), inset 0 0 0 4.5px var(--ink), 0 8px 0 var(--shadow-drop);
  animation: slidein .28s steps(2) both;
}
.title-card.out { animation: slideout .24s steps(2) both; }
@keyframes slidein { from { opacity: 0; transform: translate(-50%, 5px); } to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes slideout { from { opacity: 1; } to { opacity: 0; } }
.tc-eyebrow { font-family: var(--body, serif); font-size: 12.5px; letter-spacing: .3em; color: var(--muted); text-transform: uppercase; }
.tc-name { font-family: var(--disp); font-size: clamp(30px, 9vw, 46px); color: var(--ink); margin: 4px 0 2px; }
.tc-sub { font-family: var(--body, serif); font-style: italic; font-size: 14px; color: var(--muted); }

/* Academy film leader: countdown circle w/ clock wipe, crosshair, flutter */
.leader {
  position: relative; width: 140px; height: 140px; margin: 24px auto 8px;
  border-radius: 50%; border: 3px solid var(--ink);
  box-shadow: 0 0 0 6px var(--card, #f2eee4), 0 0 0 7.5px var(--ink);
  overflow: hidden;
}
.leader-sweep {
  position: absolute; inset: 0; border-radius: 50%;
}
.leader-cross { position: absolute; inset: 0; }
.leader-cross::before, .leader-cross::after {
  content: ""; position: absolute; background: var(--rule-half);
}
.leader-cross::before { left: 50%; top: 0; bottom: 0; width: 1.5px; }
.leader-cross::after { top: 50%; left: 0; right: 0; height: 1.5px; }
.leader-num {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--body); font-weight: 700;
  font-size: 60px; color: var(--ink);
}
#loadScreen .overlay-card { animation: flutter .34s steps(1) infinite; }
@keyframes flutter { 0%,100% { opacity: 1; } 55% { opacity: .93; } }

/* the HUD steps aside while the iris owns the frame */
.hud.dimmed { opacity: .12; transition: opacity .25s ease; }

/* ── S5: period print & the keep-the-mistakes motion pass ─────────────────
   Machine-perfect easing is a 2020s tell. UI motion steps like a hand-cranked
   print: cards deal in on twos, pops land in two frames, the score clunks over
   like an odometer wheel. */
.overlay:not(.hidden) .overlay-card { animation: cardIn .22s steps(2) both; }
/* the alternating pair: the death card re-triggers its deal-in at the iris
   hand-off with a class toggle — no reflow, no forced layout */
.overlay:not(.hidden) .overlay-card.deal2 { animation-name: cardIn2; }
@keyframes cardIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes cardIn2 { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
#distance.roll { animation: odoroll .28s steps(2); }
#distance.roll2 { animation: odoroll2 .28s steps(2); }
@keyframes odoroll { 0% { transform: skewX(-6deg) translateY(-3px); } 50% { transform: skewX(-6deg) translateY(2px); } 100% { transform: skewX(-6deg); } }
@keyframes odoroll2 { 0% { transform: skewX(-6deg) translateY(-3px); } 50% { transform: skewX(-6deg) translateY(2px); } 100% { transform: skewX(-6deg); } }
[data-theme="vintage"] #distance.roll { animation: odoroll-flat .28s steps(2); }
[data-theme="vintage"] #distance.roll2 { animation: odoroll-flat2 .28s steps(2); }
@keyframes odoroll-flat { 0% { transform: translateY(-3px); } 50% { transform: translateY(2px); } 100% { transform: none; } }
@keyframes odoroll-flat2 { 0% { transform: translateY(-3px); } 50% { transform: translateY(2px); } 100% { transform: none; } }
@keyframes pop-steps { from { transform: scale(.6); } to { transform: none; } }
/* the alternating twin: an acknowledgement label swap re-fires the stamp with a
   class toggle — no reflow, no void offsetWidth */
@keyframes pop-steps2 { from { transform: scale(.6); } to { transform: none; } }
@keyframes pop-steps-center { from { transform: translateX(-50%) scale(.6); } to { transform: translateX(-50%); } }
[data-theme="vintage"] .banner { animation-timing-function: steps(3); }
/* the logo bob breathes on sixes — a hand-held rostrum camera, not a tween */
[data-theme="vintage"] .overlay-card h1 { animation-timing-function: steps(6); }

/* ── S5 coherence fixes (blind panel round 2) ─────────────────────────────
   The keeper's name lives ONCE, in the top chip — the mid-screen DOM ghost
   read as a z-order bug to every judge who saw it. */
[data-theme="vintage"] .boss-name { display: none; }
/* Meters are printed gauges, not app pills: ink frame, paper gap, ink fill. */
[data-theme="vintage"] .boss-bar, [data-theme="vintage"] .boost-meter {
  border-radius: 3px; border: 3px solid var(--ink); background: var(--card, #efece3);
  padding: 2px; height: 18px; box-sizing: border-box; box-shadow: 0 3px 0 var(--shadow-drop);
}
[data-theme="vintage"] .boost-meter { height: 16px; }
[data-theme="vintage"] .boss-fill, [data-theme="vintage"] .boost-fill {
  background: var(--ink); box-shadow: none; border-radius: 1px; transition: width .15s steps(3);
}
/* Graduation ticks so an empty gauge still reads as a printed instrument. */
[data-theme="vintage"] .boss-bar, [data-theme="vintage"] .boost-meter {
  background-image: repeating-linear-gradient(90deg, transparent 0 9px, var(--rule-faint) 9px 10px);
}
/* REDLINE (M2): the STEAM track's last 15% is cross-hatched — a printed
   danger zone — and past it the whole instrument rattles on twos. */
[data-theme="vintage"] .boost-meter {
  background-image: repeating-linear-gradient(90deg, transparent 0 9px, var(--rule-faint) 9px 10px),
    repeating-linear-gradient(45deg, transparent 0 3px, var(--rule-faint) 3px 5px);
  background-size: 100% 100%, 15% 100%;
  background-position: 0 0, 100% 0;
  background-repeat: repeat, no-repeat;
}
.boost-meter.redline { animation: needlerattle .18s steps(2) infinite; }
@keyframes needlerattle { 0% { transform: translateX(-1px); } 100% { transform: translateX(1px); } }
/* The 6px stroke ate the slab face's letter gap — CRASHED read double-struck. */
[data-theme="vintage"] .overlay-card h1 { letter-spacing: .1em; -webkit-text-stroke-width: 5px; }

/* ── M3: THE VERDICT + THE ALBUM ──────────────────────────────────────────
   The verdict is a letterpress plate stamped over the keeper's fall — paper
   plate, double ink rule (the title-card material), steps() motion, no glow
   (§0.8). It waits half a beat so the KO card lands first. */
.verdict {
  position: absolute; left: 50%; top: 46%; z-index: 5; pointer-events: none;
  transform: translateX(-50%) rotate(-3deg);
  font-family: var(--disp); font-size: clamp(17px, 5vw, 30px); letter-spacing: .12em;
  color: var(--ink, #1a1410); text-transform: uppercase; text-align: center; white-space: nowrap;
  padding: 10px 20px 12px; max-width: 94vw;
  background: var(--card, #f2eee4);
  border: 3px solid var(--ink, #1a1410);
  border-radius: var(--card-radius, 6px);
  box-shadow: inset 0 0 0 2.5px var(--card, #f2eee4), inset 0 0 0 4px var(--ink, #1a1410), 0 6px 0 var(--shadow-drop);
  opacity: 0;
}
.verdict.play { animation: verdictstamp .3s steps(2) both; animation-delay: .45s; }
.verdict.play2 { animation: verdictstamp2 .3s steps(2) both; animation-delay: .45s; }
@keyframes verdictstamp2 { from { opacity: 0; transform: scale(1.6); } to { opacity: 1; transform: scale(1); } }
@keyframes verdictstamp {
  from { opacity: 0; transform: translateX(-50%) rotate(-3deg) scale(1.5); }
  to   { opacity: 1; transform: translateX(-50%) rotate(-3deg) scale(1); }
}

/* The album line — six printer's marks (● ◐ ✕ ○), small type under the
   subtitle / stats. Sorts from the case, not emoji. */
.album-line {
  font-family: var(--body, serif); font-size: 12px; letter-spacing: .28em; text-indent: .28em;
  color: var(--muted); margin-top: 6px; text-transform: uppercase;
}
[data-theme="vintage"] .album-line { color: var(--ink); opacity: .72; }

/* ── M4: TODAY'S ROUTE — the daily appointment ────────────────────────────
   The schedule card is set type only: the postmark is a rotated double ring
   with the route number inside (a rubber stamp, not an image). The board's
   TODAY · ALL-TIME line is one typeset rule — the live word underlined like
   a typeset link (§0.8), never a modern pill tab. */
.lb-tabs { display: flex; align-items: center; justify-content: center; gap: 2px; margin: 2px 0 0; }
.lb-tab {
  background: none; border: none; cursor: pointer; pointer-events: auto;
  min-height: 44px; display: inline-flex; align-items: center; padding: 6px 10px;
  font-family: var(--disp); font-size: 14px; letter-spacing: .1em; color: var(--accent-on-paper);
  -webkit-text-stroke: var(--stroke-card) var(--ink); paint-order: stroke fill; opacity: .62;
}
.lb-tab.on { opacity: 1; text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 2px; }
.lb-tab:active { transform: translateY(1px); }
.lb-tab-sep { font-family: var(--disp); font-size: 14px; color: var(--muted); }
.lb-subhead { margin: 2px 0 0; letter-spacing: .18em; text-transform: uppercase; }

.daily-post {
  width: 148px; height: 148px; margin: 16px auto 2px; border-radius: 50%;
  border: 2.5px solid var(--ink);
  box-shadow: inset 0 0 0 5px var(--card, #f2eee4), inset 0 0 0 6.5px var(--ink);
  display: grid; place-content: center; gap: 2px; transform: rotate(-7deg);
}
.daily-no {
  font-family: var(--disp); font-size: 34px; letter-spacing: .04em;
  color: var(--accent-on-paper); -webkit-text-stroke: var(--stroke-card) var(--ink); paint-order: stroke fill;
}
.daily-hint { margin: 14px 0 16px; }
#dailyStatus { color: var(--accent-on-paper); }   /* a status is not a challenge banner */
#dailyPanel .play-btn { margin-top: 10px; }
#dailyPodium { max-height: 24vh; margin-top: 4px; }

/* ── transition grammar: every user-dismissed overlay exits (dismiss() in
   game.js); being covered by a stacked panel is NOT a dismissal ── */
.overlay.closing { animation: fadeout .18s ease both; pointer-events: none; }
/* descendants re-assert pointer-events:auto (overlay hit-testing law), which
   would keep an invisible fading panel tappable — a dismissing overlay is inert */
.overlay.closing * { pointer-events: none !important; }
.overlay.closing .overlay-card { animation: cardOut .18s steps(2) both; }
@keyframes fadeout { to { opacity: 0 } }
@keyframes cardOut { to { opacity: 0; transform: translateY(4px) } }
/* no infinite animation may run on occluded elements (S6): show()/hide()
   keep body.overlay-up current; everything else infinite lives inside
   overlays and stops via display:none */
body.overlay-up .ring-icon::after, body.overlay-up .chev { animation-play-state: paused; }

/* ── H2b THE HOME STAGE — DOM props over the canvas scene ─────────────────
   The scene (backdrop, boards, spotlight, cat, curtains) is canvas-drawn and
   film-graded; #startScreen carries only set dressing. The container is
   pointer-transparent so tap-anywhere reaches the stage; every prop that
   acts re-asserts pointer-events. House dim lives in canvas layer 4 — the
   overlay wash and the blur are gone from this screen on every theme. */
#startScreen { display: block; background: none; animation: none; pointer-events: none; }
html:not([data-theme="vintage"]) #startScreen { backdrop-filter: none; }
#startScreen .marquee, #startScreen .cat-bubble, #startScreen .start-hint { pointer-events: none; }
#startScreen .challenge-line { display: none; }   /* text carrier for the bubble ladder */

/* the hanging marquee plate — title-card material, bobbing on sixes */
/* ---- THE CURTAIN CALL: the drawn logo, and the paper apron ------------------
   The painting occupies a panel whose exact top/bottom the canvas publishes as
   --stage-top / --stage-bot (one source of truth: if the pan-and-scan maths
   changes, the paper follows it without a second set of numbers). Below the
   panel is the apron: real paper, where every control lives. */
:root { --stage-top: 0%; --stage-bot: 100%; --cat-top: 42%; }

.marquee {
  position: absolute; top: calc(1.2% + var(--ui-top)); left: 0; right: 0; margin: 0 auto;
  width: fit-content; max-width: 68vw;
  padding: 10px 18px;                     /* air on all four sides, per the note */
  text-align: center; pointer-events: none; z-index: 3;
}
.cc-logo {
  display: block; width: min(62vw, 360px); height: auto;
  /* the drawn line is already inked; it only needs to sit ON the world, so a
     single hard letterpress drop, no glow, no stroke, no skew */
  filter: drop-shadow(0 3px 0 var(--shadow));
}
.album-line, .challenge-line { position: relative; z-index: 3; }

/* the apron: the paper the painting stands on. Portrait only — in landscape the
   stage is full-bleed and the controls sit over the painting's dark edges. */
.home-apron { display: none; }   /* retired: the painting is full-bleed now */
@media (min-aspect-ratio: 95/100) {
  /* LANDSCAPE: the painting is full-bleed, so there is no apron to sit on —
     the controls ride the painting's own dark floor above the plaque. */
  .home-apron { display: none; }


  .cc-logo { width: min(30vw, 340px); }
}
/* ---- THE PILOT'S STRIP + THE SIDE COLUMN ------------------------------------
 The reference carries identity and progress along the top edge. Ours does the
 same with REAL numbers, and when no wallet is attached the strip becomes one
 unmistakable call to action IN THE SAME SHAPE AND PLACE — connecting fills the
 furniture in rather than rearranging it. */
.pilot-strip {
/* THE LOBBY MARGIN: one inset, used by the strip and both prop columns, so
   nothing in the lobby ever touches the frame's edge. */
position: absolute; top: calc(11.5% + var(--ui-top));
left: calc(var(--lobby-pad) + env(safe-area-inset-left, 0px));
right: calc(var(--lobby-pad) + env(safe-area-inset-right, 0px));
display: flex; align-items: center; gap: 9px;
padding: 6px 10px; box-sizing: border-box;
background-color: rgba(20,18,16,.62);
border: 2px solid var(--ink);
border-radius: 11px 8px 10px 8px / 8px 11px 8px 10px;
pointer-events: none; z-index: 4;
}
.pilot-face {
flex: 0 0 auto; width: 38px; height: 38px;
background: center/contain no-repeat url('assets/vintage/cc-face.png');
}
.pilot-body { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; min-width: 0; }
.pilot-name {
font-family: var(--disp); font-size: 13px; letter-spacing: .04em;
color: var(--paper, #f2eee4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pilot-stats {
font-family: var(--body); font-size: 11.5px; letter-spacing: .05em;
color: #cfc7b6; white-space: nowrap;
}
/* the default state: a real button, unmissable, but the same material family */
.pilot-cta {
margin-left: auto; flex: 0 0 auto; pointer-events: auto; cursor: pointer;
font-family: var(--disp); font-size: 11.5px; letter-spacing: .08em;
padding: 8px 12px; min-height: 38px; color: var(--ink);
/* PAPER, not the accent token: in vintage --accent-on-paper IS ink, so an accent
   fill produced dark type on a dark plate — invisible. A paper plate on the dark
   strip is unmissable AND the same material as PLAY, which is what "consistent
   with the connected state" has to mean. */
background-color: var(--chip, #f7f4ec); background-image: var(--paper-grain);
border: 2px solid var(--ink);
border-radius: 9px 7px 8px 7px / 7px 9px 7px 8px;
box-shadow: 0 3px 0 var(--ink); transition: none;
}
.pilot-cta:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--ink); }

/* the two side buttons hang under the strip, right-aligned to the SAME inset as
 ROUTE in the deck below — one number, so the two edges can never drift */
.side-col {
position: absolute; top: calc(22.5% + var(--ui-top));
right: calc(var(--lobby-pad) + env(safe-area-inset-right, 0px));
display: flex; flex-direction: column; align-items: center; gap: 9px;
pointer-events: none; z-index: 3;
}


.cat-bubble {
  /* Hangs off his CROWN, so it can never cover him. It needs no floor against the
     pilot strip: a max() would only shove it DOWN onto his head, and the two
     constraints genuinely have no solution when the crown rides above the strip.
     Room is guaranteed upstream instead — catH is capped so the crown always
     clears the strip plus this bubble (see CAT_HEAD_ROOM in game.js). */
  position: absolute; left: 50%; top: calc(var(--cat-top, 40%) - 14px);
  transform: translate(-50%, -100%);
  max-width: 62vw; padding: 8px 13px 9px;
  font-family: var(--disp); font-size: 13.5px; letter-spacing: .07em; line-height: 1.45;
  text-align: center; text-wrap: balance;  /* two even lines, never a one-word second line */
  color: var(--ink);   /* lines arrive typeset — "No." keeps its period case */
  background-color: var(--card); background-image: var(--paper-grain);
  border: 3px solid var(--ink);
  border-radius: 10px 8px 10px 9px / 8px 10px 9px 10px;
  box-shadow: 0 4px 0 var(--shadow-drop);
  animation: bubpop .22s steps(2) both;
}
.cat-bubble.bub2 { animation-name: bubpop2; }   /* alternating names restart the pop — no reflow */
.cat-bubble::after {   /* the tail: a rotated square pointing at the cat's mouth */
  content: ""; position: absolute; left: 50%; bottom: -9px;
  width: 14px; height: 14px; transform: translateX(-50%) rotate(45deg);
  background-color: var(--card);
  border-right: 3px solid var(--ink); border-bottom: 3px solid var(--ink);
}
/* scale-only pop: visibility must never ride an animation's end state */
@keyframes bubpop  { from { transform: translate(-50%, -100%) scale(.6); } to { transform: translate(-50%, -100%); } }
@keyframes bubpop2 { from { transform: translate(-50%, -100%) scale(.6); } to { transform: translate(-50%, -100%); } }

/* corner-column letterpress chips — paper chip material, fixed column spots */
/* the two corner columns: absolutely placed, but their chips are IN FLOW so a
   two-line chip pushes instead of overprinting; pointer-transparent between
   chips so tap-anywhere still reaches the stage */
/* ---- THE CONTROL DECK ------------------------------------------------------
   Founder's layout, mapped off the reference lobby: PLAY dead centre at the
   bottom, SOUND to its left, ROUTE to its right, and MISSIONS + TOP PILOTS
   stacked in a column that hangs directly above ROUTE. Buttons are PROPS: the
   illustrated object is the button, and a small caps label only names it, so the
   type can stay quiet while the object does the talking. */
.deck {
  position: absolute; left: 0; right: 0; bottom: calc(2.6% + var(--ui-bottom));
  /* CENTRED, NOT BOTTOM-ALIGNED. flex-end lined the plaque's bottom up with the
     bottom of the props' LABELS, so PLAY's optical centre floated above the two
     icons either side of it. Centring makes the three read as one row. */
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px; box-sizing: border-box;
  /* each side takes ITS OWN inset — one shared value applied to both would
     shift the deck sideways on a notched phone held in landscape */
  padding-left: calc(var(--lobby-pad) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--lobby-pad) + env(safe-area-inset-right, 0px));
  pointer-events: none; z-index: 3;
}
.deck-col { display: flex; flex-direction: column; align-items: center; gap: 8px; }

.prop-btn {
  display: flex; flex-direction: column; align-items: center; gap: 5px;   /* the label had 2px and sat ON the art */
  background: none; border: 0; padding: 2px; margin: 0;
  cursor: pointer; pointer-events: auto;
  min-width: 44px; min-height: 44px;                 /* the touch floor holds */
  transition: none;                                   /* a press is discrete */
}
.prop-art {
  display: block; width: var(--prop-art); aspect-ratio: 1 / 1;
  background: center/contain no-repeat;
  filter: drop-shadow(0 2px 0 rgba(16,14,12,.5));
}
.prop-art[data-prop="sound"]    { background-image: url('assets/vintage/prop-sound.png'); }
.prop-art[data-prop="route"]    { background-image: url('assets/vintage/prop-route.png'); }
.prop-art[data-prop="missions"] { background-image: url('assets/vintage/prop-missions.png'); }
.prop-art[data-prop="pilots"]   { background-image: url('assets/vintage/prop-pilots.png'); }
/* the label: SMALL caps. It names the prop, it never competes with it — so its
   plate must stay INSIDE the prop's own width. At 2.3vw/.08em, TOP PILOTS and
   ROUTE 210 printed WIDER than the art above them, which is exactly why the
   right-hand props read as the smaller pair. */
.prop-label {
  font-family: var(--disp); font-size: clamp(8px, 2.15vw, 9.5px);
  letter-spacing: .05em; line-height: 1.05; white-space: nowrap;
  color: var(--paper, #f2eee4);
  /* a plate, not a shadow: over the lit drape a glow still merged into white */
  padding: 3px 6px 4px; border-radius: 5px;
  background: rgba(18,16,14,.82);
}
.prop-btn:active .prop-art { transform: translateY(2px); }

/* PLAY: the one loud thing on the stage. Its own material, twice the presence. */
.play-plaque {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: clamp(128px, 40vw, 210px); min-height: 62px; padding: 10px 16px;
  cursor: pointer; pointer-events: auto; transition: none;
  background-color: var(--chip); background-image: var(--paper-grain);
  border: 3px solid var(--ink);
  border-radius: 12px 9px 11px 9px / 9px 12px 9px 11px;
  box-shadow: 0 5px 0 var(--ink), 0 5px 0 4px rgba(16,14,12,.35);
}
.play-plaque::before {                                /* the inner letterpress rule */
  content: ""; position: absolute; inset: 4px;
  border: 1.5px solid var(--ink); border-radius: 8px 6px 7px 6px / 6px 8px 6px 7px;
  pointer-events: none;
}
.play-word {
  font-family: var(--disp); font-size: clamp(22px, 7vw, 34px);
  letter-spacing: .1em; color: var(--ink); line-height: 1;
}
.play-plaque:active { transform: translateY(4px); box-shadow: 0 1px 0 var(--ink); }

/* THE BUBBLE must never cover him: it is pinned ABOVE his head, which the canvas
   reports as --cat-top (feet minus height), so it tracks any scale change. */
.home-status {
  position: absolute; left: 0; right: 0; top: 4.5%; margin: 0;
  text-align: center; pointer-events: none; z-index: 3;
  font-family: var(--body); font-size: 12px; letter-spacing: .06em;
  color: var(--paper, #f2eee4); text-shadow: 0 1px 0 rgba(16,14,12,.9);
}
.start-hint { display: none; }
.start-hint-unused {   /* retired: the bubble speaks and M7 teaches in-run */
  position: absolute; left: 50%; top: auto; bottom: calc(4.5% + 70px); transform: translateX(-50%);
  margin: 0; white-space: nowrap; letter-spacing: .12em;
  /* it sits on painted deck boards: type alone was illegible, so it prints on
     its own slip of paper like every other label in the house */
  padding: 5px 12px; color: var(--ink);
  background-color: var(--chip); background-image: var(--paper-grain);
  border: 2px solid var(--ink); border-radius: 7px 6px 7px 6px / 6px 7px 6px 7px;
  box-shadow: 0 2px 0 var(--shadow-drop);
}

/* the HUD steps offstage while the home scene / reel owns the frame; its
   infinite spin pauses there (S6 — nothing animates while occluded) */
.hud.offstage { opacity: 0; }
.hud.offstage .ring-icon::after { animation-play-state: paused; }

/* ---- THE STAT ROW (reference-shaped) + THE BACK ARROW -----------------------
   The reference reads its top bar as icon+value pairs. Ours does the same with
   real numbers: rank, best metres, lifetime coins, keepers beaten. */
.pilot-stats { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.pilot-stats .stat { display: inline-flex; align-items: center; gap: 4px; }
.pilot-stats b {
  font-family: var(--disp); font-size: 12.5px; letter-spacing: .02em;
  color: var(--paper, #f2eee4); font-weight: 400;
}
.stat-ic { width: 15px; height: 15px; display: block; background: center/contain no-repeat; opacity: .95; }
.ic-rank { background-image: url('assets/vintage/prop-pilots.png'); }
.ic-best { background-image: url('assets/vintage/prop-route.png'); }
.ic-coin { background-image: url('assets/vintage/ston-coin.png'); }   /* the real filename */
.ic-keep { background-image: url('assets/vintage/prop-missions.png'); }
.pilot-unlink {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer; pointer-events: auto;
  font-family: var(--body); font-size: 10.5px; letter-spacing: .06em;
  color: #b9b0a0; text-decoration: underline; text-underline-offset: 2px;
}
/* the drawn back arrow — one prop, every panel, top-left of the card. The arrow
   owns a BAND of its own: at 6px of air a long title (YOU CROSSED THE BRIDGE)
   printed straight through the arrowhead. 22px clears the art on every card. */
.overlay-card > .back-arrow + h2 { margin-top: 22px; }
.back-arrow {
  position: absolute; left: 8px; top: 6px; z-index: 5;
  width: 44px; height: 44px; padding: 0; margin: 0;      /* the touch floor holds (was 40) */
  background: center/58% no-repeat url('assets/vintage/prop-back.png');
  border: 0; cursor: pointer; pointer-events: auto; transition: none;
}
.back-arrow:active { transform: translateX(-2px); }
.overlay-card { position: relative; }        /* so the arrow can anchor to it */
.album-key { color: var(--muted); letter-spacing: .04em; margin-top: 2px; }
/* SOUND OFF is its own drawing, not a restyle of the horn */
.prop-art[data-prop="sound"].off { background-image: url('assets/vintage/prop-soundoff.png'); }


/* ── THE TAKEOFF (curtain call) ─────────────────────────────────────────────
   While the sequence plays the frame belongs to the stage. Everything the
   lobby put on top steps off — chips, plaque, bubble, pilot strip, hint —
   and ONLY the STONIC marquee stays, hanging over the scene like a theatre
   sign. One class, so every prop leaves and returns together. */
body.reel-up #startScreen > *:not(.marquee) { display: none !important; }
body.reel-up #catBubble,
body.reel-up .pilot-strip,
body.reel-up .deck,
body.reel-up .side-col { display: none !important; }


/* ── THE PILOT'S FACE (Mini App) ────────────────────────────────────────────
   A round letterpress disc carrying the pilot's initial, struck in our own ink
   so a leaderboard of strangers still looks like one printed page. When a
   Telegram photo exists it is laid OVER this as a background — so an expired
   CDN url degrades to the letter rather than to a broken image, which is why
   the letter is always painted underneath rather than instead. */
.lb-face {
  flex: 0 0 auto; width: 26px; height: 26px; margin-right: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--disp); font-size: 13px; line-height: 1; color: var(--ink);
  background-color: var(--chip); background-size: cover; background-position: center;
  border: 2px solid var(--ink); border-radius: 50%;
  box-shadow: 0 2px 0 var(--shadow-drop);
  overflow: hidden; user-select: none;
}
.lb-row.you .lb-face { box-shadow: 0 2px 0 var(--ink); }

/* ═══════════════════════════════════════════════════════════════════════════
   THE SECOND CHAIR (co-op builds only)

   The chair is drawn on the canvas; this button is its hit target, positioned
   from the --chair-* vars drawHomeScene publishes. Doing it this way rather
   than hit-testing the canvas means focus, hit slop, keyboard access and
   VoiceOver all come for free, and it matches the deck's own "the prop is the
   button" language.

   It stays .hidden on the review build — the markup ships, the chair does not.
   ═══════════════════════════════════════════════════════════════════════════ */
.chair-hit {
  position: absolute;
  left: var(--chair-cx, 30%);
  top: var(--chair-top, 40%);
  height: var(--chair-h, 30%);
  width: calc(var(--chair-h, 30%) * 0.62);
  transform: translateX(-50%);
  background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; z-index: 6;
  -webkit-tap-highlight-color: transparent;
}
.chair-hit:disabled { cursor: default; }
/* The + is the invite gesture. It rides the chair's top-left like a tack on the
   playbill, and only appears when tapping would actually do something. */
.chair-plus {
  position: absolute; left: 50%; top: -6%;
  transform: translateX(-50%);
  display: grid; place-items: center;
  width: 34px; height: 34px;
  font: 700 22px/1 var(--disp, Georgia, serif);
  color: var(--card, #f2eee4);
  background: var(--ink, #1a1a18);
  border: 2px solid var(--card, #f2eee4);
  border-radius: 50%;
  box-shadow: 0 2px 0 var(--shadow, rgba(26,26,24,.35));
  transition: transform .12s ease-out;
}
.chair-hit:active .chair-plus { transform: translateX(-50%) scale(0.92); }
.chair-hit.busy .chair-plus { display: none; }

/* The room code / status line, set under the chair on the apron. */
.chair-note {
  position: absolute;
  left: var(--chair-cx, 30%);
  top: calc(var(--chair-top, 40%) + var(--chair-h, 30%) + 6px);
  transform: translateX(-50%);
  margin: 0; z-index: 6;
  text-align: center; white-space: nowrap;
  letter-spacing: .06em; text-transform: uppercase;
  font-weight: 800;
  text-shadow: 0 1px 0 var(--card, #f2eee4);
}
