/* oktodrop — "midnight arcade in the abyss" (site tokens) */
:root{
  --abyss-0:#03040c; --abyss-1:#070a1c; --abyss-2:#0c1334;
  --ink:#e9f1ff; --ink-dim:#8290c0; --ink-faint:#4d5a85;
  --mag:#c061ff; --mag-deep:#7c3aed;           /* Okto-Drop accent */
  --gold:#ffd23f;
  --panel:rgba(11,17,40,.74); --edge:rgba(124,154,255,.18);
  --font-ui:'Chakra Petch', ui-sans-serif, system-ui, sans-serif;
}
*{box-sizing:border-box}
/* wave-4 (2026-09-25) — scroll containment: the document itself never scrolls
   (fixed full-bleed + overflow + overscroll kill), so #stage's JS sizing to
   the visual viewport is the single geometry truth — no large/visual-viewport
   mismatch can pan content into the unsafe zone. */
html,body{position:fixed;inset:0;margin:0;height:100%;overflow:hidden;overscroll-behavior:none}
body{background:var(--abyss-0);color:var(--ink);font-family:var(--font-ui);
  display:flex;align-items:center;justify-content:center;overflow:hidden;touch-action:none}
/* Task 38 — OS touch hardening (iPhone Safari / Android Chrome): no long-press
   callout, no selection loupe, no tap flash, no rubber-band overscroll. The
   body's touch-action:none already stops native pan/zoom on descendants; the
   tappable game surfaces repeat it explicitly so no future child rule can
   re-open a native gesture. Pointer-event input is unaffected. */
html,body{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;
  -webkit-tap-highlight-color:transparent;overscroll-behavior:none}
#game,#intermission,#rotate-prompt,#hud{touch-action:none}
/* HUD-redesign Task 6 — the stage is FULL-BLEED on EVERY pointer class: the
   aspect-fit 580/720 desktop cabinet (radius + shadow frame) is retired; the
   touch-only full-bleed media override below died with it (now the base).
   The authored 580-wide canvas survives only in the phone portrait branch
   (game.js recomputeLayout). */
/* wave-4 — the vh pair is the PRE-JS fallback only: recomputeLayout writes
   explicit style.width/height from window.innerWidth/innerHeight (the VISUAL
   viewport — 100vh is the iOS LARGE viewport and let the HUD sit in the
   unsafe zone when Safari's bars changed state). 100dvh wins where supported. */
#stage{position:relative;width:100vw;height:100vh;height:100dvh;
  background:radial-gradient(90% 70% at 50% 30%, var(--abyss-2), var(--abyss-1) 55%, var(--abyss-0))}
#game{width:100%;height:100%;display:block}
/* HUD (built by game.js inside #stage) — themed per era via --era-* custom
   props set by applyEraTheme() from the era palette.
   Task 44 — three-region layout: a TEXT-ONLY top strip (SCORE + LINES x/y)
   and a RIGHT rail (NEXT-2 queue) on desktop/touch-wide; the phone keeps its
   maximized board (NEXT-2 folds into the strip's right end, controls stay in
   the bottom band). HUD-redesign Task 16 (user ruling 2026-09-24): the LEFT
   rail is DELETED on every layout — the corner-meter pill
   (#undertow-fab.flush-pill) is the one flush meter everywhere (phone: the
   bottom band; wide: the board's bottom-left corner, docked inline by
   applyHudScale). Only sizes/styles live here. */
#hud{position:absolute;inset:0;pointer-events:none;z-index:3;
  --era-accent:var(--mag);--era-text:var(--ink);--era-dim:var(--ink-dim)}
#hud-top{position:absolute;display:flex;align-items:stretch;justify-content:center;gap:12px}
#hud-top .hud-box{flex:0 1 auto;padding:2px 14px;text-align:center}
#hud-top .hud-label{margin-bottom:1px}
/* Wave 2a (playtest 2026-09-24, text visibility): the value selectors target
   .hud-value BY CLASS — the old `.hud-text div:last-child` form silently
   stopped matching #hud-lines when the goal-track div was appended inside the
   LINES box (last-child moved to the track), leaving LINES at the 16px
   browser default next to a 22px SCORE. Sizes raised while here: values 24
   wide / 18 phone (glanceable from ~1 m / ~40 cm), labels 12 / 11. */
#hud-lines-box .goal-track{width:100%;min-width:64px;margin-top:2px}
#hud-top #hud-next{margin-left:auto;align-self:center}   /* phone: queue parks at the strip's right end */
/* Mode wave T5 — the DAILY TIME countdown cell (spec §2): renders only in
   daily mode ([hidden] is the mode gate, written by updateDailyHud), rides
   the gated #hud container (the chrome-gating law's container leg — no new
   selector-list entry needed). Wide: the centered strip's third cell (DOM
   order). Phone: its auto margin pushes it toward the strip's right end,
   where it sits beside the parked NEXT queue (the two auto margins split the
   free space). Tabular figures inherit .hud-value. The coda window switches
   the readout to the era accent + a flash-pulse (charge-driven family — the
   RM kill-switch stills the pulse to a static accent readout). */
#hud-time-box[hidden]{display:none}
#hud-time{min-width:3.4ch;text-align:center}
#hud.hud-phone #hud-time-box{margin-left:auto;padding:1px 8px}
/* fix round 1: --time-accent is injected inline by applyEraTheme — #hud
   carries CONSTANT --era-* fallbacks (the text-only strip law), so the
   era-accent switch needs its own channel into the cell */
#hud-time.coda{color:var(--time-accent,#c061ff)}
#hud-time.coda{animation:time-coda 1.1s ease-in-out infinite}
@keyframes time-coda{0%,100%{color:var(--time-accent,#c061ff)}50%{color:#fff}}
@media (max-width:359px){ #hud.hud-phone #hud-time-box{padding:1px 4px} }
/* HUD-redesign Task 16 — the left rail (#hud-left/.vgauge/#flush-gauge) is
   DELETED on every layout: the corner-meter pill below is the one flush
   meter (the wide dock is written inline by applyHudScale). */
/* right dock — the NEXT-2 queue panel (desktop/touch-wide only). HUD-redesign
   Task 7: the dock is positioned + sized by formula in applyHudScale
   (boardRight + 8, top-aligned, panel = 4 mini-cells + 16 gutter); the
   #hud-reserved box is DELETED (Task 16 — the pill meters from the board's
   bottom-left corner, not this rail). */
#hud-right{position:absolute;display:flex;flex-direction:column}
#hud-next-wrap{flex:0 0 auto;border:1px solid color-mix(in srgb, var(--era-accent) 30%, transparent);
  border-radius:8px;background:color-mix(in srgb, var(--era-panel, var(--panel)) 70%, transparent);
  padding:6px 0}
#hud-right #hud-next{display:block;margin:0 auto}   /* sizes come inline from applyHudScale; the phone face is CSS-driven below */
/* shared box chrome + goal track */
.hud-box{background:color-mix(in srgb, var(--era-panel, var(--panel)) 88%, transparent);
  border:1px solid color-mix(in srgb, var(--era-accent) 38%, transparent);
  border-radius:8px;padding:8px;text-align:center}
.hud-label{font-size:11px;letter-spacing:.14em;color:var(--era-dim);margin-bottom:6px;
  text-shadow:0 1px 3px var(--era-halo, rgba(4,8,15,.85))}   /* HUD-redesign Task 7 — the labelValue halo port (era bg0): microlabels read over bright art; values keep their panel backing */
.hud-text .hud-value{font-size:19px;font-weight:600;color:var(--era-text);font-variant-numeric:tabular-nums}
.goal-track{display:block;height:6px;margin-top:5px;border-radius:3px;   /* w2c — ×1.5 with the 32px value */
  background:color-mix(in srgb, var(--era-accent) 15%, transparent);overflow:hidden}
#goal-prog{display:block;height:100%;width:0;background:var(--era-accent);
  transition:width .25s, filter .2s}   /* HUD-redesign Task 11 — the filter leg fades the flare back 1.6→1 */
#goal-prog.full{background:var(--era-flash,#fff);box-shadow:0 0 8px 1px var(--era-accent)}
/* HUD-redesign Task 11 — existing-element reactions: the flare snaps ON (no
   filter leg while .tick — brightness 1.6 immediate) and the class removal
   rides the BASE rule's filter .2s for the fade back; the width glide stays
   alive throughout. The .full completion glow above is unchanged. */
#goal-prog.tick{filter:brightness(1.6);transition:width .25s}
#hud-score.score-pop{animation:score-pop .3s ease-out}
@keyframes score-pop{40%{transform:scale(1.18)}}
/* RM: the Task-24 kill-switch strips the keyframe + transitions, and game.js
   never arms either class under reducedMotion(). */
/* Task 44 phone strip: authored at text-floor sizes (label ≥10, value ≥12) */
/* HUD-redesign Task 7 fix-1 — the WIDE strip is TEXT-ONLY (the F2·A pick, the
   sheet's labelValue): no chip boxes; labels AND values carry the era halo
   (nothing floats on raw art without backing — the halo IS the backing), and
   the hairline goal track stays under the LINES value. The PHONE strip below
   keeps its chip boxes (the F1·B look). */
#hud:not(.hud-phone) .hud-box{background:none;border:none;border-radius:0;padding:0 10px}
#hud:not(.hud-phone) .hud-label{margin-bottom:2px;font-size:13px}
#hud:not(.hud-phone) .hud-value{font-size:30px;   /* w2c peripheral scale — 24→30 (psychological impact, corner-of-eye) */
  text-shadow:0 1px 3px var(--era-halo, rgba(4,8,15,.85))}
#hud:not(.hud-phone) #hud-lines{font-size:32px}   /* w2c — LINES leads: rows-progress is the primary read (user brief) */
#hud.hud-phone .hud-label{font-size:12px;letter-spacing:.06em;margin-bottom:0}
#hud.hud-phone .hud-value{font-size:21px}
#hud.hud-phone #hud-lines{font-size:22px}         /* w2c — LINES leads on phone too */
#hud.hud-phone #hud-next{width:96px;height:36px;border-radius:8px;   /* its own container — no label */
  border:1px solid color-mix(in srgb, var(--era-accent) 30%, transparent);
  background:color-mix(in srgb, var(--era-panel, var(--panel)) 70%, transparent)}
#hud.hud-phone #hud-lines-box{min-width:86px}
/* Task 44 phone floor: strip text shrinks at the 320 floor */
@media (max-width:359px){
  #hud.hud-phone{gap:2px}
  #hud.hud-phone .hud-box{padding:1px 6px}
  #hud.hud-phone .hud-value{font-size:17px}
  #hud.hud-phone #hud-lines{font-size:18px}
  #hud.hud-phone #hud-next{width:78px;height:29px}
}
/* HUD-redesign Task 4 — the phone band (F1·B, user pick): the corner-meter
   pill at the band's left + the right-packed icon row. The pill IS the meter.
   W2B (§6b V1, user pick 2026-09-25) — the E×B×D hybrid fill: E progression
   (near-clear glass body while charging — the rim carries the charge), B rim
   (a living inner border + halo that thicken + brighten meter-fill→flash with
   --pill-charge, set by setUndertowMeter), D/F ready flood but DARKER than
   the old wash (vertical deep meter-fill gradient — the white centered
   wordmark stays high-contrast at full; label color never inverts).
   .ready keeps the bloom (+6px taller + the READY ring — perf(T18), user pick
   C2b-BA: the crisp ring sits STEADY at the old geometry and the PULSE is the
   .pill-halo, a preceding sibling UNDER the whole face — see the ring block).
   HUD-redesign Task 16 — .flush-pill is the ONE face (never toggled).
   perf(T18): the DOCK (this phone CSS + recomputeLayout's wide inline writes)
   rides the #fab-wrap wrapper so the halo can tuck under the face — a child
   can never paint behind its own parent's background (negative z-index still
   paints above it), so backmost = sibling. The pill itself is
   position:relative inside the wrap; [hidden] toggles on BOTH (a hidden fab
   inside a live wrap would leave the halo a stray glow at the dock). */
#fab-wrap{position:absolute;left:10px;right:auto;bottom:calc(10px + env(safe-area-inset-bottom));
  width:150px;z-index:4}
#fab-wrap[hidden]{display:none}
#undertow-fab.flush-pill{position:relative;
  width:100%;height:44px;padding:0;border-radius:10px;overflow:visible;
  display:flex;align-items:center;justify-content:center;
  /* E — near-clear glass body (was the flat meter track) */
  background:color-mix(in srgb, var(--era-panel, var(--panel)) 28%, transparent);
  /* B — the living rim: crisp core border, thickness + brightness ride --pill-charge */
  border-style:solid;
  border-width:calc(1.5px + 3.5px * var(--pill-charge, 0));
  border-color:color-mix(in srgb, var(--era-meter-fill,var(--era-accent)),
    var(--era-flash,#fff) calc(var(--pill-charge, 0) * 100%));
  /* B — the soft halo behind the core (an inset glow ring, not a flat shadow) */
  box-shadow:inset 0 0 calc(5px + 8px * var(--pill-charge, 0)) calc(1px + 3px * var(--pill-charge, 0))
    color-mix(in srgb, var(--era-meter-fill,var(--era-accent))
      calc(18% + 30% * var(--pill-charge, 0)), transparent);
  transition:border-color .25s, box-shadow .25s}
@media (max-width:374px){ #fab-wrap{width:128px} }
#undertow-fab.flush-pill.ready{height:50px}              /* bloom: +6px */
#undertow-fab.flush-pill .pill-fill{position:absolute;left:0;top:0;bottom:0;width:0;
  border-radius:inherit;                                  /* E — a faint tint sweep only (the rim is the meter) */
  background:color-mix(in srgb, var(--era-meter-fill,var(--era-accent)) 26%, transparent);
  transition:width .25s}
#undertow-fab.flush-pill.ready .pill-fill{               /* the darker ready flood — white label stays high-contrast */
  /* w2c retune set the dark stops; w3c (§6c D5, user pick) adds the VIVID
     FLASH CROWN: a saturated meter-fill→flash band crowns the pill above the
     text zone (a hue statement, not just brightness) while the text band's
     stops stay dark — sheet-probed wordmark ≥4.5:1 at the line (4.81). */
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--era-meter-fill,var(--era-accent)) 60%, var(--era-flash,#fff)),
    color-mix(in srgb, var(--era-meter-fill,var(--era-accent)) 48%, #000) 30%,
    color-mix(in srgb, var(--era-meter-fill,var(--era-accent)) 38%, #000))}
#undertow-fab.flush-pill.gain{animation:pill-gain .45s ease-out}   /* w2b — flashes the whole face (rim+glass), not just the fill */
@keyframes pill-gain{0%{filter:brightness(2.6)}100%{filter:brightness(1)}}
/* Touch fix 1 (2026-10-14) — the REFUSED two-finger tap BOUNCES the pill: a
   short shake, transform-only (compositor-carried — the icon-flash law: RM's
   kill-switch stills the keyframe, no JS branch, a lingering class is inert;
   game.js self-clears on animationend, name-guarded). Ruled AFTER .gain: the
   two one-shot classes share the animation shorthand, so the rarer verb (the
   refusal cue) takes the slot when they collide inside a gain flash. */
#undertow-fab.flush-pill.reject{animation:pill-reject .32s ease-out}
@keyframes pill-reject{0%{transform:translateX(0)}25%{transform:translateX(-7px)}
  50%{transform:translateX(5px)}75%{transform:translateX(-2px)}100%{transform:translateX(0)}}
/* perf(T18) — the READY ring, the C2b-BA port (user pick, sheet round 2:
   "less amplitude AND the backmost layer"). The old infinite box-shadow
   keyframes (blur 6→16 / spread 1→4 / alpha 60→90% — a paint-only,
   main-thread re-raster ~150×/s while charged) are RETIRED. Now:
   · the crisp border ring parks STEADY at today's exact geometry (the old
     base shadow 12px/2px @70% — the ::after never animates);
   · the pulse is the .pill-halo — a PRECEDING SIBLING under the whole face
     (the wrap's first child; the fab's z-index keeps the face over it), so
     the glow emanates from BEHIND the pill and only ever reads outside it;
   · ½-swing keyframes (sheet C2b-BA verbatim): scale .875↔1 + opacity
     .833↔1 — compositor-carried (measured: 1 raster per charge window vs
     the old ring's ~150/s; mockups/oktodrop-pill-ring-mockup.html).
   RM base state (THE PICK'S RULING, carried here): the kill-switch stills the
   halo to its base — the max glow BEHIND the face, reading strictly OUTSIDE
   the pill (the face-covered part is hidden by construction). The halo rides
   the wrap's own .ready class (toggled with the fab's in setUndertowMeter). */
#undertow-fab.flush-pill.ready::after{                   /* the steady ring — no animation, ever */
  content:'';position:absolute;inset:-4px;border-radius:14px;pointer-events:none;
  border:2px solid color-mix(in srgb, var(--era-flash,#fff) 60%, transparent);
  box-shadow:0 0 12px 2px color-mix(in srgb, var(--era-accent) 70%, transparent)}
#fab-wrap .pill-halo{                                    /* the pulse — backmost, compositor-driven */
  position:absolute;inset:-4px;border-radius:14px;pointer-events:none;display:none;
  box-shadow:0 0 16px 4px color-mix(in srgb, var(--era-accent) 90%, transparent)}
#fab-wrap.ready .pill-halo{display:block;animation:pill-ring-halo 1.6s ease-in-out infinite}
@keyframes pill-ring-halo{0%,100%{transform:scale(.875);opacity:.833}
  50%{transform:scale(1);opacity:1}}
#undertow-fab.flush-pill .pill-marks{position:relative;display:flex;align-items:center;justify-content:center;
  /* w3c (§6c D5): the wordmark itself is a readiness channel — ≈42% white
     empty, 55% at half (the linear 42+26·charge ladder), snapping to FULL
     white only at .ready. Charge-driven, never time-driven (RM-safe; the
     .25s color ease rides the kill-switch like the rim's). */
  color:color-mix(in srgb, var(--era-flash,#fff) calc(42% + 26% * var(--pill-charge, 0)), transparent);
  font-weight:700;letter-spacing:.08em;
  font-size:max(12px,calc(13px * var(--hud-scale,1)));
  transition:color .25s;
  text-shadow:0 0 3px rgba(3,4,12,.85), 0 1px 2px rgba(3,4,12,.9)}   /* labelValue halo law: reads over glass AND flood */
#undertow-fab.flush-pill.ready .pill-marks{color:var(--era-flash,#fff)}   /* the D5 snap: full white only at ready */
#undertow-fab.flush-pill .pill-label{font-family:var(--font-ui)}
/* Mode wave T5 — the daily progress ring on the pill's CROWN (spec §2): a
   thin conic arc riding --daily-t (elapsed/duration, written by
   updateDailyHud — a data-driven var exactly like --pill-charge, so RM needs
   NO branch: the ring creeps once per second, it never animates on its own).
   Shown only while a daily run is live (.daily on the fab); the coda window
   marks the remaining arc in the era FLASH (the D5 crown accent — the window
   is visible, not secret). Sits OUTSIDE the ready ring's -4px band. */
#undertow-fab .pill-crown{display:none}
#undertow-fab.daily .pill-crown{display:block;position:absolute;inset:-8px;border-radius:14px;pointer-events:none;
  background:conic-gradient(color-mix(in srgb, var(--era-accent) 85%, transparent)
    calc(var(--daily-t, 0) * 360deg), transparent 0);
  -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2px));
  mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2px))}
#undertow-fab.daily.coda .pill-crown{
  background:conic-gradient(var(--era-flash,#fff) calc(var(--daily-t, 0) * 360deg), transparent 0);
  -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 3px));
  mask:radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 3px))}
/* the icon row — 40px filled-glyph buttons (aria-labelled, NO text). Phone:
   #band-right packs them at the band's right; wide (desktop + touch-wide):
   #ctrl-cluster docks them top-right of the SCREEN (HUD-redesign Task 7 — the
   wide text chips and the canvas F-pill are gone; desktop shows the cluster
   too). Styling is SHARED and container-scoped — the four buttons never live
   outside one of the two containers (applyHudScale moves them, the
   #hud-next container-move idiom). */
#band-right{position:absolute;right:calc(10px + env(safe-area-inset-right));
  bottom:calc(10px + env(safe-area-inset-bottom));display:flex;gap:6px;z-index:4}
#ctrl-cluster{position:absolute;right:calc(14px + env(safe-area-inset-right));
  top:calc(8px + env(safe-area-inset-top));display:flex;gap:6px;z-index:4}
/* (HUD-redesign Task 5's buttons-mode relocation rules — the pill + icon row
   lifting above the pad — died with the pad, 2026-09-30: gestures are the
   only touch input, the band never moves.) */
#band-right .icon-btn,#ctrl-cluster .icon-btn{position:static;width:40px;height:40px;padding:0;
  border-radius:8px;display:grid;place-items:center;cursor:pointer;
  font-family:var(--font-ui);font-weight:700;letter-spacing:.06em;
  color:var(--era-text,var(--ink));touch-action:none;user-select:none;-webkit-user-select:none;
  background:color-mix(in srgb, var(--era-panel,var(--panel)) 88%, transparent);
  border:1px solid color-mix(in srgb, var(--era-accent) 55%, transparent)}
#band-right .icon-btn:active,#ctrl-cluster .icon-btn:active{
  background:color-mix(in srgb, var(--era-accent) 30%, transparent)}
#band-right .icon-btn canvas,#ctrl-cluster .icon-btn canvas{width:22px;height:22px;display:block}
#band-right .icon-btn[hidden],#ctrl-cluster .icon-btn[hidden]{display:none}   /* must out-specify the shared block above */
/* ===== 1.7.25 T3 — the mute chip redesign (user ruling 2026-10-10) ===== */
/* The 44-CSS floor via HIT-SLOP, never drawn-size inflation (the 1.7.6
   ruling): the drawn chip stays 40×40 (perception-approved; the 320-floor
   math below stays valid). The slop is NOT a ::before box — firefox counts
   an overhanging pseudo in scrollWidth (40 vs clientWidth 38) and trips the
   "nothing truncated" chip pin; overflow:clip kills the hit without fixing
   the metric. The DOM twin of the canvas scrSlop law lives in game.js: a
   capture-phase pointerdown forwarder pads each chip's tappable rect by 2px
   (≥44×44, nearest-center tiebreak at the ≤330px gap:3 corner). The
   containers carry touch-action:none so ring taps match the chips' own
   gesture posture. */
#band-right,#ctrl-cluster{touch-action:none}
/* The missing focus ring — the .og-close precedent: the chips are
   tab-reachable and focus was invisible. */
#band-right .icon-btn:focus-visible,#ctrl-cluster .icon-btn:focus-visible{outline:2px solid #e9f1ff;outline-offset:2px}
/* Dual-channel MUTED state: the chip ITSELF carries the off signal (accent-
   tinted panel + near-full accent border + dim glyph) on top of the glyph
   swap. MUSIC = exactly the shared chip chrome above. Deliberately AFTER
   :active (equal specificity): a press on a MUTED chip fires the .flash
   acknowledgment below instead — the stronger, state-confirming cue. */
#band-right .icon-btn.muted,#ctrl-cluster .icon-btn.muted{
  background:color-mix(in srgb, var(--era-accent) 20%, var(--era-panel,var(--panel)));
  border-color:color-mix(in srgb, var(--era-accent) 85%, transparent)}
#band-right .icon-btn.muted canvas,#ctrl-cluster .icon-btn.muted canvas{opacity:.7}   /* 1.7.27 — lifted from .6 (the perception gate: the cone partly dissolved into the tint at phone size; the slash + tint stay the primary channels) */
/* The press-acknowledgment flash (the scrBtn opts.flash precedent, DOM form):
   a ≤100 ms brightness one-shot, filter-only (never layout). game.js adds
   .flash via remove-reflow-add and self-clears on animationend. Reduced
   motion: the kill-switch block at the tail (`* { animation: none !important }`,
   which reaches *::before/*::after) nulls this — deliberately NO JS branch. */
#band-right .icon-btn.flash,#ctrl-cluster .icon-btn.flash{animation:icon-flash .1s ease-out}
@keyframes icon-flash{0%{filter:brightness(1.9)}100%{filter:brightness(1)}}
@media (max-width:330px){ #band-right{gap:3px} }   /* 320 floor: pill 128 + 3×40 + gaps ≤ vw */
/* Task 35 — the descend text floors on phone; wave-4 T8 — the hero slot rule
   is SHARED with the post mount (the old 220px phone cap and the post slot's
   missing phone rule both died with it).
   Sitting 2026-10-17 (the post-1.7.45 accessibility pass): the phone card's
   accessibility floors — the two headlines reach 24px, the kicker ≥12, the
   subtitle 13.5, the mechanic line 14, the GOAL chip's label 13 (it read 10).
   The ≤520px landscape compact block below keeps its own tighter sizes — a
   short window is a geometry problem, not a type-scale one. */
#stage.phone-hud #intermission .im-name{font-size:24px}
#stage.phone-hud #intermission .im-goal .g-main{font-size:22px}
#stage.phone-hud #intermission .im-credit .t-title{font-size:24px}
#stage.phone-hud #intermission .im-kicker{font-size:12px}
#stage.phone-hud #intermission .im-sub{font-size:13.5px}
#stage.phone-hud #intermission .im-mech{font-size:14px}
#stage.phone-hud #intermission .im-goal .g-label{font-size:13px}
#stage.phone-hud #intermission .im-goal .g-prog{font-size:12px}
/* Task 27 → HUD-redesign Task 4 → Task 16 (user ruling 2026-09-24): the
   corner-meter pill IS the meter on EVERY layout — the wide ready-gated text
   button died with the vertical rail (and its .ready-pulse with it); fine
   pointers get the pill too, so desktop gains its first direct flush trigger
   (the same pointerdown listener). Base rule = shared props only
   (position/typography/pointer); the .flush-pill block above owns the pill
   face (track/fill/label/dock), and this later :active keeps the press
   feedback. Era-styled via the --era-* props applyEraTheme() sets on #stage. */
/* perf(T18): the fab's absolute DOCK moved to #fab-wrap (the backmost-halo
   wrapper); position:relative comes from .flush-pill above. z-index:4 stays
   on the fab too — it is what keeps the whole face ABOVE the .pill-halo
   sibling behind it. */
#undertow-fab{position:relative;z-index:4;font-family:var(--font-ui);
  font-weight:700;letter-spacing:.08em;color:var(--era-text,var(--ink));cursor:pointer;
  border-radius:10px;touch-action:none;user-select:none;-webkit-user-select:none}
#undertow-fab:active{background:color-mix(in srgb, var(--era-accent) 30%, transparent)}
#undertow-fab[hidden]{display:none}
/* HUD-redesign Task 7 — the standalone wide text-chip CSS is DELETED: the
   control buttons live in #band-right (phone) or #ctrl-cluster (wide) and
   carry the shared .icon-btn rules above. */

/* HUD-redesign Task 2 — D1 state gating: game chrome exists only in play.
   #stage[data-state] is setStage()'s DOM mirror (ONE writer); the :not()
   pair hides the strip/chips in EVERY other state (title, map, descend,
   intermission, gameover, nameentry, complete). Gating on the END states
   (not-_playing AND not_paused) is load-bearing: startStage/startEndless
   route title→playing inside one tick, so a transient 'title' never paints.
   display/z rules only — the reduced-motion kill-switch is unaffected. The
   JS show-keys (updateCtrlButtons) mirror the same gate
   so the [hidden] diff-caches stay consistent; this block is the backstop.
   (The input chip + button pad selectors died with the pad, 2026-09-30.) */
#stage:not([data-state="playing"]):not([data-state="paused"]) #hud,
#stage:not([data-state="playing"]):not([data-state="paused"]) #band-right,
#stage:not([data-state="playing"]):not([data-state="paused"]) #ctrl-cluster,
#stage:not([data-state="playing"]):not([data-state="paused"]) #pause-btn,
#stage:not([data-state="playing"]):not([data-state="paused"]) #mute-btn,
#stage:not([data-state="playing"]):not([data-state="paused"]) #fs-btn{display:none !important}

/* Rotate prompt — same query string as the JS listener (repo convention).
   wave-4 — safe-area padding (max with the authored base) so the centered
   text never sits under an island/home indicator in any orientation. */
#rotate-prompt{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  background:rgba(3,4,12,.92);color:var(--ink);font-size:20px;font-weight:700;letter-spacing:.14em;z-index:5;
  padding:max(0px,env(safe-area-inset-top)) max(0px,env(safe-area-inset-right))
          max(0px,env(safe-area-inset-bottom)) max(0px,env(safe-area-inset-left))}
#rotate-prompt[hidden]{display:none}

/* ===== Task 13 + Task 30 + wave-4 T8: campaign flow ===== */
/* Intermission overlay — TWO panels (Task 30): the pre-level descend screen
   (data-role=pre: phase-descend dive → phase-reveal credit beat →
   phase-card → phase-ready WAIT) and the post-clear beat (data-role=post:
   phase-cleared → phase-stats). Timings live in game.js DESCEND_PHASES /
   POST_PHASES. #diorama-slot is the Task 20 slot, pre-level since Task 30.
   Task 26 — the art owns the ENTIRE descend window; wave-4 T8 makes it the
   HERO: ONE shared slot geometry on both screens (the --im-hero vars below),
   so the post→descend handoff is a pure rise/crossfade with ZERO size or
   position change. The panels share ONE structural skeleton (topzone → slot
   → midzone) — that skeleton, not the surrounding text, is what pins the
   art's box; the text bands are absolutely anchored to the bottom and never
   push it. */
#intermission{position:absolute;inset:0;z-index:6;display:flex;align-items:center;justify-content:center;
  background:rgba(3,4,12,.82);pointer-events:none;   /* tap-skip targets the canvas — the veil must not swallow it */
  /* wave-4 T8 — the ONE hero geometry (both art mounts read the same vars):
     height cap 70% of the window; on short windows the art YIELDS by the
     same rule on BOTH screens (the bottom text band keeps its readable
     zone); the 96px floor keeps the box real on absurd tiles. */
  --im-top:84px;      /* reserved band: the D1 depth counter / the STAGE CLEARED message */
  --im-mid:92px;      /* the music block band right under the art (two 19/15px track lines + margins) */
  --im-text:320px;    /* the bottom text band (D3 card / READY / stats, natural size) */
  --im-hero-h:max(96px, min(70dvh, calc(100dvh - var(--im-top) - var(--im-mid) - var(--im-text))));
  --im-hero-w:min(calc(var(--im-hero-h) * 16 / 9), 100%)}
#intermission.hidden{display:none}
/* wave-4 — the panels carry safe-area padding (max with the authored bases):
   both roles are full-bleed-centered, and a landscape island or home
   indicator must never sit under the card's text bands. */
#intermission .im-panel{position:relative;display:flex;flex-direction:column;align-items:center;
  text-align:center;font-family:var(--font-ui);width:100%;height:100%;
  padding:max(0px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right))
          max(0px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left))}
/* only the active role's panel is visible */
#intermission .im-panel[data-role=pre],#intermission .im-panel[data-role=post],
#intermission .im-panel[data-role=daily]{display:none}
#intermission.phase-descend .im-panel[data-role=pre],#intermission.phase-reveal .im-panel[data-role=pre],
#intermission.phase-card .im-panel[data-role=pre],#intermission.phase-ready .im-panel[data-role=pre]{display:flex}
#intermission.phase-cleared .im-panel[data-role=post],#intermission.phase-stats .im-panel[data-role=post]{display:flex}
/* Mode wave T5 — the daily PRE-DIVE card (spec §8.3): its own phase class,
   written ONLY by showDailyCard (never showDescend) while the game state
   stays 'title'. The state-gating backstop joins the chrome-gating family:
   even with the class forced, the card cannot render off-title. The panel
   re-enables pointer events (the veil stays pointer-none outside it, so the
   canvas press-anywhere dive path carries taps on the veil).
   Fix round 1 (perception gate): the panel is an OPAQUE MODAL — the canvas
   title/logo/prompt beneath an .82 veil bled through and stacked under the
   card text (the gate's ghost layers); and the cardtext lifts ABOVE the
   readyset (the phase-ready posture — both groups are bottom-anchored and
   used to overprint each other). Fix round 2: rgba .95 still let a faint
   large-letterform ghost read behind the track line — the daily panel is
   now SOLID (#03040c, the site's --abyss-0): zero bleed by construction.
   Scoped to the daily panel ONLY — the descend card family keeps its
   translucency. */
#intermission.phase-daily .im-panel[data-role=daily]{display:flex}
#stage:not([data-state="title"]) #intermission.phase-daily{display:none !important}
#intermission.phase-daily .im-panel[data-role=daily]{pointer-events:auto;
  background:#03040c}
#intermission.phase-daily .im-panel[data-role=daily] .im-kicker,
#intermission.phase-daily .im-panel[data-role=daily] .im-name,
#intermission.phase-daily .im-panel[data-role=daily] .im-sub,
#intermission.phase-daily .im-panel[data-role=daily] .im-credit,
#intermission.phase-daily .im-panel[data-role=daily] .im-ready,
#intermission.phase-daily .im-panel[data-role=daily] .im-hint,
#intermission.phase-daily .daily-board{opacity:1}
#intermission.phase-daily .im-cardtext{
  bottom:calc(max(10px,env(safe-area-inset-bottom)) + 76px)}   /* the phase-ready lift — clears the DIVE + hint group */
@media (max-height:520px){
  #intermission.phase-daily .im-cardtext{bottom:calc(max(10px,env(safe-area-inset-bottom)) + 52px)}
}
/* fix round 1: the phone hint floor — never under 12px CSS on the daily card */
#stage.phone-hud #intermission.phase-daily .im-hint{font-size:12px}
/* the daily board block (today's top rows + YOUR BEST + the expand) — sits
   in the skeleton's hero band; static DOM, no transitions (the RM posture) */
.daily-board{flex:0 0 auto;width:min(430px,100%);margin:6px 0;
  border:1px solid rgba(143,208,255,.35);border-radius:10px;background:rgba(10,18,34,.6);
  padding:12px 16px;text-align:left}
.daily-board .db-head{font-size:11px;letter-spacing:.28em;color:#8290c0;margin-bottom:8px;text-align:center}
.daily-board .db-rows{list-style:none;margin:0;padding:0}
/* LB UX wave 2026-10-03 (user ruling 1 — board figures too small): rows 15→16,
   rank 13→14, name 600→700 (title weight), YOUR BEST 14→15 */
.daily-board .db-row{display:flex;align-items:baseline;gap:14px;font-size:16px;line-height:1.75}
.daily-board .db-rank{flex:0 0 34px;color:#5f6f92;font-size:14px}
.daily-board .db-name{flex:0 1 auto;color:#e9f1ff;font-weight:700;letter-spacing:.12em}
.daily-board .db-score{flex:0 0 auto;margin-left:auto;color:#c3cee6;font-variant-numeric:tabular-nums}
.daily-board .db-empty{color:#5f6f92;font-size:13px;justify-content:center;line-height:2}
.daily-board .db-best{margin-top:10px;padding-top:8px;border-top:1px solid rgba(130,144,192,.25);
  text-align:center;color:#8fd0ff;font-size:15px;font-weight:700;letter-spacing:.06em}
.daily-board .db-full{display:block;margin:10px auto 0;padding:7px 16px;font-family:var(--font-ui);
  font-size:11px;letter-spacing:.18em;font-weight:700;color:#c3cee6;background:none;cursor:pointer;
  border:1px solid rgba(143,208,255,.4);border-radius:7px}
/* Flow wave 2026-10-03 — the daily card's modal CLOSE. The first cut put a
   TITLE button inside the board box (a styled twin of FULL BOARD) and the
   user rejected it as out of place: an exit is CARD CHROME, not board
   content. The ✕ is the standard modal-dismissal corner — top-right of the
   EMPTY topzone band (the daily card has no depth counter there), maximally
   far from the DIVE band. One Deck T5 (§2.3 close final pass): the button
   MIGRATED to the standardized og-close — the family base (44×44 circular
   disc + the stroke-drawn × + focus ring) lives at the class selector in the
   deck block below; THIS rule now carries only the card's own PLACEMENT
   (unchanged from the .im-close era: the topzone band, safe-area aware; the
   panel is position:relative). The handler is byte-identical
   (closeDailyCard). Static DOM, no transitions (the RM posture). */
#intermission .im-panel[data-role=daily] .og-close{position:absolute;
  top:max(12px, env(safe-area-inset-top) + 4px);
  right:max(12px, env(safe-area-inset-right))}
/* 1.7.51 — the descend panel's own exit (nav unification): same corner
   placement + safe-area formula as the daily card's; the pre panel is the
   D1–D4 surface (an INDEFINITE READY wait — touch finally has an exit).
   The im-topzone band reserves the visual space. */
#intermission .im-panel[data-role=pre] .og-close{position:absolute;
  top:max(12px, env(safe-area-inset-top) + 4px);
  right:max(12px, env(safe-area-inset-right));z-index:5;
  pointer-events:auto}   /* the veil is pointer-events:none (tap-skip targets the canvas — the law); the exit re-enables ITS OWN events only (the daily-card idiom at 431) */
#intermission.hidden .im-panel[data-role=pre] .og-close{pointer-events:none}
/* (The zen picker's .im-close scope died with the picker itself, One Deck T1
   2026-10-08 — the deck carries its own standardized og-close, below.) */
/* wave-4 T8 — the transform-scale card (--hud-scale / --card-fit) is RETIRED:
   the hero layout is viewport-driven CSS geometry; the text keeps natural
   size and the art yields by the shared min() above. */
/* cumulative child reveal within each panel */
#intermission .im-cleared,#intermission .im-stats,
#diorama-slot,#diorama-slot-post,#intermission .im-kicker,#intermission .im-name,
#intermission .im-sub,#intermission .im-mech,#intermission .im-goal,
#intermission .im-credit,#intermission .im-ready,#intermission .im-hint{opacity:0;
  transition:opacity .4s ease}
#intermission.phase-cleared .im-cleared{opacity:1}
#intermission.phase-stats .im-stats{opacity:1}
/* Task 27 — the cleared era's art is lit for the WHOLE post-clear beat (with
   the message at P1, with stats at P2); torn down only at the next
   descend/complete/map exit (hideIntermission → stopImArt, the one funnel) */
#intermission.phase-cleared #diorama-slot-post,
#intermission.phase-stats #diorama-slot-post{opacity:1}
/* Task 26 — the art slot stays lit through EVERY descend phase: D1 entry →
   D2 credit beat → D3 card → D4 READY hold (was reveal-only — the slot faded
   out the moment the D3 card text appeared) */
#intermission.phase-descend #diorama-slot,
#intermission.phase-reveal #diorama-slot,
#intermission.phase-card #diorama-slot,
#intermission.phase-ready #diorama-slot{opacity:1}
/* Intermission wave 2026-10-01 — the structured music block (two tracks:
   title · composer · year · origin) is a headline feature: lit from D2 through
   READY, right under the hero art (the old single credit line was D2-only). */
#intermission.phase-reveal .im-credit,
#intermission.phase-card .im-credit,
#intermission.phase-ready .im-credit{opacity:1}
/* Intermission wave 2026-10-01 — the era card PERSISTS through READY (the
   D3→D4 crossfade-out left only READY + hint; the card now stays visible
   alongside the READY line). */
#intermission.phase-card .im-kicker,#intermission.phase-ready .im-kicker,
#intermission.phase-card .im-name,#intermission.phase-ready .im-name,
#intermission.phase-card .im-sub,#intermission.phase-ready .im-sub,
#intermission.phase-card .im-mech,#intermission.phase-ready .im-mech,
#intermission.phase-card .im-goal,#intermission.phase-ready .im-goal{opacity:1}
#intermission.phase-ready .im-ready,#intermission.phase-ready .im-hint{opacity:1}
/* post-clear beat (wave-4 T7 — the .im-stars row is gone; ratings removed) */
#intermission .im-cleared{font-size:26px;font-weight:700;letter-spacing:.12em;color:#e9f1ff;margin-top:12px}
#intermission .im-stats{font-size:13px;color:#8290c0;line-height:1.9}
#intermission .im-stats b{color:#e9f1ff}
/* wave-4 T8 — the shared structural skeleton (BOTH panels, byte-identical):
   the fixed top band, then the hero slot, then the credit band. This flow —
   never the text below — is what positions the art, so the two screens agree
   to the pixel and the handoff cannot jump. */
#intermission .im-topzone{flex:0 0 auto;height:var(--im-top);width:100%;
  display:flex;justify-content:center;align-items:flex-start}
#intermission .im-midzone{flex:0 0 auto;min-height:var(--im-mid);max-width:100%;
  display:flex;justify-content:center;align-items:center}
/* the ONE hero slot rule — descend + post mounts, same box */
#diorama-slot,#diorama-slot-post{flex:0 0 auto;width:var(--im-hero-w);
  border:1px solid rgba(143,208,255,.35);
  border-radius:10px;background:rgba(10,18,34,.6);overflow:hidden}
#diorama-slot canvas,#diorama-slot-post canvas{display:block;width:100%;height:auto}
#diorama-slot span{font-size:11px;color:#5f6f92;letter-spacing:.06em}
/* wave-4 T8 — the bottom text groups: ONE anchor (the panel's bottom, above
   the safe area), so the D3→D4 flip is a crossfade IN PLACE and the post
   stats share the descend's text zone */
#intermission .im-cardtext,#intermission .im-readyset,#intermission .im-stats{
  position:absolute;left:50%;transform:translateX(-50%);
  bottom:max(10px,env(safe-area-inset-bottom));width:min(480px,calc(100% - 24px))}
/* Intermission wave 2026-10-01 — the card persists through READY, so it must
   clear the bottom-anchored READY + hint group: in phase-ready the cardtext
   rides ABOVE it (READY ~24px + hint ~24px + seam). bottom transitions with
   the opacity crossfade (the RM kill-switch snaps it). */
#intermission .im-cardtext{transition:opacity .4s ease,bottom .3s ease,background-color .3s ease,padding .3s ease}
#intermission.phase-ready .im-cardtext{
  bottom:calc(max(10px,env(safe-area-inset-bottom)) + 72px);
  /* judgment call: on windows ≤~520px tall the lifted card cannot clear the
     art + credit band (geometry floor — the reserved bands exceed the height);
     a dark scrim keeps the card readable and clearly layered where it lands
     on them. On taller windows nothing overlaps, so the scrim is invisible
     against the veil. */
  background:rgba(3,4,12,.72);border-radius:10px;padding:6px 10px}
#intermission .im-kicker{font-size:11px;letter-spacing:.3em;color:#8290c0;margin-top:12px}
/* Intermission wave fix-round 1 (2026-10-01, perception gate on 844×390): the
   reserved bands (84+56+320) exceed a ≤~520px window outright — the hero
   collapses to the 96px floor and the card/music/GOAL overprint each other
   mid-screen. On windows that short the screen COMPACTS instead: smaller top
   band, the GOAL box hidden (the mechanic line carries the essentials), the
   card text tightened, so art + music + card + READY stack legibly. */
@media (max-height:520px){
  #intermission{--im-top:40px;--im-mid:52px;--im-text:210px}
  /* flow wave 2026-10-03 — the short-window top band (40px) is shallower
     than the daily ✕'s 56px footprint (12px offset + the 44px hit floor):
     shift the board block down so the corner close clears the box instead
     of shrinking the target (the 44px floor is non-negotiable). */
  #intermission.phase-daily .daily-board{margin-top:24px}
  #intermission .im-goal{display:none}
  #intermission .im-kicker{margin-top:4px}
  #intermission .im-name{font-size:19px}
  #intermission .im-sub{margin-top:0}
  #intermission .im-mech{margin-top:2px;font-size:11px}
  #intermission .im-credit{font-size:13px;line-height:1.3}
  #intermission .im-credit .t-title{font-size:15px}
  #intermission .im-ready{font-size:16px}
  #intermission.phase-ready .im-cardtext{bottom:calc(max(10px,env(safe-area-inset-bottom)) + 48px);padding:4px 8px}
}
#intermission .im-name{font-size:26px;font-weight:700;color:var(--era-accent,#8fd0ff);margin-top:2px}
#intermission .im-sub{font-size:12px;color:#8290c0;margin-top:2px}
#intermission .im-mech{font-size:12px;font-style:italic;color:#aebadb;margin-top:6px}
#intermission .im-goal{margin:10px auto 0;padding:8px 12px;max-width:340px;border:1px solid var(--era-accent,#8fd0ff);
  border-radius:8px;background:rgba(0,0,0,.35);text-align:left}
#intermission .im-goal .g-label{display:block;font-size:10px;letter-spacing:.3em;color:var(--era-accent,#8fd0ff)}
#intermission .im-goal .g-main{display:block;font-size:26px;font-weight:700;color:var(--era-text,#e9f1ff);line-height:1.15}
#intermission .im-goal .g-prog{display:block;font-size:10px;color:#8290c0}
/* Refinement round 2026-10-02 — the music block is a HEADLINE (bigger title,
   brighter attribution; the em-dash separator is gone: size + color separate
   title from attribution). Title 19px/700 era-accent, attribution 15px. */
#intermission .im-credit{font-size:15px;color:#c3cee6;max-width:100%;line-height:1.45}
#intermission .im-credit .t-line{margin:4px 0}
/* 2026-10-03 user ruling — the title stands on its OWN line, the attribution
   below it (phone wraps then break where we choose, never mid-title). */
#intermission .im-credit .t-title{display:block;font-size:19px;font-weight:700;color:var(--era-accent,#8fd0ff);line-height:1.25}
#intermission .im-credit .t-attr{display:block}
#intermission .im-ready{font-size:20px;font-weight:700;letter-spacing:.3em;color:#c061ff}
#intermission .im-hint{font-size:11px;color:#61708f;margin-top:10px}
/* D1/P3 descend veil — the depth counter floats over the canvas dive.
   Intermission wave 2026-10-01: the flex-start top-band geometry lives in the
   BASE rule (it was a phase-rule override alongside opacity-only transitions,
   so the D1→D2 class flip teleported the counter from top to screen-center
   while still fading — the "flashes below the art" report). The phase rule
   now toggles ONLY opacity: the counter fades out in place. */
#intermission #descend-veil{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:flex-start;gap:10px;opacity:0;pointer-events:none;
  padding-top:max(8px,env(safe-area-inset-top));
  transition:opacity .3s ease}
/* Task 26 — the art slot owns the overlay's center from D1 entry, so the
   depth counter rides ABOVE it; wave-4 T8 — the reserved top band
   (--im-top) is the counter's structural home (label + counter ≈ 65px +
   breathing room), so the counter NEVER touches the hero art's top edge on
   short desktops. The veil fades out with the D1→D2 flip either way. */
#intermission.phase-descend #descend-veil{opacity:1}
#intermission.phase-descend{background:transparent}   /* the canvas dive IS the backdrop */
#intermission #descend-veil .dv-label{font-family:var(--font-ui);font-size:11px;
  letter-spacing:.4em;color:#61708f}
#intermission #descend-veil #depth-counter{font-family:var(--font-ui);font-size:34px;font-weight:700;
  letter-spacing:.06em;color:var(--era-text,#e9f1ff);font-variant-numeric:tabular-nums;
  text-shadow:0 0 18px var(--era-accent,#8fd0ff)}
/* (The tutorial toast block died with the toast system, telegraphs wave
   2026-10-05 — the flush pill owns readiness cues outright.) */

/* ===== The deck (One Deck, 2026-10-08) — the shared track-list overlay ===== */
/* Spec 2026-10-08-oktodrop-playlist-unification-design.md §2.1/§2.3/§2.5: the
   zen picker promoted to ONE shared deck — a FULL-VIEWPORT overlay (HIG:
   content-rich lists go full-screen) whose content column is min(560px,100%)
   centered: full-bleed on phones, a calm column on desktop. Era group
   headers (sticky, tri-state bulk checkboxes) over one row per library track
   (the TRACK_INFO line's own style — title 19px/700 in the HOME era's
   accent, attribution 15px; a home-era swatch; a TRAILING include-toggle
   ≥44×44). State-gating law, INVERSE leg: curate renders ONLY on the title
   screen — the [hidden] attribute is the open flag (the modal open-flag
   idiom) and the title-state rule is the ONLY thing that can lift display,
   so the deck can never leak into a run screen even if a future path forgets
   to close it (onStageChanged also closes it on every transition). z 6 = the
   intermission league; nothing else is up on title. Perception round 1
   (2026-10-08): the backdrop is OPAQUE #03040c — a FULL-SCREEN FLOW, never
   a translucent sheet (the lb-screen opaque-scrim posture; the title
   ghosted through the old .86/.74 stack on both viewports); the column
   keeps its own panel styling on top of the scrim. The open transition
   (§2.5, the container-transform flavor) is the deck's ONE transition — the
   RM kill-switch snaps it; every toggle/tri-state swap is class-driven, no
   animation. */
#og-deck{position:absolute;inset:0;z-index:6;display:none;
  align-items:center;justify-content:center;background:#03040c;
  padding:max(10px,env(safe-area-inset-top)) max(14px,env(safe-area-inset-right))
           max(10px,env(safe-area-inset-bottom)) max(14px,env(safe-area-inset-left));
  touch-action:none;font-family:var(--font-ui)}
#stage[data-state="title"] #og-deck:not([hidden]){display:flex}
#og-deck .og-panel{display:flex;flex-direction:column;width:min(560px,100%);height:100%;
  border:1px solid color-mix(in srgb, var(--era-accent,#8fd0ff) 40%, transparent);
  border-radius:12px;background:var(--panel);overflow:hidden;
  transition:transform .2s cubic-bezier(.2,.7,.3,1)}
/* §2.5 — the container-transform open, as a TWO-CLASS dance (a transition
   reads the BEFORE-change style's transition-property: collapsing the
   pre-state and the suppression into one class leaves the before-style at
   transition:none and the open NEVER animates — found by the T1 test). .og-pre
   holds the pre-state (scale .92 + a 12px rise); .og-snap suppresses the
   transition while it commits. game.js: add BOTH + flush (pre-state lands,
   unanimated) → drop .og-snap + flush (the transition ARMS while the
   pre-state holds — no change, no animation) → drop .og-pre (pre→identity:
   the declared transition above runs). RM (the kill-switch) and
   transition-unsupported engines both degrade to an instant, harmless snap. */
#og-deck .og-panel.og-pre{transform:translateY(12px) scale(.92)}
#og-deck .og-panel.og-snap{transition:none}
#og-deck .og-head{position:relative;flex:0 0 auto;padding:14px 64px 10px;text-align:center}
#og-deck .og-title{font-size:17px;font-weight:700;letter-spacing:.14em;color:var(--era-text,#e9f1ff)}
#og-deck .og-sub{font-size:11px;color:#8290c0;margin-top:4px;letter-spacing:.05em}
/* og-close (§2.3, the ONE standardized close button — T5's final pass; RESTYLED
   1.7.52 as THE ONE ✕, the unification ruling): the shared BASE lives at the
   CLASS level (44×44 CSS CIRCULAR target border-radius:50%, the brighter
   .82 disc + 1.5px cyan ring, a STROKE-DRAWN × that is BRIGHT AT REST — the
   pre-1.7.52 dim #8290c0 glyph was the "struggle to find the close button"
   report — span 24 CSS (12 of the 24-unit box), stroke 2.8, round caps, NEVER
   a text glyph, the font-fallback law; a press brighten + a focus-visible
   ring). The SAME numbers as the canvas scrCloseChip and the map's corner ✕ —
   one design, every surface. Each surface carries only its POSITION override
   below. Surfaces: the deck's head (top-right of the panel), Now Playing
   (top-right of the column), the daily card (the topzone band, safe-area
   aware — migrated off .im-close in T5, placement unchanged), the descend
   exit. Always VISIBLE on any overlay that blocks the game beneath (NN/g),
   never gesture-only dismissal. */
.og-close{width:44px;height:44px;padding:0;margin:0;
  display:flex;align-items:center;justify-content:center;
  background:rgba(11,17,40,.82);border:1.5px solid rgba(143,208,255,.45);border-radius:50%;
  color:#e9f1ff;cursor:pointer;font-family:var(--font-ui)}
.og-close svg{width:24px;height:24px;display:block;overflow:visible}   /* 1.7.52 — arms run EDGE-TO-EDGE of the 24-unit box (ink span 24+2.8 = 26.8 CSS, the canvas chip's exact figure); overflow:visible keeps the stroke's outer half unclipped */
.og-close:active{color:#ffffff;border-color:rgba(143,208,255,.75);background:rgba(24,36,72,.9)}
.og-close:focus-visible{outline:2px solid #e9f1ff;outline-offset:2px}
/* the deck's placement — 1.7.54 T2, THE CORNER LAW: the button is a DIRECT
   child of #og-deck (re-parented out of .og-head), so the absolute offsets
   resolve against the OVERLAY's padding box = the screen corner. Same env()
   formula as every surface (the daily card below, the canvas scrCloseChip,
   the map's own paint); the head's 64px side reservation stays as harmless
   title breathing room. On wide viewports the ✕ intentionally sits at the
   SCREEN corner, off the 560px panel — consistency over proximity (the
   user's "consistent everywhere" ruling). */
#og-deck .og-close{position:absolute;
  top:max(12px, env(safe-area-inset-top) + 4px);
  right:max(12px, env(safe-area-inset-right))}
/* the rows viewport — scrollable (22 today, grows per delivery); touch
   scrolling must stay native, hence pan-y HERE even though the backdrop is
   touch-action:none (the deck listens for real clicks — no blockOsGestures,
   see game.js). scroll-padding keeps keyboard-revealed rows clear of the
   sticky group headers. */
#og-deck .og-rows{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  touch-action:pan-y;-webkit-overflow-scrolling:touch;padding:2px 8px;
  scroll-padding-top:45px;
  border-top:1px solid var(--edge);border-bottom:1px solid var(--edge)}
/* era group headers — sticky, OPAQUE backing (the panel var is translucent;
   rows must not bleed through a stuck header), era accent + name + the live
   included/total chip (visible only when the era has an exclusion) + the
   tri-state bulk box. ≥44 CSS tall (the touch law). */
#og-deck .og-gh{position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:10px;
  width:100%;min-height:44px;padding:4px 8px;background:rgb(11,17,40);
  border:0;border-bottom:1px solid var(--edge);
  font-family:var(--font-ui);cursor:pointer;text-align:left;touch-action:manipulation}
#og-deck .og-box{flex:0 0 auto;width:22px;height:22px;color:#8fd0ff;display:flex}
#og-deck .og-box svg{width:100%;height:100%;display:block}
#og-deck .og-gh .og-check,#og-deck .og-gh .og-dash{display:none}
#og-deck .og-gh[data-state="all"] .og-check{display:block}
#og-deck .og-gh[data-state="some"] .og-dash{display:block}
#og-deck .og-gh-name{flex:0 0 auto;font-size:13px;font-weight:700;letter-spacing:.16em}
#og-deck .og-gh .og-gh-count{flex:0 0 auto;font-size:11px;color:#8290c0;
  font-variant-numeric:tabular-nums;visibility:hidden}
#og-deck .og-gh.og-partial .og-gh-count{visibility:visible}
/* a row = the main target + the trailing toggle (siblings, never nested —
   play mode T2 splits their verbs). Both ≥44 CSS; the row body toggles in
   curate (bigger target, same action). */
#og-deck .og-row{display:flex;align-items:center;width:100%;border-radius:8px;min-height:50px}
#og-deck .og-row.sel{outline:1px solid rgba(124,154,255,.35)}
#og-deck .og-main{flex:1 1 auto;display:flex;align-items:center;gap:12px;min-width:0;min-height:50px;
  padding:6px 10px;background:none;border:0;border-radius:8px 0 0 8px;
  font-family:var(--font-ui);color:inherit;text-align:left;cursor:pointer;
  touch-action:manipulation}
#og-deck .og-row.sel .og-main{background:rgba(124,154,255,.12)}
/* Perception round 1: an EXCLUDED row never carries the lighter sel card —
   dimmed content inside a brighter box read as "highlighted/selected" before
   the slash icon registered. The cursor keeps its 1px outline on excluded
   rows (keyboard parity); the excluded channels are dim + slash icon ONLY —
   nothing on an excluded row reads brighter than its included neighbors. */
#og-deck .og-row.sel.off .og-main{background:none}
#og-deck .og-row.off .og-main{opacity:.45}                  /* dimmed, never removed (the greyed-out precedent) */
#og-deck .og-sw{flex:0 0 auto;width:14px;height:14px;border-radius:4px;
  box-shadow:inset 0 0 0 2px rgba(4,8,15,.55)}
#og-deck .og-line{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}
/* the TRACK_INFO line's own sizes — the descend music block (19/700 accent
   title + 15px attribution; .im-credit's exact figures) */
#og-deck .og-t{font-size:19px;font-weight:700;line-height:1.2;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Fix round 1 (perception P4): the attribution is SEGMENTED (composer /
   year / origin — each with its leading '· ' folded into the span so a drop
   takes its separator too); deckFitRows (game.js) drops segments
   RIGHT-TO-LEFT (origin, then year) once the line overflows, and only THEN
   the character ellipsis bites — the scrHint shrink-then-drop law's row
   analog, measured against the loaded font (never fallback metrics). Wide
   viewports keep full lines (no drops). */
#og-deck .og-a{font-size:15px;color:#c3cee6;line-height:1.25;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#og-deck .og-a.og-drop-origin .og-seg-2,
#og-deck .og-a.og-drop-origin .og-sep-2{display:none}
#og-deck .og-a.og-drop-year .og-seg-1,
#og-deck .og-a.og-drop-year .og-sep-1{display:none}
/* the trailing include-toggle — its OWN ≥44×44 target; icon-only both states
   (check-circle = included, slashed circle = excluded); the floor-locked
   state is aria-disabled + dimmed + pointer-dead (deckToggle is the belt) */
#og-deck .og-tg{flex:0 0 auto;width:44px;height:44px;margin:0 2px;
  display:flex;align-items:center;justify-content:center;background:none;border:0;
  border-radius:50%;color:#8fd0ff;cursor:pointer;touch-action:manipulation}
#og-deck .og-tg svg{width:22px;height:22px;display:block}
#og-deck .og-tg .og-off{display:none}
#og-deck .og-row.off .og-tg{color:#8290c0}
#og-deck .og-row.off .og-tg .og-on{display:none}
#og-deck .og-row.off .og-tg .og-off{display:block}
#og-deck .og-tg[aria-disabled="true"]{opacity:.35;pointer-events:none;cursor:not-allowed}
/* Perception-gate round 1 (haiku read, 900×800, the picker era — carried
   over): when the list folds the fold must read as a scroll viewport, never
   a truncated list. A sticky ::after rides the fold line (its box sticks to
   the scrollport bottom while its static position is at the content end);
   the .more class (game.js, scroll-state hook) gates it so it clears at the
   bottom. Static opacity swap only — no transition (RM).
   Fix round 2 (F2-1, the desktop re-gate FAIL): the zero-height shadow band
   landed ABOVE the last visible row's content — the attribution text stayed
   uniformly bright into a hard shear while the dim rode empty background.
   The ::after is now a 28px GRADIENT overlay pinned 2px PAST the shear line
   (bottom:-2px), darkening the last visible content progressively into the
   footer — transparent → ~.55 at 55% → ~.97 at the line. margin-bottom:-28
   keeps its net flow contribution ZERO, so scrollHeight (and every
   fold/scroll assert) is unchanged; sticky paints it above row content;
   pointer-events:none keeps the rows tappable through it. */
#og-deck .og-rows::after{content:'';display:block;position:sticky;bottom:-2px;flex:0 0 auto;
  height:28px;margin-bottom:-28px;pointer-events:none;opacity:0;
  background:linear-gradient(to bottom, rgba(3,4,12,0), rgba(3,4,12,.55) 55%, rgba(3,4,12,.97) 92%, #03040c)}
#og-deck .og-rows.more::after{opacity:1}
/* the context footer — live count line + the floor hint (space RESERVED so
   its appearance never shifts the layout; .og-floored is driven by deckPaint
   whenever any row toggle is disabled) + ALL/NONE (the A/N keys' touch
   affordance — every hinted action keeps a visible affordance) + the ONE
   primary DRIFT (oversized ~56px, persistent while the list scrolls) */
#og-deck .og-foot{flex:0 0 auto;padding:10px 12px 0;text-align:center}
#og-deck .og-count{font-size:11.5px;line-height:1.35;color:#8290c0;min-height:16px}
#og-deck .og-hintline{font-size:11px;line-height:1.35;color:#e05a5a;margin-top:2px;min-height:15px;
  visibility:hidden}
#og-deck.og-floored .og-hintline{visibility:visible}
/* Fix round 1 (perception P3): in PLAY mode the hint band does NOT reserve
   space while idle — the mini-NP strip sits at the list's heel (the first
   cut reserved the line unconditionally, and its curate-worded text wraps
   to two lines at 390 = a ~50px dead dark band between the fold and the
   strip, which also detached the fold fade — P2's read). The reservation
   returns only while a row is actually floor-locked (a one-time shift when
   a play-mode toggle hits the floor — the finding's sanctioned trade). */
#og-deck[data-mode="play"] .og-hintline{display:none;min-height:0;margin-top:0}
#og-deck[data-mode="play"].og-floored .og-hintline{display:block;min-height:15px;margin-top:2px}
#og-deck .og-actions{display:flex;gap:10px;align-items:stretch;justify-content:center;margin-top:8px}
#og-deck .og-tool{flex:0 0 auto;font-family:var(--font-ui);font-size:12px;font-weight:600;
  letter-spacing:.14em;color:#c3cee6;background:rgba(124,154,255,.10);
  border:1px solid rgba(124,154,255,.30);border-radius:7px;padding:8px 16px;cursor:pointer;
  min-height:56px}
/* Perception round 1: DRIFT is the family's ONE FILLED PRIMARY — #c061ff +
   white label (the chrome law / scrBtn's recipe), NEVER the era accent (the
   russia accent read as an unexplained blue button); the inset top highlight
   + :active brighten mirror scrBtn's press dip. */
#og-deck .og-drift{flex:1 1 auto;min-width:150px;font-family:var(--font-ui);font-size:16px;font-weight:800;
  letter-spacing:.14em;color:#fff;background:#c061ff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22);
  border:0;border-radius:8px;min-height:56px;padding:12px 20px;cursor:pointer}
#og-deck .og-drift:active{background:#d283ff}
#og-deck .og-keys{flex:0 0 auto;padding:8px 12px 14px;text-align:center;font-size:10.5px;color:#61708f}
/* nav 1.7.2 grammar — touch gets a TAP-verb counterpart in the same slot
   (.og-touch rides isTouchDevice(), set once when the deck's DOM is built —
   the pointer class is stable for a page's life, like every isTouchDevice()
   gate) */
#og-deck .og-touchkeys{flex:0 0 auto;padding:8px 12px 14px;text-align:center;font-size:10.5px;color:#61708f;display:none}
#og-deck.og-touch .og-keys{display:none}
#og-deck.og-touch .og-touchkeys{display:block}
/* ===== One Deck T2 — PLAY mode (the jukebox deck): the SAME element keyed
   on title AND complete; [data-mode="play"] (game.js openDeck) gates the
   play-only chrome and hides the curate-only footer/key lines. The attr
   selector's specificity (id+attr+class) sits above the .og-touch pair's
   (id+2 classes), so the mode gate wins in both pointer classes. */
#stage[data-state="complete"] #og-deck[data-mode="play"]:not([hidden]){display:flex}
#og-deck[data-mode="play"] .og-count,
#og-deck[data-mode="play"] .og-actions,
#og-deck[data-mode="play"] .og-keys,
#og-deck[data-mode="play"] .og-touchkeys{display:none}
#og-deck:not([data-mode="play"]) .og-pkeys,
#og-deck:not([data-mode="play"]) .og-ptouchkeys,
#og-deck:not([data-mode="play"]) .og-np{display:none}
#og-deck .og-pkeys{flex:0 0 auto;padding:8px 12px 14px;text-align:center;font-size:10.5px;color:#61708f;display:none}
#og-deck[data-mode="play"]:not(.og-touch) .og-pkeys{display:block}
#og-deck .og-ptouchkeys{flex:0 0 auto;padding:8px 12px 14px;text-align:center;font-size:10.5px;color:#61708f;display:none}
#og-deck[data-mode="play"].og-touch .og-ptouchkeys{display:block}
/* era headers in play mode: the bulk box swaps for a play glyph (the header
   tap plays the era — the same sticky chrome, a different verb) */
#og-deck .og-gh-play{flex:0 0 auto;width:16px;height:16px;color:#8fd0ff;display:none}
#og-deck .og-gh-play svg{width:100%;height:100%;display:block}
#og-deck[data-mode="play"] .og-gh-play{display:block}
#og-deck[data-mode="play"] .og-box{display:none}
/* the NOW-PLAYING row (play mode): the leading swatch swaps for 3 EQ bars —
   CSS-animated, but the BASE heights are a valid static arrangement (the RM
   kill-switch freezes them legibly — never a zero-height sliver). Fix round
   1 (perception P1): the accent tint rides the WHOLE row (main + trailing
   toggle = ONE continuous fill — the first cut tinted .og-main only and the
   toggle's band read as a separate attached block); the sel card yields to
   it (np wins, one tone — the .sel.off precedent). */
#og-deck .og-eq{flex:0 0 auto;display:none;width:14px;height:16px;gap:2px;align-items:flex-end}
#og-deck .og-eq i{display:block;width:3px;border-radius:1px;background:currentColor;transform-origin:50% 100%}
#og-deck .og-eq i:nth-child(1){height:8px}
#og-deck .og-eq i:nth-child(2){height:15px}
#og-deck .og-eq i:nth-child(3){height:11px}
#og-deck[data-mode="play"] .og-row.np .og-eq{display:flex}
#og-deck[data-mode="play"] .og-row.np .og-sw{display:none}
#og-deck[data-mode="play"] .og-row.np{background:color-mix(in srgb, var(--np-accent,#8fd0ff) 12%, transparent)}
#og-deck[data-mode="play"] .og-row.np .og-main{background:none}
@keyframes og-eq-bounce{0%,100%{transform:scaleY(.4)}50%{transform:scaleY(1)}}
#og-deck[data-mode="play"] .og-row.np .og-eq i{animation:og-eq-bounce .9s ease-in-out infinite}
#og-deck[data-mode="play"] .og-row.np .og-eq i:nth-child(1){animation-delay:-.45s}
#og-deck[data-mode="play"] .og-row.np .og-eq i:nth-child(2){animation-delay:-.15s}
#og-deck[data-mode="play"] .og-row.np.paused .og-eq i{animation-play-state:paused}
/* the mini NOW-PLAYING strip (play mode's footer, pinned at the deck's
   bottom): the era's album tile (T4) + live title + the i/n · ERA line +
   the expand chevron. One Deck T5: the TAP opens the full Now Playing view
   (jbShowNow) — the strip is the deck's live door to it (the T2
   visible-but-inert seam died with the view). */
#og-deck .og-np{display:flex;align-items:center;gap:12px;width:100%;min-height:56px;margin-top:8px;
  padding:8px 12px;background:rgba(11,17,40,.74);border:1px solid rgba(124,154,255,.32);
  border-radius:10px;font-family:var(--font-ui);color:inherit;text-align:left;cursor:pointer;
  touch-action:manipulation}
#og-deck .og-np[hidden]{display:none}
#og-deck .og-np-sw{flex:0 0 auto;width:36px;height:36px;border-radius:8px;
  box-shadow:inset 0 0 0 2px rgba(4,8,15,.55)}
  /* This box styles the PENDING-state accent swatch (T2's placeholder); once
     the era's tile bakes, game.js (deckPaint) swaps in an <img> that fills the
     box (object-fit:cover) — the F1 vinyl tile is SELF-FRAMED (disc + light
     ring + shadow live in the bake), so the img carries NO inline radius/ring
     and restyling this rule no longer diverges anything. */
#og-deck .og-np-line{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}
#og-deck .og-np-t{font-size:15px;font-weight:700;line-height:1.2;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#og-deck .og-np-i{font-size:11.5px;color:#8290c0;line-height:1.25;font-variant-numeric:tabular-nums}
#og-deck .og-np-x{flex:0 0 auto;width:20px;height:20px;color:#8290c0}
#og-deck .og-np-x svg{width:100%;height:100%;display:block}
/* phone floor — the row stack tightens on narrow windows (19/15 text keeps
   its size; the panel is already width-capped) */
@media (max-width:379px){
  #og-deck .og-row{min-height:44px}
  #og-deck .og-main{min-height:44px;gap:8px;padding:5px 6px}
  #og-deck .og-drift{font-size:14px;padding:10px 14px}
  #og-deck .og-np{min-height:50px;padding:6px 10px}
  #og-deck .og-np-sw{width:30px;height:30px}
}

/* ===== One Deck T5 — the NOW PLAYING view (spec §2.4, the R1-R8 layout) =====
   A sibling overlay of #og-deck: the same [hidden]-attribute posture, the
   jukebox's play-mode state gating (title AND complete — the deck's own
   inverse-leg law). The era BACKDROP art paints as the overlay's own
   background (game.js sets background-image per playing era — a style swap,
   never a transition: RM-clean by construction); the veil on top carries the
   scrim. No transitions exist in this block at all (the kill-switch is the
   belt). z 7 sits above the deck's 6 — never both visible, belt only. */
#og-now{position:absolute;inset:0;z-index:7;display:none;overflow:hidden;
  background-color:#03040c;background-size:cover;background-position:center;
  touch-action:none;font-family:var(--font-ui);color:#e9f1ff}
#stage[data-state="title"] #og-now:not([hidden]),
#stage[data-state="complete"] #og-now:not([hidden]){display:block}
/* R8 — the scrim (one pointer-dead overlay, three compounding layers): a
   uniform ~.38 base (the art stays the showpiece up top), a mid PLATEAU ≥.52
   across the info/scrubber band, and a floor fade packing to ~.86 under the
   transport/foot (the localized-scrim technique). The text/control bands
   (R3-R6) sit at ≥50% EFFECTIVE dim — the layers compound multiplicatively
   (e.g. base .38 + plateau .52 ≈ .70; NN/g: 30% fails, 50% passes). */
#og-now .ogn-veil{position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(to top, rgba(3,4,12,.86), rgba(3,4,12,.55) 16%, rgba(3,4,12,0) 34%),
    linear-gradient(to bottom, rgba(3,4,12,0) 22%, rgba(3,4,12,.52) 34%, rgba(3,4,12,.52) 74%, rgba(3,4,12,.42) 100%),
    linear-gradient(to bottom, rgba(3,4,12,.38), rgba(3,4,12,.38))}
/* the content column — the deck's own geometry: min(560px,100%) centered,
   safe-area aware. The horizontal paddings ride VARS so the percentage
   children below can resolve against the column's FULL width (the spec's
   "82% of the column" is the OUTER column, Spotify's screen-relative inset —
   a bare % would resolve against the padded content box and read ~5pp
   small). */
#og-now .ogn-col{position:relative;display:flex;flex-direction:column;
  width:min(560px,100%);height:100%;margin:0 auto;
  --ogn-pl:max(16px,env(safe-area-inset-left));--ogn-pr:max(16px,env(safe-area-inset-right));
  padding:max(14px,env(safe-area-inset-top)) var(--ogn-pr)
           max(12px,env(safe-area-inset-bottom)) var(--ogn-pl)}
/* 1.7.54 T2, THE CORNER LAW — the button is a DIRECT child of #og-now
   (re-parented out of .ogn-col): the offsets resolve against the overlay's
   padding box = the screen corner, same env() formula as every surface (the
   column's own env() padding never shifted an absolute child — the pre-
   1.7.54 rule at 6/6 of the column ignored the notch by construction) */
#og-now .og-close{position:absolute;
  top:max(12px, env(safe-area-inset-top) + 4px);
  right:max(12px, env(safe-area-inset-right))}
/* R2 — the album tile: square ~82% of the column width, centered, radius
   ≤3% (Spotify-era square-cornered art). The art BAND is the flex leftover:
   on short viewports max-height CLAMPS the tile (the imgs center-crop via
   object-fit — the cutout is center-cropped already) while every band below
   keeps flex:0 0 auto — the transport stays anchored. The container's own
   background is the era-accent placeholder while the bake is pending
   (no src = empty imgs, the background shows through).
   1.7.12 — the spin-split (the user's device ruling: "the spin should only
   be the vinyl circle! the black surround should not spin"): the tile is a
   THREE-LAYER stack of imgs at the ONE square geometry — frame (the STATIC
   surround: ground plate, cast shadow, black ring, outer light ring) ·
   disc (the ROTATING record: grooves, gap rings, label art, spindle) ·
   sheen (the diagonal specular + catchlight arcs — a FIXED light source,
   static ABOVE the rotating disc). Only the DISC img spins; frame + sheen
   carry no animation. The layers are decorative — pointer-events:none (the
   sheen must never intercept; the tile itself has no hit affordance, the
   view's controls are unchanged). overflow:hidden + the radius crops the
   stack exactly as the single img's own radius did. */
#og-now .ogn-art{flex:1 1 auto;min-height:0;display:flex;align-items:center;justify-content:center;
  padding:6px 0}
#og-now .ogn-tile{display:block;position:relative;max-height:100%;aspect-ratio:1/1;
  width:calc((100% + var(--ogn-pl) + var(--ogn-pr)) * .82);
  border-radius:50%;background:var(--ogn-accent,#8fd0ff);
  box-shadow:0 10px 34px rgba(0,0,0,.55);overflow:hidden}
/* 1.7.53 item 2 — the tile is CIRCULAR and the NP frame layer carries NO
   plate: the era's dimmed backdrop shows through around the record (the
   black square died on NP; the flat bake keeps its plate for the OS lock
   screen). radius 50% makes BOTH the pending accent placeholder AND the
   container's own box-shadow read as the record's disc, never a square
   halo; the F1 outer light ring (r 251.5 of 256) survives the crop. */
#og-now .ogn-ly{position:absolute;inset:0;width:100%;height:100%;display:block;
  object-fit:cover;pointer-events:none}
/* 1.7.11/1.7.12 — the vinyl spin (user-greenlit flourish, "buttery smooth,
   it does not need to be fast"); since the 1.7.12 spin-split it lives on
   the DISC LAYER ONLY: the record rotates slowly while PLAYING while the
   surround + sheen stay put — the turntable read (the user's device ruling
   retired the whole-tile rotation of the single baked img; the F1 rings
   are radially symmetric so only the label carries the motion).
   ~24 s per revolution, linear infinite, transform-ONLY (compositor-
   driven — no layout properties, no will-change until a measured need).
   Paused FREEZES the disc in place via animation-play-state (never a
   spin-down animation); game.js mirrors the element's paused truth onto
   #og-now.paused (npPaint — the same read the play glyph uses). The global
   RM kill-switch (`* { animation: none !important }`, the block at the
   sheet's foot) kills it entirely — reduced motion shows a static stack
   (the CSS side of the reduced-motion branches law). The STRIP thumb
   (.og-np-sw img) never spins: this selector is the NP disc layer only. */
#og-now #ogn-disc{animation:ogn-spin 24s linear infinite}
@keyframes ogn-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
#og-now.paused #ogn-disc{animation-play-state:paused}
/* R3 — the info block: native title (the PLAYING era's accent, bold) +
   attribution, wrapped (never ellipsized — the block has room), left-aligned
   per the converged pattern */
#og-now .ogn-info{flex:0 0 auto;padding:4px 2px 8px;text-align:left}
#og-now .ogn-t{font-size:20px;font-weight:700;line-height:1.3;
  color:var(--ogn-accent,#8fd0ff);overflow-wrap:break-word}
#og-now .ogn-a{font-size:14.5px;line-height:1.4;color:#c3cee6;margin-top:3px;overflow-wrap:break-word}
/* R4 — the scrubber: thin track (4px) + enlarged thumb (14px) + the pressed
   halo (.live); the WHOLE block is the ≥44-tall hit band (bar + times ride
   ONE target — tap and drag both seek through radioSeek). Elapsed left /
   −remaining right at reduced brightness. Static class swaps only — no
   transitions (RM-clean by construction). */
#og-now .ogn-scrub{flex:0 0 auto;display:flex;flex-direction:column;justify-content:center;gap:7px;
  min-height:44px;padding:6px 2px;cursor:pointer;touch-action:none}
#og-now .ogn-bar{position:relative;height:4px;border-radius:2px;background:rgba(130,144,192,.34)}
#og-now .ogn-fill{position:absolute;left:0;top:0;bottom:0;width:0%;border-radius:2px;
  background:var(--ogn-accent,#8fd0ff)}
#og-now .ogn-thumb{position:absolute;right:-7px;top:50%;width:14px;height:14px;border-radius:50%;
  transform:translateY(-50%);background:#e9f1ff;
  box-shadow:0 0 0 1px rgba(4,8,15,.6)}
#og-now .ogn-scrub.live .ogn-thumb{
  box-shadow:0 0 0 1px rgba(4,8,15,.6),
    0 0 0 7px color-mix(in srgb, var(--ogn-accent,#8fd0ff) 30%, transparent)}   /* the pressed halo */
#og-now .ogn-times{display:flex;justify-content:space-between;
  font-size:11.5px;color:#8290c0;font-variant-numeric:tabular-nums}
/* R5 — the transport: shuffle · prev · PLAY/PAUSE · next · repeat, ONE row,
   space-between (the proportional distribution — equal fixed gaps leave dead
   space on wide columns). PLAY is the dominant control: the filled accent
   disc ≈20% of the column (Spotify's 74/375) with the glyph at ~52% of the
   disc — 1.5-2× the side glyphs (26px); side glyphs BARE (no disc). Every
   target ≥44×44, adjacent gaps ≥8 CSS (space-between only grows them). */
#og-now .ogn-tr{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:10px 2px;touch-action:manipulation}
#og-now .ogn-ic{position:relative;flex:0 0 auto;width:56px;height:56px;padding:0;margin:0;
  display:flex;align-items:center;justify-content:center;
  background:none;border:0;color:#c3cee6;cursor:pointer}
#og-now .ogn-ic svg{width:26px;height:26px;display:block}
/* the colorblind-safe dual coding (Spotify's): active toggles take the
   accent AND a small dot UNDER the glyph — never a filled-variant swap.
   1.7.25 — the dot is ABSOLUTELY positioned at the button's bottom center:
   it NEVER participates in the button's flex centering, so the glyph sits
   on the same optical line as prev/next/play (in-flow, the 5px dot + 3px
   gap lifted the toggle glyphs 4px — they read raised/truncated). */
#og-now .ogn-ic.on{color:var(--ogn-accent,#8fd0ff)}
#og-now .ogn-dot{position:absolute;left:50%;bottom:6px;transform:translateX(-50%);
  width:5px;height:5px;border-radius:50%;background:transparent}
#og-now .ogn-ic.on .ogn-dot{background:var(--ogn-accent,#8fd0ff)}
/* the drawn "1" rides ONLY repeat-one (the off → all → one cycle; the loop
   glyph itself never changes shape) */
#og-now #ogn-repeat .ogn-one{display:none}
#og-now #ogn-repeat[data-repeat="one"] .ogn-one{display:block}
#og-now .ogn-playbtn{flex:0 0 auto;aspect-ratio:1/1;padding:0;margin:0;
  width:clamp(64px, calc((100% + var(--ogn-pl) + var(--ogn-pr)) * .2), 112px);
  display:flex;align-items:center;justify-content:center;border:0;border-radius:50%;
  background:var(--ogn-accent,#8fd0ff);color:rgba(6,12,26,.92);cursor:pointer;
  box-shadow:0 6px 20px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.22)}
#og-now .ogn-playbtn:active{filter:brightness(1.12)}
#og-now .ogn-playbtn svg{width:52%;height:52%;display:block}
/* Fix round 1 (F1-3): the display rules carry the `svg` TAG — the size rule
   above (#id + .class + tag) OUTRANKED a bare class rule and BOTH glyphs
   rendered side-by-side in every capture. Tag-qualified, the state rules
   win; each state renders EXACTLY ONE glyph (pinned by test).
   1.7.12 — the ACTION-labeled mapping (the user-caught inversion fix,
   superseding the T5 status-labeled pair): the icon shows what the press
   DOES — PLAYING → the PAUSE bars, paused/idle → the PLAY triangle
   (Apple HIG / Material / every major player; the old pair showed the
   CURRENT STATE, backwards). The .paused class mirror is unchanged —
   only the state→glyph mapping flips. */
#og-now .ogn-playbtn svg.ogn-iplay{display:none}
#og-now .ogn-playbtn.paused svg.ogn-ipause{display:none}
#og-now .ogn-playbtn.paused svg.ogn-iplay{display:block}
/* R6 — the queue context line (left) + the LIST return (bottom-right,
   Spotify's queue placement) */
#og-now .ogn-foot{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:2px 2px 0}
#og-now .ogn-ctx{font-size:11.5px;letter-spacing:.06em;color:#8290c0;font-variant-numeric:tabular-nums}
#og-now .ogn-list{flex:0 0 auto;width:44px;height:44px;padding:0;margin:0 -8px 0 0;
  display:flex;align-items:center;justify-content:center;background:none;border:0;
  color:#c3cee6;cursor:pointer}
#og-now .ogn-list svg{width:24px;height:24px;display:block}
#og-now .ogn-list:active{color:#e9f1ff}
/* pointer-gated hint lines (the deck's grammar; .og-touch rides the same
   isTouchDevice() gate game.js sets at build time). Fix round 1 (F1-2):
   ONE line, ever — nowrap + hidden, and game.js (npFitHintLine) drops
   TRAILING segments on overflow (the scrHint fit law's DOM edition) so the
   line never wraps to an orphaned trailing verb on phone widths. */
#og-now .ogn-keys{flex:0 0 auto;padding:6px 12px 4px;text-align:center;font-size:10.5px;color:#61708f;
  white-space:nowrap;overflow:hidden}
#og-now .ogn-touchkeys{flex:0 0 auto;padding:6px 12px 4px;text-align:center;font-size:10.5px;color:#61708f;
  white-space:nowrap;overflow:hidden;display:none}
#og-now.og-touch .ogn-keys{display:none}
#og-now.og-touch .ogn-touchkeys{display:block}
/* phone floor — the transport tightens (the deck's own @379 precedent);
   44px sides + the 64px disc floor still fit a 320 column */
@media (max-width:379px){
  #og-now .ogn-ic{width:44px;height:44px}
  #og-now .ogn-ic svg{width:24px;height:24px}
  #og-now .ogn-dot{bottom:4px}
}

/* Task 24 — reduced-motion kill-switch: gauge/goal pulses all snap to their
   end state. Canvas motion (plankton drift) is gated
   separately in game.js via the same media query. HUD-redesign Task 4: the
   selector set grew *::before/*::after — `*` alone does NOT reach
   pseudo-elements (caught by the p4 RM test: the ring pulsed under RM).
   perf(T18): the pill's PULSE is now the .pill-halo — a plain ELEMENT
   (reached by `*` directly); the ::after arms stay for the remaining
   pseudo animations (.gain and friends). Under RM the halo stills to its
   base — the ruled C2b-BA frozen read (see the ring block above). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
