/* =========================================================================
   Gauntlet Runner — all of the UI, one file.

   Colour comes from the music. render.js (or ui.applyPalette) writes these on
   :root and the whole interface recolours with the key:

     --pal-hue     0..360   tonic pitch class -> hue rotation
     --pal-hue2    0..360   secondary hue (chord colour)
     --pal-sat     0..100   energy -> saturation
     --pal-energy  0..1     drives glow strength
     --pal-accent / --pal-accent-2 / --pal-bg   optional hard overrides

   Flags written by settings.js:
     :root[data-motion="reduced"|"full"]  [data-cb="0"|"1"]  [data-quality=...]
   ========================================================================= */

@property --beat { syntax: '<number>'; initial-value: 0; inherits: false; }

:root {
  --pal-hue: 196;
  --pal-hue2: 268;
  --pal-sat: 82;
  --pal-energy: 0.6;

  --sat: calc(var(--pal-sat) * 1%);
  --accent:    var(--pal-accent,   hsl(var(--pal-hue) var(--sat) 64%));
  --accent-2:  var(--pal-accent-2, hsl(var(--pal-hue2) var(--sat) 70%));
  --accent-ink: hsl(var(--pal-hue) 70% 6%);
  --bg:        var(--pal-bg, hsl(var(--pal-hue) 32% 4%));
  --panel:     hsl(var(--pal-hue) 26% 6% / 0.84);
  --panel-2:   hsl(var(--pal-hue) 26% 9% / 0.55);
  --line:      hsl(var(--pal-hue) 45% 72% / 0.14);
  --line-2:    hsl(var(--pal-hue) 55% 72% / 0.30);
  --ink:       hsl(var(--pal-hue) 22% 97%);
  --dim:       hsl(var(--pal-hue) 20% 78% / 0.60);
  --dimmer:    hsl(var(--pal-hue) 20% 74% / 0.34);
  --warn:      hsl(38 96% 64%);
  --bad:       hsl(352 92% 68%);
  --good:      hsl(150 70% 58%);
  --glow:      hsl(var(--pal-hue) var(--sat) 62% / calc(0.20 + var(--pal-energy) * 0.28));

  --display: "Archivo Narrow", "Oswald", "Avenir Next Condensed", "HelveticaNeue-CondensedBold",
             "Helvetica Neue", "Segoe UI", system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Roboto Mono", Menlo, Consolas, monospace;

  --r: 3px;                 /* corner radius: almost square. vector game, not an app */
  --gut: clamp(18px, 3.2vw, 44px);
  --anim: 1;
  --shake-amount: 1;
  --ui-motion: 1;
  --beat-dur: 0.5s;
  --bar-dur: 2s;

  color-scheme: dark;
}

/* colour-blind safe: same music, separated by brightness instead of hue */
:root[data-cb="1"] {
  --accent:   hsl(var(--pal-hue) 22% 92%);
  --accent-2: hsl(var(--pal-hue) 16% 58%);
  --accent-ink: hsl(var(--pal-hue) 30% 6%);
  --line-2:   hsl(var(--pal-hue) 12% 82% / 0.34);
  --bad:      hsl(var(--pal-hue) 8% 70%);
  --good:     hsl(var(--pal-hue) 10% 96%);
  --glow:     hsl(var(--pal-hue) 10% 80% / 0.22);
}

:root[data-motion="reduced"] *,
:root[data-motion="reduced"] *::before,
:root[data-motion="reduced"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}

/* ---------- page ---------- */
* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--display);
  overflow: hidden;
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}
canvas { display: block; width: 100%; height: 100%; }
body > canvas, #game-canvas { position: fixed; inset: 0; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* =========================================================================
   shell
   ========================================================================= */
.ui {
  position: fixed; inset: 0; z-index: 10;
  pointer-events: none;
  font-family: var(--display);
  color: var(--ink);
  contain: layout style;
}
/* :where() keeps the reset at zero specificity so a later .action / .mini / .seg-b
   rule wins on source order. A bare `.ui button` here (0-1-1) would silently beat
   every single-class button rule in this file. */
.ui :where(button, input) { font: inherit; color: inherit; background: none; border: 0; margin: 0; cursor: pointer; }
.ui :focus { outline: none; }
.ui :focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r);
}

.lbl {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px; font-weight: 500;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--dimmer);
}
.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

.kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 20px; padding: 0 6px;
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-bottom-width: 2px;
  border-radius: 3px;
  background: hsl(var(--pal-hue) 30% 14% / 0.7);
  box-shadow: inset 0 1px 0 hsl(var(--pal-hue) 40% 80% / 0.10);
}

/* =========================================================================
   HUD
   ========================================================================= */
.hud {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 0.22s ease;
  pointer-events: none;
  text-shadow: 0 1px 14px hsl(var(--pal-hue) 60% 4% / 0.9);
}
.ui[data-state="playing"] .hud { opacity: 1; }
.ui[data-state="paused"]  .hud { opacity: 0.30; }
.hud::before, .hud::after {
  content: ""; position: absolute; left: 0; right: 0; height: 130px; pointer-events: none;
}
.hud::before { top: 0;    background: linear-gradient(hsl(var(--pal-hue) 45% 3% / 0.68), transparent); }
.hud::after  { bottom: 0; background: linear-gradient(transparent, hsl(var(--pal-hue) 45% 3% / 0.72)); }

.hud-tl, .hud-tr, .hud-bl, .hud-bc { position: absolute; }
.hud-tl { top: var(--gut); left: var(--gut); }
.hud-tr { top: var(--gut); right: var(--gut); text-align: right; }
.hud-bl { bottom: var(--gut); left: var(--gut); display: flex; gap: 0; }
.hud-bc { bottom: calc(var(--gut) - 4px); left: 50%; transform: translateX(-50%); }

/* `.hud-tl` is absolutely positioned, so its flex children have infinite room to grow
   and would happily run off the right edge of a phone. The max-width is what makes
   `flex-wrap` mean anything here: the graze pop drops to its own line instead of
   pushing the score off screen. */
.hud-tl { max-width: calc(100vw - var(--gut) * 2); }
.hud-score-row { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.hud-score {
  font-size: clamp(38px, 5.4vw, 66px);
  font-weight: 700; line-height: 0.86; letter-spacing: -0.025em;
  color: var(--ink);
  filter: drop-shadow(0 0 22px var(--glow));
}
/* ---------- the multiplier chip, which is a gauge ----------
   THREE FACTS, ONE 60x22 px OBJECT, all readable without looking away from the obstacle:

     the number     what am I holding      (text; `data-step` styles the rung)
     --charge       how far to the next    (the bar along the bottom edge)
     --fuse         how long have I got    (the bar along the top edge, and `is-fading`)

   The rung ladder is deliberately not a ramp of one colour: 1.2x is an outline, 1.5x is
   filled, 2x adds the second accent, 3x adds a moving sweep and 4x goes white-hot. Each
   one is a different OBJECT at a glance, because "am I on 3 or 4" is a decision the player
   makes in peripheral vision at 87 presses a minute. */
.hud-mult {
  position: relative;
  font-family: var(--mono);
  font-size: 15px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--dim);
  padding: 3px 9px 4px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: hsl(var(--pal-hue) 40% 10% / 0.42);
  transform-origin: left center;
  overflow: hidden;
  transition: color 0.12s linear, background 0.12s linear, border-color 0.12s linear;
}
.mult-val { position: relative; z-index: 1; }
/* charge: the bottom edge fills as you climb toward the next rung */
.mult-charge, .mult-fuse {
  position: absolute; left: 0; right: 0; height: 2px;
  transform-origin: left center;
  pointer-events: none;
}
.mult-charge {
  bottom: 0;
  background: var(--accent-2);
  transform: scaleX(var(--charge, 0));
  opacity: 0.9;
}
/* fuse: the top edge empties as the safe stretch runs on. Full = you just shaved something. */
.mult-fuse {
  top: 0;
  background: var(--accent);
  transform: scaleX(var(--fuse, 0));
  opacity: 0.75;
}

/* rung 1 — 1.2x: the outline lights up, nothing else */
.hud-mult[data-step="1"] { color: var(--accent); border-color: var(--accent); }
/* rung 2 — 1.5x: filled */
.hud-mult[data-step="2"] {
  color: var(--accent-ink); background: var(--accent); border-color: transparent;
  box-shadow: 0 0 calc(10px + var(--m, 0) * 14px) var(--glow);
}
/* rung 3 — 2x: two colours, so the chip stops being a shade of the rung below */
.hud-mult[data-step="3"] {
  color: var(--accent-ink);
  background: linear-gradient(100deg, var(--accent), var(--accent-2));
  border-color: transparent;
  box-shadow: 0 0 calc(14px + var(--m, 0) * 20px) var(--glow);
}
/* rung 4 — 3x: the same two colours, moving */
.hud-mult[data-step="4"] {
  color: #fff;
  background: linear-gradient(100deg, var(--accent), var(--accent-2), var(--accent));
  background-size: 220% 100%;
  border-color: transparent;
  box-shadow: 0 0 28px var(--glow), inset 0 0 0 1px hsl(var(--pal-hue) 60% 96% / 0.35);
  animation: multsweep 1.6s linear infinite;
}
/* rung 5 — the 4x cap: white-hot, and the only rung that beats */
.hud-mult[data-step="5"], .hud-mult.is-max {
  color: #fff;
  background: linear-gradient(120deg, var(--accent), var(--accent-2));
  border-color: transparent;
  box-shadow: 0 0 34px var(--glow), inset 0 0 0 1px hsl(var(--pal-hue) 60% 96% / 0.55);
  animation: multmax 1.1s ease-in-out infinite;
}
.hud-mult[data-step="5"] .mult-charge, .hud-mult[data-step="5"] .mult-fuse { background: #fff; }
@keyframes multmax {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.24); }
}
@keyframes multsweep {
  0%   { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}

/* ---------- "ABOUT TO LOSE IT" ----------
   The one warning state in the HUD, and it has to be unmistakable without being another
   flashing light in a game that already has several: the chip drains its colour toward the
   warning hue and beats faster than anything else on screen, and the fuse bar it sits under
   is already visibly emptying. It only ever shows while there is something to lose. */
.hud-mult.is-fading {
  animation: multfade 0.38s ease-in-out infinite;
  box-shadow: 0 0 16px hsl(38 90% 60% / 0.45);
}
.hud-mult.is-fading .mult-fuse { background: var(--warn, #ffb020); opacity: 1; }
@keyframes multfade {
  0%, 100% { filter: saturate(0.35) brightness(0.82); }
  50%      { filter: saturate(1) brightness(1.1); }
}
/* losing it: one hard drop, no bounce — it must not read as a reward */
.hud-mult.lost-a, .hud-mult.lost-b { animation: multlost 0.34s ease-out; }
@keyframes multlost {
  0%   { transform: scale(1.06) translateY(-2px); filter: brightness(1.6) saturate(0); }
  100% { transform: none; filter: none; }
}

.hud-sub { display: flex; gap: 14px; margin-top: 9px; align-items: center; }
.hud-best, .streak-n {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--dim);
}

/* ---------- the risk meter (GAMEDESIGN §2, the risk/reward loop) ----------
   Six segments, one per rung of the multiplier ladder. Readable in peripheral vision at
   speed: filled/empty is a shape, "3 streak" was a word you had to stop and read.

   It shows the RISK METER, not grazes in a row. The segment you are working on fills
   continuously with --c, so the meter visibly climbs between rungs and visibly slides
   back down while it drains — the decay is a thing you watch, not a thing you deduce. */
.hud-streak { display: flex; align-items: center; gap: 9px; }
.streak-segs { display: flex; gap: 3px; }
.streak-seg {
  position: relative; overflow: hidden;
  width: 12px; height: 4px; border-radius: 1px;
  background: hsl(var(--pal-hue) 30% 70% / 0.18);
  transition: background 0.14s ease, box-shadow 0.14s ease, transform 0.14s ease;
}
.streak-seg.is-on {
  background: var(--accent-2);
  box-shadow: 0 0 10px var(--glow);
}
/* the rung being bought right now: a partial fill, no transition (it moves every frame) */
.streak-seg.is-charging::after {
  content: ""; position: absolute; inset: 0;
  background: var(--accent-2); opacity: 0.55;
  transform: scaleX(var(--c, 0)); transform-origin: left center;
}
.streak-n { color: var(--accent-2); }
.hud-streak.is-max .streak-seg.is-on {
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  box-shadow: 0 0 14px var(--glow);
}
.hud-streak.is-max .streak-n { color: var(--ink); }
/* about to lose it: the lit segments go hollow, which is the shape of "draining" */
.hud-streak.is-fading .streak-seg.is-on {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--warn, #ffb020), 0 0 8px hsl(38 90% 60% / 0.4);
  animation: segfade 0.38s ease-in-out infinite;
}
.hud-streak.is-fading .streak-seg.is-charging::after { background: var(--warn, #ffb020); }
@keyframes segfade { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }
/* The gain, not the loss: a streak breaking is already the loudest event on the HUD
   (every segment goes out at once) and animating that too would read as a reward.
   ui.setStreak flips these on the single segment that just lit — a CSS selector cannot
   name "the newest filled one" without knowing the count. */
.streak-seg.sg-a { animation: segpop 0.3s cubic-bezier(0.2, 1.7, 0.4, 1); }
.streak-seg.sg-b { animation: segpop2 0.3s cubic-bezier(0.2, 1.7, 0.4, 1); }
@keyframes segpop  { 0% { transform: scaleY(2.8) scaleX(1.2); background: #fff; } 100% { transform: none; } }
@keyframes segpop2 { 0% { transform: scaleY(2.8) scaleX(1.2); background: #fff; } 100% { transform: none; } }

/* ---------- the graze pop ----------
   Fires on the event, not on a change of state, so it still pays out on the twelfth
   graze in a row when the multiplier chip has stopped moving. It animates from
   opacity 0 back to opacity 0, which is why nothing has to time it out. */
.graze-pop {
  font-family: var(--mono); font-size: 11.5px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent-2);
  opacity: 0;
  white-space: nowrap;
  transform-origin: left center;
  pointer-events: none;
  text-shadow: 0 0 18px var(--glow);
}
/* ---------- the pop's five bands ----------
   `data-band` is how close the pass was, and it escalates in size AND in weight, so the
   difference between a pass that bought a rung and one that bought nothing is legible at
   the edge of vision. `flat` is the important one: no multiplier is printed after the
   word, it is grey, and it is the smallest thing on the HUD — that is the game telling
   you, in the moment, that proximity is not risk. */
.graze-pop[data-band="3"] { color: var(--accent-2); }                                  /* tight   */
.graze-pop[data-band="2"] { color: var(--accent); font-size: 12.5px; }                  /* close   */
.graze-pop[data-band="1"] { color: var(--ink); font-size: 13.5px; letter-spacing: 0.16em; }  /* perfect */
.graze-pop[data-band="0"] {                                                            /* razor   */
  color: #fff; font-size: 15px; letter-spacing: 0.2em;
  text-shadow: 0 0 10px #fff, 0 0 28px var(--glow);
}
.graze-pop[data-band="flat"] {
  color: var(--dimmer); font-size: 9.5px; font-weight: 500;
  letter-spacing: 0.1em; text-shadow: none;
}
/* the cut: the only red thing in the HUD, and it says what it cost */
.graze-pop[data-band="lost"] {
  color: var(--warn, #ffb020); font-size: 12px; font-weight: 700;
  text-shadow: 0 0 14px hsl(38 90% 60% / 0.5);
}
/* `data-band` owns the colour now (it is the finer-grained fact); `is-max` only adds the
   cap's halo on top, so the two cannot fight over the same property. */
.graze-pop.is-max { text-shadow: 0 0 12px #fff, 0 0 34px var(--glow); }
.graze-pop.gz-a { animation: gzpop 0.62s cubic-bezier(0.16, 1, 0.3, 1); }
.graze-pop.gz-b { animation: gzpop2 0.62s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes gzpop {
  0%   { opacity: 0; transform: translateX(-6px) scale(0.8); }
  14%  { opacity: 1; transform: translateX(0) scale(1.12); }
  38%  { opacity: 1; transform: translateX(0) scale(1); }
  100% { opacity: 0; transform: translateX(9px) scale(1); }
}
@keyframes gzpop2 {
  0%   { opacity: 0; transform: translateX(-6px) scale(0.8); }
  14%  { opacity: 1; transform: translateX(0) scale(1.12); }
  38%  { opacity: 1; transform: translateX(0) scale(1); }
  100% { opacity: 0; transform: translateX(9px) scale(1); }
}

.hud-key {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "lbl chord" "stack stack";
  align-items: baseline; column-gap: 18px;
  padding: 7px 12px 9px;
  border: 1px solid var(--line);
  border-right: 2px solid var(--accent);
  border-radius: var(--r);
  background: hsl(var(--pal-hue) 40% 8% / 0.5);
  min-width: 172px;
}
.hud-key .lbl { grid-area: lbl; text-align: left; }
.key-stack { grid-area: stack; position: relative; height: 26px; overflow: hidden; margin-top: 2px; }
.key-now, .key-prev {
  display: block;
  font-size: 22px; font-weight: 700; letter-spacing: -0.01em; line-height: 26px;
  white-space: nowrap; text-align: right;
}
/* The current key stays in flow so the readout sizes itself to the longest key
   name ("F# harmonicMinor"); only the outgoing key is lifted out to animate
   over it. If both were absolute the box could not measure them and long names
   would be clipped by the stack's overflow. */
.key-now { position: relative; color: var(--ink); }
.key-prev { position: absolute; right: 0; top: 0; color: var(--dimmer); opacity: 0; }
/* the chord under the player, in roman numerals — it sits on the label's baseline
   so the readout reads as one instrument rather than three stacked scraps */
.key-chord {
  grid-area: chord; justify-self: end;
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--accent-2);
  min-height: 12px; min-width: 1ch; text-align: right;
}
.hud-key.mod-a { animation: keymod 0.62s cubic-bezier(0.2, 0.9, 0.2, 1); }
.hud-key.mod-b { animation: keymod2 0.62s cubic-bezier(0.2, 0.9, 0.2, 1); }
.hud-key.mod-a .key-now, .hud-key.mod-b .key-now { animation: keyin 0.5s cubic-bezier(0.16, 1, 0.3, 1); }
.hud-key.mod-a .key-prev, .hud-key.mod-b .key-prev { animation: keyout 0.5s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes keymod {
  0%   { border-right-color: #fff; box-shadow: 0 0 0 1px var(--accent), 0 0 34px var(--glow); background: hsl(var(--pal-hue) 60% 22% / 0.6); }
  100% { border-right-color: var(--accent); box-shadow: none; background: hsl(var(--pal-hue) 40% 8% / 0.45); }
}
@keyframes keymod2 {
  0%   { border-right-color: #fff; box-shadow: 0 0 0 1px var(--accent), 0 0 34px var(--glow); background: hsl(var(--pal-hue) 60% 22% / 0.6); }
  100% { border-right-color: var(--accent); box-shadow: none; background: hsl(var(--pal-hue) 40% 8% / 0.45); }
}
@keyframes keyin  { 0% { transform: translateY(26px); opacity: 0; } 100% { transform: none; opacity: 1; } }
@keyframes keyout { 0% { transform: none; opacity: 0.75; } 100% { transform: translateY(-26px); opacity: 0; } }

.hud-stat { display: flex; flex-direction: column; gap: 5px; padding-right: 22px; }
.hud-stat + .hud-stat { padding-left: 22px; border-left: 1px solid var(--line); }
.hud-dist, .hud-graze {
  font-family: var(--mono); font-size: 17px; font-weight: 600; letter-spacing: 0.01em;
  color: var(--ink);
}
.hud-graze.pop-a { animation: pop 0.26s ease-out; }
.hud-graze.pop-b { animation: pop2 0.26s ease-out; }
.hud-mult.pop-a { animation: pop 0.3s cubic-bezier(0.2, 1.6, 0.4, 1); }
.hud-mult.pop-b { animation: pop2 0.3s cubic-bezier(0.2, 1.6, 0.4, 1); }
.hud-mult.drop-a, .hud-mult.drop-b { animation: drop 0.22s ease-out; }
@keyframes pop  { 0% { transform: scale(1.34); color: #fff; } 100% { transform: scale(1); } }
@keyframes pop2 { 0% { transform: scale(1.34); color: #fff; } 100% { transform: scale(1); } }
@keyframes drop { 0% { transform: scale(0.82); opacity: 0.5; } 100% { transform: scale(1); opacity: 1; } }

.hud-fps {
  margin-top: 8px; font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.14em; color: var(--dimmer);
}
.hud-fps.is-bad { color: var(--warn); }

/* Beat cues. Audio is never required to play, so these must be legible at rest,
   not only on the frame they fire. */
.pips { display: flex; gap: 10px; align-items: center; }
.pip {
  --s: 0.6;
  width: 28px; height: 3px; border-radius: 2px;
  background: hsl(var(--pal-hue) 34% 74% / 0.30);
}
.pip.hit-a { animation: pip var(--beat-dur) cubic-bezier(0.1, 0.8, 0.3, 1); }
.pip.hit-b { animation: pip2 var(--beat-dur) cubic-bezier(0.1, 0.8, 0.3, 1); }
.pip.is-down { width: 38px; height: 4px; background: hsl(var(--pal-hue) 40% 80% / 0.42); }
@keyframes pip {
  0%   { background: var(--accent); box-shadow: 0 0 18px var(--glow); transform: scaleY(2.6); }
  100% { background: hsl(var(--pal-hue) 34% 74% / 0.30); box-shadow: none; transform: scaleY(1); }
}
@keyframes pip2 {
  0%   { background: var(--accent); box-shadow: 0 0 18px var(--glow); transform: scaleY(2.6); }
  100% { background: hsl(var(--pal-hue) 34% 74% / 0.30); box-shadow: none; transform: scaleY(1); }
}

/* =========================================================================
   screens
   ========================================================================= */
.screen {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; visibility: hidden; pointer-events: none;
  /* visibility flips discretely, delayed on hide so the fade-out is still seen.
     Interpolating it instead leaves a just-shown screen computing `hidden` for
     part of a frame, and focus() into it silently does nothing. */
  transition: opacity 0.16s ease, visibility 0s linear 0.16s;
}
.ui[data-state="title"]  .screen--title,
.ui[data-state="dead"]   .screen--death,
.ui[data-state="paused"] .screen--pause,
.ui[data-overlay="settings"] .screen--settings,
.ui[data-overlay="scores"]   .screen--scores {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity 0.16s ease, visibility 0s linear 0s;
}

/* ---------- the death panel waits ----------
   The shatter is 75 shards, two rings and a 120 ms hitstop, and it was being covered by a
   backdrop-blurred panel that started fading up on the frame it began. So the panel — and
   the dimming behind it — hold for --death-wait before they start, and the shatter gets
   the screen to itself.

   VISIBILITY IS NOT DELAYED, only opacity. The panel is therefore focusable from the frame
   the state flips, `_focusSoon` still lands on the Retry button first time, and `R` is live
   throughout. A retry pressed EARLIER than that — while the player was still shattering —
   is buffered in ui.js and spent the instant this screen exists, so the fastest possible
   path back into a run got shorter, not longer. */
.ui { --death-wait: 180ms; }
.ui[data-state="dead"] .screen--death {
  transition: opacity 0.16s ease var(--death-wait), visibility 0s linear 0s;
}
/* The panel's own entry, restarted by a class flip in ui.showDeath — a bare `animation:` on
   the element runs once at boot, behind a hidden screen, and is finished long before anybody
   dies. Two identical keyframe sets so the flip can retrigger it without a forced reflow. */
.panel--death.pin-a { animation: panelin  0.22s cubic-bezier(0.16, 1, 0.3, 1) var(--death-wait) backwards; }
.panel--death.pin-b { animation: panelin2 0.22s cubic-bezier(0.16, 1, 0.3, 1) var(--death-wait) backwards; }
@keyframes panelin2 { 0% { opacity: 0; transform: translateY(10px) scale(0.985); } 100% { opacity: 1; transform: none; } }
/* an open overlay pushes whatever is behind it back */
.ui[data-overlay="settings"] .screen--title, .ui[data-overlay="scores"] .screen--title,
.ui[data-overlay="settings"] .screen--death, .ui[data-overlay="scores"] .screen--death,
.ui[data-overlay="settings"] .screen--pause, .ui[data-overlay="scores"] .screen--pause {
  opacity: 0.16; filter: blur(2px); pointer-events: none;
}

/* ---------- title ---------- */
.screen--title {
  /* `safe center` centres when there is room and falls back to flex-start when
     there is not — plain `center` would push the top of the block off-screen
     where scrolling cannot reach it. */
  flex-direction: column; align-items: flex-start; justify-content: safe center;
  padding: var(--gut) clamp(24px, 6vw, 96px) calc(46px + var(--gut));  /* keep clear of .hintbar */
  /* Dark enough for the wordmark, light enough that the live world still reads
     through it — the title screen is a demo of the engine, not a poster. */
  background:
    /* pooled under the menu, where small type has to survive the running world */
    radial-gradient(42% 34% at 2% 76%, hsl(var(--pal-hue) 55% 2% / 0.90), transparent 78%),
    radial-gradient(52% 56% at 4% 26%, hsl(var(--pal-hue) 55% 2% / 0.66), transparent 78%),
    linear-gradient(90deg, hsl(var(--pal-hue) 50% 2% / 0.86) 0%, hsl(var(--pal-hue) 50% 2% / 0.60) 24%,
                           hsl(var(--pal-hue) 50% 2% / 0.16) 50%, transparent 72%);
}
.title-block, .title-panel { filter: drop-shadow(0 2px 18px hsl(var(--pal-hue) 60% 2% / 0.9)); }
.title-block { max-width: 640px; }
.eyebrow {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: 0.34em; text-transform: uppercase; color: var(--accent);
}
.wordmark {
  margin: 14px 0 0; padding: 0;
  font-weight: 700; line-height: 0.80; letter-spacing: -0.02em;
  text-transform: uppercase;
  display: flex; flex-direction: column;
  position: relative;
}
.wordmark-l1, .wordmark-l2 {
  font-size: clamp(58px, 11.5vw, 148px);
  display: block; position: relative;
}
.wordmark-l1 {
  background: linear-gradient(178deg, #fff 8%, var(--accent) 62%, var(--accent-2) 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  filter: drop-shadow(0 0 calc(26px + 30px * var(--beat, 0)) var(--glow));
}
.wordmark-l2 {
  color: transparent;
  -webkit-text-stroke: 2px hsl(var(--pal-hue) 30% 92% / 0.72);
}
/* the outline alone goes hollow on big displays; a faint fill gives it body */
.wordmark-l2::before {
  content: attr(data-text);
  position: absolute; inset: 0;
  -webkit-text-stroke: 0;
  color: hsl(var(--pal-hue) 40% 80% / 0.09);
}
.ui.bar-a .wordmark-l1 { animation: beatglow var(--bar-dur) linear infinite; }
.ui.bar-b .wordmark-l1 { animation: beatglow2 var(--bar-dur) linear infinite; }
@keyframes beatglow  { 0% { --beat: 1; } 22% { --beat: 0.1; } 50% { --beat: 0.42; } 72% { --beat: 0.06; } 100% { --beat: 0; } }
@keyframes beatglow2 { 0% { --beat: 1; } 22% { --beat: 0.1; } 50% { --beat: 0.42; } 72% { --beat: 0.06; } 100% { --beat: 0; } }

.tagline {
  margin: 18px 0 0; max-width: 44ch;
  font-family: var(--mono); font-size: 12px; line-height: 1.75;
  letter-spacing: 0.04em; color: var(--dim);
}
.title-panel { margin-top: clamp(22px, 4vh, 42px); }

.menu {
  display: flex; flex-direction: column; width: min(330px, 74vw);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.menu-item {
  position: relative;
  display: flex; align-items: center; gap: 11px;
  height: 44px; padding: 0 12px 0 0;
  text-align: left;
  /* its own scrim: the world is genuinely running underneath the menu */
  background: linear-gradient(90deg, hsl(var(--pal-hue) 50% 2% / 0.62), hsl(var(--pal-hue) 50% 2% / 0.16) 88%, transparent);
  transition: background 0.16s;
}
.menu-item + .menu-item { border-top: 1px solid hsl(var(--pal-hue) 40% 70% / 0.08); }
.menu-mark {
  width: 3px; height: 0; background: var(--accent); border-radius: 2px;
  transition: height 0.18s cubic-bezier(0.2, 0.9, 0.2, 1);
  box-shadow: 0 0 14px var(--glow);
}
.menu-text {
  flex: 1;
  font-size: 19px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--dim);
  transition: color 0.16s, transform 0.16s cubic-bezier(0.2, 0.9, 0.2, 1);
}
.menu-item .kbd { opacity: 0.3; transition: opacity 0.16s; }
.menu-item:hover, .menu-item:focus-visible { background: linear-gradient(90deg, hsl(var(--pal-hue) 50% 40% / 0.12), transparent 74%); }
.menu-item:hover .menu-mark, .menu-item:focus-visible .menu-mark { height: 20px; }
.menu-item:hover .menu-text, .menu-item:focus-visible .menu-text { color: var(--ink); transform: translateX(6px); }
.menu-item:hover .kbd, .menu-item:focus-visible .kbd { opacity: 1; }
.menu-item--primary { height: 56px; }
.menu-item--primary .menu-text { color: var(--ink); font-size: 26px; letter-spacing: 0.03em; }
.menu-item--primary .menu-mark { height: 26px; }
.menu-item--primary .kbd { opacity: 1; }

.seed-row {
  display: flex; align-items: center; gap: 8px;
  margin-top: 15px; min-height: 32px;
}
.seed-row .lbl { color: var(--dimmer); }
.seed-val, .seed-input {
  font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: 0.14em;
  color: var(--accent);
}
.seed-row.has-best::after {
  content: "on this seed " attr(data-best);
  margin-left: 2px; padding-left: 10px;
  border-left: 1px solid var(--line);
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--dimmer);
}
.seed-input {
  display: none; width: 150px; padding: 5px 8px;
  background: hsl(var(--pal-hue) 40% 10% / 0.8);
  border: 1px solid var(--line-2); border-radius: var(--r);
  color: var(--ink); text-transform: uppercase;
}
.seed-row.editing .seed-val { display: none; }
.seed-row.editing .seed-input { display: block; }
.mini {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--dimmer);
  padding: 7px 9px; min-height: 30px;
  border: 1px solid var(--line); border-radius: var(--r);
  transition: color 0.14s, border-color 0.14s;
}
.mini:hover { color: var(--ink); border-color: var(--line-2); }
.title-best {
  margin-top: 14px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--dimmer);
}

.hintbar {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; gap: 22px; justify-content: center; align-items: center;
  height: 46px;
  border-top: 1px solid var(--line);
  background: hsl(var(--pal-hue) 40% 4% / 0.5);
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--dimmer);
}
.hintbar span { display: inline-flex; align-items: center; gap: 7px; }

/* ---------- panels ---------- */
.screen--death, .screen--pause, .screen--overlay {
  background: radial-gradient(90% 70% at 50% 50%, hsl(var(--pal-hue) 50% 2% / 0.80), hsl(var(--pal-hue) 55% 1% / 0.93));
  backdrop-filter: blur(8px) saturate(120%);
  -webkit-backdrop-filter: blur(8px) saturate(120%);
  padding: var(--gut);
}
.panel {
  position: relative;
  display: flex; flex-direction: column;
  width: min(560px, 94vw);
  /* never taller than the window: the scrolling body absorbs the shortfall */
  max-height: calc(100vh - var(--gut) * 2);
  max-height: calc(100dvh - var(--gut) * 2);
  padding: 26px 28px 24px;
  background: linear-gradient(hsl(var(--pal-hue) 28% 8% / 0.90), hsl(var(--pal-hue) 30% 5% / 0.92));
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: 0 30px 90px hsl(var(--pal-hue) 60% 1% / 0.7), inset 0 1px 0 hsl(var(--pal-hue) 40% 80% / 0.08);
  animation: panelin 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes panelin { 0% { opacity: 0; transform: translateY(10px) scale(0.985); } 100% { opacity: 1; transform: none; } }
/* corner ticks: cheap, and they read as "instrument", not "web form" */
.panel::before, .panel::after {
  content: ""; position: absolute; width: 12px; height: 12px; pointer-events: none;
  border-color: var(--accent); opacity: 0.85;
}
.panel::before { top: -1px; left: -1px; border-top: 2px solid; border-left: 2px solid; }
.panel::after { bottom: -1px; right: -1px; border-bottom: 2px solid; border-right: 2px solid; }

.panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.panel-title { margin: 6px 0 0; font-size: 30px; font-weight: 700; letter-spacing: -0.01em; text-transform: uppercase; }
.panel-note { margin: 10px 0 0; font-family: var(--mono); font-size: 11.5px; line-height: 1.65; color: var(--dim); }
.panel-body {
  margin-top: 12px; max-height: min(60vh, 520px);
  overflow-y: auto; overscroll-behavior: contain;
  padding: 8px 10px 0 0;
  /* the fade says "there is more" without a scrollbar doing the talking. The 8px
     top padding keeps the first row clear of the top fade at scrollTop 0. */
  --m: linear-gradient(transparent 0, #000 8px, #000 calc(100% - 26px), transparent 100%);
  mask-image: var(--m); -webkit-mask-image: var(--m);
  flex: 0 1 auto; min-height: 0;
}
.panel > *:not(.panel-body) { flex: none; }
.panel-body::-webkit-scrollbar { width: 6px; }
.panel-body::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 3px; }
.panel-body::-webkit-scrollbar-track { background: transparent; }

.badge {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase;
  padding: 5px 9px; border-radius: 2px;
  color: var(--accent-ink); background: var(--accent);
  box-shadow: 0 0 24px var(--glow);
  animation: badgein 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  white-space: nowrap;
}
.badge--seed { background: var(--accent-2); }
@keyframes badgein { 0% { transform: scale(0.6) rotate(-4deg); opacity: 0; } 100% { transform: none; opacity: 1; } }

/* ---------- death ---------- */
.death-score { margin-top: 14px; display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.big-num {
  font-size: clamp(56px, 9vw, 86px); font-weight: 700; line-height: 0.86; letter-spacing: -0.03em;
  filter: drop-shadow(0 0 30px var(--glow));
  animation: scorein 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes scorein { 0% { opacity: 0; transform: translateY(8px); } 100% { opacity: 1; transform: none; } }
.death-best, .death-rank {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--dim);
}
.death-rank { margin-top: 10px; color: var(--accent); }

.stat-grid {
  margin-top: 22px;
  display: grid; grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--line); border-left: 1px solid var(--line);
}
.stat {
  padding: 13px 14px;
  border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 6px;
}
.stat-v { font-size: 22px; font-weight: 600; letter-spacing: -0.005em; }

.seed-line { margin-top: 16px; display: flex; align-items: center; gap: 10px; }
.seed-line .num { font-family: var(--mono); font-size: 12.5px; letter-spacing: 0.16em; color: var(--accent); }
.tag {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase;
  padding: 3px 6px; border: 1px solid var(--line-2); border-radius: 2px; color: var(--accent-2);
}

/* ---------- actions ---------- */
.actions { display: flex; gap: 9px; margin-top: 22px; flex-wrap: wrap; }
.actions--split { justify-content: space-between; }
.actions--split .action--primary { flex: 0 0 auto; min-width: 140px; justify-content: center; }
.action {
  display: inline-flex; align-items: center; gap: 9px;
  min-height: 46px; padding: 0 16px;
  border: 1px solid var(--line-2); border-radius: var(--r);
  background: hsl(var(--pal-hue) 30% 12% / 0.5);
  font-size: 13.5px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink);
  transition: background 0.14s, border-color 0.14s, transform 0.1s, box-shadow 0.14s;
}
.action:hover { background: hsl(var(--pal-hue) 40% 18% / 0.65); border-color: var(--accent); }
.action:active { transform: translateY(1px); }
.action--primary {
  flex: 1 1 auto;
  background: linear-gradient(180deg, var(--accent), hsl(var(--pal-hue) var(--sat) 52%));
  border-color: transparent; color: var(--accent-ink);
  box-shadow: 0 0 26px var(--glow);
}
.action--primary:hover { filter: brightness(1.12); background: linear-gradient(180deg, var(--accent), hsl(var(--pal-hue) var(--sat) 52%)); }
.action--primary .kbd { background: hsl(var(--pal-hue) 60% 12% / 0.28); border-color: hsl(var(--pal-hue) 60% 10% / 0.4); color: var(--accent-ink); }
.action--ghost { background: none; border-color: var(--line); color: var(--dim); }
.action--ghost:hover { color: var(--ink); }

/* ---------- revive (ADDENDUM D) ----------
   Its own row between the run actions and the share row, and deliberately quieter than
   Retry: the primary answer to dying is still one key and one frame, and a second way
   back in must not compete with it for the eye. Accent-outlined rather than filled, so
   it reads as an offer rather than as the default. */
.actions--revive { margin-top: 10px; gap: 8px 12px; align-items: center; }
.action--revive {
  flex: 0 0 auto; justify-content: center;
  border-color: var(--accent-2); color: var(--accent-2);
  background: hsl(var(--pal-hue2) 40% 12% / 0.34);
  font-size: 12.5px; letter-spacing: 0.12em;
}
.action--revive:hover { color: var(--ink); background: hsl(var(--pal-hue2) 45% 20% / 0.5); border-color: var(--accent-2); }
.action--revive .kbd { border-color: hsl(var(--pal-hue2) 50% 60% / 0.45); }
/* Waiting. Today the answer comes back in the same microtask; the day whatever grants it
   has to go and fetch something, this is what stops the button being pressed four times. */
.action--revive.is-working {
  cursor: progress; opacity: 0.7;
  border-style: dashed;
}
.action--revive.is-none { border-color: var(--dimmer); color: var(--dimmer); background: none; }
.revive-note {
  flex: 1 1 180px; margin: 0;
  font-family: var(--mono); font-size: 10px; line-height: 1.5; letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dimmer);
}

/* ---------- the challenge link (ADDENDUM C1) ----------
   Its own row, under the run actions: "what do I do next" and "send this to someone"
   are different questions and a player scanning for Retry should never have to read
   past a share button to find it. Quiet by default, loud for the ~2.5 s after a copy —
   a share button with no visible confirmation is indistinguishable from a broken one. */
.actions--share { margin-top: 10px; gap: 8px; }
.action--share {
  flex: 1 1 auto; justify-content: center;
  background: none; border-style: dashed; border-color: var(--line-2); color: var(--dim);
  font-size: 12.5px; letter-spacing: 0.12em;
}
.action--share:hover { color: var(--ink); border-color: var(--accent); background: hsl(var(--pal-hue) 40% 14% / 0.4); }
.action--share .share-label { transition: none; }
.action--share.is-copied {
  border-style: solid; border-color: var(--good); color: var(--good);
  background: hsl(150 60% 30% / 0.14);
}
/* the tick is drawn, not typed: two borders and a rotate, so it needs no font */
.action--share.is-copied::after {
  content: ""; width: 9px; height: 5px; margin-left: 2px;
  border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg) translate(1px, -2px);
  animation: tick-in 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tick-in { 0% { opacity: 0; transform: rotate(-45deg) translate(1px, -2px) scale(0.4); } 100% { opacity: 1; } }
.action--share.is-manual { border-style: solid; border-color: var(--warn); color: var(--warn); }

/* the manual route: shown only when the browser refuses the clipboard, and then it is
   the whole point of the row, so it is the loudest thing on it. */
.share-url {
  flex: 1 1 100%; min-width: 0;
  padding: 10px 12px; min-height: 42px;
  border: 1px solid var(--accent); border-radius: var(--r);
  background: hsl(var(--pal-hue) 40% 4% / 0.7);
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.04em; color: var(--ink);
  cursor: text; user-select: all; -webkit-user-select: all;
}
.share-url::selection { background: var(--accent); color: var(--accent-ink); }
.share-note {
  flex: 1 1 100%; margin: 0;
  font-family: var(--mono); font-size: 10.5px; line-height: 1.6; color: var(--warn);
}

/* ---------- settings ---------- */
.panel--settings { width: min(520px, 94vw); }
.set-group + .set-group { margin-top: 26px; }
.set-group-title {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--accent);
  padding-bottom: 9px; margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.set-row { padding: 13px 0; border-bottom: 1px solid hsl(var(--pal-hue) 40% 70% / 0.07); }
.set-row:last-child { border-bottom: 0; }
.set-hint, .set-warn {
  margin: 7px 0 0; max-width: 46ch;
  font-family: var(--mono); font-size: 10.5px; line-height: 1.6; letter-spacing: 0.02em;
  color: var(--dimmer);
}
.set-warn {
  color: var(--warn);
  padding-left: 9px; border-left: 2px solid var(--warn);
}

.ctl { display: block; width: 100%; text-align: left; }
.ctl-label { font-size: 15px; font-weight: 600; letter-spacing: 0.01em; color: var(--ink); }

.ctl--slider {
  display: grid; grid-template-columns: 1fr auto; grid-template-areas: "label val" "track track";
  gap: 9px 12px; cursor: pointer;
}
.ctl--slider .ctl-label { grid-area: label; }
.sl-val {
  grid-area: val;
  font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: 0.08em;
  color: var(--accent); font-variant-numeric: tabular-nums;
}
.ctl--slider.is-zero .sl-val { color: var(--dimmer); }
.sl-track {
  grid-area: track; position: relative; display: block;
  height: 22px; margin: -6px 0;
}
.sl-track::before {
  content: ""; position: absolute; left: 0; right: 0; top: 9px; height: 3px;
  background: hsl(var(--pal-hue) 30% 70% / 0.18);
}
.sl-fill {
  position: absolute; left: 0; right: 0; top: 9px; height: 3px;
  background: var(--accent); transform-origin: left center;
  box-shadow: 0 0 12px var(--glow);
}
.sl-knob {
  position: absolute; top: 3px; width: 5px; height: 15px; margin-left: -2.5px;
  background: var(--ink); border-radius: 1px;
  box-shadow: 0 0 10px var(--glow);
  transition: transform 0.12s;
}
.ctl--slider:hover .sl-knob, .ctl--slider:focus-visible .sl-knob { transform: scaleY(1.35); }

.ctl--switch { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 26px; }
.sw {
  position: relative; flex: none;
  width: 42px; height: 22px;
  border: 1px solid var(--line-2); border-radius: 2px;
  background: hsl(var(--pal-hue) 30% 12% / 0.6);
  transition: background 0.16s, border-color 0.16s;
}
.sw-knob {
  position: absolute; top: 3px; left: 3px; width: 14px; height: 14px;
  background: var(--dim); border-radius: 1px;
  transition: transform 0.16s cubic-bezier(0.2, 0.9, 0.2, 1), background 0.16s;
}
.ctl--switch.is-on .sw { background: hsl(var(--pal-hue) var(--sat) 40% / 0.45); border-color: var(--accent); }
.ctl--switch.is-on .sw-knob { transform: translateX(20px); background: var(--accent); box-shadow: 0 0 12px var(--glow); }

.ctl--choice { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.seg { display: flex; border: 1px solid var(--line-2); border-radius: var(--r); overflow: hidden; }
.seg-b {
  min-height: 30px; padding: 0 12px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--dim);
  transition: background 0.14s, color 0.14s;
}
.seg-b + .seg-b { border-left: 1px solid var(--line-2); }
.seg-b:hover { color: var(--ink); background: hsl(var(--pal-hue) 40% 20% / 0.3); }
.seg-b.is-on { background: var(--accent); color: var(--accent-ink); }

/* ---------- scores ---------- */
.panel--scores { width: min(580px, 94vw); }
.daily-card {
  display: flex; justify-content: space-between; align-items: flex-end; gap: 16px;
  margin-top: 16px; padding: 10px 14px;
  border: 1px solid var(--line-2); border-left: 2px solid var(--accent-2); border-radius: var(--r);
  background: hsl(var(--pal-hue2) 40% 14% / 0.22);
}
.daily-r { text-align: right; }
.daily-seed { font-family: var(--mono); font-size: 13px; letter-spacing: 0.13em; color: var(--accent-2); margin-top: 5px; }
.daily-best { font-size: 22px; font-weight: 700; margin-top: 2px; }

.score-list { list-style: none; margin: 0; padding: 0; }
.score-row {
  display: grid; grid-template-columns: 24px 1fr auto; grid-template-areas: "rank score seed" "rank meta meta";
  gap: 1px 12px; align-items: baseline;
  padding: 7px 8px; border-bottom: 1px solid hsl(var(--pal-hue) 40% 70% / 0.08);
}
.score-row.is-top { background: linear-gradient(90deg, hsl(var(--pal-hue) 50% 40% / 0.12), transparent 65%); }
.score-rank { grid-area: rank; font-family: var(--mono); font-size: 10.5px; color: var(--dimmer); }
.score-row.is-top .score-rank { color: var(--accent); }
.score-val { grid-area: score; font-size: 19px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.15; }
.score-seed { grid-area: seed; font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; color: var(--accent); }
.score-meta { grid-area: meta; font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--dimmer); }
.run-stats {
  display: flex; gap: 26px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
}
.run-stats span { display: flex; flex-direction: column; gap: 4px; }
.run-stats b { font-size: 17px; font-weight: 600; }

/* ---------- toast ---------- */
.toast {
  position: absolute; left: 50%; top: 84px; transform: translate(-50%, -10px);
  padding: 8px 14px;
  border: 1px solid var(--line-2); border-radius: var(--r);
  background: hsl(var(--pal-hue) 40% 8% / 0.9);
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink);
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s, transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
  white-space: nowrap;
}
.toast.is-on { opacity: 1; transform: translate(-50%, 0); }
.toast--key { border-color: var(--accent); color: var(--accent); box-shadow: 0 0 30px var(--glow); text-transform: none; letter-spacing: 0.08em; font-size: 13px; }
.toast--up { border-color: var(--accent-2); color: var(--accent-2); }

/* =========================================================================
   narrow / touch
   ========================================================================= */
@media (max-width: 720px) {
  :root { --gut: 16px; }
  .hud-score { font-size: 34px; }
  .hud-key { min-width: 128px; padding: 6px 9px 7px; }
  .key-now, .key-prev { font-size: 17px; line-height: 22px; }
  .key-stack { height: 22px; }
  .hud-stat { padding-right: 14px; }
  .hud-stat + .hud-stat { padding-left: 14px; }
  /* The pop shrinks on a phone but the LADDER survives it: a razor pass still has to be
     visibly bigger than a tight one, or the readout stops carrying the fact it exists for. */
  .graze-pop { font-size: 10px; letter-spacing: 0.1em; }
  .graze-pop[data-band="2"] { font-size: 11px; }
  .graze-pop[data-band="1"] { font-size: 12px; }
  .graze-pop[data-band="0"] { font-size: 13.5px; }
  .graze-pop[data-band="flat"] { font-size: 8.5px; }
  .streak-seg { width: 9px; }
  .hud-sub { gap: 10px; }
  /* Revive keeps its own line on a phone rather than sharing one with a note that
     would then be two words wide. */
  .action--revive { flex: 1 1 100%; }
  .revive-note { flex: 1 1 100%; }
  .wordmark-l1, .wordmark-l2 { font-size: clamp(44px, 15vw, 74px); }
  .tagline { display: none; }
  .menu { width: 100%; }
  .menu-item { height: 52px; }              /* thumb-sized, still fits */
  .menu-item--primary { height: 60px; }
  .panel { padding: 20px 18px 18px; }
  .stat-grid { grid-template-columns: 1fr 1fr; }
  .action { flex: 1 1 46%; justify-content: center; }
  .actions--split .action--primary { flex: 1 1 46%; }
  .hintbar { gap: 12px; font-size: 9px; }
  .hintbar span:nth-child(n+3) { display: none; }
  .run-stats { gap: 16px; }
}
@media (max-width: 560px) {
  /* the per-seed best wraps to two lines at this width; it is on the scores
     screen anyway, so drop it rather than let it push the row around */
  .seed-row.has-best::after { display: none; }
}

/* Short viewports (laptop landscape, phone landscape). The title screen is the
   only screen tall enough to overflow, so it sheds parts in priority order. */
@media (max-height: 620px) {
  .wordmark-l1, .wordmark-l2 { font-size: clamp(34px, 7.5vw, 58px); }
  .tagline { display: none; }
  .title-panel { margin-top: 14px; }
  .menu-item { height: 38px; }
  .menu-item--primary { height: 46px; }
  .menu-item--primary .menu-text { font-size: 22px; }
  .menu-text { font-size: 17px; }
  .seed-row { margin-top: 10px; min-height: 26px; }
  .title-best { margin-top: 8px; }
  .panel-body { max-height: 46vh; }
}
@media (max-height: 560px) {
  .hintbar { display: none; }
  .screen--title { padding-bottom: var(--gut); }   /* no hintbar to clear */
  .title-best { display: none; }
}
/* Last resort: if it still will not fit, it scrolls rather than vanishing off
   the top of the window where no input can reach it. */
.screen--title { overflow-y: auto; overscroll-behavior: contain; }
@media (pointer: coarse) {
  .mini { min-height: 38px; padding: 9px 12px; }
  .seg-b { min-height: 40px; padding: 0 16px; }
  .action { min-height: 52px; }
}
