:root {
  --accent: #ff5a1f;
  --panel: rgba(20, 24, 34, 0.85);
  --panel-light: rgba(255, 255, 255, 0.92);
  --text-dark: #1c2230;

  /* ----- Flywheel brand tokens -----
     Every value below was fitted on the level-start READY gate, which is where
     the game found its look. They are tokens rather than literals so the gate,
     the landing screen and the ordinary .btn stay the same object. */
  --fw-gold: #ffd23f;          /* the wordmark, headings, every accent */
  --fw-gold-hot: #ff8a3f;      /* punctuation and other single-glyph accents */
  --fw-ink: #0a0e18;           /* the outline ring around type */
  --fw-ink-2: #10141f;         /* borders on solid blocks — one step lighter so
                                  a border next to a ring still separates */
  --fw-extrude-1: #8e2f00;     /* first step of the downward block extrude */
  --fw-extrude-2: #4a1900;     /* second, darker step */
  --fw-orange-1: #ffa23f;      /* CTA pill gradient, top to bottom */
  --fw-orange-2: #ff7a2b;
  --fw-orange-3: #ff5a1f;
  --fw-steel-1: #5a6a91;       /* the same pill, drained of urgency */
  --fw-steel-2: #3b4763;
  --fw-steel-extrude: #1b2133;
  --fw-plate: rgba(12, 16, 28, .92); /* dark plate behind quiet gold lines */

  /* ----- combo heat ramp -----
     The one part of the old combo pill that was right: js/ui/hud.js escalated
     its label through white -> gold -> hot -> red, and that progression is kept
     and extended to the ring's eight levels. Tokens rather than the literals
     they were, so the ramp is one object the meter and any future readout share.
     Every step is measured against the dark plate behind it, which is why the
     plate is dark: --fw-heat-8 on ink clears the contrast bar and would not on
     a light ground. */
  --fw-heat-1: #ffffff;
  --fw-heat-2: #ffeaa0;
  --fw-heat-3: #ffd23f;   /* = --fw-gold */
  --fw-heat-4: #ffb03f;
  --fw-heat-5: #ff9a3f;
  --fw-heat-6: #ff7a2b;   /* = --fw-orange-2 */
  --fw-heat-7: #ff5a1f;   /* = --fw-orange-3 */
  --fw-heat-8: #ff2d1f;   /* MAX: the only red on the HUD, and it means one thing */

  /* 8-direction hard outline ring. This is what lets gold type sit on a bright,
     moving city with no scrim behind it: the ring measures 19:1 against white.
     Two sizes — 3px for display type, 2px for small uppercase lines. */
  --fw-ring-3:
    3px 0 0 var(--fw-ink), -3px 0 0 var(--fw-ink), 0 3px 0 var(--fw-ink), 0 -3px 0 var(--fw-ink),
    2px 2px 0 var(--fw-ink), -2px 2px 0 var(--fw-ink), 2px -2px 0 var(--fw-ink), -2px -2px 0 var(--fw-ink);
  --fw-ring-2:
    2px 0 0 var(--fw-ink), -2px 0 0 var(--fw-ink), 0 2px 0 var(--fw-ink), 0 -2px 0 var(--fw-ink),
    1px 1px 0 var(--fw-ink), -1px 1px 0 var(--fw-ink), 1px -1px 0 var(--fw-ink), -1px -1px 0 var(--fw-ink);

  /* ----- safe-area insets, hoisted into custom properties -----
     env() has no JS equivalent, and js/controls.js has to know the insets to
     keep the joystick ring off the notch and the home indicator. Naming them
     here is the only way to hand them over: getComputedStyle on the root
     resolves --sai-* to used pixels. The 0px fallbacks make every consumer a
     no-op on a device without insets, so these can be used unconditionally
     rather than behind a query. Requires viewport-fit=cover in index.html —
     without it env() resolves to 0 everywhere and the notch is never reported. */
  --sai-top: env(safe-area-inset-top, 0px);
  --sai-right: env(safe-area-inset-right, 0px);
  --sai-bottom: env(safe-area-inset-bottom, 0px);
  --sai-left: env(safe-area-inset-left, 0px);

  /* The virtual joystick's ring DIAMETER, and the single place it is decided:
     controls.js measures the rendered element rather than carrying its own copy
     (JOY_THROW_FRAC is a proportion of this, not a second number), and the
     first-run hint's ghost ring reads it directly. Sized in vmin, not px: the
     old fixed 120 px was 31% of the width of a 390 px phone — a control that
     ate a third of the screen it was drawn on. 24vmin is 94 px there (24%) and
     stays 24% through every phone size, capped at 120 px so a tablet does not
     grow a dinner plate and floored at 92 px so a 320 px phone still gets a
     ring a thumb can find. */
  --joy-size: clamp(92px, 24vmin, 120px);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body, #app { width: 100%; height: 100%; overflow: hidden; }
#app { position: relative; }
/* Subtle peripheral vignette (Stage 3 lighting polish) */
#app::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  box-shadow: inset 0 0 110px rgba(10, 14, 24, 0.32);
  transition: box-shadow 0.25s ease-out;
}

/* Active Power-Up Ambient Screen Glows */
#app.pu-vortex-active::after {
  box-shadow: inset 0 0 160px rgba(0, 240, 255, 0.45), inset 0 0 40px rgba(0, 240, 255, 0.6);
  animation: fwPuVortexPulse 1.2s ease-in-out infinite alternate;
}
#app.pu-titan-active::after {
  box-shadow: inset 0 0 170px rgba(255, 51, 102, 0.45), inset 0 0 45px rgba(255, 51, 102, 0.65);
  animation: fwPuTitanPulse 1.0s ease-in-out infinite alternate;
}
#app.pu-frenzy-active::after {
  box-shadow: inset 0 0 170px rgba(168, 85, 247, 0.48), inset 0 0 45px rgba(168, 85, 247, 0.7);
  animation: fwPuFrenzyPulse 0.8s ease-in-out infinite alternate;
}
#app.pu-speed-active::after {
  box-shadow: inset 0 0 150px rgba(255, 183, 3, 0.42), inset 0 0 35px rgba(255, 183, 3, 0.6);
  animation: fwPuSpeedPulse 0.9s ease-in-out infinite alternate;
}

@keyframes fwPuVortexPulse {
  0% { box-shadow: inset 0 0 130px rgba(0, 240, 255, 0.35), inset 0 0 30px rgba(0, 240, 255, 0.5); }
  100% { box-shadow: inset 0 0 180px rgba(0, 240, 255, 0.55), inset 0 0 55px rgba(0, 240, 255, 0.75); }
}
@keyframes fwPuTitanPulse {
  0% { box-shadow: inset 0 0 140px rgba(255, 51, 102, 0.35), inset 0 0 35px rgba(255, 51, 102, 0.5); }
  100% { box-shadow: inset 0 0 190px rgba(255, 51, 102, 0.58), inset 0 0 60px rgba(255, 51, 102, 0.8); }
}
@keyframes fwPuFrenzyPulse {
  0% { box-shadow: inset 0 0 140px rgba(168, 85, 247, 0.38), inset 0 0 35px rgba(168, 85, 247, 0.55); }
  100% { box-shadow: inset 0 0 190px rgba(168, 85, 247, 0.60), inset 0 0 60px rgba(168, 85, 247, 0.85); }
}
@keyframes fwPuSpeedPulse {
  0% { box-shadow: inset 0 0 130px rgba(255, 183, 3, 0.32), inset 0 0 25px rgba(255, 183, 3, 0.45); }
  100% { box-shadow: inset 0 0 170px rgba(255, 183, 3, 0.52), inset 0 0 50px rgba(255, 183, 3, 0.75); }
}
body {
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  background: #0e1220; color: #fff;
  -webkit-user-select: none; user-select: none;
  touch-action: none;
}
#game-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.hidden { display: none !important; }

/* ---------- HUD ---------- */
#hud { position: absolute; inset: 0; pointer-events: none; z-index: 10; }
/* Every fixed HUD corner adds its safe-area inset to the margin it already
   had, so the layout is unchanged to the pixel on a device with no insets and
   simply moves inboard of a notch, a rounded corner or a home indicator where
   there is one. */
#hud-left {
  position: absolute; width: min(320px, 42vw);
  top: calc(10px + var(--sai-top)); left: calc(12px + var(--sai-left));
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
}
/* Level / City name: Demoted from a dominant screen card to a subtle, unobtrusive
   micro-capsule subtitle, freeing prime mobile visual hierarchy for the action. */
#level-banner {
  display: inline-flex; align-items: center;
  background: rgba(10, 14, 28, 0.72);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 6px; padding: 2px 8px;
  font-weight: 800; letter-spacing: 1.5px;
  font-size: clamp(9px, 1.5vw, 11px);
  color: rgba(255, 255, 255, 0.72);
  text-transform: uppercase;
  box-shadow: 0 2px 6px rgba(0,0,0,.4);
}
#mass-bar-wrap {
  width: 100%; height: 14px; border-radius: 8px;
  background: rgba(6, 8, 16, 0.75);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  border: 1.5px solid rgba(255, 255, 255, 0.4);
  overflow: hidden;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.6), 0 2px 8px rgba(0, 0, 0, 0.4);
}
#mass-bar {
  height: 100%; width: 0%;
  background: linear-gradient(90deg, #ffd23f 0%, #ff9f1c 100%);
  border-radius: 6px;
  transition: width 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
  box-shadow: 0 0 8px rgba(255, 210, 63, 0.5);
}
#mass-label {
  display: inline-flex; align-items: center;
  background: rgba(12, 16, 32, 0.82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #ffffff; font-weight: 800;
  border-radius: 12px; padding: 3px 12px;
  font-size: clamp(10px, 1.8vw, 13px);
  letter-spacing: 0.6px;
  box-shadow: 0 2px 6px rgba(0,0,0,.4);
}
#combo-label {
  display: inline-flex; align-items: center;
  background: #ffd23f; color: #4a3200; font-weight: 800; border-radius: 12px;
  padding: 3px 12px; font-size: clamp(10px, 1.8vw, 13px);
}

/* ---------- Top-Center Master Countdown Clock ----------
   The prime mobile eye zone: urgent time-attack constraint anchored at top-center. */
#level-clock {
  position: absolute;
  top: calc(10px + var(--sai-top));
  left: 50%;
  transform: translateX(-50%);
  display: flex; align-items: center; justify-content: center; gap: 6px;
  background: linear-gradient(180deg, rgba(14, 18, 36, 0.94) 0%, rgba(6, 10, 22, 0.98) 100%);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border: 2px solid rgba(255, 255, 255, 0.28);
  color: #ffffff; font-weight: 950;
  font-size: clamp(16px, 3.4vw, 24px);
  letter-spacing: 2px;
  border-radius: 20px;
  padding: 4px 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.9), 0 0 10px rgba(255, 255, 255, 0.2);
  box-shadow:
    0 4px 18px rgba(0, 0, 0, 0.7),
    inset 0 1px 0 rgba(255, 255, 255, 0.2);
  z-index: 20;
  pointer-events: none;
  transition: border-color 0.25s, color 0.25s, box-shadow 0.25s, transform 0.25s;
}
#level-clock.hidden { display: none !important; }
#level-clock.warn {
  color: #ffd23f;
  border-color: #ffd23f;
  text-shadow: 0 0 14px rgba(255, 210, 63, 0.8), 0 2px 4px rgba(0, 0, 0, 0.9);
  box-shadow: 0 0 22px rgba(255, 210, 63, 0.5), 0 4px 18px rgba(0, 0, 0, 0.7);
}
#level-clock.urgent {
  color: #ff3838;
  border-color: #ff3838;
  font-size: clamp(18px, 4vw, 28px);
  text-shadow: 0 0 18px rgba(255, 56, 56, 0.95), 0 2px 4px rgba(0, 0, 0, 0.9);
  box-shadow: 0 0 30px rgba(255, 56, 56, 0.7), 0 4px 18px rgba(0, 0, 0, 0.8);
  animation: levelClockPulse 0.55s infinite alternate cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes levelClockPulse {
  0% { transform: translateX(-50%) scale(1); background: rgba(22, 6, 8, 0.96); }
  100% { transform: translateX(-50%) scale(1.08); background: rgba(58, 10, 14, 0.98); }
}

/* ---------- Top-Right Utility & Currency Bar ---------- */
#hud-right {
  position: absolute;
  top: calc(10px + var(--sai-top));
  right: calc(12px + var(--sai-right));
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
  pointer-events: none;
}
#hud-controls-row {
  display: flex; align-items: center; justify-content: flex-end; gap: 6px;
  pointer-events: none;
}
#hud-right .round-btn { pointer-events: auto; }
.round-btn {
  width: 40px; height: 40px; border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.35);
  background: rgba(14, 18, 36, 0.85);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  color: #ffffff; font-size: 16px;
  cursor: pointer; margin-left: 0;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 3px 8px rgba(0,0,0,.4);
  transition: transform 0.12s, border-color 0.12s, background 0.12s;
}
.round-btn:hover {
  transform: scale(1.06);
  border-color: rgba(255, 255, 255, 0.6);
  background: rgba(24, 30, 56, 0.95);
}
.pause-glyph { display: inline-flex; gap: 3px; align-items: center; justify-content: center; }
.pause-glyph span { width: 4px; height: 14px; border-radius: 2px; background: #ffffff; }

#timer {
  display: inline-flex; align-items: center;
  background: rgba(12, 16, 32, 0.85);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: #ffffff; font-weight: 800;
  font-size: clamp(11px, 1.8vw, 14px);
  border-radius: 16px; padding: 4px 12px;
  text-align: center; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}
#timer.low { background: #ff4747; color: #fff; }
body.mode-sandbox #timer {
  font-size: clamp(11px, 1.8vw, 14px); font-weight: 800; opacity: 0.9;
  min-width: 0; padding: 4px 12px;
}
body.mode-sandbox #mass-label {
  font-size: clamp(10px, 1.8vw, 13px); padding: 3px 12px;
}
body.mode-sandbox #mass-bar-wrap { height: 14px; }

/* A beaten record on the results screen. Gold on the same dark screen the rest
   of the stats sit on, and it says the word as well as changing the colour. */
.rec-new {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 9px;
  background: var(--fw-gold); color: var(--fw-ink); font-size: 11px;
  font-weight: 900; letter-spacing: 1px; vertical-align: middle;
}

/* ---------- score plate (sandbox only) ----------
   Sandbox-scoped: clean tabular figures with glowing gold palette. */
#score-plate { display: none; }
body.mode-sandbox #score-plate {
  display: flex; align-items: baseline; gap: 6px;
  margin-top: 4px; padding: 3px 10px; border-radius: 10px;
  background: rgba(10, 14, 28, 0.8);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 210, 63, 0.35);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  min-width: 9ch;
}
#score-label {
  font-size: clamp(8px, 1.2vw, 10px); font-weight: 800; letter-spacing: 1.5px;
  color: rgba(255, 210, 63, 0.75);
}
#score-value {
  margin-left: auto;
  font-size: clamp(14px, 2.4vw, 20px); font-weight: 900; letter-spacing: 0.8px;
  color: var(--fw-gold); text-shadow: 0 0 8px rgba(255, 210, 63, 0.5);
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum";
}
body.mode-sandbox #score-plate.gain #score-value { animation: score-tick .18s ease-out; }
@keyframes score-tick {
  0% { transform: scale(1); }
  40% { transform: scale(1.08); }
  100% { transform: scale(1); }
}

/* ---------- combo meter (sandbox only) ----------
   Radial and hot: its own screen region, its own shape, its own motion — the
   three ways it is told apart from the consumption band and the SIZE pop
   without reading any of them. */
#combo-meter { display: none; }
body.mode-sandbox #combo-meter {
  display: block; position: relative; margin-top: 10px; margin-left: auto;
  width: clamp(64px, 13vmin, 92px); height: clamp(64px, 13vmin, 92px);
  --cm-heat: var(--fw-heat-1);
  opacity: .55; transition: opacity .18s ease-out;
}
body.mode-sandbox #combo-meter.live { opacity: 1; }
#combo-meter svg { width: 100%; height: 100%; transform: rotate(-90deg); display: block; }
#combo-meter circle { fill: none; stroke-width: 9; stroke-linecap: round; }
.cm-track { stroke: rgba(10, 14, 24, .85); }
.cm-arc {
  stroke: var(--cm-heat);
  /* r=42 -> circumference 263.89. The drive sets stroke-dashoffset only, which
     is a paint-only property on an already-composited element: no layout, no
     node churn, at eleven-plus eats a second. */
  stroke-dasharray: 263.89; stroke-dashoffset: 263.89;
  filter: drop-shadow(0 0 6px var(--cm-heat));
}
.cm-readout {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; line-height: 1;
}
#cm-chain {
  font-size: clamp(18px, 3.2vw, 26px); font-weight: 900; color: #fff;
  text-shadow: var(--fw-ring-2); font-variant-numeric: tabular-nums;
}
/* The unit for the number above it. Deliberately the smallest, quietest thing
   in the ring: it has to be readable when looked at and invisible when not,
   because the ring is glanced at eleven times a second during a chain. */
.cm-unit {
  font-size: clamp(6px, 1.05vw, 8px); font-weight: 700; letter-spacing: 1.4px;
  color: rgba(255, 255, 255, .62); text-shadow: var(--fw-ring-2);
  margin-top: 1px; line-height: 1;
}
/* The multiplier is the reason the chain count matters, so it is a separate
   line in the meter's own heat rather than another white number — colour is
   never the only channel, the two differ in size and position as well. */
#cm-mult {
  margin-top: 2px; font-size: clamp(10px, 1.8vw, 13px); font-weight: 800;
  letter-spacing: 1px; color: var(--cm-heat); text-shadow: var(--fw-ring-2);
}
/* Topped out: the ladder's summit, expressed as a STATE rather than as the word
   MAX over a chain count that keeps climbing (T-311). Deliberately PAINT ONLY —
   no padding, no border box. The ring is 64px on a phone and the readout is
   three lines inside a 46px inner diameter, so a treatment that grew the
   multiplier's box pushed it onto the arc; a glow costs no layout at any size. */
body.mode-sandbox #combo-meter.topped #cm-mult {
  color: #fff;
  text-shadow: 0 0 7px var(--cm-heat), 0 0 14px var(--cm-heat), var(--fw-ring-2);
}
/* Step pulse: a concentric ring OUT of the meter, ~250 ms. Short, sharp and
   repeatable — the opposite of the band's wide slow sweep, on purpose. */
#combo-meter::after {
  content: ''; position: absolute; inset: -6px; border-radius: 50%;
  border: 3px solid var(--cm-heat); opacity: 0; pointer-events: none;
}
body.mode-sandbox #combo-meter.step::after { animation: cm-pulse .25s ease-out; }
@keyframes cm-pulse {
  0% { opacity: .9; transform: scale(.9); }
  100% { opacity: 0; transform: scale(1.35); }
}
/* Broken, and visibly not the same thing as never-started: the ring snaps to
   empty and the readout dims through a short collapse rather than simply
   sitting at rest. */
body.mode-sandbox #combo-meter.broke .cm-arc { animation: cm-break .35s ease-in; }
@keyframes cm-break {
  0% { opacity: 1; stroke: var(--cm-heat); }
  100% { opacity: 0; stroke: #6b7280; }
}

/* ---------- Endgame Remaining Blocks Pill ---------- */
#blocks-left-pill {
  position: absolute;
  top: calc(48px + var(--sai-top));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 6px;
  background: linear-gradient(180deg, rgba(230, 40, 40, 0.94) 0%, rgba(130, 10, 20, 0.98) 100%);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 2px solid #ffd23f;
  border-radius: 14px;
  padding: 3px 14px;
  color: #ffffff;
  font-weight: 900;
  font-size: clamp(11px, 2vw, 14px);
  letter-spacing: 1px;
  box-shadow: 0 4px 14px rgba(220, 40, 40, 0.6), 0 0 10px rgba(255, 210, 63, 0.5);
  animation: blocksLeftPulse 0.8s infinite alternate ease-in-out;
  pointer-events: none;
  z-index: 20;
  white-space: nowrap;
}
#blocks-left-pill.hidden { display: none !important; }
@keyframes blocksLeftPulse {
  0% { transform: translateX(-50%) scale(1); box-shadow: 0 0 10px rgba(255, 210, 63, 0.4); }
  100% { transform: translateX(-50%) scale(1.06); box-shadow: 0 0 22px rgba(255, 210, 63, 0.85); }
}

/* ---------- Visually Loud Anime Combo Burst Overlay (Covers Combo Meter +20%) ---------- */
#cm-burst {
  position: absolute;
  inset: -12%; /* Covers circle + text area with generous margin */
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 35;
  user-select: none;
  background: radial-gradient(circle, rgba(12, 16, 32, 0.98) 0%, rgba(20, 26, 52, 0.98) 65%, rgba(6, 8, 18, 0.92) 100%);
  border: 3px solid var(--cm-burst-color, #ffd23f);
  box-shadow:
    0 0 28px var(--cm-burst-glow, rgba(255, 210, 63, 0.95)),
    inset 0 0 20px var(--cm-burst-glow, rgba(255, 210, 63, 0.65)),
    0 8px 24px rgba(0, 0, 0, 0.85);
  animation: cmBurstPop 1.1s cubic-bezier(0.18, 0.89, 0.32, 1.25) forwards;
}

@keyframes cmBurstPop {
  0% { transform: scale(0.3) rotate(-14deg); opacity: 0; }
  22% { transform: scale(1.3) rotate(4deg); opacity: 1; }
  35% { transform: scale(1.0) rotate(0deg); opacity: 1; }
  80% { transform: scale(1.0) rotate(0deg); opacity: 1; }
  100% { transform: scale(0.85) rotate(0deg); opacity: 0; }
}

.cm-burst-halo {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 2px dashed var(--cm-burst-color, #ffd23f);
  opacity: 0.85;
  animation: cmBurstHaloSpin 2.4s linear infinite;
}

@keyframes cmBurstHaloSpin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.cm-burst-ring {
  position: absolute;
  inset: -10px;
  border-radius: 50%;
  border: 2.5px solid var(--cm-burst-color, #ffd23f);
  opacity: 0;
  animation: cmBurstRingShock 0.45s cubic-bezier(0.1, 0.9, 0.2, 1) forwards;
}

@keyframes cmBurstRingShock {
  0% { transform: scale(0.7); opacity: 1; }
  100% { transform: scale(1.65); opacity: 0; }
}

.cm-burst-body {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.cm-burst-mult {
  font-size: clamp(22px, 4.6vw, 32px);
  font-weight: 950;
  letter-spacing: 1px;
  color: #ffffff;
  text-shadow:
    0 0 14px var(--cm-burst-glow, #ffd23f),
    0 0 28px var(--cm-burst-color, #ffd23f),
    0 3px 0 rgba(0, 0, 0, 0.95);
  animation: cmMultSlam 0.25s ease-out;
}

@keyframes cmMultSlam {
  0% { transform: scale(1.4); }
  100% { transform: scale(1); }
}

.cm-burst-sub {
  font-size: clamp(8px, 1.5vw, 11px);
  font-weight: 950;
  letter-spacing: 2px;
  color: var(--cm-burst-color, #ffd23f);
  text-transform: uppercase;
  margin-top: 2px;
  text-shadow: 0 0 8px var(--cm-burst-glow, rgba(255, 210, 63, 0.9));
}

/* ---------- active power-up flyout badges ---------- */
#active-powerups {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  margin-top: 8px;
  pointer-events: none;
  z-index: 15;
}
.powerup-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(12, 18, 32, 0.95);
  border: 1.5px solid var(--pu-border, #00d2ff);
  box-shadow: 0 0 16px var(--pu-glow, rgba(0, 210, 255, 0.5)), 0 6px 16px rgba(0, 0, 0, 0.6);
  border-radius: 22px;
  padding: 6px 12px 6px 8px;
  font-family: inherit;
  font-weight: 800;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 1;
  transform: translateX(0);
  transition: transform 0.3s cubic-bezier(0.18, 0.9, 0.32, 1.2), opacity 0.3s ease-out;
}

/* Flyout entrance: slides in smoothly from the right */
.powerup-pill.flyout-in {
  opacity: 0 !important;
  transform: translateX(120%) scale(0.85) !important;
}

/* Expiring warning: smooth pulse/fade when running low on time */
.powerup-pill.is-expiring {
  animation: pu-expiring-fade 0.75s ease-in-out infinite alternate;
}

@keyframes pu-expiring-fade {
  0% {
    opacity: 1;
    border-color: var(--pu-border, #00d2ff);
    box-shadow: 0 0 16px var(--pu-glow, rgba(0, 210, 255, 0.5)), 0 6px 16px rgba(0, 0, 0, 0.6);
  }
  100% {
    opacity: 0.45;
    border-color: rgba(255, 255, 255, 0.4);
    box-shadow: 0 0 6px var(--pu-glow, rgba(0, 210, 255, 0.2)), 0 2px 8px rgba(0, 0, 0, 0.4);
  }
}

/* Flyout exit: slides back offscreen right when finished */
.powerup-pill.flyout-out {
  opacity: 0 !important;
  transform: translateX(130%) scale(0.9) !important;
}

.pu-icon {
  font-size: 18px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
}
.pu-details {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 82px;
}
.pu-name-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.8px;
  color: var(--pu-color, #fff);
  text-transform: uppercase;
}
.pu-timer {
  font-size: 11px;
  font-weight: 900;
  color: #fff;
  margin-left: 6px;
}
.pu-bar-bg {
  width: 100%;
  height: 5px;
  background: rgba(255, 255, 255, 0.18);
  border-radius: 3px;
  overflow: hidden;
}
.pu-bar-fill {
  height: 100%;
  background: var(--pu-color, #00d2ff);
  box-shadow: 0 0 8px var(--pu-color, #00d2ff);
  border-radius: 3px;
  transition: width 0.05s linear;
}

/* ---------- consumption band ----------
   Full-width, horizontal, gold on ink: the widest thing in the mix, fired a
   handful of times in a whole level. It sits ABOVE the play area in the same
   band #toast occupies, and it is pointer-transparent so it can never take a
   touch meant for the world. */
/* -------------------------------------------------------------------------
   Consumption & Combo Anime Battle Banner (#hype-band)
   ------------------------------------------------------------------------- */
#hype-band {
  position: absolute;
  left: 0;
  right: 0;
  top: 24%;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 35;
  overflow: visible;
}

.hype-anime-container {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 10px 42px;
  transform: skew(-9deg);
  background: linear-gradient(90deg, transparent 0%, rgba(8, 10, 20, 0.96) 14%, rgba(8, 10, 20, 0.96) 86%, transparent 100%);
  box-shadow: 0 0 35px rgba(0, 0, 0, 0.9);
  border-top: 2px solid var(--hype-accent, #ffd23f);
  border-bottom: 2px solid var(--hype-accent, #ffd23f);
  min-width: clamp(290px, 78vw, 720px);
}

.hype-speed-streak {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.12) 0px,
    rgba(255, 255, 255, 0.12) 3px,
    transparent 3px,
    transparent 14px
  );
  opacity: 0.55;
  pointer-events: none;
  mix-blend-mode: overlay;
}

.hype-content-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: skew(3deg);
  text-align: center;
  z-index: 2;
}

.hype-sub {
  font-size: clamp(10px, 2.2vw, 15px);
  font-weight: 900;
  letter-spacing: 3.5px;
  color: var(--hype-sub-color, #ffd23f);
  text-transform: uppercase;
  text-shadow: 0 0 10px var(--hype-sub-glow, rgba(255, 210, 63, 0.85)), 0 2px 4px rgba(0,0,0,0.8);
  background: rgba(0, 0, 0, 0.65);
  padding: 3px 16px;
  border-radius: 4px;
  border: 1px solid var(--hype-sub-border, rgba(255, 210, 63, 0.45));
  margin-bottom: 3px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}

.hype-text {
  font-size: clamp(22px, 5.5vw, 50px);
  font-weight: 950;
  letter-spacing: 2px;
  text-transform: uppercase;
  line-height: 1.05;
  text-align: center;
  color: #ffffff;
  text-shadow:
    0 0 18px var(--hype-glow, #ff5500),
    0 0 36px var(--hype-glow, #ff5500),
    0 3px 0 var(--hype-shadow-1, #b30000),
    0 6px 0 var(--hype-shadow-2, #550000),
    0 9px 20px rgba(0, 0, 0, 0.9);
}

/* Tier Variants */
#hype-band.tier-combo {
  --hype-accent: #00e5ff;
  --hype-sub-color: #70e000;
  --hype-sub-glow: rgba(112, 224, 0, 0.85);
  --hype-sub-border: rgba(112, 224, 0, 0.55);
  --hype-glow: #00d2ff;
  --hype-shadow-1: #0077b6;
  --hype-shadow-2: #03045e;
}

#hype-band.tier-roar {
  --hype-accent: #ffd23f;
  --hype-sub-color: #ffd23f;
  --hype-sub-glow: rgba(255, 210, 63, 0.9);
  --hype-sub-border: rgba(255, 210, 63, 0.6);
  --hype-glow: #ff3300;
  --hype-shadow-1: #b30000;
  --hype-shadow-2: #4a0000;
}

#hype-band.tier-hype {
  --hype-accent: #ff8800;
  --hype-sub-color: #ffd23f;
  --hype-sub-glow: rgba(255, 210, 63, 0.75);
  --hype-sub-border: rgba(255, 210, 63, 0.45);
  --hype-glow: #ff5500;
  --hype-shadow-1: #a33200;
  --hype-shadow-2: #4d1800;
}

#hype-band.tier-nudge {
  --hype-accent: #38bdf8;
  --hype-sub-color: #38bdf8;
  --hype-sub-glow: rgba(56, 189, 248, 0.75);
  --hype-sub-border: rgba(56, 189, 248, 0.45);
  --hype-glow: #0284c7;
  --hype-shadow-1: #0369a1;
  --hype-shadow-2: #075985;
}

#hype-band.tier-size {
  --hype-accent: #10b981;
  --hype-sub-color: #34d399;
  --hype-sub-glow: rgba(52, 211, 153, 0.85);
  --hype-sub-border: rgba(52, 211, 153, 0.55);
  --hype-glow: #059669;
  --hype-shadow-1: #047857;
  --hype-shadow-2: #064e3b;
}

#hype-band.tier-powerup {
  --hype-accent: #a855f7;
  --hype-sub-color: #c084fc;
  --hype-sub-glow: rgba(192, 132, 252, 0.85);
  --hype-sub-border: rgba(192, 132, 252, 0.55);
  --hype-glow: #9333ea;
  --hype-shadow-1: #7e22ce;
  --hype-shadow-2: #581c87;
}

#hype-band.show {
  animation: animeBannerSlashIn 0.32s cubic-bezier(0.18, 0.89, 0.32, 1.25) forwards,
             animeBannerSlashOut 0.28s ease-in 1.45s forwards;
}

@keyframes animeBannerSlashIn {
  0% { opacity: 0; transform: scale(1.35) translateX(-8%); }
  60% { opacity: 1; transform: scale(0.97) translateX(0); }
  100% { opacity: 1; transform: scale(1) translateX(0); }
}

@keyframes animeBannerSlashOut {
  0% { opacity: 1; transform: scale(1) translateX(0); }
  100% { opacity: 0; transform: scale(0.9) translateX(10%); }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  body.mode-sandbox #score-plate.gain #score-value,
  body.mode-sandbox #combo-meter.step::after,
  body.mode-sandbox #combo-meter.broke .cm-arc { animation: none; }
  #hype-band.show { animation: hype-fade 1.8s ease-out forwards; }
  #level-clock.urgent { animation: none; background: rgba(255, 45, 31, .28); }
}
body.reduced-motion.mode-sandbox #score-plate.gain #score-value,
body.reduced-motion.mode-sandbox #combo-meter.step::after,
body.reduced-motion.mode-sandbox #combo-meter.broke .cm-arc { animation: none; }
body.reduced-motion #level-clock.urgent { animation: none; background: rgba(255, 45, 31, .28); }
body.reduced-motion #hype-band.show { animation: hype-fade 1.8s ease-out forwards; }
body.reduced-motion.mode-sandbox #combo-meter.broke .cm-arc,
body.mode-sandbox #combo-meter.broke .cm-arc { stroke: #6b7280; }
@keyframes hype-fade {
  0% { opacity: 0; }
  8% { opacity: 1; }
  85% { opacity: 1; }
  100% { opacity: 0; }
}
#minimap-wrap {
  position: absolute; border-radius: 12px;
  border: 3px solid rgba(255,255,255,.8); overflow: hidden; background: #06121c;
  width: 150px; height: 150px;
  right: calc(14px + var(--sai-right)); bottom: calc(14px + var(--sai-bottom));
}
/* The canvas has a fixed 150x150 backing store (js/ui/hud.js draws in those
   coordinates). #minimap-wrap is overflow:hidden, so shrinking the wrapper
   alone would CROP the map rather than scale it — this is what makes the
   coarse-pointer rule below a resize instead of a guillotine. */
#minimap { display: block; width: 100%; height: 100%; }
#toast {
  position: absolute; left: 50%; top: 18%; transform: translateX(-50%);
  background: var(--panel); border: 2px solid #fff; border-radius: 14px;
  padding: 10px 22px; font-size: clamp(14px, 2.6vw, 20px); font-weight: 700;
}

#big-pop {
  position: absolute;
  left: 50%;
  top: 44%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 40;
  user-select: none;
  animation: bigpop 1.25s cubic-bezier(0.18, 0.89, 0.32, 1.2) forwards;
}

.big-pop-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  transform: skew(-6deg);
}

.big-pop-sub {
  font-size: clamp(11px, 2.4vw, 15px);
  font-weight: 900;
  letter-spacing: 3px;
  color: #00e5ff;
  background: rgba(4, 8, 20, 0.85);
  padding: 3px 14px;
  border-radius: 6px;
  border: 1px solid rgba(0, 229, 255, 0.6);
  text-shadow: 0 0 10px #00e5ff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
}

.big-pop-text {
  font-size: clamp(44px, 9.5vw, 86px);
  font-weight: 950;
  letter-spacing: 2px;
  color: #ffd23f;
  text-transform: uppercase;
  line-height: 1;
  text-shadow:
    0 0 25px rgba(255, 210, 63, 0.85),
    0 0 45px rgba(255, 120, 0, 0.65),
    0 3px 0 #b35900,
    0 6px 0 #5a2600,
    0 8px 18px rgba(0, 0, 0, 0.95);
}

@keyframes bigpop {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.3) rotate(-6deg); }
  18% { opacity: 1; transform: translate(-50%, -50%) scale(1.15) rotate(1deg); }
  32% { transform: translate(-50%, -50%) scale(1) rotate(0deg); }
  75% { opacity: 1; transform: translate(-50%, -54%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -64%) scale(1.06); }
}

/* ---------- Virtual joystick ----------
   Dynamic origin: js/controls.js writes left/top in client pixels wherever the
   thumb lands on the left half, clamped so the ring never renders half off the
   glass. Everything about its SIZE lives in --joy-size (see :root) and every
   part below is a fraction of it, so the ring, the knob and the throw radius
   controls.js measures cannot drift apart. */
#joystick {
  position: absolute; width: var(--joy-size); height: var(--joy-size);
  border-radius: 50%;
  border: 3px solid rgba(255,255,255,.5); background: rgba(255,255,255,.12);
  pointer-events: none; transform: translate(-50%, -50%);
}
/* Armed but not yet drawn — the stick has a pointer but has not passed the dead
   zone, so it may still turn out to be the first finger of a pinch. Opacity and
   NOT `display:none`, deliberately: js/controls.js measures this element with
   getBoundingClientRect to derive the stick's throw, and a display:none ring
   measures 0 and silently falls back to a 120px assumption that is wrong on
   every phone. Kept out of the layout-affecting properties so the measurement
   taken while latent is the same one the visible ring would give. */
#joystick.latent { opacity: 0; }
/* 43% of the ring, which is where the old 52px-in-120px pair sat. Big enough to
   read under a thumb that is covering most of it, small enough that its edge
   still shows the deflection against the ring at the dead zone (0.15 of a
   throw of 0.42 of the width = 6% of the width, i.e. a visible nudge). */
#joystick-knob {
  position: absolute; left: 50%; top: 50%;
  width: 43%; height: 43%;
  border-radius: 50%; background: rgba(255,255,255,.75);
  transform: translate(-50%, -50%);
}

/* ---------- First-run touch hint ----------
   The joystick is invisible until a thumb lands on it, which is correct for a
   returning player and tells a new one nothing at all: on a phone the playing
   screen has a mass bar, a timer, two round buttons and no control anywhere.
   This is a ghost of the ring, in the place the ring will appear, sweeping the
   way a thumb would. Shown by js/controls.js on the first frame of play, on a
   coarse pointer only, once ever per device (localStorage), and retired by the
   first touch of any kind — plus a nine-second self-destruct in case the player
   is watching rather than touching. pointer-events:none throughout: it must
   never be the thing that eats the touch it is asking for. */
#touch-hint {
  position: absolute; left: 26%; top: 66%; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  pointer-events: none; z-index: 1;
  animation: th-life 9s ease-in-out forwards;
}
#touch-hint .th-ring {
  position: relative; width: var(--joy-size); height: var(--joy-size);
  border-radius: 50%; border: 3px dashed rgba(255,255,255,.5);
  background: rgba(255,255,255,.07);
}
#touch-hint .th-knob {
  position: absolute; left: 50%; top: 50%; width: 43%; height: 43%;
  border-radius: 50%; background: rgba(255,255,255,.5);
  /* Rides at 0.84 of the ring RADIUS, which is exactly the throw controls.js
     clamps a real thumb to (JOY_THROW_FRAC 0.42 of the diameter) — the demo
     shows the actual travel, not a bigger one the stick would refuse. */
  animation: th-sweep 3.2s ease-in-out infinite;
}
/* Same outline-ring treatment as the READY gate's hint, for the same reason:
   this lands on a live city with no scrim under it. */
#touch-hint .th-label {
  font-size: clamp(11px, 3vw, 14px); font-weight: 800;
  letter-spacing: 2px; text-transform: uppercase; color: #fff; white-space: nowrap;
  text-shadow: var(--fw-ring-2), 0 3px 6px rgba(0,0,0,.9);
}
@keyframes th-life {
  0% { opacity: 0; }
  6%, 80% { opacity: 1; }
  100% { opacity: 0; }
}
/* Up, round through up-left to left, back to centre — one lap of the directions
   the stick now means literally, which is the whole point of demonstrating it
   under direct steer. translate percentages are of the KNOB, and the knob is
   43% of the ring, so a full throw (0.42 of the ring's diameter) is
   0.42/0.43 = 98% of the knob's own width; the diagonal splits that 98% across
   both axes as 98/sqrt(2) = 69%, so the knob traces a circle rather than the
   corner of a square the real clamp would never let it reach. */
@keyframes th-sweep {
  0%, 8% { transform: translate(-50%, -50%); }
  26%, 34% { transform: translate(-50%, -148%); }
  52%, 60% { transform: translate(-119%, -119%); }
  78%, 86% { transform: translate(-148%, -50%); }
  100% { transform: translate(-50%, -50%); }
}

/* phone landscape: ~390px tall, and the hint's ring + label stack is ~125px of
   it. Pulled up and inboard so the stack clears the bottom edge and the label
   does not sit on the home indicator. */
@media (orientation: landscape) and (max-height: 480px) {
  #touch-hint { top: 58%; left: 24%; gap: 10px; }
}

/* ---------- Screens ---------- */
#screen-root { position: absolute; inset: 0; z-index: 20; pointer-events: none; }
/* Padding carries the safe-area insets so a full-screen takeover (settings,
   shop, level select) cannot put its first row under a notch or its last under
   the home indicator. Identical to the old flat 24px wherever the insets are 0,
   which is every desktop and every phone without a cutout. */
.screen {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: safe center; pointer-events: auto;
  background: rgba(10, 14, 26, 0.72); backdrop-filter: blur(4px);
  overflow-y: auto;
  padding: calc(24px + var(--sai-top)) calc(24px + var(--sai-right))
           calc(24px + var(--sai-bottom)) calc(24px + var(--sai-left));
}
/* Boot splash: covers the app until main.js signals ready, with glowing animated progress bar */
#boot-splash {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
  background: radial-gradient(circle at center, #141b2d 0%, #080c16 85%, #05070e 100%);
  color: var(--fw-gold, #ffd23f);
  font-family: var(--fw-font, system-ui, -apple-system, sans-serif);
  padding: 24px;
  user-select: none;
  transition: opacity 0.45s ease, transform 0.45s ease;
  pointer-events: auto;
}

#boot-splash.fade-out {
  opacity: 0;
  transform: scale(1.03);
  pointer-events: none;
}

.boot-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.boot-logo {
  font-size: clamp(38px, 8vw, 54px);
  filter: drop-shadow(0 0 16px rgba(255, 210, 63, 0.45));
  animation: bootGearSpin 12s linear infinite;
}

@keyframes bootGearSpin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.boot-title {
  font-weight: 900;
  letter-spacing: 6px;
  font-size: clamp(28px, 7vw, 46px);
  color: var(--fw-gold, #ffd23f);
  text-shadow: 0 0 24px rgba(255, 210, 63, 0.4), 0 4px 0 rgba(0, 0, 0, 0.8);
}

.boot-tagline {
  font-size: clamp(11px, 2.2vw, 13.5px);
  font-weight: 800;
  letter-spacing: 3.5px;
  color: rgba(255, 255, 255, 0.65);
}

.boot-loader-box {
  width: min(90vw, 360px);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.boot-track {
  width: 100%;
  height: 12px;
  background: rgba(0, 0, 0, 0.65);
  border: 2px solid rgba(255, 210, 63, 0.35);
  border-radius: 999px;
  overflow: hidden;
  position: relative;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.8), 0 0 12px rgba(255, 210, 63, 0.15);
}

.boot-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #ff9e00 0%, #ffd23f 50%, #fff3b0 100%);
  border-radius: 999px;
  position: relative;
  transition: width 0.08s ease-out;
  box-shadow: 0 0 14px rgba(255, 210, 63, 0.8);
}

.boot-shimmer {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.65) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  animation: bootShimmerAnim 1.4s infinite linear;
}

@keyframes bootShimmerAnim {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

.boot-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: 800;
  font-size: clamp(11px, 2vw, 12.5px);
  letter-spacing: 1.2px;
}

#boot-status-text {
  color: #90e0ef;
  font-family: monospace, monospace;
}

#boot-percentage-text,
#city-percentage-text {
  color: var(--fw-gold, #ffd23f);
  font-family: monospace, monospace;
  font-size: 13px;
  font-weight: 900;
}

#city-status-text {
  color: #90e0ef;
  font-family: monospace, monospace;
}

/* City Scene Transition Loader */
.screen.loading-screen {
  background: rgba(10, 14, 26, 0.94);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
}

.loading-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.loading-brand h2 {
  margin: 0;
  font-size: clamp(20px, 4.5vw, 32px);
  letter-spacing: 2px;
}

.loading-label {
  color: var(--fw-gold, #ffd23f);
  font-size: clamp(13px, 2.6vw, 17px);
  font-weight: 800;
  letter-spacing: 3px;
  text-transform: uppercase;
  background: rgba(255, 210, 63, 0.12);
  border: 1px solid rgba(255, 210, 63, 0.35);
  border-radius: 999px;
  padding: 4px 18px;
}
/* Headings wear the brand ring so every screen reads as one game rather than as
   a settings panel bolted to a title card. STATIC on purpose: the pop-in and bob
   belong to the landing screen and the READY gate, where the type IS the moment.
   On a shop or results header the same motion only delays reading it. The 2px
   ring (not the wordmark's 3px) because these sit at 20-44px, where a 3px ring
   starts closing up the counters of an 'e'. */
.screen h1, .screen h2 {
  color: var(--fw-gold); font-weight: 900; letter-spacing: 1px;
  text-shadow: var(--fw-ring-2), 0 4px 0 var(--fw-extrude-1), 0 8px 14px rgba(0,0,0,.55);
}
.screen h1 { font-size: clamp(26px, 5vw, 44px); margin-bottom: 18px; }
.screen h2 { font-size: clamp(20px, 3.5vw, 30px); margin-bottom: 12px; }
/* The ordinary button is the GO! pill with the volume down: same gradient, same
   3px dark border, same hard bottom extrude and press-down, two steps smaller.
   Padding is 11/32 against the old 13/34 because the 3px border it never had is
   now doing that work — the box lands within a pixel of where it used to. */
.btn {
  font-family: inherit;
  background: linear-gradient(180deg, var(--fw-orange-1) 0%, var(--fw-orange-2) 44%, var(--fw-orange-3) 100%);
  color: #2b1200; border: 3px solid var(--fw-ink-2); border-radius: 999px;
  padding: 11px 32px; font-size: clamp(15px, 2.4vw, 19px); font-weight: 900;
  letter-spacing: 1.2px; cursor: pointer; margin: 6px;
  box-shadow: 0 5px 0 var(--fw-extrude-1), 0 10px 18px rgba(0,0,0,.45);
}
.btn:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--fw-extrude-1), 0 6px 12px rgba(0,0,0,.45); }
.btn:focus-visible { outline: 3px solid var(--fw-gold); outline-offset: 3px; }
.btn.secondary {
  background: linear-gradient(180deg, var(--fw-steel-1) 0%, var(--fw-steel-2) 100%);
  color: #fff; box-shadow: 0 5px 0 var(--fw-steel-extrude), 0 10px 18px rgba(0,0,0,.45);
}
.btn.secondary:active { box-shadow: 0 2px 0 var(--fw-steel-extrude), 0 6px 12px rgba(0,0,0,.45); }
.btn:disabled {
  background: #555; color: #ddd; cursor: default; opacity: .6;
  box-shadow: 0 5px 0 #2b2b2b, 0 10px 18px rgba(0,0,0,.45);
}
/* Armed two-step confirm (pause RESTART/CITIES in screens.js showPause). */
.btn.danger {
  background: linear-gradient(180deg, #ff6b5f 0%, #e0382b 100%);
  color: #fff; box-shadow: 0 5px 0 #7a1408, 0 10px 18px rgba(0,0,0,.45);
}

.metro-row { width: min(1180px, 94vw); margin-bottom: 18px; }
.metro-row h3 { margin: 8px 0; font-size: clamp(15px, 2.6vw, 20px); color: #ffd23f; }
.level-cards { display: flex; flex-wrap: wrap; gap: 8px; }
.level-card {
  width: 74px; height: 86px; border-radius: 10px; background: var(--panel);
  border: 2px solid rgba(255,255,255,.35); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  font-weight: 800;
}
.level-card.locked { opacity: .38; cursor: default; }
.level-card .stars { color: #ffd23f; font-size: 13px; letter-spacing: 1px; }
.level-card .mech { font-size: 15px; }
.level-card:hover:not(.locked) { border-color: var(--accent); }

/* --- Modern Mobile Game Shop UI with Category Tabs & Incremental Upgrades --- */
.shop-screen {
  display: flex; flex-direction: column; align-items: center; width: 100%;
  max-width: min(1080px, 96vw); margin: 0 auto; padding-bottom: 24px;
}
.shop-header {
  position: sticky; top: 0; z-index: 12; width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; margin-bottom: 12px;
  background: rgba(18, 23, 34, 0.92); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08); border-radius: 0 0 16px 16px;
}
.shop-back-btn {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 16px; font-size: 13px; font-weight: 700; letter-spacing: 0.8px;
  color: #fff; background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.16); border-radius: 20px;
  cursor: pointer; transition: all 0.2s ease;
}
.shop-back-btn:hover {
  background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.3);
  transform: translateX(-2px);
}
.shop-title-block {
  text-align: center; flex: 1; min-width: 0;
}
.shop-title-block h2 {
  margin: 0; font-size: clamp(18px, 3.5vw, 24px); letter-spacing: 1.5px;
  color: #fff; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.shop-progress-summary {
  font-size: 11px; font-weight: 600; color: rgba(255, 255, 255, 0.6);
  letter-spacing: 0.5px; margin-top: 2px;
}
.shop-coin-pill {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 14px; font-size: 14px; font-weight: 800;
  color: #ffd23f; background: linear-gradient(135deg, rgba(255, 210, 63, 0.18), rgba(255, 140, 0, 0.12));
  border: 1px solid rgba(255, 210, 63, 0.35); border-radius: 20px;
  box-shadow: 0 0 14px rgba(255, 210, 63, 0.15); white-space: nowrap;
}

/* Category Tab Bar */
.shop-tab-bar {
  display: flex; gap: 8px; width: 100%; max-width: 100%;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  padding: 4px 4px 10px 4px; margin-bottom: 12px;
}
.shop-tab-bar::-webkit-scrollbar { display: none; }
.shop-tab {
  flex: 1 0 auto; min-width: 80px; min-height: 46px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  padding: 6px 12px; font-size: 11px; font-weight: 700; letter-spacing: 0.5px;
  color: rgba(255, 255, 255, 0.65); background: rgba(255, 255, 255, 0.05);
  border: 1.5px solid rgba(255, 255, 255, 0.08); border-radius: 12px;
  cursor: pointer; transition: all 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  user-select: none;
}
.shop-tab:hover {
  color: #fff; background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.2);
}
.shop-tab.active {
  color: #fff; background: linear-gradient(180deg, rgba(56, 189, 248, 0.22), rgba(56, 189, 248, 0.08));
  border-color: #38bdf8; box-shadow: 0 0 16px rgba(56, 189, 248, 0.25);
  transform: translateY(-2px);
}
.shop-tab .tab-icon {
  font-size: 18px; line-height: 1; filter: drop-shadow(0 1px 3px rgba(0,0,0,0.5));
}
.shop-tab .tab-badge {
  font-size: 9px; font-weight: 800; opacity: 0.8;
  background: rgba(0, 0, 0, 0.35); padding: 1px 5px; border-radius: 8px; margin-top: 1px;
}
.shop-tab.active .tab-badge {
  background: rgba(56, 189, 248, 0.3); color: #38bdf8;
}

/* Category Sub-Header Banner */
.shop-category-banner {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px; margin-bottom: 14px;
  background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 12px;
}
.shop-category-banner h3 {
  margin: 0; font-size: 14px; font-weight: 700; color: #fff; letter-spacing: 0.5px;
}
.shop-category-banner p {
  margin: 2px 0 0 0; font-size: 11px; color: rgba(255, 255, 255, 0.55);
}

/* Grid of Cosmetic Items */
.shop-items {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px; width: 100%; margin-bottom: 20px;
}
.shop-item {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  background: rgba(24, 30, 44, 0.75); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border-radius: 14px; padding: 14px; width: auto;
  border: 1.5px solid rgba(255, 255, 255, 0.1);
  transition: all 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  position: relative; overflow: hidden;
}
.shop-item:hover {
  border-color: rgba(255, 255, 255, 0.25); transform: translateY(-3px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}
.shop-item.is-equipped {
  border-color: #4be34b; box-shadow: 0 0 16px rgba(75, 227, 75, 0.22);
}
.shop-item .preview {
  width: 140px; height: 140px; display: block; margin: 4px auto 10px;
  border-radius: 10px; background: #121622; object-fit: contain;
}
.shop-item .swatch {
  width: 70px; height: 70px; border-radius: 50%; margin: 35px auto 35px;
  border: 3px solid #fff; box-shadow: 0 0 14px rgba(255, 255, 255, 0.2);
}
.shop-item h4 {
  margin: 0 0 4px 0; font-size: 14px; font-weight: 700; color: #fff; letter-spacing: 0.4px;
}
.shop-item .blurb {
  font-size: 11px; line-height: 1.35; min-height: 38px; color: rgba(255, 255, 255, 0.65);
  margin: 2px 0 10px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.shop-item .price {
  color: #ffd23f; font-weight: 800; margin: 4px 0 8px; font-size: 13px;
}
.shop-item .card-footer {
  width: 100%; margin-top: auto;
}
.shop-item .btn {
  width: 100%; min-height: 38px; font-size: 12px; font-weight: 700; letter-spacing: 0.6px;
  border-radius: 10px; cursor: pointer; transition: all 0.18s ease;
}
.shop-item .btn.equipped {
  background: rgba(75, 227, 75, 0.15); color: #4be34b; border: 1px solid rgba(75, 227, 75, 0.4);
  cursor: default; opacity: 1;
}
.shop-item .btn.equip {
  background: rgba(56, 189, 248, 0.18); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.4);
}
.shop-item .btn.equip:hover {
  background: #38bdf8; color: #000; box-shadow: 0 0 12px rgba(56, 189, 248, 0.5);
}
.shop-item .btn.buy {
  background: linear-gradient(135deg, #ffd23f, #f59e0b); color: #000; border: none;
  font-weight: 800; box-shadow: 0 2px 10px rgba(245, 158, 11, 0.3);
}
.shop-item .btn.buy:hover:not(:disabled) {
  transform: translateY(-1px); box-shadow: 0 4px 16px rgba(245, 158, 11, 0.5);
}
.shop-item .btn.buy:disabled {
  opacity: 0.4; cursor: not-allowed; filter: grayscale(0.6);
}

/* --- Upgrades Tab Grid & Cards --- */
.shop-upgrades-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px; width: 100%; margin-bottom: 20px;
}
.upgrade-card {
  display: flex; flex-direction: column; justify-content: space-between;
  background: rgba(24, 30, 44, 0.85); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border-radius: 16px; padding: 18px 20px;
  border: 1.5px solid rgba(255, 255, 255, 0.12);
  transition: all 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  position: relative;
}
.upgrade-card:hover {
  border-color: rgba(255, 255, 255, 0.28); transform: translateY(-3px);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
}
.upgrade-card.is-maxed {
  border-color: rgba(255, 210, 63, 0.45);
  box-shadow: 0 0 20px rgba(255, 210, 63, 0.12);
}
.upgrade-top-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px;
}
.upgrade-icon-title {
  display: flex; align-items: center; gap: 10px;
}
.upgrade-icon-wrap {
  width: 44px; height: 44px; border-radius: 12px;
  background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.14);
  display: flex; align-items: center; justify-content: center; font-size: 24px;
}
.upgrade-titles h4 {
  margin: 0; font-size: 15px; font-weight: 700; color: #fff; letter-spacing: 0.3px;
}
.upgrade-titles .stat-name {
  font-size: 11px; font-weight: 600; color: rgba(255, 255, 255, 0.55); margin-top: 1px;
}
.upgrade-rank-pill {
  font-size: 11px; font-weight: 800; padding: 4px 10px; border-radius: 12px;
  background: rgba(56, 189, 248, 0.15); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.35);
  white-space: nowrap;
}
.upgrade-card.is-maxed .upgrade-rank-pill {
  background: rgba(255, 210, 63, 0.18); color: #ffd23f; border-color: rgba(255, 210, 63, 0.45);
}
.upgrade-desc {
  font-size: 12px; line-height: 1.4; color: rgba(255, 255, 255, 0.7);
  margin: 0 0 14px 0; min-height: 34px;
}

/* 20-segment progress track */
.upgrade-meter-wrap {
  margin-bottom: 14px;
}
.upgrade-meter-label {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 11px; font-weight: 700; color: rgba(255, 255, 255, 0.7); margin-bottom: 6px;
}
.upgrade-meter-label .boost-num {
  color: #4be34b; font-weight: 800;
}
.upgrade-meter {
  display: flex; gap: 3px; height: 10px; width: 100%;
  background: rgba(0, 0, 0, 0.3); padding: 2px; border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.upgrade-pip {
  flex: 1; height: 100%; border-radius: 2px;
  background: rgba(255, 255, 255, 0.1); transition: all 0.25s ease;
}
.upgrade-pip.filled {
  background: linear-gradient(135deg, #38bdf8, #4be34b);
  box-shadow: 0 0 4px rgba(56, 189, 248, 0.4);
}
.upgrade-card.is-maxed .upgrade-pip.filled {
  background: linear-gradient(135deg, #ffd23f, #ff9800);
  box-shadow: 0 0 4px rgba(255, 210, 63, 0.5);
}

.upgrade-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 4px;
}
.upgrade-next-hint {
  font-size: 11px; font-weight: 600; color: rgba(255, 255, 255, 0.5);
}
.upgrade-next-hint b {
  color: #ffd23f;
}
.upgrade-action-btn {
  min-height: 38px; padding: 0 18px; font-size: 12px; font-weight: 800; letter-spacing: 0.6px;
  border-radius: 10px; cursor: pointer; transition: all 0.18s ease; white-space: nowrap;
}
.upgrade-action-btn.primary {
  background: linear-gradient(135deg, #ffd23f, #f59e0b); color: #000; border: none;
  font-weight: 800; box-shadow: 0 2px 10px rgba(245, 158, 11, 0.3);
}
.upgrade-action-btn.primary:hover:not(:disabled) {
  transform: translateY(-1px); box-shadow: 0 4px 16px rgba(245, 158, 11, 0.5);
}
.upgrade-action-btn.primary:disabled {
  opacity: 0.4; cursor: not-allowed; filter: grayscale(0.6);
}
.upgrade-action-btn.maxed {
  background: rgba(255, 210, 63, 0.15); color: #ffd23f; border: 1px solid rgba(255, 210, 63, 0.35);
  cursor: default; opacity: 0.9;
}
.coins { font-size: clamp(16px, 3vw, 22px); color: #ffd23f; font-weight: 800; margin-bottom: 10px; }

.results-stars { font-size: clamp(34px, 8vw, 60px); color: #ffd23f; letter-spacing: 8px; margin: 10px 0; }
.results-stats { background: var(--panel); border-radius: 14px; padding: 16px 26px; margin: 10px 0; line-height: 1.8; min-width: min(360px, 86vw); }
.results-stats b { color: #ffd23f; float: right; margin-left: 40px; }
/* SETTINGS rows. Every row used to be `LABEL <span style="float:right">VALUE`,
   which is the pattern that wraps the moment label + value exceed the width —
   and it wraps INVISIBLY to script, because a floated value is out of flow, so
   the row's own height stays one line while the value renders below it. Measured
   at 320px: the Touch row reported height 29px (one line) while visibly
   occupying two. Any assertion about row height against the float layout is
   therefore meaningless, which is half the reason this moved to flex.

   Flex fixes both: the value keeps its own line because it never wraps, the
   label absorbs the remaining space, and the row's height is now the truth.

   The other half of the fix is width. `.results-stats` is padded 16px/26px for
   the results screen, and at a 320px viewport that plus `min-width: min(360px,
   86vw)` left only 223px of content for rows that needed up to 390px. The
   settings panel gets its own class so the results screen keeps its padding. */
/* 584px is the panel's natural flex width, and it is 26px wider than the 558px
   the floated layout produced. That 26px is the honest cost of rows that no
   longer overlap: the widest row (turn sensitivity) needs 516px including the
   flex gap, and capping the panel back to 560px leaves only 508px, which wraps
   it. A 26px wider panel is the smaller of the two visible changes. */
.set-panel { min-width: 0; max-width: min(584px, 94vw); }
.set-row {
  display: flex; align-items: center; gap: 10px;
  margin: 8px 0; clear: both;
}
.set-label { flex: 1 1 auto; min-width: 0; }
/* Second line under a setting's name, for the ones whose label does not say
   what turning them on costs. Block so it takes its own line inside the label
   rather than competing with the ON/OFF value for the row's single line, and
   deliberately quiet: it is a consequence, not a second label. */
.set-hint { display: block; font-size: 12px; font-weight: 400; opacity: .62; line-height: 1.3; margin-top: 2px; }
/* nowrap is what actually guarantees one line: without it the value is free to
   break internally and the flex row grows instead of the value overflowing. */
.set-val { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; white-space: nowrap; font-weight: 700; }
.set-val input[type="range"] { width: 120px; vertical-align: middle; margin: 0; }
.set-val .btn { margin: 0; padding: 4px 18px; font-size: 14px; }
/* ADVANCED fold for the dev physics sliders (screens.js showSettings). */
.set-details { clear: both; margin-top: 14px; }
.set-details summary {
  cursor: pointer; font-weight: 900; letter-spacing: 1.2px; font-size: 14px;
  opacity: .8; margin-bottom: 6px;
}
/* BACK pinned to the viewport bottom of the scrolling settings screen: the
   panel outgrows a phone viewport and the button used to land below the fold
   (playtest finding — measured at y≈1303 in a 900px viewport). */
.set-back {
  position: sticky; bottom: calc(12px + var(--sai-bottom)); z-index: 5;
}

/* Phone width ONLY. Everything below is inside the query on purpose: at 768px
   and up there are ~508px of row width, every row already fits on one line, and
   the desktop/tablet layout must not move. Recover the pixels the desktop
   padding can spare, then shrink the two widest row parts — the slider and the
   numeric readout — neither of which needs desktop size to be usable. */
@media (max-width: 420px) {
  .set-panel { width: 94vw; padding: 14px 12px; }
  .set-row { gap: 8px; margin: 6px 0; }
  .set-val { font-size: 13px; }
  .set-val input[type="range"] { width: 78px; }
  .set-val .btn { padding: 4px 12px; font-size: 13px; }

}

/* Two rows whose label and value cannot share a line until the panel is quite
   wide — measured, not assumed: at 320px the camera label alone is 240px of a
   277px row, and the turn-sensitivity row needs 516px of content, which the
   panel only reaches at about a 604px viewport. Stacking is DELIBERATE: the
   value takes its own full-width line, right-aligned, so it reads as a designed
   two-line row rather than an accidental wrap.

   This breakpoint is 700px and NOT 420px like the block above, because those are
   two different questions. 420px is "shrink the slider and readout"; 700px is
   "does this row fit at all". Tying them together left a dead band where the row
   had already stopped stacking but had not yet grown enough to fit, and it
   wrapped for the whole of it. 620px was still inside that band — measured, the
   panel only reaches its full 584px (532px of content) at about a 660px
   viewport, and the row needs between 521px and 532px. */
@media (max-width: 700px) {
  .set-row--stack { flex-wrap: wrap; }
  .set-row--stack .set-label { flex: 1 0 100%; }
  .set-row--stack .set-val { flex: 1 0 100%; justify-content: flex-end; }
}

.mechanic-intro {
  background: var(--panel); border: 2px solid #ffd23f; border-radius: 14px;
  padding: 14px 22px; margin: 12px 0; max-width: min(520px, 90vw); text-align: center;
}

/* The gate owns the game HUD while it is up: a live '0% · 0 voxels' readout
   behind a READY? splash tells the player the level already started, and at
   phone-landscape height the BROOKLYN pill collides with the mass bar.
   Driven by body classes so no element owned by hud.js is ever mutated.
   Order matters: .rg-gate-still must follow the transition rule to win it. */
body.rg-gate-up #hud,
body.rg-gate-down #hud { transition: opacity .28s ease; }
body.rg-gate-still #hud { transition: none; }
body.rg-gate-up #hud { opacity: 0; }
/* #hud is pointer-events:none but #hud-right re-enables it — kill the whole
   subtree so an invisible mute/pause button can't be clicked or tabbed to. */
body.rg-gate-up #hud,
body.rg-gate-up #hud * { pointer-events: none; }

/* ---------- Flywheel brand layer (unscoped) ----------
   The level-start READY gate is where this game found its voice, and until now
   every rule that made it work was scoped to #ready-gate. They live here
   instead: the wordmark, the orange pill, the dark plate and the hold-still
   behaviour belong to the GAME, not to the gate, so the landing screen is
   literally the same treatment rather than a lookalike that drifts apart on the
   next edit. The gate section below keeps only what is genuinely about the gate
   — its sizing against a live 3D frame, its scrim, its hint and its exit. */

/* readable by a screen reader, invisible on screen */
.fw-a11y {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ----- the block wordmark ----- */
/* Built by js/ui/blockword.js. Callers own the font-size and nothing else:
   --fw-fit comes from the builder and shrinks a longer-than-design word rather
   than letting it overflow a phone in portrait. */
.fw-title {
  position: relative; z-index: 0;
  display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-end;
  gap: 0 .3em; max-width: 96vw;
  font-weight: 900; line-height: .95; letter-spacing: .01em;
  /* the 10px block extrude paints outside the line box — keep whatever follows
     clear of the overhang */
  padding-bottom: .12em;
}
.fw-word { display: flex; flex-wrap: nowrap; }
.fw-ch {
  display: inline-block; color: var(--fw-gold); transform-origin: 50% 88%;
  text-shadow:
    var(--fw-ring-3),
    0 7px 0 var(--fw-extrude-1), 0 10px 0 var(--fw-extrude-2),
    0 0 30px rgba(255,210,63,.5), 0 14px 26px rgba(0,0,0,.6);
  animation:
    fw-pop .56s cubic-bezier(.18,1.6,.42,1) calc(var(--i) * 58ms) both,
    fw-bob 2.9s ease-in-out calc(760ms + var(--i) * 110ms) infinite;
}
.fw-ch-accent { color: var(--fw-gold-hot); }
/* ambient gold pool so the wordmark separates from whatever is lit behind it */
.fw-glow {
  position: absolute; left: 50%; top: 50%; width: 132%; height: 200%;
  transform: translate(-50%, -50%); z-index: -1; pointer-events: none;
  background: radial-gradient(closest-side, rgba(255,210,63,.3),
    rgba(255,138,43,.13) 52%, rgba(255,210,63,0) 78%);
  animation: fw-glow 3.4s ease-in-out infinite;
}
/* sparkles live OUTSIDE the letter block so they never sit on a glyph */
.fw-spark {
  position: absolute; width: clamp(18px, 4.5vw, 40px); aspect-ratio: 1;
  background: #fff; pointer-events: none;
  clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%);
  filter: drop-shadow(0 0 9px rgba(255,210,63,1)) drop-shadow(0 2px 3px rgba(0,0,0,.75));
  animation: fw-twinkle 2.6s ease-in-out calc(600ms + var(--i) * 700ms) infinite;
}
.fw-title .fw-spark:nth-of-type(1) { left: -10%; top: -8%; }
.fw-title .fw-spark:nth-of-type(2) { right: -10%; top: 10%; }
.fw-title .fw-spark:nth-of-type(3) { right: 6%; top: -28%; }

/* ----- the dark plate -----
   Gold on near-black (13:1) rather than the HUD's white-on-orange, so the one
   orange thing on any screen is the one thing to press. */
.fw-plate {
  background: var(--fw-plate); color: var(--fw-gold);
  border: 2px solid rgba(255, 255, 255, .55); border-radius: 22px;
  padding: 5px clamp(14px, 3vw, 28px); font-weight: 800;
  letter-spacing: clamp(2px, .5vw, 4px);
  font-size: clamp(11px, min(2.4vw, 2.2vh), 19px);
  box-shadow: 0 3px 0 rgba(0,0,0,.5), 0 8px 20px rgba(0,0,0,.45);
}

/* ----- the primary CTA pill -----
   Consumers set padding and font-size; everything that makes it feel like a
   physical button is here. The wrap exists so the expanding ping ring has
   something to sit on that is not the button's own box. */
.fw-cta-wrap {
  position: relative; display: inline-flex;
  animation:
    fw-cta-in .6s cubic-bezier(.2,1.7,.4,1) .38s both,
    fw-cta-idle 1.7s ease-in-out 1.2s infinite;
}
.fw-cta-wrap::before {
  content: ''; position: absolute; inset: -7px; border-radius: 999px;
  border: 3px solid rgba(255,210,63,.8); pointer-events: none;
  animation: fw-ping 1.9s ease-out .9s infinite;
}
.fw-cta {
  position: relative; font-family: inherit; cursor: pointer;
  background: linear-gradient(180deg, var(--fw-orange-1) 0%, var(--fw-orange-2) 44%, var(--fw-orange-3) 100%);
  color: #2b1200; border: 3px solid var(--fw-ink-2); border-radius: 999px;
  font-weight: 900; letter-spacing: 1.6px;
  box-shadow: 0 7px 0 var(--fw-extrude-1), 0 14px 26px rgba(0,0,0,.55);
}
.fw-cta:active {
  transform: translateY(4px);
  box-shadow: 0 3px 0 var(--fw-extrude-1), 0 8px 16px rgba(0,0,0,.55);
}
.fw-cta:focus-visible { outline: 4px solid #fff; outline-offset: 5px; }

/* ----- the mark: a voxel sprocket -----
   Built by js/ui/sprocket.js. --fw-sp is the wheel's DIAMETER and every
   measurement below is a fraction of it, so one clamp resizes the whole mark
   and the tooth/rim/hub proportions can never drift apart. */
.fw-sprocket {
  --fw-sp: clamp(72px, min(19vw, 17vh), 132px);
  position: relative; flex: none; width: var(--fw-sp); height: var(--fw-sp);
  /* Grounding lives on the STATIC outer box, never on the spinning layer: a
     filter here is applied after the child's rotation, so the extrude and the
     cast shadow keep pointing down while the wheel turns. Put it on the teeth
     and the light source would orbit with them. */
  filter: drop-shadow(0 5px 0 var(--fw-extrude-1)) drop-shadow(0 12px 16px rgba(0,0,0,.55));
}
.fw-sprocket-spin {
  position: absolute; inset: 0;
  /* 20 s per revolution = 18 deg/s. Slow enough to read as mass and momentum
     rather than as a loading spinner, fast enough that a tooth visibly moves
     while you look at it — one 30 deg pitch every 1.7 s. */
  animation: fw-spin 20s linear infinite;
}
/* Teeth are literal squares with a hard dark border and a lighter cap on the
   outward end: flat fill plus one lit face is exactly how the 1 m voxel stock
   the cities are cut from reads, so the mark is made of the same material the
   player eats. Square and stubby is also what separates a sprocket from a sun —
   the first pass ran them at 1.3:1 and 0.4 of the wheel long, and it read as a
   flower. They span 0.70 to 0.98 of the radius, so a fifth of each tooth is
   buried in the rim and only the working part shows. */
.fw-tooth {
  position: absolute; left: 50%; top: 50%;
  width: calc(var(--fw-sp) * .14); height: calc(var(--fw-sp) * .14);
  margin: calc(var(--fw-sp) * -.07) 0 0 calc(var(--fw-sp) * -.07);
  background: linear-gradient(180deg, #ffe27a 0 32%, #ffb02e 32% 100%);
  border: 2px solid var(--fw-ink-2); border-radius: 1px;
  transform: rotate(var(--a)) translateY(calc(var(--fw-sp) * -.42));
}
/* The rim does NOT spin. It is radially symmetric, so rotating it would buy
   nothing and would drag its vertical light split around with it — the teeth
   are the motion cue and the rim is the fixed lighting. Its outer edge sits at
   0.80 of the radius, well past the 0.70 where the teeth root, so the joins are
   covered and the silhouette stays one solid wheel. */
.fw-sprocket-rim {
  position: absolute; inset: 10%; border-radius: 50%;
  background: linear-gradient(180deg, #ffd06a 0 46%, #f2921f 46% 100%);
  border: 3px solid var(--fw-ink-2);
}
/* The void, deliberately empty: it is the hole the player drives, not a badge
   with room for a letter. The inset shadow is what sells it as depth rather
   than a painted dot; the faint warm bounce off the bottom lip is the only
   thing that stops it reading as a hole punched clean through the page. */
.fw-sprocket-hub {
  position: absolute; inset: 32%; border-radius: 50%;
  background: #070a12; border: 3px solid var(--fw-ink-2);
  box-shadow: inset 0 5px 9px rgba(0,0,0,.95), inset 0 -2px 6px rgba(255,178,60,.16);
}

/* ----- hold still -----
   Applied by whichever surface owns the reduced-motion decision: ready.js reads
   the OS setting, the landing screen reads save.settings.reducedMotion. Tilt is
   KEPT (it is layout, not movement) so the wordmark holds its character, and
   the sprocket parks 7.5 deg off — half a tooth pitch, so it reads as a
   deliberate pose rather than as a wheel that snapped to a grid. */
.fw-still .fw-glow,
.fw-still .fw-spark,
.fw-still .fw-cta-wrap,
.fw-still .fw-cta-wrap::before { animation: none; }
.fw-still .fw-ch { animation: none; transform: rotate(var(--rot)); }
.fw-still .fw-sprocket-spin { animation: none; transform: rotate(7.5deg); }

@keyframes fw-pop {
  0% { opacity: 0; transform: translateY(40%) scale(.32) rotate(calc(var(--rot) * 2.6)); }
  55% { opacity: 1; transform: translateY(-10%) scale(1.17) rotate(calc(var(--rot) * -.5)); }
  78% { transform: translateY(2%) scale(.96) rotate(var(--rot)); }
  100% { opacity: 1; transform: translateY(0) scale(1) rotate(var(--rot)); }
}
@keyframes fw-bob {
  0%, 100% { transform: translateY(0) scale(1) rotate(var(--rot)); }
  50% { transform: translateY(-5%) scale(1.03) rotate(calc(var(--rot) + 1.3deg)); }
}
@keyframes fw-glow {
  0%, 100% { opacity: .75; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 1; transform: translate(-50%, -50%) scale(1.09); }
}
@keyframes fw-twinkle {
  0%, 55%, 100% { opacity: 0; transform: scale(.2) rotate(0deg); }
  72%, 84% { opacity: 1; transform: scale(1) rotate(35deg); }
}
@keyframes fw-cta-in {
  0% { opacity: 0; transform: translateY(60%) scale(.6); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes fw-cta-idle {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-4%) scale(1.045); }
}
@keyframes fw-ping {
  0% { opacity: .85; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(1.32); }
}
@keyframes fw-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ---------- READY gate (level start, layered over LIVE gameplay) ----------
   z-index 15 sits above the HUD (10) and below the Screens takeovers (20), so
   the canvas keeps rendering behind it and screens.clear() can't wipe it.
   Contrast does NOT come from a backdrop — every glyph carries its own dark
   outline ring, so the type reads over any part of a moving, bright city. */
#ready-gate {
  position: absolute; inset: 0; z-index: 15;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
/* The establishing shot IS the feature — the gate exists to make the player
   look at the city, so the scrim must not bury it. A full-screen scrim cost
   46% of the frame's luma and 87% of its legible pixels (measured). This is a
   local pool sized to the type block instead: high enough at dead centre to
   seat the sign, at zero by the frame edge. The glyphs carry their own
   contrast via the outline ring (19:1 against white), so the pool is comfort,
   not compliance, and can afford to be small. No top/bottom bands — the top
   one guarded the HUD, which the gate now mutes outright. */
#ready-gate .rg-scrim {
  position: absolute; inset: 0; pointer-events: none;
  /* Two edge bands, not one centred pool. The centred version was fitted when
     the type sat in the middle of the frame; with the type split to the edges
     it darkened only the establishing shot, and it was measured on desktop
     alone — where the city is wide and mostly outside a 44%x38% core, so it
     cost 3% of city luma. In portrait the city is a small central patch and the
     same gradient sat straight on it, costing 39%. Anchored to the top and
     bottom edges it tracks wherever the type went, and the middle of the frame
     stays clear at every aspect. It is not carrying legibility either way —
     the title, hint, subtitle plate and CTA each hold their own contrast — so
     this is modality and settling, and it can stay light. */
  background:
    radial-gradient(78% 22% at 50% 0%,
      rgba(4,7,14,.55) 0%, rgba(4,7,14,.34) 45%, rgba(4,7,14,0) 100%),
    radial-gradient(78% 20% at 50% 100%,
      rgba(4,7,14,.55) 0%, rgba(4,7,14,.34) 45%, rgba(4,7,14,0) 100%);
  animation: rg-scrim-in .3s ease-out both;
}
/* The stack fills the frame and pushes its two clusters to the top and bottom
   edges instead of stacking them in the middle. The establishing shot is the
   point of this screen and a centred block sat directly on it: measured against
   the settled hold pose, the centred stack covered 91% of the visible city on
   desktop and 100% in portrait. Split, it clears the band the skyline occupies.
   Top/bottom rather than one side because the split has to survive either
   camera — the live bounded orbit and the static orbitArc-0 alternative frame
   the city at different widths, but both leave the vertical extremes empty. */
#ready-gate .rg-stack {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center;
  justify-content: space-between;
  /* The stack pushes its two clusters to the vertical extremes, which is
     exactly where a notch and a home indicator live — so the insets are added
     to the padding it already had. Byte-identical computed padding on any
     device reporting 0 insets, which is what keeps the gate's "must not drift"
     rule (.wiki/conventions.md) honest. */
  padding: calc(clamp(12px, 3vh, 30px) + var(--sai-top)) calc(2vw + var(--sai-right))
           calc(clamp(12px, 3vh, 30px) + var(--sai-bottom)) calc(2vw + var(--sai-left));
  text-align: center;
}
/* Each cluster keeps the rhythm the single stack used to have, so splitting it
   changes where the type sits and nothing else about how it reads. */
#ready-gate .rg-top,
#ready-gate .rg-bot {
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(18px, 4vh, 40px); max-width: 96vw;
}
/* The subtitle IS the shared .fw-plate; the gate adds only its entrance, which
   drops the pill in from above the frame edge before the title lands. */
#ready-gate .rg-sub { animation: rg-sub-in .5s cubic-bezier(.2,1.5,.4,1) .06s both; }

/* Gate sizing for the shared wordmark and CTA. This is the only thing the gate
   overrides on either: it is measured against a live 3D frame at arm's length,
   so it runs far larger than the landing screen's. 20vh caps it against the
   viewport height as well as the width, or phone landscape loses the city. */
#ready-gate .fw-title {
  font-size: calc(clamp(52px, min(22vw, 20vh), 148px) * var(--fw-fit, 1));
}
#ready-gate .fw-cta {
  padding: clamp(11px, 2vh, 19px) clamp(40px, 9vw, 78px);
  font-size: clamp(20px, min(5.5vw, 3.6vh), 34px);
}
#ready-gate .rg-hint {
  font-size: clamp(11px, min(1.9vw, 1.9vh), 16px); font-weight: 700;
  letter-spacing: 2px; text-transform: uppercase; color: #fff;
  /* Same trick as the title: the hint carries its own outline ring rather than
     relying on the scrim. It lands on Coney Island's boardwalk sand (luma 252)
     in the Brooklyn establishing shot, and a soft shadow alone did not hold. */
  text-shadow:
    2px 0 0 #0a0e18, -2px 0 0 #0a0e18, 0 2px 0 #0a0e18, 0 -2px 0 #0a0e18,
    1px 1px 0 #0a0e18, -1px 1px 0 #0a0e18, 1px -1px 0 #0a0e18, -1px -1px 0 #0a0e18,
    0 3px 6px rgba(0,0,0,.9), 0 0 14px rgba(0,0,0,.85);
  animation:
    rg-hint-in .5s ease-out .66s both,
    rg-hint-idle 2.6s ease-in-out 1.4s infinite;
}
#ready-gate .rg-hint-tap { display: none; }
/* Control cheat-sheet on the gate (playtest finding: the key map lived only at
   the bottom of SETTINGS, and A/D TURN rather than strafe was discovered by
   driving in circles). Same key/tap split as rg-hint; quieter, no idle pulse. */
#ready-gate .rg-controls {
  font-size: clamp(10px, min(1.6vw, 1.6vh), 14px); font-weight: 700;
  letter-spacing: 1.2px; text-transform: uppercase; color: #fff; opacity: .85;
  text-shadow:
    2px 0 0 #0a0e18, -2px 0 0 #0a0e18, 0 2px 0 #0a0e18, 0 -2px 0 #0a0e18,
    0 3px 6px rgba(0,0,0,.9);
  animation: rg-hint-in .5s ease-out .72s both;
}
#ready-gate .rg-controls-tap { display: none; }
/* The tier rule in one plain sentence (remediation re-run residual: SIZE was
   on the HUD but "eat what's smaller than you" was never said out loud). */
#ready-gate .rg-rule {
  font-size: clamp(11px, min(1.8vw, 1.8vh), 15px); font-weight: 800;
  letter-spacing: 1.6px; text-transform: uppercase; color: var(--fw-gold, #ffd23f);
  text-shadow:
    2px 0 0 #0a0e18, -2px 0 0 #0a0e18, 0 2px 0 #0a0e18, 0 -2px 0 #0a0e18,
    0 3px 6px rgba(0,0,0,.9);
  animation: rg-hint-in .5s ease-out .78s both;
}
#ready-gate.rg-out .rg-rule { animation: rg-fade-out .18s ease-in forwards; }
#ready-gate.fw-still .rg-rule { animation: none; }
#ready-gate.rg-out .rg-controls { animation: rg-fade-out .18s ease-in forwards; }
#ready-gate.fw-still .rg-controls { animation: none; }
@media (hover: none) and (pointer: coarse) {
  #ready-gate .rg-hint-key { display: none; }
  #ready-gate .rg-hint-tap { display: inline; }
  #ready-gate .rg-controls-key { display: none; }
  #ready-gate .rg-controls-tap { display: inline; }
  /* The minimap is a 150px square pinned bottom-right, i.e. 38% of the width of
     a 390px phone parked on the corner of the city the right-half orbit drag
     starts from. It cannot EAT that drag (#hud is pointer-events:none and only
     #hud-right re-enables it), but it occludes it, and a player who cannot see
     what their finger is over will not drag there. 26vmin is 101px on that
     phone — 26% instead of 38% — and it is capped at the authored 150 so a
     tablet is unchanged. Campaign only in practice: hud.js hides the wrapper
     outright in the sandbox, which has no minimap data source. */
  #minimap-wrap { width: clamp(96px, 26vmin, 150px); height: clamp(96px, 26vmin, 150px); }
  /* #hud-right lays out mute, pause and the timer pill as inline-blocks in a
     shrink-to-fit absolutely-positioned box, so on a wide viewport they sit on
     ONE row ~223px long — which on a 390px phone reaches back to x=153 and
     lands underneath #level-banner, whose own box runs to x=193 (46vw + the
     14px offset). Measured in a 390x844 portrait screenshot: the mute button
     was drawn straight through the middle of "BRIDGES TO CONEY ISLAND". A
     max-width forces the timer onto its own second row, which is what the
     margin-top on #timer was always drawn for, and pulls the button row's left
     edge out to x=276 — 83px clear of the banner. Deliberately vw and not a
     pixel breakpoint: at 42vw the cap only bites once the viewport is narrow
     enough for the collision to exist (a 768px coarse-pointer tablet gets
     322px, more than the row needs, and is unchanged). */
  #hud-right { max-width: 42vw; }
}
/* phone landscape: ~390px tall, so tighten everything and drop the sparkles */
@media (orientation: landscape) and (max-height: 480px) {
  /* Keeps the inset terms — a phone in landscape is the case where the notch is
     on a SIDE, so dropping them here would be dropping them where they matter
     most. */
  #ready-gate .rg-stack {
    padding: calc(10px + var(--sai-top)) calc(2vw + var(--sai-right))
             calc(10px + var(--sai-bottom)) calc(2vw + var(--sai-left));
  }
  #ready-gate .rg-top,
  #ready-gate .rg-bot { gap: clamp(14px, 3.5vh, 22px); }
  #ready-gate .fw-title { font-size: calc(clamp(40px, 21vh, 92px) * var(--fw-fit, 1)); }
  #ready-gate .fw-spark { display: none; }
  #ready-gate .fw-cta { padding: 9px clamp(26px, 6vw, 48px); box-shadow: 0 5px 0 var(--fw-extrude-1), 0 10px 20px rgba(0,0,0,.55); }
}

/* ----- exit: the hole eats the sign while the camera dives into the circle ----- */
#ready-gate.rg-out { pointer-events: none; }
#ready-gate.rg-out .fw-ch {
  animation: rg-eat .42s cubic-bezier(.55,-0.25,.75,.35) calc(var(--i) * 26ms) forwards;
}
#ready-gate.rg-out .rg-scrim { animation: rg-fade-out .4s ease-in forwards; }
#ready-gate.rg-out .fw-glow { animation: rg-fade-out .28s ease-in forwards; }
#ready-gate.rg-out .fw-spark { animation: rg-fade-out .18s ease-in forwards; }
#ready-gate.rg-out .rg-sub { animation: rg-slip-out .3s ease-in forwards; }
#ready-gate.rg-out .rg-hint { animation: rg-fade-out .18s ease-in forwards; }
#ready-gate.rg-out .fw-cta-wrap { animation: rg-cta-out .34s cubic-bezier(.3,-0.6,.7,.4) forwards; }
#ready-gate.rg-out .fw-cta-wrap::before { animation: rg-fade-out .2s ease-in forwards; }

/* ----- reduced motion: appear, sit still, disappear. The shared .fw-still rules
   in the brand layer already hold the wordmark, glow, sparks and CTA; these are
   the gate's own pieces, and they need the #ready-gate prefix to outrank the
   #ready-gate rules that armed them. ----- */
#ready-gate.fw-still .rg-scrim,
#ready-gate.fw-still .rg-sub,
#ready-gate.fw-still .rg-hint { animation: none; }

@keyframes rg-scrim-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rg-fade-out { to { opacity: 0; } }
@keyframes rg-sub-in {
  0% { opacity: 0; transform: translateY(-140%) scale(.7); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes rg-slip-out { to { opacity: 0; transform: translateY(-150%) scale(.8); } }
@keyframes rg-eat {
  0% { opacity: 1; transform: translate(0, 0) scale(1) rotate(var(--rot)); }
  20% { opacity: 1; transform: translate(0, -14%) scale(1.1, .9) rotate(var(--rot)); }
  100% { opacity: 0; transform: translate(var(--dx), 150%) scale(.04) rotate(calc(var(--rot) * -14)); }
}
@keyframes rg-cta-out {
  0% { transform: scale(1); opacity: 1; }
  30% { transform: scale(1.14); opacity: 1; }
  100% { transform: scale(.2); opacity: 0; }
}
@keyframes rg-hint-in { 0% { opacity: 0; transform: translateY(10px); } 100% { opacity: 1; transform: translateY(0); } }
@keyframes rg-hint-idle { 0%, 100% { opacity: .78; } 50% { opacity: 1; } }

/* ---------- Landing screen ----------
   Seven identically-weighted buttons used to sit here, which told the player
   nothing about any of them. They are three different KINDS of thing: one
   campaign entry, four free-play scenes, two utilities. The grouping below is
   that information, not decoration — the eye should land on the mark, then on
   PLAY, and only then discover that there are cities to go and wander. */
.fw-landing {
  gap: clamp(12px, 2.2vh, 24px);
  /* The wordmark's sparkles are absolutely positioned outside the letter block,
     and .screen's overflow-y:auto makes the x axis compute to auto too — so
     without this a sparkle could hand a 320px phone a horizontal scrollbar. */
  overflow-x: hidden;
}
.fw-hero {
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(6px, 1.4vh, 14px);
}
.fw-hero-text {
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(6px, 1.2vh, 12px);
}
/* 8 letters is the design length passed to blockword.js, so FLYWHEEL renders at
   --fw-fit 1 and the clamp alone does the sizing. Capped by 12vh as well as
   13vw or the wordmark eats a short viewport before the landscape query fires.
   At the 320px floor this measures ~213px against 272px of usable width. */
.fw-landing .fw-title { font-size: clamp(38px, min(13vw, 12vh), 96px); }
.fw-landing .fw-cta {
  padding: clamp(11px, 1.9vh, 16px) clamp(46px, 11vw, 72px);
  font-size: clamp(20px, min(5.5vw, 3.4vh), 30px);
}

/* The free-play group. A label plus a repeating chip is what makes four scenes
   read as one shelf rather than as four more decisions; the chips carry no
   per-scene colour, because the thing they have in common (they are all voxel
   cities you can just go and eat) is the point. */
.fw-group { width: min(660px, 100%); display: flex; flex-direction: column; gap: 8px; }
.fw-group-label {
  align-self: center; color: rgba(255,255,255,.7);
  font-size: 12px; font-weight: 800; letter-spacing: 2.4px; text-transform: uppercase;
  text-shadow: 0 2px 4px rgba(0,0,0,.7);
}
/* auto-fit at a 150px floor: two columns from ~330px up, one below it, and no
   media query needed to get there. */
.fw-chips { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.fw-chip {
  position: relative; overflow: hidden;
  font-family: inherit; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--fw-plate); color: #fff;
  border: 2px solid rgba(255,255,255,.26); border-radius: 12px;
  padding: 9px 13px 9px 18px; /* the extra 5px on the left clears the accent bar */
  box-shadow: 0 4px 0 rgba(0,0,0,.45);
}
/* The gold bar is a pseudo-element rather than a border-left so that :hover can
   recolour the frame without also wiping the accent out. */
.fw-chip::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  background: var(--fw-gold);
}
.fw-chip:hover { border-color: var(--fw-gold); background: rgba(26, 32, 50, .95); }
.fw-chip:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(0,0,0,.45); }
.fw-chip:focus-visible { outline: 3px solid var(--fw-gold); outline-offset: 3px; }
.fw-chip-name { font-weight: 900; letter-spacing: 1.1px; font-size: clamp(13px, 2.2vw, 15px); }
.fw-chip-sub { font-size: clamp(11px, 1.9vw, 12.5px); font-weight: 600; opacity: .7; }
/* Returning-player record on a city chip (screens.js showTitle). Gold-tinted
   like the tag but quieter: it is a fact, not a recommendation. */
.fw-chip-progress { font-size: clamp(10px, 1.7vw, 11.5px); font-weight: 800; letter-spacing: .8px; color: var(--fw-gold); }
/* Coin bank on the landing screen — save-state recognition at the front door. */
.fw-bank {
  margin-top: 10px; background: var(--fw-gold); color: #4a3200;
  border-radius: 999px; padding: 4px 16px; font-weight: 900;
  font-size: clamp(13px, 2vw, 16px); box-shadow: 0 3px 0 rgba(0,0,0,.35);
}
.fw-chip-tag {
  align-self: flex-start; margin-top: 3px;
  background: var(--fw-gold); color: #4a3200; border-radius: 7px;
  padding: 1px 7px; font-size: 10px; font-weight: 900; letter-spacing: 1.3px;
}

/* Shop and Settings are utilities, not offers: same pill geometry so they still
   belong to the game, drained of fill and extrude so they sit behind the chips
   in the reading order instead of competing with them. */
.fw-utility { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
.fw-utility .btn {
  margin: 0; padding: 7px 22px; font-size: 14px; letter-spacing: 1.4px;
  background: rgba(12, 16, 28, .55); color: rgba(255,255,255,.8);
  border: 2px solid rgba(255,255,255,.24); box-shadow: none;
}
.fw-utility .btn:hover { color: #fff; border-color: var(--fw-gold); }
.fw-utility .btn:active { transform: translateY(2px); box-shadow: none; }

/* The landing screen's footer: the sound manifest and the legal line, as one
   stack. They sit inside a wrapper rather than as two children of .fw-landing
   because that container's gap is a SECTION gap (up to 24px) — the right
   distance between the shelf and the utility row, and far too much between two
   lines of the same fine print. Wrapping also costs the screen one gap instead
   of two, which matters on a screen whose content already exceeds a typical
   desktop viewport height before either line is drawn. */
.fw-foot {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  max-width: 100%;
}

/* The CC0 sound manifest — the same quiet small type as arena.html's
   #sound-credits, parked under the utility row on the landing screen. */
.fw-credits {
  max-width: min(560px, 92vw); font-size: 9px; letter-spacing: 1.2px;
  line-height: 1.6; opacity: 0.38; font-weight: 600;
  text-align: center; pointer-events: none;
}

/* The legal footer, under the sound manifest and quieter than it: the manifest
   is a thank-you the game chooses to print, this is the fine print every page
   carries. Two plain links to two standalone documents (privacy.html,
   terms.html) — not .btn, not .fw-utility, because nothing here is a place to
   play and giving them pill geometry would put them in the same reading tier as
   SHOP.

   The one thing that is NOT minimised is the hit box: 10px line-height plus 7px
   of vertical padding is a 24px target, which is the WCAG 2.2 minimum, and the
   padding is what carries it rather than the type size. Fine print is allowed to
   be small; it is not allowed to be unclickable on a phone. */
.fw-legal {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 0 clamp(12px, 4vw, 22px);
  font-size: 9px; font-weight: 700; letter-spacing: 1.6px;
}
.fw-legal a {
  display: inline-block; padding: 7px 6px; line-height: 10px;
  color: rgba(255, 255, 255, .4); text-decoration: none;
}
.fw-legal a:hover { color: var(--fw-gold); text-decoration: underline; }
.fw-legal a:focus-visible { outline: 2px solid var(--fw-gold); outline-offset: 2px; }

/* phone landscape: ~390px tall. The hero turns on its side — mark beside the
   wordmark rather than above it — which is what buys back the ~140px the
   stacked version costs, and everything else just tightens. */
@media (orientation: landscape) and (max-height: 480px) {
  .fw-landing { padding: 12px 18px; gap: 10px; }
  .fw-hero { flex-direction: row; gap: clamp(10px, 2.4vw, 22px); }
  .fw-hero-text { align-items: flex-start; }
  .fw-landing .fw-title { justify-content: flex-start; font-size: clamp(32px, 15vh, 70px); }
  .fw-landing .fw-spark { display: none; }
  .fw-landing .fw-cta { padding: 9px clamp(30px, 7vw, 54px); font-size: clamp(18px, 4.4vh, 26px); }
  .fw-chips { gap: 8px; }
  .fw-chip { padding: 6px 11px 6px 16px; }
  .fw-chip-tag { display: none; }
}

/* ---------- Reduced motion (OS-level) ----------
   Mirrors the .fw-still class, which is what the in-game Reduced Motion setting
   drives. Placed last so the #ready-gate-scoped rules here outrank the ones
   further up that armed those animations; ready.js also stills the gate in JS,
   so this is belt-and-braces for it and the sole mechanism everywhere else. */
@media (prefers-reduced-motion: reduce) {
  .fw-glow, .fw-spark, .fw-cta-wrap, .fw-cta-wrap::before { animation: none; }
  .fw-ch { animation: none; transform: rotate(var(--rot)); }
  .fw-sprocket-spin { animation: none; transform: rotate(7.5deg); }
  #ready-gate .rg-scrim,
  #ready-gate .rg-sub,
  #ready-gate .rg-controls,
  #ready-gate .rg-rule,
  #ready-gate .rg-hint { animation: none; }
  /* The hint's knob sweep goes; its fade-in and self-destruct do NOT. The sweep
     is decoration (the label already says what to do), but th-life is the only
     thing that ever takes the hint off the screen when the player is reading
     rather than touching — killing it would leave a permanent ghost ring on the
     city, which is a worse outcome than the motion it was avoiding. It parks
     off-centre, up and to the left, so the ring still reads as a stick that has
     been pushed somewhere rather than as an empty target. */
  #touch-hint .th-knob { animation: none; transform: translate(-119%, -119%); }
}

/* ---------- Landing: the live city behind the title ----------
   js/ui/menuscene.js mounts a real sandbox scene on the game canvas and puts
   body.fw-scene on when it succeeds. EVERY rule in this block is keyed off that
   class, which is what makes the flat dark field the fallback rather than a
   second thing to maintain: no scene, no class, and the landing screen renders
   exactly as it did before.

   The screen's own translucent fill has to go when the scene is up — a 72%
   wash plus a 4px backdrop blur over a moving city is neither the city nor a
   panel, it is mush. What replaces it is a fixed scrim that is dark where the
   type is and clear where the skyline is: two edge bands for the wordmark and
   the shelf, a vignette to seat the whole frame, and a warm pool behind the
   hero. Same reasoning as the READY gate's rg-scrim, sized for a screen that
   carries far more type than the gate does. */
body.fw-scene .fw-landing {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
body.fw-scene .fw-landing::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    /* the warm pool the wordmark sits in — brand hue, not black, so the mark
       reads as lit by the city rather than cut out of it */
    radial-gradient(60% 34% at 50% 30%,
      rgba(28, 16, 6, .62) 0%, rgba(14, 10, 8, .34) 55%, rgba(10, 12, 22, 0) 100%),
    /* the shelf's band: the chips are dark plates, and this is what stops a lit
       facade showing through the gaps between them */
    linear-gradient(180deg, rgba(6, 9, 18, 0) 40%, rgba(6, 9, 18, .74) 72%, rgba(5, 7, 14, .9) 100%),
    linear-gradient(0deg, rgba(6, 9, 18, 0) 82%, rgba(6, 9, 18, .6) 100%),
    radial-gradient(130% 96% at 50% 42%,
      rgba(6, 9, 18, .18) 0%, rgba(6, 9, 18, .58) 66%, rgba(3, 5, 11, .88) 100%);
}
/* The scene fades in BEHIND a menu that is already on screen — the menu never
   waits for it. Opacity only, one compositor property, and it runs once
   because the class goes on at the moment the first frame is ready. */
body.fw-scene #game-canvas { animation: fw-scene-fade 1s ease-out both; }
@keyframes fw-scene-fade { from { opacity: 0; } to { opacity: 1; } }

/* ---------- Landing: weight ----------
   PLAY is the only high-saturation, motion-carrying thing on this screen. The
   cards and the utility row are still fully usable and fully legible; they are
   quieter, which is a different thing from smaller. */

/* Press/hover/focus feel, shared by every interactive element on the landing
   screen. Tokens rather than literals so the reduced-motion block at the foot
   of this file can swap the durations and the travel without turning any of it
   off — a press that gives no feedback is a broken button, not a calm one. */
.fw-landing {
  --fw-press: 60ms;
  --fw-release: 180ms;
  --fw-back: cubic-bezier(0.34, 1.56, 0.64, 1);
  --fw-lift: -3px;
  --fw-stagger: 40ms;
}
/* Layered, and tinted to the scene rather than to black: a warm near-navy for
   the contact shadow and a low gold bloom for the spread, so a card lifting off
   a lit city does not punch a grey hole in it. */
.fw-landing .fw-chip,
.fw-landing .fw-utility .btn,
.fw-landing .fw-cta {
  transition:
    transform var(--fw-release) var(--fw-back),
    box-shadow var(--fw-release) ease,
    border-color 140ms ease,
    background-color 140ms ease;
}
.fw-landing .fw-chip:active,
.fw-landing .fw-utility .btn:active,
.fw-landing .fw-cta:active {
  transform: scale(0.96);
  transition-duration: var(--fw-press);
  transition-timing-function: ease-out;
}
/* Keyboard and gamepad focus is a MATERIAL change — the card grows and gains a
   rim — because a colour swap alone is invisible to anyone who cannot see the
   colour and nearly invisible over a moving city. */
.fw-landing .fw-chip:focus-visible,
.fw-landing .fw-utility .btn:focus-visible,
.fw-landing .fw-cta:focus-visible {
  outline: none;
  transform: scale(1.045);
  border-color: var(--fw-gold);
  box-shadow:
    0 0 0 3px var(--fw-ink), 0 0 0 6px var(--fw-gold),
    0 10px 22px rgba(4, 7, 16, .6);
}

/* The cards: quieter frame, quieter accent, same size. Only the lead card keeps
   a full-strength gold bar, because only one card is the recommendation. */
.fw-landing .fw-chip {
  background: rgba(9, 12, 22, .82);
  border-color: rgba(255, 255, 255, .14);
  box-shadow: 0 3px 0 rgba(3, 5, 11, .55), 0 8px 18px rgba(4, 7, 16, .45);
  min-height: 44px;
  animation: fw-chip-in .3s cubic-bezier(.2, 1, .3, 1) calc(var(--i, 0) * var(--fw-stagger)) backwards;
}
.fw-landing .fw-chip::before { background: rgba(255, 210, 63, .32); }
.fw-landing .fw-chip--lead::before { background: var(--fw-gold); }
.fw-landing .fw-chip-name { color: rgba(255, 255, 255, .88); }
.fw-landing .fw-chip:hover {
  transform: translateY(var(--fw-lift));
  background: rgba(20, 26, 42, .92);
  border-color: rgba(255, 210, 63, .7);
  box-shadow:
    0 7px 0 rgba(3, 5, 11, .55), 0 14px 26px rgba(4, 7, 16, .55),
    0 0 26px rgba(255, 170, 63, .16);
}
.fw-landing .fw-chip:hover::before { background: var(--fw-gold); }
/* The START HERE pill was the second-loudest gold on the screen. It stays a
   pill and stays gold-lettered, on a dark plate, so PLAY owns the only solid
   gold fill above the fold. */
.fw-landing .fw-chip-tag {
  background: rgba(255, 210, 63, .14); color: var(--fw-gold);
  border: 1px solid rgba(255, 210, 63, .45);
}
.fw-landing .fw-group-label { color: rgba(255, 255, 255, .52); }

/* The utility row: last in the reading order, and now last in weight too. */
.fw-landing .fw-utility .btn {
  min-height: 44px; padding: 10px 24px;
  background: rgba(8, 11, 20, .6);
  color: rgba(255, 255, 255, .66);
  border-color: rgba(255, 255, 255, .16);
}
.fw-landing .fw-utility .btn:hover {
  transform: translateY(var(--fw-lift));
  color: #fff; border-color: rgba(255, 210, 63, .7);
  box-shadow: 0 8px 18px rgba(4, 7, 16, .5);
}
.fw-landing .fw-utility .btn:active { transform: scale(0.96); }
/* The one element allowed to keep its full voice. */
.fw-landing .fw-cta:hover {
  transform: translateY(var(--fw-lift));
  box-shadow:
    0 10px 0 var(--fw-extrude-1), 0 18px 30px rgba(4, 7, 16, .6),
    0 0 40px rgba(255, 138, 43, .28);
}

/* ---------- Landing: the player's own line ----------
   Personal best and the gap to the next unlock, read from the save (see
   personalBest/nextUnlock in js/ui/screens.js). One row, up to two cells, no
   cell rendered for a record that has never been set. */
.fw-status {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: stretch;
  gap: 8px; width: min(660px, 100%);
  animation: fw-chip-in .3s cubic-bezier(.2, 1, .3, 1) backwards;
}
.fw-stat {
  display: flex; flex-direction: column; gap: 3px; justify-content: center;
  background: rgba(9, 12, 22, .82); border: 2px solid rgba(255, 255, 255, .12);
  border-radius: 12px; padding: 7px 14px;
  box-shadow: 0 3px 0 rgba(3, 5, 11, .55);
}
.fw-stat--goal { flex: 1 1 220px; min-width: 200px; }
.fw-stat-k {
  font-size: 10px; font-weight: 800; letter-spacing: 1.6px;
  color: rgba(255, 255, 255, .58); text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.fw-stat-v {
  font-size: clamp(15px, 2.4vw, 19px); font-weight: 900; letter-spacing: .8px;
  color: var(--fw-gold); font-variant-numeric: tabular-nums;
  text-shadow: var(--fw-ring-2);
}
.fw-stat-note {
  font-size: 10px; font-weight: 800; letter-spacing: 1.2px;
  color: rgba(255, 255, 255, .5); font-variant-numeric: tabular-nums;
}

/* ---------- Landing: the identity chip ----------
   The first cell of the strip, and the only one that is a control: it is the
   player's name on the row that holds the player's records, and it opens the
   PROFILE screen. A real <button>, not a styled div with a click handler, so it
   is in the tab order, answers Enter and Space, and reports itself as a button
   to a screen reader without anyone having to hand-fit a role.

   It borrows .fw-stat's plate wholesale and only adds what a button needs: the
   font a button does not inherit, the pointer, and the press/hover/focus feel
   the rest of the landing screen already uses via its tokens (so the
   reduced-motion block further down swaps the durations here too, rather than
   this being a second thing to remember). */
.fw-id,
.fw-stat--interactive {
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  align-items: flex-start; min-height: 44px;
  transition: border-color var(--fw-release, 160ms) ease,
              transform var(--fw-press, 60ms) var(--fw-back, ease);
}
/* The note line is the affordance — VIEW PROFILE / SHOP / HOW TO CLAIM ONE — so it is
   the one .fw-stat-note on the screen that is not quiet. */
.fw-id .fw-stat-note,
.fw-stat--interactive .fw-stat-note { color: var(--fw-gold); }
.fw-id:hover,
.fw-stat--interactive:hover { border-color: var(--fw-gold); }
.fw-id:active,
.fw-stat--interactive:active { transform: scale(.97); }
.fw-id:focus-visible,
.fw-stat--interactive:focus-visible { outline: 3px solid var(--fw-gold); outline-offset: 3px; }
/* No name yet. The VALUE goes quiet and the invitation stays gold: NO NAME YET
   rendered in the same gold as a personal best would read as a thing achieved,
   which is the opposite of what the cell is saying. */
.fw-id--none .fw-stat-v {
  color: rgba(255, 255, 255, .62); text-shadow: none;
  font-size: clamp(13px, 2vw, 15px); letter-spacing: 1.2px;
}
/* The bar fills by transform, never by width: the track is a fixed box and the
   fill is scaled inside it, so nothing on this screen ever animates layout. */
.fw-bar {
  display: block; height: 8px; border-radius: 999px; overflow: hidden;
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(0, 0, 0, .5);
}
.fw-bar-fill {
  display: block; height: 100%; width: 100%; transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--fw-gold) 0%, var(--fw-gold-hot) 100%);
}

/* ---------- Landing: the graphic skin progress meter ---------- */
.fw-stat--graphic-meter {
  display: flex; flex-direction: column; gap: 5px; justify-content: center;
  width: 100%;
}
.fw-meter-header {
  display: flex; justify-content: space-between; align-items: center; width: 100%;
}
.fw-meter-pct {
  font-size: 11px; font-weight: 900; letter-spacing: 0.8px;
  color: var(--fw-gold); background: rgba(255, 210, 63, 0.14);
  border: 1px solid rgba(255, 210, 63, 0.4);
  border-radius: 4px; padding: 1px 6px; font-variant-numeric: tabular-nums;
}
.fw-meter-pct--ready {
  background: var(--fw-gold); color: #090c16; border-color: var(--fw-gold-hot);
  box-shadow: 0 0 8px rgba(255, 210, 63, 0.6);
  font-weight: 900;
}
.fw-graphic-meter {
  display: flex; flex-direction: column; gap: 3px; width: 100%;
}
.fw-meter-track {
  position: relative; width: 100%; height: 11px;
  background: rgba(4, 6, 12, 0.95);
  border: 1.5px solid rgba(255, 255, 255, 0.22);
  border-radius: 6px; overflow: hidden;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.85);
}
.fw-meter-fill {
  height: 100%;
  background: linear-gradient(90deg, #ff8c1a 0%, #ffd23f 65%, #fff080 100%);
  border-radius: 3px; position: relative;
  box-shadow: 0 0 10px rgba(255, 210, 63, 0.8);
  transition: width 0.3s cubic-bezier(0.2, 1, 0.3, 1);
}
.fw-meter-fill--full {
  background: linear-gradient(90deg, #ffd23f 0%, #ffef99 50%, #ffd23f 100%);
  animation: fw-gold-sweep 2s ease-in-out infinite alternate;
}
@keyframes fw-gold-sweep {
  0% { filter: brightness(1); }
  100% { filter: brightness(1.25) drop-shadow(0 0 4px #ffd23f); }
}
.fw-meter-glare {
  position: absolute; top: 0; left: 0; right: 0; height: 45%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 100%);
}
.fw-meter-ticks {
  position: absolute; inset: 0; pointer-events: none;
}
.fw-tick {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: rgba(255, 255, 255, 0.28);
}
.fw-meter-segments {
  display: flex; gap: 3px; width: 100%;
}
.fw-meter-seg {
  flex: 1; height: 3.5px; border-radius: 1px;
  background: rgba(255, 255, 255, 0.08);
  border: 0.5px solid rgba(255, 255, 255, 0.05);
  transition: background 0.2s ease, box-shadow 0.2s ease;
}
.fw-meter-seg.fw-seg--lit {
  background: var(--fw-gold);
  box-shadow: 0 0 5px rgba(255, 210, 63, 0.65);
}
.fw-meter-footer {
  display: flex; justify-content: space-between; align-items: baseline;
  width: 100%; font-size: 10.5px; font-weight: 700; letter-spacing: 0.5px;
  color: rgba(255, 255, 255, 0.7);
}
.fw-meter-target strong {
  color: #fff; font-weight: 900;
}
.fw-meter-coins {
  font-variant-numeric: tabular-nums;
}
.fw-meter-coins strong {
  color: var(--fw-gold); font-weight: 900;
}
.fw-meter-coins--gold {
  color: var(--fw-gold);
}

/* ---------- Landing: the locked card ----------
   The cheapest unowned shop row, drawn in the city shelf as a silhouette: the
   shape reads, the colour does not, and the condition is written out in words
   underneath. Every city is open, so this is the only genuinely locked thing
   the save knows about. */
.fw-landing .fw-chip--locked {
  border-style: dashed;
  border-color: rgba(255, 255, 255, .16);
  background: rgba(6, 8, 16, .78);
  padding-right: 34px; /* the silhouette's column, so no line runs under it */
}
.fw-landing .fw-chip--locked::before { background: rgba(255, 255, 255, .14); }
/* Cased like every other card on the shelf: the shelf is the vocabulary, and a
   product name in title case would read as a different kind of row. */
.fw-landing .fw-chip--locked .fw-chip-name {
  color: rgba(255, 255, 255, .58); text-transform: uppercase;
}
.fw-landing .fw-chip--locked .fw-chip-sub { text-transform: uppercase; }
.fw-landing .fw-chip--locked .fw-chip-progress { color: rgba(255, 210, 63, .78); }
.fw-lock-art {
  position: absolute; right: 9px; top: 10px; width: 20px; height: 20px;
  border-radius: 50%;
  background: #05070e; border: 2px solid rgba(255, 255, 255, .22);
  box-shadow: inset 0 3px 6px rgba(0, 0, 0, .95);
}
.fw-landing .fw-chip--locked:hover { border-style: solid; }

@keyframes fw-chip-in {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* Reduced motion, both sources: the durations and the travel are swapped out,
   the feedback is not. A press still moves, a focus ring still grows, cards
   still arrive — instantly, and without the back-out overshoot. The live city
   keeps rendering; js/ui/menuscene.js is what freezes its camera drift. */
@media (prefers-reduced-motion: reduce) {
  .fw-landing {
    --fw-press: 1ms; --fw-release: 1ms; --fw-back: linear;
    --fw-lift: 0px; --fw-stagger: 0ms;
  }
  .fw-landing .fw-chip, .fw-status { animation: none; }
  body.fw-scene #game-canvas { animation: none; }
}
.fw-still .fw-chip, .fw-still .fw-status { animation: none; }
.fw-still {
  --fw-press: 1ms; --fw-release: 1ms; --fw-back: linear;
  --fw-lift: 0px; --fw-stagger: 0ms;
}

/* ---------- Landing: the affordable unlock ----------
   The same row as .fw-chip--locked, in the state where the player can already
   pay for it. A dashed silhouette and the words UNLOCKS AT read as a goal, and a
   goal the bank has already met is a contradiction — so the frame goes solid,
   the accent bar goes gold like a real offer, and the dark lock disc becomes a
   coin. screens.js decides which of the two states renders; this file only draws
   them. */
.fw-landing .fw-chip--ready {
  border-style: solid;
  border-color: rgba(255, 210, 63, .5);
  background: rgba(14, 12, 18, .84);
  padding-right: 34px;
}
.fw-landing .fw-chip--ready::before { background: var(--fw-gold); }
.fw-landing .fw-chip--ready .fw-chip-name {
  color: rgba(255, 255, 255, .9); text-transform: uppercase;
}
.fw-landing .fw-chip--ready .fw-chip-sub { text-transform: uppercase; }
.fw-landing .fw-chip--ready .fw-chip-progress { color: var(--fw-gold); }
.fw-chip--ready .fw-lock-art {
  background: var(--fw-gold);
  border-color: rgba(74, 50, 0, .55);
  box-shadow: inset 0 -3px 5px rgba(120, 74, 0, .55);
}
/* The strip's goal cell in the same state: no bar, because there is no gap left
   to draw, and the note names the thing rather than counting down to it. */
.fw-stat--ready .fw-stat-note { color: var(--fw-gold); }

/* ---------- Landing on a phone: give the city back its band ----------
   Phones are the primary platform, and in portrait the stack was tall enough to
   cover the live scene end to end — the one thing this screen gained was the one
   thing a phone player could not see. Four moves, all layout, none of them
   shrinking a tap target below 44px or thinning the plates the type sits on:

     1. The top rail is reserved for #mp-link (index.html, same breakpoint).
     2. The status strip halves its height: the goal cell takes the full row and
        every cell loses its outer padding rather than its type size.
     3. The card shelf SCROLLS instead of forcing eight rows above the fold. It
        is capped at 42vh and fades at its bottom edge, so the cut row reads as
        "there is more" rather than as the end of the list.
     4. The scrim's lower band lifts, because the chips are already opaque
        plates — the band only ever needed to stop facades showing through the
        GAPS between them, and at phone width those gaps are where the city is
        allowed to be visible. */
@media (max-width: 640px) and (orientation: portrait) {
  .fw-landing {
    /* 10px offset + 44px rail + 10px breathing room. */
    padding-top: calc(64px + var(--sai-top));
    padding-bottom: calc(14px + var(--sai-bottom));
    gap: clamp(8px, 1.4vh, 14px);
  }
  .fw-hero { gap: clamp(4px, 1vh, 10px); }
  .fw-hero-text { gap: clamp(4px, .9vh, 9px); }

  .fw-status { gap: 6px; }
  .fw-stat { padding: 5px 11px; border-radius: 10px; }
  .fw-stat--goal { flex: 1 1 100%; min-width: 0; gap: 2px; }
  .fw-stat-v { font-size: 15px; }

  /* The shelf absorbs whatever height is left over and scrolls inside it, which
     is what keeps the SCREEN from scrolling: everything above it — mark,
     wordmark, PLAY, the strip — stays pinned where the player left it, and the
     one scroll gesture on the page belongs to the list that has more rows than
     fit. 150px floor so it can never collapse to a sliver on a short viewport
     (below that the screen scrolls again, which is the correct fallback).
     grid-auto-rows: min-content because a stretched row inside a bounded scroll
     container clips the tallest card's last line — that is what ate START HERE.
     Padding + matching negative margin so the focus ring's scale-up is not
     clipped by the scroll container the cards now live inside. */
  /* Only the shelf flexes. Everything else keeps its natural height — a flex
     item that shrinks below its content does not compact, it spills, which is
     how SHOP/SETTINGS ended up drawn underneath the cards on a short phone.
     When even the shelf's floor cannot fit, the screen scrolls, which is the
     correct fallback rather than a collision. */
  .fw-landing > * { flex: none; }
  /* A hard floor, not 0 and not min-content: at 0 the group shrinks past the
     shelf's own 150px floor and spills over the utility row instead of
     compressing, and min-content resolves to the shelf's full 44vh here, which
     never yields any space back. 190px = the label plus that floor. Below it
     the screen scrolls, which is the right fallback on a very short phone. */
  .fw-group { flex: 1 1 auto; min-height: 190px; }
  .fw-chips {
    flex: 1 1 auto; min-height: 150px; max-height: 44vh;
    grid-auto-rows: min-content; align-content: start;
    overflow-y: auto; overflow-x: hidden;
    overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
    padding: 4px; margin: -4px;
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 92%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 0%, #000 92%, transparent 100%);
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 210, 63, .45) transparent;
  }
  .fw-chips::-webkit-scrollbar { width: 4px; }
  .fw-chips::-webkit-scrollbar-thumb {
    background: rgba(255, 210, 63, .45); border-radius: 999px;
  }
  .fw-chips::-webkit-scrollbar-track { background: transparent; }
  /* Two lines of the CC0 manifest, by height rather than by line-clamp: the
     clamp collapsed the block to zero here, and silently dropping an attribution
     is not a layout saving. */
  .fw-credits { max-height: 2.9em; overflow: hidden; }
  body.fw-scene .fw-landing::before {
    background:
      radial-gradient(66% 26% at 50% 24%,
        rgba(28, 16, 6, .58) 0%, rgba(14, 10, 8, .3) 55%, rgba(10, 12, 22, 0) 100%),
      linear-gradient(180deg, rgba(6, 9, 18, 0) 44%, rgba(6, 9, 18, .5) 76%, rgba(5, 7, 14, .72) 100%),
      linear-gradient(0deg, rgba(6, 9, 18, 0) 84%, rgba(6, 9, 18, .5) 100%),
      radial-gradient(140% 96% at 50% 40%,
        rgba(6, 9, 18, .1) 0%, rgba(6, 9, 18, .42) 66%, rgba(3, 5, 11, .72) 100%);
  }
}

/* Short phones flatten the shelf. The pinned-shelf design above needs roughly
   860px of viewport height to keep the utility row on screen (content above
   the shelf is ~510px, the shelf floor is 150px, and RUN/RECORDS/SHOP/
   SETTINGS/footer need ~190px below it). Below ~780px there is no shelf size
   at which that fits, and the fallback the block above promises — "the screen
   scrolls" — was broken in practice: the shelf's bottom edge lands at the
   viewport's bottom edge, so the entire lower screen is a nested scroller
   whose overscroll-behavior:contain swallows the one gesture that could
   scroll the page, leaving RECORDS/SHOP/SETTINGS painted off-screen and
   unreachable while masked (near-invisible) chips take the taps — the
   "tapped RECORDS, got Cambridge" report. So under 780px the shelf rejoins
   the document flow: no inner scroller, no mask, one page scroll, and every
   button is where it looks like it is. */
@media (max-width: 640px) and (orientation: portrait) and (max-height: 780px) {
  .fw-group { flex: none; min-height: 0; }
  .fw-chips {
    flex: none; min-height: 0; max-height: none;
    overflow: visible;
    -webkit-mask-image: none; mask-image: none;
  }
}

/* ---------- Records and profile ----------
   The board is deliberately ordinary HTML table/form markup. It stays usable
   without the optional board module, and the narrow viewport has one horizontal
   scroll region rather than forcing each score into an ambiguous card. */
.fw-board-slot { width: min(760px, 100%); overflow-x: auto; }
.fw-board-table {
  width: 100%; min-width: 510px; border-collapse: collapse; text-align: left;
  font-variant-numeric: tabular-nums; background: rgba(7, 10, 18, .82);
  border: 1px solid rgba(255, 255, 255, .16); border-radius: 12px;
  overflow: hidden;
}
.fw-board-table caption {
  caption-side: top; padding: 11px 12px; color: var(--fw-gold); font-size: 11px;
  font-weight: 800; letter-spacing: 1.1px; text-align: left;
}
.fw-board-table th, .fw-board-table td {
  padding: 10px 12px; border-top: 1px solid rgba(255, 255, 255, .1);
  white-space: nowrap;
}
.fw-board-table th { color: rgba(255, 255, 255, .58); font-size: 10px; letter-spacing: 1px; }
.fw-board-table td:first-child { color: var(--fw-gold); font-weight: 800; }
.fw-board-table tbody tr:nth-child(odd) { background: rgba(255, 255, 255, .035); }
.fw-board-report { min-height: 34px; padding: 6px 10px; font-size: 10px; }
.fw-board-note { max-width: 620px; color: rgba(255, 255, 255, .67); line-height: 1.5; }
/* The route to a name, on the PROFILE screen (js/ui/screens.js showProfile).
   The identity chip now brings players here who hold no name at all, so this
   screen has to end in a door and not just in a fact. Grouped in one column so
   the sentence and the button read as a single block — .screen centres its
   children individually, and a lone .btn between two board notes reads as a
   stray control rather than as the consequence of the paragraph above it. */
.fw-claim-path {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  width: min(620px, 100%);
  /* Above: separation from boards.js's own empty-state line, which is a
     different sentence and was reading as one run-on paragraph with this one.
     Below: clearance for the .btn's 5px extrude and 18px cast shadow, which the
     YOUR HISTORY heading that follows was sitting inside. */
  margin: 10px 0 20px;
}

.fw-claim-slot { width: min(520px, 100%); }
.fw-claim-slot form { display: flex; gap: 8px; width: min(520px, 100%); }
.fw-claim-slot input {
  min-width: 0; flex: 1; min-height: 44px; padding: 0 12px; color: #fff;
  background: rgba(7, 10, 18, .82); border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 8px; font: inherit;
}
.fw-claim-slot input:focus-visible { outline: 3px solid var(--fw-gold); outline-offset: 2px; }
@media (max-width: 520px) {
  .fw-claim-slot form { flex-direction: column; }
  .fw-claim-slot .btn { width: 100%; }
}

/* ---------- Global Branded Scrollbars ---------- */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 210, 63, .45) rgba(8, 11, 20, .6);
}
::-webkit-scrollbar {
  width: 8px; height: 8px;
}
::-webkit-scrollbar-track {
  background: rgba(8, 11, 20, .65); border-radius: 999px;
}
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, var(--fw-gold) 0%, var(--fw-orange-2) 100%);
  border-radius: 999px; border: 2px solid rgba(8, 11, 20, .85);
  box-shadow: 0 0 6px rgba(255, 210, 63, .25);
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, var(--fw-gold-hot) 0%, var(--fw-orange-1) 100%);
  box-shadow: 0 0 10px rgba(255, 210, 63, .45);
}

/* ---------- Enhanced Records & Statistics Suite ---------- */
.fw-records-wrap {
  width: min(1180px, 96vw); display: flex; flex-direction: column; gap: 16px;
  align-items: center; text-align: left; position: relative;
}
.fw-records-tabs {
  display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-bottom: 6px;
}
.fw-records-tab--active {
  background: linear-gradient(180deg, var(--fw-orange-1) 0%, var(--fw-orange-2) 44%, var(--fw-orange-3) 100%) !important;
  color: #2b1200 !important; font-weight: 900 !important;
  box-shadow: 0 4px 0 var(--fw-extrude-1), 0 8px 16px rgba(0,0,0,.5) !important;
}
.fw-stat-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px; width: 100%;
}
.fw-stat-card {
  background: rgba(9, 12, 22, .88); border: 2px solid rgba(255, 255, 255, .12);
  border-radius: 12px; padding: 12px 16px; display: flex; flex-direction: column;
  gap: 4px; box-shadow: 0 4px 0 rgba(3, 5, 11, .55);
}
.fw-stat-card .k {
  font-size: 10px; font-weight: 800; letter-spacing: 1.4px;
  color: rgba(255, 255, 255, .6); text-transform: uppercase;
}
.fw-stat-card .v {
  font-size: clamp(16px, 2.6vw, 22px); font-weight: 900;
  color: var(--fw-gold); font-variant-numeric: tabular-nums;
  text-shadow: var(--fw-ring-2);
}
.fw-stat-card .sub {
  font-size: 11px; font-weight: 700; color: rgba(255, 255, 255, .5);
}
.fw-section-header {
  font-size: 13px; font-weight: 900; letter-spacing: 1.2px;
  color: rgba(255, 255, 255, .85); text-transform: uppercase;
  margin: 12px 0 4px; display: flex; justify-content: space-between;
  align-items: center; width: 100%;
}
.fw-city-cards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: 14px; width: 100%;
}
.fw-city-card {
  background: rgba(12, 16, 28, .88); border: 2px solid rgba(255, 255, 255, .14);
  border-radius: 14px; padding: 14px; display: flex; flex-direction: column;
  gap: 8px; position: relative; transition: transform .18s ease, border-color .18s ease;
  box-shadow: 0 4px 0 rgba(3, 5, 11, .6);
}
.fw-city-card:hover {
  border-color: rgba(255, 210, 63, .6); transform: translateY(-2px);
}
.fw-city-card-header {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 8px;
}
.fw-city-card-title {
  font-size: 15px; font-weight: 900; color: #fff; letter-spacing: .8px;
}
.fw-city-card-sub {
  font-size: 11px; color: rgba(255, 255, 255, .55); line-height: 1.3;
}
.fw-badge {
  display: inline-block; padding: 3px 8px; border-radius: 6px;
  font-size: 10px; font-weight: 800; letter-spacing: .8px;
  text-transform: uppercase; white-space: nowrap;
}
.fw-badge--mastered {
  background: rgba(255, 210, 63, .18); color: var(--fw-gold);
  border: 1px solid rgba(255, 210, 63, .5);
}
.fw-badge--cleared {
  background: rgba(0, 240, 255, .14); color: #00f0ff;
  border: 1px solid rgba(0, 240, 255, .4);
}
.fw-badge--unplayed {
  background: rgba(255, 255, 255, .06); color: rgba(255, 255, 255, .45);
  border: 1px dashed rgba(255, 255, 255, .2);
}
.fw-city-stats-row {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; font-variant-numeric: tabular-nums; padding: 4px 0;
  border-top: 1px solid rgba(255, 255, 255, .07);
}
.fw-city-stats-row .lbl { color: rgba(255, 255, 255, .55); font-weight: 600; }
.fw-city-stats-row .val { color: #fff; font-weight: 800; }
.fw-city-stats-row .val.highlight { color: var(--fw-gold); }
.fw-city-play-btn {
  margin-top: 6px; width: 100%; padding: 8px 12px;
  font-size: 12px; font-weight: 800; letter-spacing: .8px;
}
.fw-campaign-banner {
  background: linear-gradient(135deg, rgba(28, 20, 10, .92) 0%, rgba(12, 16, 28, .92) 100%);
  border: 2px solid rgba(255, 210, 63, .35); border-radius: 14px;
  padding: 16px; width: 100%; display: flex; flex-wrap: wrap;
  justify-content: space-between; align-items: center; gap: 14px;
  box-shadow: 0 4px 0 rgba(3, 5, 11, .6);
}
.fw-campaign-info h4 { margin: 0 0 4px; color: var(--fw-gold); font-size: 16px; font-weight: 900; }
.fw-campaign-info p { margin: 0; color: rgba(255, 255, 255, .7); font-size: 12px; }

/* ---------- Scroll Affordance Indicator ---------- */
.fw-scroll-hint {
  position: sticky; bottom: 8px; z-index: 10; margin-top: -36px;
  display: inline-flex; align-items: center; gap: 8px;
  background: rgba(12, 16, 28, .92); border: 1.5px solid rgba(255, 210, 63, .6);
  border-radius: 999px; padding: 6px 18px; color: var(--fw-gold);
  font-size: 11px; font-weight: 800; letter-spacing: 1px;
  text-transform: uppercase; pointer-events: none;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .65), 0 0 16px rgba(255, 210, 63, .25);
  transition: opacity .25s ease, transform .25s ease;
  animation: fw-bob 2.4s ease-in-out infinite;
}
.fw-scroll-hint.is-hidden {
  opacity: 0; transform: translateY(10px); pointer-events: none;
}

/* ---------- Power-Up Showcase 5s Overlay Modal ---------- */
.pu-showcase-screen {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 8, 18, 0.75);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 10000;
  animation: puShowcaseFadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  pointer-events: auto;
}

.pu-showcase-card {
  background: linear-gradient(180deg, rgba(20, 26, 46, 0.96), rgba(12, 16, 30, 0.98));
  border: 2px solid var(--pu-accent, #00d2ff);
  box-shadow: 0 0 35px rgba(0, 210, 255, 0.28), 0 20px 40px rgba(0, 0, 0, 0.7);
  border-radius: 16px;
  padding: 24px 28px;
  max-width: min(440px, 90vw);
  width: 100%;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
  animation: puShowcaseZoom 0.28s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}

.pu-showcase-badge {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--pu-accent, #00d2ff);
  background: rgba(255, 255, 255, 0.07);
  padding: 4px 12px;
  border-radius: 20px;
  margin-bottom: 12px;
}

.pu-showcase-icon {
  font-size: 48px;
  line-height: 1;
  margin: 4px 0 8px 0;
  filter: drop-shadow(0 0 16px var(--pu-accent, #00d2ff));
}

.pu-showcase-title {
  font-size: 22px;
  font-weight: 900;
  letter-spacing: 1.5px;
  margin: 0 0 4px 0;
  color: #ffffff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
}

.pu-showcase-tagline {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: var(--pu-accent, #00d2ff);
  margin-bottom: 10px;
  text-transform: uppercase;
}

.pu-showcase-desc {
  font-size: 14px;
  line-height: 1.45;
  color: rgba(240, 246, 255, 0.9);
  margin: 0 0 14px 0;
  background: rgba(0, 0, 0, 0.25);
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.pu-showcase-meta {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
}

.pu-meta-pill {
  font-size: 12px;
  font-weight: 700;
  background: rgba(255, 255, 255, 0.1);
  padding: 3px 10px;
  border-radius: 6px;
  color: #e2e8f0;
}

.pu-showcase-timer-bar {
  width: 100%;
  height: 4px;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 8px;
}

.pu-timer-fill {
  height: 100%;
  width: 0%;
  background: var(--pu-accent, #00d2ff);
  box-shadow: 0 0 8px var(--pu-accent, #00d2ff);
  transition: width 0.05s linear;
}

.pu-showcase-countdown {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.6);
  margin-bottom: 14px;
}

.pu-showcase-countdown b {
  color: #ffffff;
  font-weight: 800;
}

.pu-resume-btn {
  width: 100%;
  padding: 10px 16px;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 1px;
  background: var(--pu-accent, #00d2ff) !important;
  color: #060913 !important;
  border: none !important;
  border-radius: 8px;
  cursor: pointer;
  transition: transform 0.15s ease, filter 0.15s ease;
}

.pu-resume-btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.15);
}

@keyframes puShowcaseFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes puShowcaseZoom {
  from { transform: scale(0.85); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* -------------------------------------------------------------------------
   Earthquake Dragon Ball / Anime Cinematic Cutscene Overlay
   ------------------------------------------------------------------------- */
#quake-cinematic-overlay {
  position: fixed;
  inset: 0;
  z-index: 85;
  pointer-events: auto;
  user-select: none;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  transition: opacity 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
}

#quake-cinematic-overlay.fading-out {
  opacity: 0;
  pointer-events: none;
}

.quake-cinematic-bar {
  width: 100%;
  height: clamp(36px, 9vh, 85px);
  background: #000000;
  box-shadow: 0 0 30px rgba(0, 0, 0, 0.9);
  position: relative;
  z-index: 10;
}

.quake-cinematic-bar.top {
  transform: translateY(-100%);
  animation: quakeBarSlideDown 0.32s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.quake-cinematic-bar.bottom {
  transform: translateY(100%);
  animation: quakeBarSlideUp 0.32s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes quakeBarSlideDown {
  to { transform: translateY(0); }
}

@keyframes quakeBarSlideUp {
  to { transform: translateY(0); }
}

.quake-impact-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.95) 0%, rgba(255, 80, 0, 0.6) 35%, rgba(180, 0, 0, 0.2) 65%, transparent 85%);
  animation: quakeFlashDecay 0.48s cubic-bezier(0.1, 0.9, 0.2, 1) forwards;
}

@keyframes quakeFlashDecay {
  0% { opacity: 1; transform: scale(1.15); }
  40% { opacity: 0.8; transform: scale(1.04); }
  100% { opacity: 0; transform: scale(1); }
}

.quake-dragonball-aura {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  background: radial-gradient(circle at 50% 50%, rgba(255, 234, 0, 0.5) 0%, rgba(255, 140, 0, 0.4) 40%, rgba(255, 50, 0, 0.18) 65%, transparent 85%);
  mix-blend-mode: screen;
  animation: quakeDragonballPulse 0.10s infinite alternate ease-in-out;
}

@keyframes quakeDragonballPulse {
  0% { transform: scale(0.96); opacity: 0.75; filter: brightness(1.1); }
  100% { transform: scale(1.06); opacity: 1.0; filter: brightness(1.45); }
}

.quake-speed-lines {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 6;
  opacity: 0.75;
  background: repeating-conic-gradient(
    from 0deg,
    rgba(255, 255, 255, 0.16) 0deg 2.5deg,
    transparent 2.5deg 8deg,
    rgba(255, 120, 0, 0.22) 8deg 10deg,
    transparent 10deg 16deg
  );
  mix-blend-mode: screen;
  animation: quakeSpeedZoom 0.08s infinite alternate ease-in-out;
}

/* The three text slams are deliberately centre-stage: this is a super-move
   declaration, not HUD copy. They only animate transform, opacity and filter
   so the short arcade cuts stay compositor-friendly on phones. */
.quake-zoom-stage {
  position: absolute;
  inset: 0;
  z-index: 14;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.quake-word {
  position: absolute;
  opacity: 0;
  color: #fff8d6;
  font-size: clamp(54px, 15vw, 164px);
  font-weight: 950;
  font-style: italic;
  letter-spacing: clamp(3px, 1.2vw, 14px);
  line-height: 0.82;
  text-transform: uppercase;
  text-shadow:
    0 0 12px #fff0a3,
    0 0 26px #ff8a00,
    0 0 52px #e12b00,
    0 5px 0 #6d0f00,
    0 10px 0 #210400,
    0 18px 30px rgba(0, 0, 0, 0.92);
  -webkit-text-stroke: clamp(1px, 0.34vw, 4px) #3a0800;
}

.quake-zoom-stage.stage-earth .quake-word-earth {
  animation: quakeWordEarth 0.72s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.quake-zoom-stage.stage-quake .quake-word-quake {
  animation: quakeWordQuake 0.72s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.quake-zoom-stage.stage-time .quake-word-time {
  animation: quakeWordTime 0.72s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes quakeWordEarth {
  0% { opacity: 0; transform: translate3d(-72vw, 38vh, 0) rotate(-17deg) scale(3.4); filter: blur(14px); }
  46% { opacity: 1; transform: translate3d(0, 0, 0) rotate(-4deg) scale(0.9); filter: blur(0); }
  68% { opacity: 1; transform: translate3d(0, 0, 0) rotate(-4deg) scale(1.08); }
  100% { opacity: 0; transform: translate3d(12vw, -9vh, 0) rotate(-4deg) scale(1.26); filter: blur(2px); }
}

@keyframes quakeWordQuake {
  0% { opacity: 0; transform: translate3d(78vw, -42vh, 0) rotate(14deg) scale(3.8); filter: blur(14px); }
  46% { opacity: 1; transform: translate3d(0, 0, 0) rotate(3deg) scale(0.88); filter: blur(0); }
  68% { opacity: 1; transform: translate3d(0, 0, 0) rotate(3deg) scale(1.11); }
  100% { opacity: 0; transform: translate3d(-12vw, 10vh, 0) rotate(3deg) scale(1.30); filter: blur(2px); }
}

@keyframes quakeWordTime {
  0% { opacity: 0; transform: translate3d(0, -92vh, 0) rotate(-8deg) scale(4.2); filter: blur(15px); }
  46% { opacity: 1; transform: translate3d(0, 0, 0) rotate(-2deg) scale(0.84); filter: blur(0); }
  68% { opacity: 1; transform: translate3d(0, 0, 0) rotate(-2deg) scale(1.14); }
  100% { opacity: 0; transform: translate3d(0, 20vh, 0) rotate(-2deg) scale(1.34); filter: blur(2px); }
}

@keyframes quakeSpeedZoom {
  0% { transform: scale(1); opacity: 0.6; }
  100% { transform: scale(1.06); opacity: 0.85; }
}

.quake-anime-banner {
  position: absolute;
  top: clamp(48px, 12vh, 100px);
  left: 50%;
  transform: translateX(-50%) skew(-8deg) scale(0.7);
  opacity: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  z-index: 12;
  text-align: center;
  pointer-events: none;
}

/* The fault-line title arrives once the three super-move declarations finish. */
.quake-zoom-stage.stage-rift ~ .quake-anime-banner,
#quake-cinematic-overlay.reduced-motion .quake-anime-banner {
  animation: quakeBannerSlam 0.38s cubic-bezier(0.18, 0.89, 0.32, 1.25) forwards;
}

@keyframes quakeBannerSlam {
  0% { opacity: 0; transform: translateX(-50%) skew(-8deg) scale(1.6); }
  60% { opacity: 1; transform: translateX(-50%) skew(-8deg) scale(0.95); }
  100% { opacity: 1; transform: translateX(-50%) skew(-8deg) scale(1); }
}

.quake-banner-sub {
  font-size: clamp(11px, 2.2vw, 16px);
  font-weight: 900;
  letter-spacing: 4.5px;
  color: #ffd23f;
  text-shadow: 0 0 10px rgba(255, 210, 63, 0.9), 0 2px 4px rgba(0, 0, 0, 0.8);
  background: rgba(0, 0, 0, 0.65);
  padding: 3px 14px;
  border-radius: 6px;
  border: 1px solid rgba(255, 210, 63, 0.4);
}

.quake-banner-title {
  font-size: clamp(26px, 6.2vw, 56px);
  font-weight: 950;
  letter-spacing: 2px;
  color: #ffffff;
  text-transform: uppercase;
  line-height: 1.05;
  text-shadow:
    0 0 16px #ff3700,
    0 0 32px #ff1100,
    0 3px 0 #b30000,
    0 6px 0 #5a0000,
    0 8px 18px rgba(0, 0, 0, 0.9);
  padding: 0 16px;
}

.quake-banner-hint {
  font-size: clamp(10px, 1.8vw, 12px);
  font-weight: 800;
  letter-spacing: 1.6px;
  color: rgba(255, 255, 255, 0.75);
  background: rgba(10, 10, 15, 0.75);
  padding: 4px 16px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  margin-top: 4px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}

@media (prefers-reduced-motion: reduce) {
  #quake-cinematic-overlay .quake-cinematic-bar,
  #quake-cinematic-overlay .quake-anime-banner,
  #quake-cinematic-overlay .quake-impact-flash,
  #quake-cinematic-overlay .quake-dragonball-aura,
  #quake-cinematic-overlay .quake-speed-lines,
  #quake-cinematic-overlay .quake-word {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* -------------------------------------------------------------------------
   Pokémon Wild Battle Encounter Power-Up Intro Overlay
   ------------------------------------------------------------------------- */
#poke-encounter-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: auto;
  user-select: none;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(4, 6, 14, 0.65);
  transition: opacity 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
}

#poke-encounter-overlay.fading-out {
  opacity: 0;
  pointer-events: none;
}

/* Pokémon Battle Screen Shutter Wipe */
.poke-battle-wipe {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 55%;
  background: #02040a;
  z-index: 2;
  pointer-events: none;
}

.poke-battle-wipe.left {
  left: 0;
  transform: skew(-15deg) translateX(-120%);
  animation: pokeWipeLeft 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.poke-battle-wipe.right {
  right: 0;
  transform: skew(-15deg) translateX(120%);
  animation: pokeWipeRight 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes pokeWipeLeft {
  0% { transform: skew(-15deg) translateX(-120%); }
  45% { transform: skew(-15deg) translateX(-8%); }
  100% { transform: skew(-15deg) translateX(-85%); }
}

@keyframes pokeWipeRight {
  0% { transform: skew(-15deg) translateX(120%); }
  45% { transform: skew(-15deg) translateX(8%); }
  100% { transform: skew(-15deg) translateX(85%); }
}

/* Stadium Radial Burst Background */
.poke-radial-burst {
  position: absolute;
  inset: -50%;
  z-index: 1;
  pointer-events: none;
  opacity: 0.45;
  background: repeating-conic-gradient(
    from 0deg,
    var(--poke-color, #ffb703) 0deg 4deg,
    transparent 4deg 12deg,
    var(--poke-glow, #ff7700) 12deg 16deg,
    transparent 16deg 24deg
  );
  mix-blend-mode: screen;
  animation: pokeBurstSpin 8s linear infinite;
}

@keyframes pokeBurstSpin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Pokémon Battle Card */
.poke-encounter-card {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  background: linear-gradient(145deg, rgba(16, 20, 36, 0.96) 0%, rgba(6, 8, 16, 0.98) 100%);
  border: 2px solid var(--poke-color, #ffd23f);
  border-radius: 16px;
  box-shadow:
    0 0 45px var(--poke-glow, rgba(255, 183, 3, 0.65)),
    0 16px 40px rgba(0, 0, 0, 0.85);
  padding: 18px 24px;
  width: clamp(310px, 86vw, 540px);
  transform: scale(0.6) rotate(-2deg);
  opacity: 0;
  animation: pokeCardSlam 0.36s cubic-bezier(0.18, 0.89, 0.32, 1.25) 0.08s forwards;
}

@keyframes pokeCardSlam {
  0% { opacity: 0; transform: scale(1.4) rotate(4deg); }
  65% { opacity: 1; transform: scale(0.96) rotate(-1deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

.poke-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  padding-bottom: 8px;
  margin-bottom: 12px;
}

.poke-wild-tag {
  font-size: clamp(10px, 2vw, 13px);
  font-weight: 900;
  letter-spacing: 2px;
  color: var(--poke-color, #ffd23f);
  text-shadow: 0 0 10px var(--poke-glow, rgba(255, 210, 63, 0.8));
}

.poke-rarity-badge {
  font-size: clamp(9px, 1.6vw, 11px);
  font-weight: 900;
  letter-spacing: 1.5px;
  background: var(--poke-color, #ffd23f);
  color: #060913;
  padding: 2px 8px;
  border-radius: 4px;
  text-transform: uppercase;
}

.poke-card-body {
  display: flex;
  align-items: center;
  gap: 18px;
}

.poke-icon-circle {
  position: relative;
  width: clamp(68px, 16vw, 92px);
  height: clamp(68px, 16vw, 92px);
  min-width: clamp(68px, 16vw, 92px);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, rgba(0, 0, 0, 0.6) 80%);
  border: 2px solid var(--poke-color, #ffd23f);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 20px var(--poke-glow, rgba(255, 183, 3, 0.5));
}

.poke-icon {
  font-size: clamp(34px, 8vw, 48px);
  animation: pokeIconFloat 1.2s ease-in-out infinite alternate;
}

@keyframes pokeIconFloat {
  0% { transform: translateY(-2px) scale(1); }
  100% { transform: translateY(3px) scale(1.08); }
}

.poke-icon-aura {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 2px dashed var(--poke-color, #ffd23f);
  opacity: 0.6;
  animation: pokeAuraSpin 4s linear infinite;
}

@keyframes pokeAuraSpin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.poke-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}

.poke-name-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.poke-powerup-name {
  font-size: clamp(18px, 4.5vw, 28px);
  font-weight: 950;
  letter-spacing: 1.2px;
  color: #ffffff;
  margin: 0;
  text-shadow: 0 0 14px var(--poke-glow, rgba(255, 255, 255, 0.5)), 0 2px 4px rgba(0, 0, 0, 0.9);
}

.poke-level {
  font-size: clamp(11px, 2.2vw, 15px);
  font-weight: 900;
  color: var(--poke-color, #ffd23f);
  letter-spacing: 1px;
}

.poke-type-pill {
  display: inline-block;
  align-self: flex-start;
  font-size: clamp(9px, 1.8vw, 12px);
  font-weight: 800;
  letter-spacing: 1.8px;
  background: rgba(0, 0, 0, 0.55);
  color: var(--poke-color, #ffd23f);
  border: 1px solid var(--poke-color, rgba(255, 210, 63, 0.45));
  padding: 2px 10px;
  border-radius: 12px;
  margin: 2px 0;
}

.poke-flavor-text {
  font-size: clamp(11px, 2.2vw, 13px);
  font-weight: 600;
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.85);
  margin: 2px 0 0 0;
}

.poke-card-footer {
  display: flex;
  justify-content: center;
  margin-top: 12px;
  padding-top: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.poke-prompt-pill {
  font-size: clamp(10px, 1.8vw, 12px);
  font-weight: 800;
  letter-spacing: 2px;
  color: rgba(255, 255, 255, 0.8);
  background: rgba(0, 0, 0, 0.6);
  padding: 4px 18px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  animation: pokePromptPulse 0.8s ease-in-out infinite alternate;
}

@keyframes pokePromptPulse {
  0% { opacity: 0.65; transform: scale(0.98); }
  100% { opacity: 1; transform: scale(1.02); }
}

/* -------------------------------------------------------------------------
   Anime Power-Up Collection & Active Screen Overlays
   ------------------------------------------------------------------------- */

/* 1. Chrono Freeze (Icy Frost Screen & Time Freeze) */
.chrono-freeze-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 25;
  user-select: none;
  overflow: hidden;
}
.freeze-vignette {
  position: absolute;
  inset: 0;
  box-shadow:
    inset 0 0 80px rgba(76, 201, 240, 0.45),
    inset 0 0 25px rgba(255, 255, 255, 0.65);
  border: 3px solid rgba(76, 201, 240, 0.6);
  animation: freezeVignettePulse 1.5s ease-in-out infinite alternate;
}
@keyframes freezeVignettePulse {
  0% { opacity: 0.75; box-shadow: inset 0 0 60px rgba(76, 201, 240, 0.35); }
  100% { opacity: 1; box-shadow: inset 0 0 100px rgba(76, 201, 240, 0.6), inset 0 0 35px rgba(255, 255, 255, 0.8); }
}
.freeze-crystals {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 0% 0%, rgba(200, 240, 255, 0.3) 0%, transparent 40%),
    radial-gradient(circle at 100% 0%, rgba(200, 240, 255, 0.3) 0%, transparent 40%),
    radial-gradient(circle at 0% 100%, rgba(200, 240, 255, 0.3) 0%, transparent 40%),
    radial-gradient(circle at 100% 100%, rgba(200, 240, 255, 0.3) 0%, transparent 40%);
  mix-blend-mode: screen;
}
.freeze-badge {
  position: absolute;
  top: calc(82px + var(--sai-top));
  left: 50%;
  transform: translateX(-50%);
  background: linear-gradient(180deg, rgba(76, 201, 240, 0.92) 0%, rgba(30, 80, 160, 0.95) 100%);
  border: 2px solid #ffffff;
  border-radius: 16px;
  padding: 3px 16px;
  color: #ffffff;
  font-weight: 950;
  font-size: clamp(10px, 1.8vw, 13px);
  letter-spacing: 2px;
  box-shadow: 0 0 20px rgba(76, 201, 240, 0.8), 0 4px 12px rgba(0, 0, 0, 0.6);
  animation: freezeBadgePop 0.8s infinite alternate ease-in-out;
}
@keyframes freezeBadgePop {
  0% { transform: translateX(-50%) scale(0.98); }
  100% { transform: translateX(-50%) scale(1.04); }
}

/* 2. Vortex Suction (Gravitational Suction Screen Warp) */
.vortex-suction-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 25;
  user-select: none;
  overflow: hidden;
}
.vortex-lens-warp {
  position: absolute;
  inset: 0;
  box-shadow:
    inset 0 0 90px rgba(0, 210, 255, 0.4),
    inset 0 0 20px rgba(0, 85, 255, 0.6);
  animation: vortexLensPulse 1s ease-in-out infinite alternate;
}
@keyframes vortexLensPulse {
  0% { opacity: 0.6; }
  100% { opacity: 0.95; }
}
.vortex-inward-rays {
  position: absolute;
  inset: -50%;
  background: repeating-conic-gradient(
    from 0deg,
    rgba(0, 210, 255, 0.08) 0deg 6deg,
    transparent 6deg 15deg,
    rgba(0, 140, 255, 0.12) 15deg 20deg,
    transparent 20deg 30deg
  );
  mix-blend-mode: screen;
  animation: vortexSpinRays 4s linear infinite;
}
@keyframes vortexSpinRays {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 3. Titan Surge (Colossal Size Expansion & Kanji) */
.titan-surge-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 25;
  user-select: none;
  overflow: hidden;
}
.titan-shock-flash {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%, rgba(217, 4, 41, 0.25) 0%, transparent 70%);
  animation: titanFlashPulse 1s ease-in-out infinite alternate;
}
@keyframes titanFlashPulse {
  0% { opacity: 0.5; }
  100% { opacity: 0.9; }
}
.titan-border-flame {
  position: absolute;
  inset: 0;
  box-shadow:
    inset 0 0 80px rgba(217, 4, 41, 0.5),
    inset 0 0 25px rgba(255, 0, 85, 0.7);
  border: 3px solid rgba(217, 4, 41, 0.6);
}
.titan-impact-badge {
  position: absolute;
  bottom: clamp(60px, 14vh, 120px);
  right: clamp(16px, 4vw, 40px);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  transform: skewX(-8deg);
  pointer-events: none;
  user-select: none;
  animation: titanImpactThrob 0.8s infinite alternate ease-in-out;
}

.titan-impact-sub {
  font-family: 'Impact', 'Arial Black', sans-serif;
  font-size: clamp(14px, 2.2vw, 22px);
  font-weight: 900;
  letter-spacing: 3px;
  color: #ffd23f;
  text-transform: uppercase;
  text-shadow:
    0 0 8px rgba(255, 210, 63, 0.9),
    0 0 16px rgba(255, 100, 0, 0.8),
    1px 1px 0 #000, -1px -1px 0 #000;
  margin-bottom: -2px;
}

.titan-impact-title {
  font-family: 'Impact', 'Arial Black', sans-serif;
  font-size: clamp(32px, 6.5vw, 64px);
  font-weight: 950;
  letter-spacing: 2px;
  color: #fff;
  text-transform: uppercase;
  -webkit-text-stroke: 2px #000;
  text-shadow:
    0 0 10px #ff0055,
    0 0 22px #ff0033,
    0 0 38px #ff2a2a,
    0 0 65px #ff7700,
    0 0 95px #ff0055,
    3px 3px 0 #900010,
    -3px -3px 0 #900010;
  line-height: 0.95;
}

@keyframes titanImpactThrob {
  0% { transform: skewX(-8deg) scale(0.96); filter: drop-shadow(0 0 12px rgba(255, 0, 85, 0.7)); }
  100% { transform: skewX(-8deg) scale(1.05); filter: drop-shadow(0 0 28px rgba(255, 50, 0, 0.95)); }
}

/* 4. Speed Frenzy (Anime Lightning Speedlines) */
.speed-frenzy-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 25;
  user-select: none;
  overflow: hidden;
}
.speed-lightning-left, .speed-lightning-right {
  position: absolute;
  top: 0; bottom: 0;
  width: 14vw;
  background: repeating-linear-gradient(
    180deg,
    rgba(255, 183, 3, 0.3) 0px 4px,
    transparent 4px 16px,
    rgba(255, 220, 100, 0.5) 16px 20px,
    transparent 20deg 32px
  );
  mix-blend-mode: screen;
  animation: speedStreakRush 0.12s linear infinite;
}
.speed-lightning-left { left: 0; }
.speed-lightning-right { right: 0; transform: scaleX(-1); }
@keyframes speedStreakRush {
  0% { transform: translateY(-40px); }
  100% { transform: translateY(40px); }
}
.speed-tunnel-lines {
  position: absolute;
  inset: 0;
  box-shadow:
    inset 0 0 70px rgba(255, 183, 3, 0.4),
    inset 0 0 20px rgba(255, 255, 255, 0.5);
  border: 2px solid rgba(255, 183, 3, 0.5);
}

/* 5. Chain Frenzy (Dragon Fire Combustion Aura) */
.chain-frenzy-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 25;
  user-select: none;
  overflow: hidden;
}
.chain-dragon-fire {
  position: absolute;
  inset: 0;
  box-shadow:
    inset 0 0 80px rgba(114, 9, 183, 0.5),
    inset 0 0 25px rgba(181, 23, 158, 0.7);
  border: 3px solid rgba(181, 23, 158, 0.6);
  animation: chainFirePulse 0.7s infinite alternate ease-in-out;
}
@keyframes chainFirePulse {
  0% { opacity: 0.65; box-shadow: inset 0 0 60px rgba(114, 9, 183, 0.4); }
  100% { opacity: 1; box-shadow: inset 0 0 100px rgba(181, 23, 158, 0.75), inset 0 0 35px rgba(255, 255, 255, 0.6); }
}
.chain-spark-burst {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%, rgba(181, 23, 158, 0.15) 0%, transparent 60%);
}

/* ==========================================================================
   Stage 1 Landing CTA Subtitle & Stage 2 City Map Selection Carousel
   ========================================================================== */

.fw-cta-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 4px 0 2px;
}

/* Stage 1 Landing Screen Hierarchy Rows */
.fw-status-row {
  width: min(440px, 94vw);
  display: flex;
  justify-content: center;
  animation: fw-chip-in .3s cubic-bezier(.2, 1, .3, 1) backwards;
}
.fw-status-row--split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.fw-status-row--split .fw-stat {
  width: auto;
  min-width: 0;
}
.fw-status-row--meter {
  display: flex;
}
.fw-status-row--meter .fw-stat--goal {
  width: 100%;
}

.fw-menu-row {
  width: min(440px, 94vw);
  display: flex;
  justify-content: center;
}
.fw-menu-row--full {
  display: flex;
}
.fw-menu-row--full .btn {
  width: 100%;
  padding: 10px 20px !important;
  font-size: 13.5px !important;
  font-weight: 800 !important;
  letter-spacing: 1.4px !important;
  border-radius: 999px;
  background: rgba(12, 16, 28, 0.65);
  color: rgba(255, 255, 255, 0.85);
  border: 2px solid rgba(255, 255, 255, 0.22);
  transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.fw-menu-row--full .btn:hover {
  border-color: var(--fw-gold, #ffd23f);
  color: #fff;
  background: rgba(22, 30, 52, 0.9);
  transform: translateY(-2px);
}
.fw-menu-row--full .btn:active {
  transform: translateY(1px);
}

.fw-menu-row--split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.fw-menu-row--split .btn {
  width: 100%;
  padding: 10px 16px !important;
  font-size: 13.5px !important;
  font-weight: 800 !important;
  letter-spacing: 1.4px !important;
  border-radius: 999px;
  background: rgba(12, 16, 28, 0.65);
  color: rgba(255, 255, 255, 0.85);
  border: 2px solid rgba(255, 255, 255, 0.22);
  transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.fw-menu-row--split .btn:hover {
  border-color: var(--fw-gold, #ffd23f);
  color: #fff;
  background: rgba(22, 30, 52, 0.9);
  transform: translateY(-2px);
}
.fw-menu-row--split .btn:active {
  transform: translateY(1px);
}

.btn--chicago {
  border-color: rgba(255, 210, 63, 0.45) !important;
  color: #ffd23f !important;
  background: rgba(20, 26, 45, 0.85) !important;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4), inset 0 0 12px rgba(255, 210, 63, 0.1) !important;
}
.btn--chicago:hover {
  border-color: #ffd23f !important;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5), 0 0 20px rgba(255, 210, 63, 0.35) !important;
  background: rgba(30, 38, 65, 0.95) !important;
}

.btn--settings {
  opacity: 0.85;
}
.btn--settings:hover {
  opacity: 1;
}

/* Stage 2 City Selection Screen */
.fw-city-select {
  padding: calc(14px + var(--sai-top, 0px)) calc(16px + var(--sai-right, 0px))
           calc(16px + var(--sai-bottom, 0px)) calc(16px + var(--sai-left, 0px));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 100%;
  max-width: 900px;
  margin: 0 auto;
  user-select: none;
}

/* Header */
.city-select-header {
  width: 100%;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 4px 0 8px;
}
.city-back-btn {
  padding: 6px 14px !important;
  font-size: 13px !important;
  font-weight: 800 !important;
  letter-spacing: 1.2px !important;
}
.city-header-center {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.city-select-title {
  margin: 0 !important;
  font-size: clamp(20px, 4vw, 30px) !important;
  font-weight: 900 !important;
  letter-spacing: 2px !important;
  color: var(--fw-gold, #ffd23f) !important;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8), 0 0 14px rgba(255, 210, 63, 0.3) !important;
}
.city-select-sub {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 2.2px;
  color: rgba(255, 255, 255, 0.55);
  text-transform: uppercase;
}
.city-progress-badge {
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(12, 16, 28, 0.85);
  border: 1.5px solid rgba(255, 210, 63, 0.45);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 1.2px;
  color: #ffd23f;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.progress-icon { font-size: 13px; }

/* Carousel Wrapper */
.city-carousel-wrapper {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(8px, 2.5vw, 24px);
  position: relative;
  flex: 1;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

/* Nav Arrows */
.city-nav-arrow {
  flex-shrink: 0;
  width: clamp(40px, 8vw, 56px);
  height: clamp(40px, 8vw, 56px);
  border-radius: 50%;
  background: rgba(15, 20, 35, 0.85);
  border: 2px solid rgba(255, 255, 255, 0.3);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease, opacity 0.15s ease;
  z-index: 5;
}
.city-nav-arrow:hover:not(:disabled) {
  border-color: var(--fw-gold, #ffd23f);
  background: rgba(30, 40, 70, 0.95);
  transform: scale(1.1);
  box-shadow: 0 0 16px rgba(255, 210, 63, 0.45);
}
.city-nav-arrow:active:not(:disabled) {
  transform: scale(0.95);
}
.city-nav-arrow:disabled {
  opacity: 0.2;
  cursor: not-allowed;
  border-color: rgba(255, 255, 255, 0.1);
}

/* Card Host & Card */
.city-card-host {
  flex: 1;
  max-width: 580px;
  display: flex;
  justify-content: center;
  align-items: center;
  perspective: 1000px;
  touch-action: pan-y;
}

.city-card {
  position: relative;
  width: 100%;
  background: linear-gradient(175deg, rgba(20, 26, 45, 0.95) 0%, rgba(10, 14, 26, 0.98) 100%);
  border: 2px solid rgba(255, 255, 255, 0.22);
  border-radius: 18px;
  padding: clamp(14px, 3.2vh, 22px) clamp(16px, 3.5vw, 26px);
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 2vh, 16px);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.65), 0 0 24px var(--city-accent, rgba(0, 240, 255, 0.15));
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  overflow: hidden;
}
.city-card--unlocked {
  border-color: var(--city-accent, rgba(255, 210, 63, 0.6));
}
.city-card--locked {
  border-color: rgba(255, 255, 255, 0.12);
  background: linear-gradient(175deg, rgba(16, 20, 32, 0.95) 0%, rgba(8, 10, 18, 0.98) 100%);
  filter: grayscale(0.25);
}

.city-card.slide-left {
  animation: cardSlideLeft 0.22s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
.city-card.slide-right {
  animation: cardSlideRight 0.22s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
@keyframes cardSlideLeft {
  0% { opacity: 0; transform: translateX(30px) scale(0.96); }
  100% { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes cardSlideRight {
  0% { opacity: 0; transform: translateX(-30px) scale(0.96); }
  100% { opacity: 1; transform: translateX(0) scale(1); }
}

.city-card-glow {
  position: absolute;
  top: -60px;
  left: 50%;
  transform: translateX(-50%);
  width: 220px;
  height: 120px;
  background: radial-gradient(circle, var(--city-accent, #00f0ff) 0%, transparent 70%);
  opacity: 0.25;
  pointer-events: none;
}

/* Card Header */
.city-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.city-badge-group {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.city-tag {
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  border-radius: 6px;
  padding: 3px 8px;
}
.city-tag--loc {
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.85);
}
.city-tag--stage {
  background: var(--city-accent, #00f0ff);
  color: #000;
  font-weight: 900;
}
.city-status-pill {
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 1.2px;
  border-radius: 999px;
  padding: 3px 10px;
  text-transform: uppercase;
}
.city-status--cleared {
  background: rgba(255, 210, 63, 0.2);
  border: 1px solid #ffd23f;
  color: #ffd23f;
  box-shadow: 0 0 10px rgba(255, 210, 63, 0.3);
}
.city-status--progress {
  background: rgba(0, 240, 255, 0.2);
  border: 1px solid #00f0ff;
  color: #00f0ff;
}
.city-status--open {
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
}
.city-status--locked {
  background: rgba(255, 60, 60, 0.15);
  border: 1px solid rgba(255, 60, 60, 0.4);
  color: #ff6b6b;
}

/* Card Hero Block */
.city-hero-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
}
.city-icon-float {
  font-size: clamp(32px, 6vw, 44px);
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.6));
  margin-bottom: 2px;
}
.city-card-title {
  margin: 0 !important;
  font-size: clamp(22px, 4.8vw, 34px) !important;
  font-weight: 900 !important;
  letter-spacing: 1.5px !important;
  color: #fff !important;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9), 0 0 18px var(--city-accent, rgba(0, 240, 255, 0.4)) !important;
}
.city-card-tagline {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 2px;
  color: var(--city-accent, #ffd23f);
  text-transform: uppercase;
}
.city-card-sub {
  font-size: clamp(11px, 2vw, 12.5px);
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
  max-width: 90%;
  line-height: 1.35;
}

/* Metrics Row */
.city-metrics-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.city-metric-box {
  background: rgba(12, 16, 28, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 10px;
  padding: 7px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1px;
}
.metric-k {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 1px;
  color: rgba(255, 255, 255, 0.5);
  text-transform: uppercase;
}
.metric-v {
  font-size: clamp(13px, 2.4vw, 16px);
  font-weight: 900;
  color: #fff;
  font-family: 'JetBrains Mono', 'Courier New', monospace;
}
.metric-sub {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.8px;
  color: var(--city-accent, rgba(255, 210, 63, 0.8));
  text-transform: uppercase;
}

/* Records Strip */
.city-records-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  background: rgba(8, 12, 22, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  padding: 6px 4px;
}
.record-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1px;
  border-right: 1px solid rgba(255, 255, 255, 0.07);
}
.record-item:last-child {
  border-right: none;
}
.rec-k {
  font-size: 8.5px;
  font-weight: 800;
  letter-spacing: 0.8px;
  color: rgba(255, 255, 255, 0.45);
  text-transform: uppercase;
}
.rec-v {
  font-size: 12px;
  font-weight: 900;
  color: var(--fw-gold, #ffd23f);
  font-family: 'JetBrains Mono', 'Courier New', monospace;
}

/* Action Row */
.city-action-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.city-launch-btn {
  width: 100%;
  padding: clamp(10px, 1.8vh, 14px) 24px !important;
  font-size: clamp(16px, 3.6vw, 22px) !important;
  font-weight: 900 !important;
  letter-spacing: 2px !important;
  text-align: center;
}
.city-launch-btn.disabled {
  background: rgba(40, 45, 60, 0.6) !important;
  color: rgba(255, 255, 255, 0.35) !important;
  border-color: rgba(255, 255, 255, 0.15) !important;
  box-shadow: none !important;
  cursor: not-allowed !important;
  transform: none !important;
}
.city-ranked-btn {
  padding: 6px 18px !important;
  font-size: 12px !important;
  letter-spacing: 1.3px !important;
}

.city-locked-notice {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(255, 60, 60, 0.12);
  border: 1.5px dashed rgba(255, 90, 90, 0.45);
  border-radius: 10px;
  padding: 8px 12px;
  width: 100%;
  box-sizing: border-box;
}
.locked-icon {
  font-size: 20px;
  flex-shrink: 0;
}
.locked-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.85);
}
.locked-info strong {
  color: #ff6b6b;
  font-weight: 900;
  letter-spacing: 1.1px;
  text-transform: uppercase;
}

/* Dots Pagination Rail */
.city-dots-rail {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(6px, 1.8vw, 12px);
  padding: 6px 0;
  width: 100%;
}
.city-dot {
  position: relative;
  background: rgba(255, 255, 255, 0.15);
  border: 1.5px solid rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  width: clamp(28px, 5.5vw, 36px);
  height: clamp(28px, 5.5vw, 36px);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.18s ease;
  color: rgba(255, 255, 255, 0.6);
  font-family: inherit;
}
.city-dot .dot-num {
  font-size: 11px;
  font-weight: 900;
}
.city-dot:hover {
  border-color: #fff;
  color: #fff;
  transform: translateY(-2px);
}
.city-dot.active {
  background: var(--fw-gold, #ffd23f);
  border-color: #fff;
  color: #000;
  transform: scale(1.15);
  box-shadow: 0 0 14px rgba(255, 210, 63, 0.7);
}
.city-dot.cleared:not(.active) {
  border-color: #ffd23f;
  color: #ffd23f;
  background: rgba(255, 210, 63, 0.2);
}
.city-dot.locked {
  opacity: 0.45;
  border-style: dashed;
}

/* Responsive queries */
@media (max-width: 640px) {
  .city-select-header {
    display: flex;
    justify-content: space-between;
  }
  .city-select-sub { display: none; }
  .city-metrics-row { gap: 5px; }
  .city-metric-box { padding: 5px 3px; }
  .city-records-strip { padding: 4px 2px; }
  .city-card { padding: 12px 14px; gap: 8px; }
  .city-nav-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
  }
  .city-nav-prev { left: -8px; }
  .city-nav-next { right: -8px; }
}
@media (max-height: 520px) and (orientation: landscape) {
  .fw-city-select { gap: 6px; padding: 6px 12px; }
  .city-select-header { padding: 0; }
  .city-card { padding: 8px 12px; gap: 6px; }
  .city-icon-float { display: none; }
  .city-card-sub { display: none; }
  .city-dots-rail { padding: 2px 0; }
}

/* -------------------------------------------------------------------------
   Dragon Ball Anime 3-Step Impact Zoom Power-Up Pickup Overlay
   ------------------------------------------------------------------------- */
#db-collect-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: auto;
  user-select: none;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  transition: opacity 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
  background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.75) 100%);
}

#db-collect-overlay.fading-out {
  opacity: 0;
  pointer-events: none;
}

.db-cinematic-bar {
  width: 100%;
  height: clamp(40px, 9.5vh, 90px);
  background: #000000;
  box-shadow: 0 0 35px rgba(0, 0, 0, 0.95);
  position: relative;
  z-index: 10;
}

.db-cinematic-bar.top {
  transform: translateY(-100%);
  animation: dbBarSlideDown 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.db-cinematic-bar.bottom {
  transform: translateY(100%);
  animation: dbBarSlideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes dbBarSlideDown {
  to { transform: translateY(0); }
}

@keyframes dbBarSlideUp {
  to { transform: translateY(0); }
}

.db-impact-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.95) 0%, var(--db-color, #ffd700) 40%, transparent 80%);
  animation: dbFlashFade 0.4s cubic-bezier(0.1, 0.9, 0.2, 1) forwards;
}

@keyframes dbFlashFade {
  0% { opacity: 1; transform: scale(1.15); }
  100% { opacity: 0; transform: scale(1); }
}

.db-ki-rays {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  background: radial-gradient(circle at 50% 50%, var(--db-color, #ffd700) 0%, transparent 70%);
  mix-blend-mode: screen;
  opacity: 0.45;
  animation: dbKiPulse 0.1s infinite alternate ease-in-out;
}

@keyframes dbKiPulse {
  0% { transform: scale(0.95); opacity: 0.35; }
  100% { transform: scale(1.08); opacity: 0.65; }
}

.db-speed-lines {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 6;
  opacity: 0.85;
  background: repeating-conic-gradient(
    from 0deg,
    rgba(255, 255, 255, 0.22) 0deg 2.5deg,
    transparent 2.5deg 8deg,
    var(--db-color, #ffd700) 8deg 10deg,
    transparent 10deg 16deg
  );
  mix-blend-mode: screen;
  animation: dbSpeedZoom 0.07s infinite alternate ease-in-out;
}

@keyframes dbSpeedZoom {
  0% { transform: scale(1); opacity: 0.7; }
  100% { transform: scale(1.07); opacity: 0.95; }
}

.db-zoom-stage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 15;
  pointer-events: none;
}

.db-anime-sub {
  position: absolute;
  top: clamp(55px, 12vh, 105px);
  font-size: clamp(11px, 2.2vw, 15px);
  font-weight: 900;
  letter-spacing: 5px;
  color: #ffd700;
  text-shadow: 0 0 12px rgba(255, 215, 0, 0.9), 0 2px 4px rgba(0, 0, 0, 0.9);
  background: rgba(10, 12, 18, 0.8);
  padding: 4px 18px;
  border-radius: 6px;
  border: 1px solid rgba(255, 215, 0, 0.5);
  text-transform: uppercase;
}

.db-word-box {
  position: absolute;
  opacity: 0;
  transform: scale(0.4) skew(-8deg);
  transition: opacity 0.15s ease, transform 0.22s cubic-bezier(0.18, 0.89, 0.32, 1.25);
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  pointer-events: none;
}

.db-word-inner {
  font-size: clamp(34px, 8.5vw, 82px);
  font-weight: 950;
  letter-spacing: 4px;
  color: #ffffff;
  text-transform: uppercase;
  line-height: 1;
  text-shadow:
    0 0 20px var(--db-color, #ffd700),
    0 0 45px var(--db-color, #ff9e00),
    0 4px 0 #000000,
    0 8px 0 #000000,
    0 12px 28px rgba(0, 0, 0, 0.95);
  background: rgba(0, 0, 0, 0.7);
  padding: 8px 28px;
  border-radius: 12px;
  border: 3px solid var(--db-color, #ffd700);
  box-shadow: 0 0 35px var(--db-color, #ffd700), inset 0 0 20px var(--db-color, #ffd700);
}

/* Stage 1: Word 1 Hypersonic Fly-In Slam */
.db-zoom-stage.stage-1 .word-1 {
  opacity: 1;
  animation: dbWord1Slam 0.42s cubic-bezier(0.18, 0.89, 0.32, 1.25) forwards;
}

@keyframes dbWord1Slam {
  0% { opacity: 0; transform: translate(-80%, -60%) scale(3.8) skew(-12deg); filter: blur(8px); }
  60% { opacity: 1; transform: translate(-10%, -20%) scale(0.92) skew(-8deg); filter: blur(0px); }
  100% { opacity: 1; transform: translate(-10%, -20%) scale(1.05) skew(-8deg); }
}

/* Stage 2: Word 2 Hypersonic Fly-In Slam */
.db-zoom-stage.stage-2 .word-1 {
  opacity: 0.75;
  transform: translate(-25%, -35%) scale(0.8) skew(-8deg);
}
.db-zoom-stage.stage-2 .word-2 {
  opacity: 1;
  animation: dbWord2Slam 0.42s cubic-bezier(0.18, 0.89, 0.32, 1.25) forwards;
}

@keyframes dbWord2Slam {
  0% { opacity: 0; transform: translate(80%, 40%) scale(3.8) skew(-12deg); filter: blur(8px); }
  60% { opacity: 1; transform: translate(15%, 5%) scale(0.95) skew(-8deg); filter: blur(0px); }
  100% { opacity: 1; transform: translate(15%, 5%) scale(1.15) skew(-8deg); }
}

/* Stage 3: Word 3 Meteor Dive Slam */
.db-zoom-stage.stage-3 .word-1 {
  opacity: 0.55;
  transform: translate(-30%, -42%) scale(0.65) skew(-8deg);
}
.db-zoom-stage.stage-3 .word-2 {
  opacity: 0.65;
  transform: translate(25%, -20%) scale(0.75) skew(-8deg);
}
.db-zoom-stage.stage-3 .word-3 {
  opacity: 1;
  animation: dbWord3Slam 0.44s cubic-bezier(0.18, 0.89, 0.32, 1.3) forwards;
}

@keyframes dbWord3Slam {
  0% { opacity: 0; transform: translate(0%, -80%) scale(4.2) skew(-12deg); filter: blur(10px); }
  60% { opacity: 1; transform: translate(0%, 20%) scale(0.92) skew(-8deg); filter: blur(0px); }
  100% { opacity: 1; transform: translate(0%, 20%) scale(1.25) skew(-8deg); }
}

/* Stage Reveal: Full Card Zoom Out */
.db-reveal-card {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.7);
  opacity: 0;
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: linear-gradient(135deg, rgba(16, 20, 28, 0.95), rgba(8, 10, 16, 0.98));
  border: 2px solid var(--db-color, #ffd700);
  box-shadow: 0 0 45px var(--db-color, #ffd700), 0 16px 40px rgba(0, 0, 0, 0.85);
  border-radius: 16px;
  padding: 24px 32px;
  max-width: clamp(320px, 86vw, 540px);
  width: 90%;
  text-align: left;
  transition: opacity 0.28s ease, transform 0.35s cubic-bezier(0.18, 0.89, 0.32, 1.2);
}

.db-zoom-stage.stage-reveal .db-word-box {
  opacity: 0;
  transform: scale(0.3);
}

.db-zoom-stage.stage-reveal .db-reveal-card {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

.db-card-header {
  display: flex;
  align-items: center;
  gap: 16px;
}

.db-card-icon {
  font-size: clamp(38px, 6vw, 52px);
  display: flex;
  justify-content: center;
  align-items: center;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  border: 2px solid var(--db-color, #ffd700);
  box-shadow: 0 0 20px var(--db-color, #ffd700);
  flex-shrink: 0;
}

.db-card-headings {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.db-card-badge {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 3px;
  color: var(--db-color, #ffd700);
  text-transform: uppercase;
}

.db-card-title {
  font-size: clamp(22px, 4.5vw, 32px);
  font-weight: 950;
  letter-spacing: 1.5px;
  color: #ffffff;
  text-transform: uppercase;
  margin: 0;
  line-height: 1.1;
  text-shadow: 0 0 16px var(--db-color, #ffd700);
}

.db-card-tagline {
  font-size: clamp(12px, 2.5vw, 15px);
  font-weight: 700;
  color: rgba(255, 255, 255, 0.85);
}

.db-card-desc {
  font-size: clamp(13px, 2.4vw, 15px);
  line-height: 1.45;
  color: rgba(240, 244, 248, 0.9);
  background: rgba(255, 255, 255, 0.04);
  border-left: 3px solid var(--db-color, #ffd700);
  padding: 10px 14px;
  border-radius: 0 8px 8px 0;
  margin: 0;
}

.db-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.db-meta-pill {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 1px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #ffffff;
  padding: 4px 12px;
  border-radius: 999px;
}

.db-card-hint {
  align-self: center;
  margin-top: 4px;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 2px;
  color: rgba(255, 255, 255, 0.6);
  background: rgba(0, 0, 0, 0.5);
  padding: 4px 14px;
  border-radius: 6px;
}



