  :root {
    --bg:          #323539;
    --card-bg:     rgba(32, 46, 82, 0.72);
    --card-border: rgba(180, 200, 240, 0.22);
    --text:        #f4f6fb;
    --text-dim:    #c6cee0;   /* WCAG AA on card-bg */
    --gold:        #ffd36e;
    --teal:        #5dd6c8;
    --coral:       #ff8a8a;
    --lilac:       #b89bff;
    --amber:       #ffa866;
    --radius-sm:   10px;
    --radius-md:   16px;
    --radius-lg:   22px;
    --radius-pill: 999px;
  }

  * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
  button, a, [role="button"] { -webkit-tap-highlight-color: transparent; }
  button:focus, button:focus-visible, button::-moz-focus-inner { outline: none; }
  button { -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
  body {
    background: var(--bg); overflow: hidden;
    font-family: 'Outfit', ui-rounded, 'SF Pro Rounded', system-ui, sans-serif;
    color: var(--text);
    -webkit-font-smoothing: antialiased;
  }
  #canvas { display: block; }

  #hud {
    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
    pointer-events: none; z-index: 10;
  }
  /* Gameplay HUD is invisible until the run starts — otherwise the stat pills,
     health/XP bars, ability bar and minimap all leak through the transparent
     landing overlay and make the title screen look like a paused game. The
     .playing class is toggled by playSelectedStage / restartGame / endGame. */
  body:not(.playing) #hud,
  body:not(.playing) #minimap,
  body:not(.playing) #top-bar,
  body:not(.playing) #ability-bar,
  body:not(.playing) #health-bar-wrap,
  body:not(.playing) #limit-break-meter,
  body:not(.playing) #wave-modifier {
    display: none !important;
  }

  #top-bar {
    position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
    display: flex; gap: 10px; align-items: center; pointer-events: none;
  }
  .stat-block {
    background: rgba(22, 26, 36, 0.28);
    backdrop-filter: blur(3px) saturate(1.1);
    -webkit-backdrop-filter: blur(3px) saturate(1.1);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--radius-pill);
    padding: 4px 14px; color: var(--text); font-size: 12px;
    text-align: center; min-width: 64px;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.75);
    display: flex; align-items: baseline; gap: 8px;
  }
  .stat-block .label {
    color: var(--text-dim); font-size: 9.5px;
    text-transform: uppercase; letter-spacing: 1.3px; font-weight: 600;
  }
  .stat-block .value { font-size: 16px; font-weight: 700; letter-spacing: 0.3px; }
  /* Enraged state — "☠ ENRAGED" is ~2.5× wider than a normal 3:21 timer.
     Hide the "Time" label so the full phrase fits the pill and neighboring
     stat-blocks aren't pushed sideways. Compact letter-spacing keeps the
     content readable inside the block's natural width. */
  .stat-block.enraged .label { display: none; }
  .stat-block.enraged .value { font-size: 13px; letter-spacing: 0.4px; white-space: nowrap; }
  #score-val { color: var(--gold); }
  #level-val { color: var(--teal); }
  #kills-val { color: var(--coral); }

  #health-bar-wrap {
    position: fixed; bottom: 72px; left: 50%; transform: translateX(-50%);
    width: 320px; pointer-events: none;
  }
  .bar-label {
    color: var(--text); font-size: 11px; text-transform: uppercase;
    letter-spacing: 1.2px; margin-bottom: 5px; font-weight: 600;
    display: flex; justify-content: space-between;
    /* Close-in dark drop + soft halo — mirrors #wave-notice. Keeps the
       "Health" / "XP" labels and live values readable on bright stages
       (snow, kingdom) where the panel sits over a near-white sky. */
    text-shadow: 0 1px 3px rgba(0,0,0,0.6), 0 0 8px rgba(0,0,0,0.35);
  }
  .bar-bg {
    /* Darker (was rgba(18,24,44,0.55)) + ~40% more opaque so bright
       stages don't bleed through. Inset 1px line crisps the edge against
       a near-white sky; outer drop separates the panel from the floor. */
    background: rgba(8, 12, 24, 0.78);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1px solid var(--card-border);
    border-radius: var(--radius-pill); height: 14px; overflow: hidden;
    box-shadow: 0 2px 8px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(0,0,0,0.30);
  }
  .bar-fill {
    height: 100%; border-radius: var(--radius-pill);
    /* Soft dark halo around the colored band so the fill pops against
       any background — block-element analogue of #wave-notice's
       text-shadow. xpDing's keyframes override during the gold flare
       and resettle to this same value afterward. */
    box-shadow: 0 0 6px rgba(0,0,0,0.45);
    transition: width 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  #health-fill { background: #4bd66a; width: 100%; transition: width 0.25s cubic-bezier(0.2, 0.8, 0.2, 1), background-color 0.35s ease; }
  #xp-fill     { background: linear-gradient(90deg, #6ec1ff 0%, #b89bff 55%, #ff9ec4 100%); width: 0%; transition: width 0.25s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.35s ease; }
  #xp-fill.ding { animation: xpDing 520ms cubic-bezier(0.3, 0.8, 0.2, 1); }
  @keyframes xpDing {
    /* 0% / 100% match .bar-fill's resting halo so the bar settles back
       to its readability shadow after the gold ding flare at 25%. */
    0%   { filter: brightness(1); box-shadow: 0 0 6px rgba(0,0,0,0.45); }
    25%  { filter: brightness(1.7) saturate(1.3); box-shadow: 0 0 18px 4px rgba(255,215,110,0.75); }
    100% { filter: brightness(1); box-shadow: 0 0 6px rgba(0,0,0,0.45); }
  }
  #level-val.level-pop { animation: levelPop 620ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  @keyframes levelPop {
    0%   { transform: scale(1); color: var(--teal); text-shadow: 0 0 0 rgba(255,215,110,0); }
    35%  { transform: scale(1.55); color: #ffe088; text-shadow: 0 0 18px rgba(255,215,110,0.95), 0 0 4px rgba(255,255,255,0.9); }
    100% { transform: scale(1); color: var(--teal); text-shadow: 0 0 0 rgba(255,215,110,0); }
  }

  #xp-bar-wrap {
    position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
    width: 320px; pointer-events: none;
  }

  #wave-notice {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
    color: var(--text); font-size: 40px; font-weight: 700; text-align: center;
    letter-spacing: 0.5px;
    text-shadow: 0 0 32px rgba(184, 155, 255, 0.55), 0 2px 12px rgba(0,0,0,0.4);
    pointer-events: none;
    opacity: 0; transition: opacity 0.5s;
  }

  /* Boss-name banner — persistent HUD label while a boss is alive. Pinned
     top-center; --boss-color is set by spawnBoss to the current boss's rally
     color so the label reads as an identity card for the boss. */
  #boss-banner {
    position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 8px 22px 10px;
    background: rgba(8, 10, 22, 0.55);
    border: 1px solid var(--boss-color, rgba(255, 46, 165, 0.85));
    border-radius: 10px;
    color: var(--text);
    box-shadow: 0 0 18px var(--boss-color, rgba(255, 46, 165, 0.35));
    pointer-events: none;
    opacity: 0; transition: opacity 0.35s;
    z-index: 90;
  }
  #boss-banner.visible { opacity: 1; }
  #boss-banner .boss-banner-label {
    font-size: 10px; letter-spacing: 2.5px; font-weight: 600;
    color: var(--boss-color, #ff2ea5);
    text-transform: uppercase;
  }
  #boss-banner .boss-banner-name {
    font-size: 20px; font-weight: 700; letter-spacing: 0.5px;
    text-shadow: 0 0 12px var(--boss-color, rgba(255, 46, 165, 0.6));
  }

  /* Stage-unlock banner — celebratory popup when a new stage is unlocked.
     Uses the .visible class to drive entrance/exit; confetti are dropped
     into the root host by _spawnVictoryConfetti. */
  #stage-unlock {
    position: fixed; inset: 0;
    display: flex; align-items: center; justify-content: center;
    pointer-events: none; z-index: 140;
    opacity: 0;
    transition: opacity 0.32s ease-out;
  }
  #stage-unlock.visible { opacity: 1; }
  #stage-unlock .stage-unlock-panel {
    position: relative;
    width: min(420px, 88vw);
    padding: 20px 22px 22px;
    border-radius: 18px;
    background: linear-gradient(180deg, rgba(38, 30, 72, 0.96), rgba(14, 18, 34, 0.97));
    border: 2px solid rgba(126, 226, 255, 0.85);
    box-shadow:
      0 0 60px rgba(126, 226, 255, 0.45),
      0 0 120px rgba(184, 155, 255, 0.25),
      0 12px 40px rgba(0, 0, 0, 0.55);
    text-align: center;
    color: #f6f8fe;
    transform: scale(0.82) translateY(14px);
    transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  #stage-unlock.visible .stage-unlock-panel {
    animation: stageUnlockPop 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  }
  @keyframes stageUnlockPop {
    0%   { transform: scale(0.7) translateY(18px); }
    55%  { transform: scale(1.06) translateY(-2px); }
    100% { transform: scale(1) translateY(0); }
  }
  #stage-unlock .stage-unlock-eyebrow {
    font-size: 13px; font-weight: 800; letter-spacing: 2.4px;
    color: #ffd36e;
    text-shadow: 0 0 14px rgba(255, 211, 110, 0.6);
    margin-bottom: 12px;
  }
  #stage-unlock .stage-unlock-thumb {
    width: 100%; aspect-ratio: 16 / 9;
    background-size: cover; background-position: center;
    border-radius: 10px;
    box-shadow: 0 0 24px rgba(126, 226, 255, 0.35), inset 0 0 0 1px rgba(255,255,255,0.08);
    margin-bottom: 12px;
  }
  #stage-unlock .stage-unlock-name {
    font-size: 26px; font-weight: 800; letter-spacing: 1.4px;
    color: #f6f8fe;
    text-shadow: 0 0 18px rgba(126, 226, 255, 0.55);
    margin-bottom: 4px;
  }
  #stage-unlock .stage-unlock-hint {
    font-size: 12px; color: rgba(246, 248, 254, 0.6);
    letter-spacing: 0.6px;
  }

  #powerup-screen {
    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
    background: radial-gradient(ellipse at center, rgba(60, 40, 100, 0.9), rgba(14, 18, 34, 0.96));
    display: flex; flex-direction: column;
    align-items: center; justify-content: center; z-index: 100;
    display: none;
    overflow: hidden;
  }
  /* Soft rotating nebula behind the cards for depth. */
  #powerup-screen::before {
    content: ''; position: absolute; inset: -25%;
    background:
      radial-gradient(circle at 30% 40%, rgba(255, 211, 110, 0.18), transparent 45%),
      radial-gradient(circle at 70% 65%, rgba(184, 155, 255, 0.22), transparent 50%),
      radial-gradient(circle at 50% 50%, rgba(126, 226, 255, 0.12), transparent 55%);
    pointer-events: none;
    animation: nebulaSpin 32s linear infinite;
  }
  @keyframes nebulaSpin {
    0%   { transform: rotate(0deg)   scale(1); }
    50%  { transform: rotate(180deg) scale(1.08); }
    100% { transform: rotate(360deg) scale(1); }
  }
  #powerup-screen > * { position: relative; z-index: 1; }
  #powerup-screen h2 {
    font-size: 34px; margin-bottom: 10px; letter-spacing: 1px; font-weight: 700;
    background: linear-gradient(120deg, #ffd36e, #ff9ec4 35%, #b89bff 65%, #ffd36e);
    background-size: 220% 100%;
    -webkit-background-clip: text; background-clip: text; color: transparent;
    animation: titleSweep 2.4s ease-in-out infinite;
    filter: drop-shadow(0 4px 24px rgba(255, 211, 110, 0.35));
  }
  /* Replay entrance animations every time the screen opens by toggling
     .entering on #powerup-screen — the class restart gives us a fresh keyframe
     run instead of the one-shot animation on mount. */
  #powerup-screen.entering h2 {
    animation: titleSweep 2.4s ease-in-out infinite,
               titleEnter 620ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  }
  #powerup-screen.entering p {
    animation: subEnter 520ms ease-out both; animation-delay: 180ms;
  }
  @keyframes titleSweep {
    0%, 100% { background-position: 0% 50%; }
    50%      { background-position: 100% 50%; }
  }
  @keyframes titleEnter {
    0%   { transform: translateY(-18px) scale(0.8); opacity: 0; }
    100% { transform: translateY(0)      scale(1);   opacity: 1; }
  }
  #powerup-screen p {
    color: var(--text-dim); font-size: 15px; margin-bottom: 32px;
  }
  @keyframes subEnter {
    0%   { transform: translateY(-8px); opacity: 0; }
    100% { transform: translateY(0);    opacity: 0.8; }
  }
  #powerup-choices {
    display: flex; gap: 22px; flex-wrap: wrap; justify-content: center;
    max-width: 940px;
    perspective: 900px;
  }
  .powerup-card {
    position: relative;
    background: linear-gradient(155deg, rgba(45, 40, 80, 0.92), rgba(24, 28, 54, 0.92));
    border: 1.5px solid rgba(184, 155, 255, 0.32);
    border-radius: var(--radius-lg);
    padding: 26px 22px; width: 210px; cursor: pointer;
    transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1),
                border-color 0.2s, box-shadow 0.28s, background 0.28s;
    text-align: center; pointer-events: all;
    transform-style: preserve-3d;
    animation: cardIn 520ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
    overflow: hidden;
  }
  .powerup-card:nth-child(1) { animation-delay: 140ms; }
  .powerup-card:nth-child(2) { animation-delay: 240ms; }
  .powerup-card:nth-child(3) { animation-delay: 340ms; }
  @keyframes cardIn {
    0%   { opacity: 0; transform: translateY(40px) rotateX(-18deg) scale(0.88); }
    70%  { opacity: 1; transform: translateY(-6px) rotateX(4deg)   scale(1.04); }
    100% { opacity: 1; transform: translateY(0)    rotateX(0)      scale(1); }
  }
  /* Shimmering highlight sweep on hover. */
  .powerup-card::before {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.12) 50%, transparent 70%);
    transform: translateX(-120%);
    transition: transform 0.6s ease;
    pointer-events: none;
  }
  .powerup-card:hover, .powerup-card:focus-visible {
    border-color: var(--lilac);
    transform: translateY(-8px) rotateX(6deg) scale(1.03);
    box-shadow: 0 20px 48px rgba(184, 155, 255, 0.38), 0 0 0 1px rgba(255, 211, 110, 0.35) inset;
    background: linear-gradient(155deg, rgba(60, 50, 100, 0.96), rgba(30, 34, 66, 0.96));
    outline: none;
  }
  .powerup-card:hover::before { transform: translateX(120%); }
  .powerup-card .icon {
    /* SVG icons from src/powerup-icons.js — sized via the child svg rule.
       The font-size is a fallback in case the icon string is ever plain
       text (e.g. during a migration). */
    width: 54px; height: 54px;
    margin: 0 auto 12px;
    font-size: 38px; line-height: 54px;
    display: flex; align-items: center; justify-content: center;
    transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.28s;
  }
  .powerup-card .icon svg { width: 100%; height: 100%; display: block; }
  .powerup-card:hover .icon {
    transform: scale(1.18) rotate(-6deg);
    filter: drop-shadow(0 4px 14px rgba(255, 211, 110, 0.55));
  }
  .powerup-card .name { color: var(--text); font-size: 15px; font-weight: 700; margin-bottom: 6px; }
  .powerup-card .desc { color: var(--text-dim); font-size: 12.5px; line-height: 1.55; }
  .powerup-card .tier {
    font-size: 10.5px; color: var(--lilac); margin-top: 10px;
    text-transform: uppercase; letter-spacing: 1.2px; font-weight: 600;
  }
  /* Per-tier hover accent — hint at what the card does through color. */
  .powerup-card[data-tier="Offense"]:hover  { box-shadow: 0 20px 48px rgba(255, 107, 107, 0.35), 0 0 0 1px rgba(255, 107, 107, 0.45) inset; border-color: #ff9e7a; }
  .powerup-card[data-tier="Defense"]:hover  { box-shadow: 0 20px 48px rgba(93, 214, 200, 0.35), 0 0 0 1px rgba(93, 214, 200, 0.45) inset; border-color: #7ee2d0; }
  .powerup-card[data-tier="Mobility"]:hover { box-shadow: 0 20px 48px rgba(126, 226, 255, 0.35), 0 0 0 1px rgba(126, 226, 255, 0.45) inset; border-color: #9fdfff; }
  .powerup-card[data-tier="Utility"]:hover  { box-shadow: 0 20px 48px rgba(255, 211, 110, 0.35), 0 0 0 1px rgba(255, 211, 110, 0.45) inset; border-color: #ffd36e; }

  @keyframes card-burst {
    0%   { transform: scale(1); box-shadow: 0 0 0 rgba(255,215,0,0); filter: brightness(1); }
    30%  { transform: scale(1.18); box-shadow: 0 0 56px 8px rgba(255,215,0,0.7); filter: brightness(1.45) saturate(1.3); }
    75%  { transform: scale(1.08); box-shadow: 0 0 80px 12px rgba(255,215,0,0.5); filter: brightness(1.2); }
    100% { transform: scale(0); opacity: 0; filter: brightness(1); }
  }
  .powerup-card.card-picked, .shop-card.card-picked,
  .stage-hero-card.card-picked,
  #overlay #start-btn.card-picked,
  #overlay #relic-bar.card-picked,
  #overlay #rankings-bar.card-picked {
    animation: card-burst 380ms cubic-bezier(0.5, 0, 0.2, 1) forwards;
    pointer-events: none; z-index: 2;
  }
  .powerup-card.card-dismissed, .shop-card.card-dismissed,
  .stage-hero-card.card-dismissed {
    transition: opacity 200ms ease, transform 200ms ease, filter 200ms ease;
    opacity: 0.08; transform: scale(0.82) translateY(12px); filter: blur(2px); pointer-events: none;
  }

  /* Full-screen gold flash when level-up opens. Pinned to the overlay root. */
  #level-up-flash {
    position: fixed; inset: 0; z-index: 150;
    background: radial-gradient(ellipse at center, rgba(255, 225, 140, 0.55), rgba(255, 180, 80, 0.2) 45%, transparent 70%);
    pointer-events: none; opacity: 0;
  }
  #level-up-flash.fire { animation: levelUpFlash 700ms ease-out; }
  @keyframes levelUpFlash {
    0%   { opacity: 0; }
    15%  { opacity: 1; }
    100% { opacity: 0; }
  }

  /* LVL chip sitting above the modal title — replaces the old floating banner
     that used to overlap the card grid. Populated from showPowerupScreen and
     replayed each open via the #powerup-screen.entering class. */
  #level-up-chip {
    display: inline-flex; align-items: baseline; gap: 10px;
    margin-bottom: 14px;
    padding: 6px 18px 7px;
    border-radius: 999px;
    background: linear-gradient(135deg, rgba(255, 211, 110, 0.24), rgba(184, 155, 255, 0.22));
    border: 1px solid rgba(255, 211, 110, 0.55);
    box-shadow: 0 6px 24px rgba(255, 211, 110, 0.22), inset 0 0 0 1px rgba(255, 255, 255, 0.08);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    font-weight: 800; letter-spacing: 2.4px; text-transform: uppercase;
    color: #ffe7a8;
    transform-origin: center;
    opacity: 0;
  }
  #level-up-chip .lvl-word { font-size: 12px; color: rgba(255, 231, 168, 0.85); }
  #level-up-chip .lvl-num {
    font-size: 22px;
    background: linear-gradient(120deg, #fff3c8, #ffd36e 45%, #ff9ec4);
    -webkit-background-clip: text; background-clip: text; color: transparent;
    filter: drop-shadow(0 2px 8px rgba(255, 211, 110, 0.55));
  }
  #powerup-screen.entering #level-up-chip {
    animation: levelChipPop 720ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  }
  @keyframes levelChipPop {
    0%   { opacity: 0; transform: translateY(10px) scale(0.7); }
    55%  { opacity: 1; transform: translateY(0)    scale(1.15); }
    100% { opacity: 1; transform: translateY(0)    scale(1);   }
  }

  /* Orbital stars dropped into the picked card's center. */
  .pick-spark {
    position: fixed; pointer-events: none; z-index: 160;
    width: 10px; height: 10px; margin: -5px 0 0 -5px;
    background: radial-gradient(circle, #fff 0%, #ffd36e 45%, transparent 70%);
    border-radius: 50%;
    animation: sparkFly 620ms cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
  }
  @keyframes sparkFly {
    0%   { transform: translate(0,0) scale(0.6); opacity: 1; }
    100% { transform: translate(var(--dx), var(--dy)) scale(0.2); opacity: 0; }
  }

  #overlay {
    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center; z-index: 200;
    isolation: isolate;
  }
  /* Background image layer — now hosts the rising photon field that replaces
     the earlier Three.js shard parallax. A radial gradient sits as the
     ambient backdrop; landing-scene.js injects `.landing-photon` spans on
     top of it. */
  #overlay-bg {
    position: absolute; inset: 0; z-index: -2;
    background:
      radial-gradient(ellipse 90% 70% at 50% 120%,
        rgba(90, 40, 80, 0.55) 0%,
        rgba(40, 20, 50, 0.3) 40%,
        transparent 75%),
      radial-gradient(ellipse 120% 90% at 50% -10%,
        rgba(20, 10, 40, 0.75) 0%,
        rgba(10, 5, 20, 0.95) 60%),
      #060310;
    overflow: hidden;
  }
  /* ─── Landing photons — rising warm-amber motes matching the game-over
     field. Three size tiers + per-photon brightness cap + occasional
     cool (cyan / pink) tints for palette variety. CSS-only animation
     means the layer costs nothing on CPU beyond the compositor. */
  #landing-photons {
    position: absolute; inset: 0; pointer-events: none;
    z-index: 0; overflow: hidden;
  }
  .landing-photon {
    position: absolute; bottom: -30px;
    border-radius: 50%;
    background: radial-gradient(circle,
      rgba(255, 240, 200, calc(0.9 * var(--lp-bright, 0.8))) 0%,
      rgba(255, 200, 120, calc(0.5 * var(--lp-bright, 0.8))) 45%,
      rgba(255, 180, 90, 0) 75%);
    box-shadow: 0 0 6px rgba(255, 210, 140, 0.4);
    opacity: 0;
    animation: landing-photon-rise linear infinite;
  }
  .landing-photon[data-tint="cyan"] {
    background: radial-gradient(circle,
      rgba(200, 240, 255, calc(0.9 * var(--lp-bright, 0.8))) 0%,
      rgba(120, 200, 240, calc(0.5 * var(--lp-bright, 0.8))) 45%,
      rgba(90, 160, 220, 0) 75%);
    box-shadow: 0 0 6px rgba(140, 210, 245, 0.4);
  }
  .landing-photon[data-tint="pink"] {
    background: radial-gradient(circle,
      rgba(255, 220, 240, calc(0.9 * var(--lp-bright, 0.8))) 0%,
      rgba(255, 160, 200, calc(0.5 * var(--lp-bright, 0.8))) 45%,
      rgba(220, 110, 170, 0) 75%);
    box-shadow: 0 0 6px rgba(255, 170, 200, 0.4);
  }
  @keyframes landing-photon-rise {
    0%   { transform: translate3d(0, 0, 0) scale(0.3); opacity: 0; }
    8%   { opacity: 1; transform: translate3d(0, -10vh, 0) scale(0.6); }
    50%  { opacity: 1; transform: translate3d(0, -55vh, 0) scale(1); }
    92%  { opacity: 0.7; transform: translate3d(0, -105vh, 0) scale(1.1); }
    100% { opacity: 0; transform: translate3d(0, -115vh, 0) scale(1.1); }
  }
  @media (prefers-reduced-motion: reduce) {
    .landing-photon { animation: none; opacity: 0; }
  }
  /* Subtle animated torch flicker over the image */
  #overlay-bg::after {
    content: ''; position: absolute; inset: 0;
    background: radial-gradient(ellipse 60% 40% at 50% 85%, rgba(255,160,80,0.18), transparent 70%);
    mix-blend-mode: screen;
    animation: torchFlicker 2.8s ease-in-out infinite;
  }
  @keyframes torchFlicker {
    0%, 100% { opacity: 0.8; }
    50%      { opacity: 1.0; }
    23%      { opacity: 0.65; }
    72%      { opacity: 0.9; }
  }
  #overlay-content {
    display: flex; flex-direction: column; align-items: center;
    padding: 32px;
    max-width: 560px; text-align: center;
  }
  #overlay h1 {
    font-size: clamp(44px, 7vw, 68px);
    margin-bottom: 12px; font-weight: 800; letter-spacing: 1px; line-height: 1;
    /* Wide gradient with an extra bright band that drifts across the text,
       repeated in an oversized background so the animation can translate
       the gradient position over time without clipping. Creates a subtle
       shimmer-sweep across the word without touching the text itself. */
    background: linear-gradient(120deg,
      #ffd36e 0%, #ff9ec4 30%, #ffffff 50%, #ff9ec4 70%, #b89bff 100%);
    background-size: 220% 100%;
    background-position: 0% 50%;
    -webkit-background-clip: text; background-clip: text; color: transparent;
    filter: drop-shadow(0 4px 24px rgba(255, 180, 120, 0.35));
    animation: overlay-title-shimmer 6s ease-in-out infinite,
               overlay-title-breathe  4.5s ease-in-out infinite;
  }
  @keyframes overlay-title-shimmer {
    0%   { background-position: 0% 50%; }
    50%  { background-position: 100% 50%; }
    100% { background-position: 0% 50%; }
  }
  @keyframes overlay-title-breathe {
    0%, 100% { filter: drop-shadow(0 4px 24px rgba(255, 180, 120, 0.35)); }
    50%      { filter: drop-shadow(0 4px 34px rgba(255, 200, 140, 0.65)); }
  }
  @media (prefers-reduced-motion: reduce) {
    #overlay h1 { animation: none; }
  }
  #overlay p  { color: var(--text-dim); font-size: 15px; margin-bottom: 24px; text-align: center; line-height: 1.7; }

  /* Requirement badge — clear, iconographic, not alarming */
  #requirements, #touch-requirements {
    display: inline-flex; align-items: center; gap: 14px;
    background: var(--card-bg);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid var(--card-border);
    border-radius: var(--radius-md);
    padding: 12px 18px; margin-bottom: 28px;
    color: var(--text);
    box-shadow: 0 4px 18px rgba(10, 14, 30, 0.35);
  }
  #touch-requirements { display: none; }
  #requirements .req-icons {
    display: inline-flex; align-items: center; gap: 6px;
    color: var(--amber); flex-shrink: 0;
  }
  #requirements .req-plus { opacity: 0.55; font-weight: 600; }
  #requirements .req-text {
    text-align: left; font-size: 13.5px; line-height: 1.45;
  }
  #requirements .req-text strong { font-weight: 700; }
  #requirements .req-hint {
    display: block; color: var(--text-dim);
    font-size: 11.5px; margin-top: 3px; letter-spacing: 0.2px;
  }
  /* Relic bar — the whole pill is a clickable button that opens the shop.
     Cyan palette so it reads distinct from rankings (violet) and PLAY (warm).
     Double-id beats the blanket `#overlay button` gradient. */
  /* Utility row holding the relic / rankings / achievements pills above PLAY.
     Flex-row that wraps on narrow phones so all three pills stay grouped as a
     unit. The row owns the bottom margin; individual pills don't add their
     own margin-bottom (would double-stack). */
  #landing-utility-row {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 10px;
    margin: 0 0 22px;
    max-width: 100%;
  }
  @media (max-width: 480px) {
    #landing-utility-row { gap: 8px; margin-bottom: 18px; }
  }

  #overlay #relic-bar {
    display: inline-flex; align-items: center; gap: 14px;
    margin: 0;
    padding: 10px 18px;
    border-radius: var(--radius-pill);
    background: rgba(10, 20, 30, 0.44);
    border: 1px solid rgba(126, 226, 255, 0.3);
    backdrop-filter: blur(10px) saturate(1.1);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
    color: inherit; font-family: inherit; font-weight: 400;
    font-size: 13px; letter-spacing: 0.5px;
    box-shadow: none;
    cursor: pointer;
    transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
                box-shadow 0.22s, border-color 0.22s, background 0.22s;
  }
  #overlay #relic-bar:hover,
  #overlay #relic-bar:focus-visible {
    transform: translateY(-1px);
    border-color: rgba(126, 226, 255, 0.55);
    background: rgba(14, 32, 48, 0.6);
    box-shadow: 0 6px 20px rgba(126, 226, 255, 0.3);
    outline: none;
  }
  #relic-bar .relic-label {
    color: #7ee2ff; font-weight: 800; letter-spacing: 0.8px;
    display: inline-flex; align-items: center; gap: 6px;
  }
  /* Brilliant-cut cyan diamond — oversized inline glyph. Sized at 1.8em so
     the gem visibly dominates the surrounding text (the currency symbol
     should feel like a precious gem, not a character next to a number).
     vertical-align keeps the baseline roughly centred. Markup lives in
     RELIC_GEM_HTML (src/meta-icons.js). */
  .relic-gem {
    display: inline-block;
    width: 1.55em; height: 1.55em;
    vertical-align: -0.42em;
    line-height: 0;
    filter: drop-shadow(0 0 4px rgba(92, 212, 255, 0.55));
  }
  .relic-gem svg { width: 100%; height: 100%; display: block; }
  /* BUY cost badges live in tight rows on the relic shop cards — keep the
     gem sized down there so the number stays on a single line. The
     game-over / victory earnings line and the landing relic pill both use
     the default 1.55em so the icon reads identically across the three
     screens (title, game-over, victory). */
  .relic-card-cost .relic-gem { width: 1.2em; height: 1.2em; vertical-align: -0.3em; }

  /* Trophy icon — inline SVG replacement for 🏆 on the rankings pill.
     Sized to match the relic gem so the pair look like a matched set.
     Violet drop-shadow picks up the rankings palette instead of cyan. */
  .trophy-icon {
    display: inline-block;
    width: 1.55em; height: 1.55em;
    vertical-align: -0.42em;
    line-height: 0;
    filter: drop-shadow(0 0 4px rgba(209, 174, 255, 0.55));
  }
  .trophy-icon svg { width: 100%; height: 100%; display: block; }
  #relic-bar .relic-count {
    color: var(--text); font-weight: 700; font-size: 15px;
    min-width: 32px; text-align: left;
  }

  /* Rankings pill — shared format used on the landing screen and at the end
     of a run. Whole pill is a single clickable button; the chevron is a
     visual affordance, not a separate control. Warm-violet palette so it
     reads distinct from relics (cyan) and PLAY (warm-amber). ID-selector
     specificity (#overlay, #gameover) beats the blanket button gradient
     defined above (`#overlay button { background: linear-gradient(...) }`). */
  #overlay .rankings-pill,
  #gameover .rankings-pill {
    display: inline-flex; align-items: center; gap: 12px;
    padding: 10px 18px;
    border-radius: var(--radius-pill);
    background: rgba(36, 22, 48, 0.44);
    border: 1px solid rgba(209, 174, 255, 0.3);
    backdrop-filter: blur(10px) saturate(1.1);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
    color: inherit; font-family: inherit; font-weight: 400;
    font-size: 13px; letter-spacing: 0.5px;
    box-shadow: none;
    cursor: pointer;
    transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
                box-shadow 0.22s, border-color 0.22s, background 0.22s;
  }
  #overlay .rankings-pill:hover,
  #gameover .rankings-pill:hover,
  #overlay .rankings-pill:focus-visible,
  #gameover .rankings-pill:focus-visible {
    transform: translateY(-1px);
    border-color: rgba(209, 174, 255, 0.55);
    background: rgba(48, 30, 64, 0.56);
    box-shadow: 0 6px 20px rgba(155, 123, 255, 0.32);
    outline: none;
  }
  .rankings-pill .rankings-label {
    color: #d1aeff; font-weight: 800; letter-spacing: 0.8px;
  }

  /* Landing placement — pill sits between the relic bar and PLAY button. */
  #rankings-bar {
    margin: 0;
  }
  /* Sync icon — floats in the overlay's top-right corner so it reads as a
     tucked-away utility instead of competing with SHOP/PLAY. Double-id
     specificity beats the blanket `#overlay button` gradient below. */
  #overlay #progress-sync-btn {
    position: absolute; top: 14px; right: 14px;
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; padding: 0;
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-dim);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 50%;
    box-shadow: none;
    cursor: pointer;
    font-family: inherit;
    transition: color 0.18s, border-color 0.18s, background 0.18s, transform 0.18s;
    z-index: 1;
  }
  #overlay #progress-sync-btn svg { display: block; }
  #overlay #progress-sync-btn:hover,
  #overlay #progress-sync-btn:focus-visible {
    color: var(--text);
    border-color: rgba(255, 255, 255, 0.28);
    background: rgba(255, 255, 255, 0.08);
    transform: translateY(-1px);
    box-shadow: none;
  }
  @media (max-width: 600px) {
    #overlay #progress-sync-btn { top: 10px; right: 10px; width: 36px; height: 36px; }
    #overlay #progress-sync-btn svg { width: 16px; height: 16px; }
  }

  /* Sync-progress nudge. Floats directly below the sync button (top-right
     corner of the overlay) with an up-pointing arrow so the connection to
     the icon reads at a glance. Hidden by default — the boot/landing-visible
     handler flips `.visible` on only when syncNudge.shouldShow() returns
     true. Dismissal permanence lives in localStorage via syncNudge.dismiss().
     Animation: slide-down entrance + gentle bob + slow border glow pulse. */
  #sync-nudge {
    position: absolute; top: 62px; right: 14px;
    display: none; align-items: flex-start; gap: 10px;
    width: 250px; padding: 12px 12px 12px 14px;
    background: linear-gradient(135deg, rgba(18, 32, 64, 0.96), rgba(12, 22, 48, 0.96));
    border: 1px solid rgba(126, 226, 255, 0.45);
    border-radius: 14px;
    box-shadow:
      0 10px 28px rgba(0, 0, 0, 0.45),
      0 0 0 1px rgba(126, 226, 255, 0.12) inset,
      0 0 22px rgba(126, 226, 255, 0.22);
    color: var(--text);
    font-family: inherit;
    pointer-events: auto;
    transform-origin: top right;
    z-index: 2;
  }
  #sync-nudge.visible {
    display: flex;
    animation:
      sync-nudge-in 0.52s cubic-bezier(0.18, 0.9, 0.32, 1.2) both,
      sync-nudge-bob 3.6s ease-in-out 0.55s infinite,
      sync-nudge-glow 2.4s ease-in-out 0.55s infinite;
  }
  #sync-nudge.dismissing {
    animation: sync-nudge-out 0.28s ease-in forwards !important;
    pointer-events: none;
  }
  .sync-nudge-arrow {
    position: absolute; top: -7px; right: 18px;
    width: 14px; height: 14px;
    background: linear-gradient(135deg, rgba(18, 32, 64, 0.96), rgba(12, 22, 48, 0.96));
    border-top: 1px solid rgba(126, 226, 255, 0.45);
    border-left: 1px solid rgba(126, 226, 255, 0.45);
    transform: rotate(45deg);
  }
  .sync-nudge-body {
    flex: 1 1 auto;
    min-width: 0;
  }
  .sync-nudge-title {
    font-size: 13px; font-weight: 800;
    letter-spacing: 0.6px; text-transform: uppercase;
    color: #cfeeff;
    margin-bottom: 3px;
  }
  .sync-nudge-msg {
    font-size: 12px;
    line-height: 1.35;
    color: var(--text-dim);
  }
  #overlay #sync-nudge-close {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0;
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-dim);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 50%;
    cursor: pointer;
    box-shadow: none;
    transition: color 0.18s, border-color 0.18s, background 0.18s, transform 0.18s;
  }
  #overlay #sync-nudge-close:hover,
  #overlay #sync-nudge-close:focus-visible {
    color: var(--text);
    border-color: rgba(255, 255, 255, 0.32);
    background: rgba(255, 255, 255, 0.12);
    transform: scale(1.08);
  }
  /* Entrance: drops from above the sync button with a tiny overshoot. */
  @keyframes sync-nudge-in {
    0%   { opacity: 0; transform: translateY(-18px) scale(0.92); }
    60%  { opacity: 1; transform: translateY(4px)  scale(1.02); }
    100% { opacity: 1; transform: translateY(0)    scale(1); }
  }
  @keyframes sync-nudge-out {
    0%   { opacity: 1; transform: translateY(0)    scale(1); }
    100% { opacity: 0; transform: translateY(-10px) scale(0.94); }
  }
  /* Gentle vertical bob so the pill reads as alive without feeling nervous. */
  @keyframes sync-nudge-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-3px); }
  }
  /* Slow border-glow pulse that loops with the bob — draws the eye to the
     arrow pointing at the sync icon. */
  @keyframes sync-nudge-glow {
    0%, 100% {
      border-color: rgba(126, 226, 255, 0.45);
      box-shadow:
        0 10px 28px rgba(0, 0, 0, 0.45),
        0 0 0 1px rgba(126, 226, 255, 0.12) inset,
        0 0 22px rgba(126, 226, 255, 0.22);
    }
    50% {
      border-color: rgba(170, 238, 255, 0.8);
      box-shadow:
        0 10px 28px rgba(0, 0, 0, 0.5),
        0 0 0 1px rgba(170, 238, 255, 0.22) inset,
        0 0 36px rgba(126, 226, 255, 0.45);
    }
  }
  @media (max-width: 600px) {
    #sync-nudge {
      top: 54px; right: 10px;
      width: min(calc(100vw - 20px), 250px);
    }
    .sync-nudge-arrow { right: 14px; }
  }
  @media (prefers-reduced-motion: reduce) {
    #sync-nudge.visible {
      animation: sync-nudge-in 0.2s ease-out both;
    }
  }

  /* Progress sync modal — same overlay chrome as the relic shop, but with a
     two-section body (export + import) instead of a grid of cards. */
  #progress-sync {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    z-index: 260;  /* above relic-shop so Export/Import can be opened on top */
    background: rgba(6, 8, 18, 0.82);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
  }
  #progress-sync.visible { display: flex; }
  #progress-sync-panel {
    width: min(620px, 92vw);
    max-height: 88vh;
    display: flex; flex-direction: column;
    background: linear-gradient(160deg, rgba(30, 36, 60, 0.96), rgba(22, 18, 40, 0.96));
    border: 1px solid rgba(126, 226, 255, 0.25);
    border-radius: 18px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
    overflow: hidden;
  }
  #progress-sync-header {
    display: flex; align-items: center; gap: 18px;
    padding: 18px 22px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  #progress-sync-title { font-size: 20px; font-weight: 800; letter-spacing: 1.2px; color: var(--text); }
  #progress-sync-subtitle { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
  #progress-sync-close {
    margin-left: auto;
    background: none; border: none; color: var(--text-dim);
    font-size: 28px; line-height: 1; cursor: pointer;
    padding: 0 4px; font-family: inherit;
    transition: color 0.15s, transform 0.15s;
  }
  #progress-sync-close:hover { color: var(--text); transform: scale(1.1); }
  #progress-sync-body {
    padding: 18px 22px 22px;
    overflow-y: auto;
    display: flex; flex-direction: column; gap: 20px;
  }
  .sync-section {
    display: flex; flex-direction: column; gap: 10px;
    padding: 14px;
    background: rgba(16, 22, 40, 0.7);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--radius-md);
  }
  .sync-section-head { display: flex; flex-direction: column; gap: 2px; }
  .sync-section-head h3 {
    margin: 0; font-size: 14px; letter-spacing: 1px; font-weight: 800;
    color: #d8f4ff;
  }
  .sync-hint { font-size: 12px; color: var(--text-dim); }
  #progress-sync-export-code,
  #progress-sync-import-code {
    width: 100%;
    resize: vertical;
    padding: 10px 12px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px; line-height: 1.45;
    color: var(--text);
    background: rgba(8, 10, 20, 0.8);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: var(--radius-md);
    word-break: break-all;
    box-sizing: border-box;
  }
  #progress-sync-export-code:focus,
  #progress-sync-import-code:focus {
    outline: none;
    border-color: rgba(126, 226, 255, 0.45);
  }
  .sync-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
  #progress-sync-copy-btn,
  #progress-sync-import-btn {
    background: rgba(126, 226, 255, 0.16);
    color: #d8f4ff;
    border: 1px solid rgba(126, 226, 255, 0.45);
    border-radius: var(--radius-pill);
    padding: 8px 18px; font-size: 12px; font-weight: 700;
    letter-spacing: 0.6px; cursor: pointer;
    font-family: inherit;
    transition: background 0.18s, transform 0.18s;
  }
  #progress-sync-copy-btn:hover,
  #progress-sync-import-btn:hover { background: rgba(126, 226, 255, 0.28); transform: translateY(-1px); }
  .sync-status { font-size: 12px; color: var(--text-dim); }
  .sync-status.ok   { color: #9fe870; }
  .sync-status.warn { color: #ffd36e; }
  .sync-status.err  { color: #ff7a7a; }

  /* Shop modal — full-screen overlay with a grid of upgrade cards. Each
     card shows icon, name, per-tier effect, current tier / max, cost, and a
     BUY button. Disabled states keep the same size so the grid doesn't
     reflow between purchases. */
  #relic-shop {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    z-index: 250;
    background: rgba(6, 8, 18, 0.82);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
  }
  #relic-shop.visible { display: flex; }
  #relic-shop-panel {
    width: min(920px, 92vw);
    max-height: 88vh;
    display: flex; flex-direction: column;
    background: linear-gradient(160deg, rgba(30, 36, 60, 0.96), rgba(22, 18, 40, 0.96));
    border: 1px solid rgba(126, 226, 255, 0.25);
    border-radius: 18px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
    overflow: hidden;
  }
  #relic-shop-header {
    display: flex; align-items: center; gap: 18px;
    padding: 18px 22px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  #relic-shop-title { font-size: 22px; font-weight: 800; letter-spacing: 1.2px; color: var(--text); }
  #relic-shop-subtitle { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
  #relic-shop-balance {
    margin-left: auto;
    display: flex; align-items: center; gap: 8px;
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    background: rgba(126, 226, 255, 0.12);
    border: 1px solid rgba(126, 226, 255, 0.35);
    font-weight: 700; font-size: 15px;
    color: #d8f4ff;
  }
  #relic-shop-close {
    background: none; border: none; color: var(--text-dim);
    font-size: 28px; line-height: 1; cursor: pointer;
    padding: 0 4px; font-family: inherit;
    transition: color 0.15s, transform 0.15s;
  }
  #relic-shop-close:hover { color: var(--text); transform: scale(1.1); }
  /* Scrollable body — wraps both the upgrade grid and the limit-break
     section so the panel has ONE continuous scroll. Keeps the header
     sticky at the top of the modal. Previously the grid had its own
     nested scroll while the limit-break section flowed below it, which
     crushed the grid to near-zero on mobile. */
  #relic-shop-body {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  #relic-shop-section-head {
    padding: 18px 22px 4px;
  }
  #relic-shop-section-title {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 14px; font-weight: 700; letter-spacing: 2px;
    color: #7ee2ff;
    text-shadow: 0 0 8px rgba(126, 226, 255, 0.35);
  }
  #relic-shop-section-subtitle {
    font-size: 11.5px; color: var(--text-dim);
    margin-top: 3px;
  }
  #relic-shop-grid {
    display: grid; gap: 12px;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    padding: 12px 22px 4px;
  }
  /* Limit break section — sits below the upgrade grid. Visual separator
     already comes from the two section headers; a thin top border
     reinforces the grouping. */
  #limit-break-section {
    padding: 4px 22px 22px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    margin-top: 14px;
  }
  #limit-break-section-head {
    padding: 14px 0 10px;
  }
  #limit-break-section-title {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 14px; font-weight: 700; letter-spacing: 2px;
    color: #ffd166;
    text-shadow: 0 0 8px rgba(255, 215, 102, 0.35);
  }
  #limit-break-section-subtitle {
    font-size: 11.5px; color: var(--text-dim);
    margin-top: 3px;
  }
  #limit-break-grid {
    display: grid; gap: 12px;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  }
  .limit-card.owned {
    border-color: rgba(255, 215, 102, 0.25);
  }
  .limit-card.equipped {
    border-color: rgba(255, 215, 102, 0.75);
    box-shadow: 0 0 20px rgba(255, 180, 80, 0.18), 0 0 4px rgba(255, 215, 102, 0.4) inset;
  }
  .relic-card {
    background: rgba(16, 22, 40, 0.7);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--radius-md);
    padding: 14px 14px 12px;
    display: flex; flex-direction: column; gap: 8px;
    transition: border-color 0.18s, transform 0.18s;
  }
  .relic-card.maxed { border-color: rgba(159, 232, 112, 0.4); }
  .relic-card.affordable:not(.maxed) { border-color: rgba(126, 226, 255, 0.35); }
  .relic-card-head {
    display: flex; align-items: center; gap: 10px;
  }
  .relic-card-icon {
    /* SVG icons from src/meta-icons.js (RELIC_ICONS / LIMIT_ICONS). Inline
       SVG inherits no intrinsic size from font metrics, so we pin the box
       and let the child svg fill it. font-size preserved as an emoji fallback
       if any caller ever passes a plain string. */
    width: 36px; height: 36px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 24px; line-height: 1;
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.45));
  }
  .relic-card-icon svg { width: 100%; height: 100%; display: block; }
  .relic-card-name { font-weight: 700; font-size: 14px; letter-spacing: 0.3px; color: var(--text); }
  .relic-card-desc { font-size: 11.5px; color: var(--text-dim); line-height: 1.45; }
  .relic-card-tier {
    display: flex; align-items: center; gap: 8px;
    font-size: 11px; color: var(--text-dim); letter-spacing: 0.4px;
  }
  .relic-card-bar {
    flex: 1; height: 4px; border-radius: 2px;
    background: rgba(255, 255, 255, 0.08);
    overflow: hidden;
  }
  .relic-card-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, #7ee2ff, #b89bff);
    transition: width 0.25s;
  }
  .relic-card-buy {
    margin-top: auto;
    display: flex; align-items: center; justify-content: space-between;
    padding: 8px 12px;
    background: rgba(126, 226, 255, 0.14);
    color: #d8f4ff;
    border: 1px solid rgba(126, 226, 255, 0.4);
    border-radius: var(--radius-pill);
    font-family: inherit; font-size: 12px; font-weight: 700;
    letter-spacing: 0.5px; cursor: pointer;
    transition: background 0.15s, transform 0.15s;
  }
  .relic-card-buy:hover:not([disabled]) { background: rgba(126, 226, 255, 0.26); transform: translateY(-1px); }
  .relic-card-buy[disabled] { opacity: 0.45; cursor: not-allowed; }
  .relic-card.maxed .relic-card-buy {
    background: rgba(159, 232, 112, 0.14);
    color: #9fe870; border-color: rgba(159, 232, 112, 0.4);
  }
  .relic-card-cost { font-weight: 700; }

  /* Stage picker — 5 cards on the landing screen. Cards wrap to two rows on
     narrow viewports. Selected card pulses the gradient border; locked
     cards dim their contents and show a padlock + the gate-wave hint. */
  #stage-select {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    z-index: 250;
    background: rgba(6, 8, 18, 0.86);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
  }
  #stage-select.visible { display: flex; }
  #stage-select.visible #stage-select-panel {
    animation: stageSelectPanelIn 0.42s cubic-bezier(0.34, 1.4, 0.64, 1) both;
  }
  @keyframes stageSelectPanelIn {
    0%   { transform: scale(0.88) translateY(12px); opacity: 0; }
    100% { transform: scale(1) translateY(0);        opacity: 1; }
  }
  #stage-select-panel {
    width: min(1080px, 94vw);
    max-height: 92vh;
    display: flex; flex-direction: column;
    background: linear-gradient(160deg, rgba(30, 36, 60, 0.96), rgba(22, 18, 40, 0.96));
    border: 1px solid rgba(255, 215, 110, 0.25);
    border-radius: 20px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6),
                0 0 120px rgba(184, 155, 255, 0.08);
    overflow: hidden;
  }
  #stage-select-header {
    display: flex; align-items: center; gap: 18px;
    padding: 20px 24px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  #stage-select-title { font-size: 22px; font-weight: 800; letter-spacing: 1.4px; color: var(--text); }
  #stage-select-subtitle { font-size: 12px; color: var(--text-dim); margin-top: 3px; }
  #stage-select-close {
    margin-left: auto;
    background: none; border: none; color: var(--text-dim);
    font-size: 28px; line-height: 1; cursor: pointer;
    padding: 0 4px; font-family: inherit;
    transition: color 0.15s, transform 0.15s;
  }
  #stage-select-close:hover { color: var(--text); transform: scale(1.1); }

  /* First-run tutorial modal. Shown once — between PLAY and stage-select —
     for players with no prior bestRun. Layout is a compact panel with four
     icon+hint rows and one CTA. Desktop and touch copy both live in the DOM;
     body.touch flips which hint is visible so we don't need JS to pick. */
  #tutorial {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    z-index: 260;
    background: rgba(6, 8, 18, 0.86);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
  }
  #tutorial.visible { display: flex; }
  #tutorial.visible #tutorial-panel {
    animation: tutorialPanelIn 0.42s cubic-bezier(0.34, 1.4, 0.64, 1) both;
  }
  @keyframes tutorialPanelIn {
    0%   { transform: scale(0.9) translateY(14px); opacity: 0; }
    100% { transform: scale(1) translateY(0);      opacity: 1; }
  }
  #tutorial-panel {
    width: min(520px, 92vw);
    max-height: 92vh;
    padding: 26px 26px 22px;
    background: linear-gradient(160deg, rgba(30, 36, 60, 0.96), rgba(22, 18, 40, 0.96));
    border: 1px solid rgba(184, 155, 255, 0.3);
    border-radius: 18px;
    box-shadow: 0 26px 70px rgba(0, 0, 0, 0.6),
                0 0 100px rgba(184, 155, 255, 0.08);
    overflow-y: auto;
  }
  #tutorial-title {
    font-size: 22px; font-weight: 800; letter-spacing: 1.4px;
    color: var(--text);
    text-align: center;
    margin-bottom: 20px;
  }
  #tutorial-body {
    display: flex; flex-direction: column; gap: 14px;
    margin-bottom: 22px;
  }
  #tutorial .tu-row {
    display: flex; align-items: flex-start; gap: 14px;
    padding: 12px 14px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 12px;
  }
  #tutorial .tu-icon {
    flex: 0 0 auto;
    width: 38px; height: 38px;
    display: inline-flex; align-items: center; justify-content: center;
    color: #b89bff;
    background: rgba(184, 155, 255, 0.1);
    border: 1px solid rgba(184, 155, 255, 0.25);
    border-radius: 10px;
  }
  #tutorial .tu-text { flex: 1 1 auto; min-width: 0; }
  #tutorial .tu-name {
    font-size: 13px; font-weight: 700;
    color: var(--text);
    letter-spacing: 0.6px;
    text-transform: uppercase;
    margin-bottom: 4px;
  }
  #tutorial .tu-hint {
    font-size: 13px; line-height: 1.45;
    color: var(--text-dim);
  }
  #tutorial .tu-hint b {
    color: var(--text);
    background: rgba(184, 155, 255, 0.14);
    padding: 1px 6px;
    border-radius: 4px;
    font-weight: 700;
  }
  /* Show the desktop copy by default; body.touch flips to the touch copy. */
  #tutorial .tu-touch { display: none; }
  body.touch #tutorial .tu-desktop { display: none; }
  body.touch #tutorial .tu-touch   { display: block; }

  #tutorial-got-it {
    display: block;
    width: 100%;
    padding: 14px 0;
    font-family: inherit; font-size: 15px; font-weight: 800;
    letter-spacing: 1.4px;
    color: #0b0e17;
    background: linear-gradient(135deg, #7ee2ff 0%, #b89bff 100%);
    border: 1.5px solid rgba(255, 255, 255, 0.35);
    border-radius: 12px;
    cursor: pointer;
    box-shadow: 0 10px 28px rgba(126, 226, 255, 0.45);
    transition: transform 0.15s ease, box-shadow 0.2s ease, filter 0.15s ease;
  }
  #tutorial-got-it:hover,
  #tutorial-got-it:focus-visible {
    transform: translateY(-1px);
    box-shadow: 0 14px 32px rgba(126, 226, 255, 0.6), 0 0 24px rgba(184, 155, 255, 0.6);
    outline: none;
  }
  #tutorial-got-it:active { transform: translateY(0) scale(0.99); }
  #stage-select-grid {
    display: grid; gap: 16px;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    padding: 22px 24px 26px;
    overflow-y: auto;
  }
  /* Hero card — desktop: media (image + lock/CTA) on top, footer (name + diff)
     below. Unlocked images render at full colour; locked images are greyscaled
     so the lock affordance reads clearly without an extra dark gradient.
     Mobile (≤520px): card flips to a compact horizontal row with a small
     thumbnail on the left and text + CTA on the right. */
  .stage-hero-card {
    position: relative;
    display: block;
    border-radius: 14px;
    overflow: hidden;
    cursor: pointer;
    background: #111622;
    border: 1px solid rgba(255, 255, 255, 0.08);
    padding: 0;
    font-family: inherit;
    color: var(--text);
    transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
                border-color 0.2s, box-shadow 0.25s;
    -webkit-tap-highlight-color: transparent;
    /* Staggered entrance — --stage-card-i is set per-card in JS. */
    opacity: 0;
    animation: stageCardIn 0.42s cubic-bezier(0.34, 1.4, 0.64, 1) forwards;
    animation-delay: calc(180ms + var(--stage-card-i, 0) * 55ms);
  }
  @keyframes stageCardIn {
    0%   { opacity: 0; transform: translateY(18px) scale(0.96); }
    100% { opacity: 1; transform: translateY(0)     scale(1);    }
  }
  /* Newest-unlock border — set by renderStageSelect on the highest unlocked
     card. Layered effect: ::before is a blurred conic-gradient aura behind
     the card (big halo), and the card itself paints a sharp conic-gradient
     border via padding-box/border-box compositing. Both gradients share the
     same --stage-card-angle and spin together so the "moving light" reads
     as one continuous sweep with a soft bleed. Browsers without @property
     support still see the same gradient, just static. */
  @property --stage-card-angle {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
  }
  .stage-hero-card.is-newest {
    --stage-card-angle: 0deg;
    border: 3px solid transparent;
    background:
      linear-gradient(#111622, #111622) padding-box,
      conic-gradient(from var(--stage-card-angle),
        #7ee2ff 0deg,
        #ffd36e 90deg,
        #ff9ec4 180deg,
        #b89bff 270deg,
        #7ee2ff 360deg) border-box;
    /* Layered ambient glow. Can't use a blurred pseudo-element behind the
       card because the card sets overflow:hidden for its image clip — the
       pseudo would be cropped. Stacked box-shadows aren't clipped by
       overflow so they're the cheapest way to make the card feel lit. */
    box-shadow: 0 0 18px rgba(126, 226, 255, 0.55),
                0 0 36px rgba(255, 215, 110, 0.35),
                0 0 64px rgba(184, 155, 255, 0.28);
    animation: stageCardIn 0.42s cubic-bezier(0.34, 1.4, 0.64, 1) forwards,
               stageCardBorderSpin 3.6s linear infinite;
  }
  @keyframes stageCardBorderSpin {
    to { --stage-card-angle: 360deg; }
  }
  .stage-hero-card .stage-hero-badge {
    position: absolute; top: 10px; left: 10px;
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    background: linear-gradient(135deg, #ffd36e, #7ee2ff);
    color: #1a1530;
    font-size: 10px; font-weight: 800; letter-spacing: 1.4px;
    box-shadow: 0 6px 18px rgba(126, 226, 255, 0.35);
    pointer-events: none;
  }
  .stage-hero-card .stage-hero-media {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
  }
  .stage-hero-card .stage-hero-image {
    position: absolute; inset: 0;
    background-size: cover; background-position: center bottom;
    transition: transform 0.45s ease, filter 0.25s;
  }
  /* Name + difficulty overlay over the image — absolute-positioned against the
     card (not the media) so the dark scrim paints over the bottom of the
     thumbnail without any separate body block that could be clipped by the
     grid's row track. */
  .stage-hero-card .stage-hero-body {
    position: absolute; left: 0; right: 0; bottom: 0;
    padding: 22px 16px 14px;
    display: flex; flex-direction: column; gap: 3px;
    text-align: left;
    background: linear-gradient(180deg,
      rgba(10, 8, 22, 0) 0%,
      rgba(10, 8, 22, 0.55) 45%,
      rgba(6, 4, 14, 0.92) 100%);
    pointer-events: none;
  }
  .stage-hero-card .stage-hero-name {
    font-size: 17px; font-weight: 800; letter-spacing: 0.6px; color: #ffffff;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.65);
  }
  .stage-hero-card .stage-hero-diff {
    font-size: 10.5px; letter-spacing: 1px; color: #ffd36e;
    font-weight: 700; text-transform: uppercase;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
  }
  .stage-hero-card.locked .stage-hero-diff {
    color: rgba(216, 208, 184, 0.75);
    text-transform: none; letter-spacing: 0.3px; font-weight: 600;
  }
  .stage-hero-card .stage-hero-cta {
    position: absolute; top: 12px; right: 12px;
    padding: 6px 13px;
    border-radius: var(--radius-pill);
    background: rgba(10, 14, 30, 0.78);
    backdrop-filter: blur(6px);
    border: 1px solid rgba(255, 215, 110, 0.55);
    color: #ffd36e;
    font-size: 11px; font-weight: 800; letter-spacing: 1.2px;
    transition: background 0.15s, transform 0.15s;
  }
  .stage-hero-card:hover:not(.locked) { transform: translateY(-3px); border-color: rgba(255, 215, 110, 0.55); box-shadow: 0 14px 34px rgba(255, 155, 196, 0.24); }
  /* Newest card keeps its transparent border on hover so the gradient ring
     stays visible; hover layers additional lift onto the ambient glow. */
  .stage-hero-card.is-newest:hover:not(.locked) {
    border-color: transparent;
    box-shadow: 0 14px 34px rgba(255, 155, 196, 0.28),
                0 0 24px rgba(126, 226, 255, 0.7),
                0 0 48px rgba(255, 215, 110, 0.45),
                0 0 72px rgba(184, 155, 255, 0.32);
  }
  .stage-hero-card:hover:not(.locked) .stage-hero-image { transform: scale(1.05); }
  .stage-hero-card:hover:not(.locked) .stage-hero-cta { background: linear-gradient(135deg, #ffd36e, #ff9ec4 55%, #b89bff); color: #1a1530; border-color: transparent; }
  .stage-hero-card.locked { cursor: not-allowed; }
  .stage-hero-card.locked .stage-hero-image { filter: grayscale(0.85) brightness(0.35); }
  .stage-hero-card.locked .stage-hero-cta { display: none; }
  .stage-hero-card .stage-hero-lock {
    position: absolute; inset: 0;
    display: none;
    align-items: center; justify-content: center;
    color: #d8d0b8;
  }
  .stage-hero-card.locked .stage-hero-lock { display: flex; }
  .stage-hero-card .stage-hero-lock svg {
    width: 30px; height: 30px; opacity: 0.85;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6));
  }
  /* Daily Run tile — surfaces only when ?dailies=1 is set. Uses the same
     stage-hero-card frame so it sits naturally in the grid, but adds a
     gradient sheen + DAILY badge in the prismatic palette so it reads as
     a separate tier from the regular stages. */
  .stage-hero-card.daily-card .stage-hero-name {
    color: #ffe9aa;
  }
  .stage-hero-card.daily-card .stage-hero-diff {
    color: #b89bff;
    text-transform: uppercase;
  }
  .stage-hero-card.daily-card .stage-hero-countdown {
    margin-top: 4px;
    font-size: 10px; letter-spacing: 0.8px;
    color: rgba(216, 208, 184, 0.75);
    font-weight: 600;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .stage-hero-card .daily-badge {
    background: linear-gradient(135deg, #b89bff, #ff9ec4 55%, #ffd36e);
    color: #1a1030;
    box-shadow: 0 6px 18px rgba(184, 155, 255, 0.45);
  }
  .stage-hero-card.daily-card .stage-hero-image::after {
    content: '';
    position: absolute; inset: 0;
    background:
      linear-gradient(120deg,
        rgba(184, 155, 255, 0.18) 0%,
        rgba(126, 226, 255, 0.10) 40%,
        rgba(255, 215, 110, 0.18) 100%);
    mix-blend-mode: screen;
    pointer-events: none;
  }

  /* "Coming soon" placeholder cards — same shape as a locked stage card but
     with a subtle animated aurora instead of a stage thumbnail so it reads as
     a teaser rather than a grayed-out stage the player could have unlocked. */
  .stage-hero-card.coming-soon { cursor: not-allowed; }
  .stage-hero-card.coming-soon .stage-hero-image {
    background: linear-gradient(135deg, #2a1a4a 0%, #3a2060 50%, #4a2870 100%);
    filter: none; opacity: 0.95;
  }
  .stage-hero-card.coming-soon .stage-hero-media::after {
    content: '';
    position: absolute; inset: 0;
    background:
      radial-gradient(ellipse 55% 40% at 30% 30%, rgba(126, 226, 255, 0.28), transparent 70%),
      radial-gradient(ellipse 50% 35% at 70% 70%, rgba(255, 158, 196, 0.22), transparent 70%);
    mix-blend-mode: screen;
    animation: comingSoonAurora 6s ease-in-out infinite alternate;
    pointer-events: none;
  }
  @keyframes comingSoonAurora {
    0%   { transform: translate(-4%, -2%) scale(1.02); }
    100% { transform: translate(4%,  2%) scale(1.08); }
  }
  .stage-hero-card.coming-soon .stage-hero-lock { display: none; }
  .stage-hero-card.coming-soon .stage-hero-cta { display: none; }
  .stage-hero-card.coming-soon .stage-hero-name { color: #d6c7ff; }
  .stage-hero-card.coming-soon .stage-hero-diff {
    color: #7ee2ff; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase;
  }
  .stage-hero-card.coming-soon .stage-hero-badge {
    background: rgba(126, 226, 255, 0.18); color: #7ee2ff;
    border: 1px solid rgba(126, 226, 255, 0.55);
  }
  .stage-hero-card.coming-soon:hover {
    transform: none;
    border-color: rgba(126, 226, 255, 0.35);
    box-shadow: 0 6px 18px rgba(126, 226, 255, 0.12);
  }

  @media (max-width: 520px) {
    #stage-select-panel {
      width: 100vw; height: 100vh; height: 100dvh;
      max-height: 100vh; max-height: 100dvh;
      border-radius: 0; border: none;
    }
    #stage-select-header { padding: 14px 18px 10px; }
    #stage-select-title { font-size: 17px; }
    #stage-select-subtitle { font-size: 11px; }
    /* Flex column (not grid) on mobile so rows size to content — avoids the
       grid's default row stretching when the panel fills the viewport. */
    #stage-select-grid {
      display: flex; flex-direction: column;
      grid-template-columns: none;
      gap: 8px;
      padding: 10px 14px 18px;
      flex: 1 1 auto; min-height: 0; overflow-y: auto;
    }

    /* Compact row: fixed-size thumbnail on the left, body fills the rest.
       Lock/CTA stay absolute-positioned against the card — in the row layout
       they read as "right side of the row". */
    .stage-hero-card {
      display: flex; flex-direction: row; align-items: stretch;
      border-radius: 10px;
      flex-shrink: 0;
    }
    .stage-hero-card .stage-hero-media {
      width: 96px; aspect-ratio: 4 / 3;
      flex-shrink: 0;
    }
    .stage-hero-card .stage-hero-body {
      position: static;
      padding: 10px 64px 10px 14px; /* right padding reserves space for CTA/lock */
      justify-content: center;
      border-top: none;
      background: linear-gradient(90deg, rgba(28, 24, 52, 0.95), rgba(18, 16, 36, 0.98));
      flex: 1 1 auto; min-width: 0;
    }
    .stage-hero-card .stage-hero-name { font-size: 14.5px; }
    .stage-hero-card .stage-hero-diff { font-size: 10.5px; }
    .stage-hero-card .stage-hero-cta {
      top: 50%; right: 12px; transform: translateY(-50%);
      padding: 6px 13px; font-size: 10.5px;
      background: linear-gradient(135deg, #ffd36e, #ff9ec4 55%, #b89bff);
      color: #1a1530;
      border-color: transparent;
    }
    .stage-hero-card:hover:not(.locked) { transform: none; }
    .stage-hero-card:hover:not(.locked) .stage-hero-image { transform: none; }
    .stage-hero-card .stage-hero-lock svg { width: 22px; height: 22px; }
  }

  /* Touch-only warning — becomes visible via JS detection */
  #requirements.unsupported {
    border-color: rgba(255, 168, 102, 0.55);
    background: linear-gradient(145deg, rgba(80, 40, 30, 0.85), rgba(40, 22, 30, 0.85));
  }
  #touch-warning {
    margin-top: 16px;
    color: var(--amber);
    font-size: 13px; max-width: 360px; line-height: 1.5;
    font-weight: 500;
  }
  #overlay button, #gameover button {
    color: #1a1530; border: none; border-radius: var(--radius-pill);
    padding: 15px 44px; font-size: 16px; cursor: pointer;
    font-family: inherit; letter-spacing: 0.5px; font-weight: 700;
    transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
                box-shadow 0.25s;
  }
  #overlay button {
    background: linear-gradient(135deg, #ffd36e, #ff9ec4 55%, #b89bff);
    box-shadow: 0 8px 26px rgba(184, 155, 255, 0.35);
  }
  #overlay button:hover, #overlay button:focus-visible {
    transform: translateY(-2px) scale(1.02);
    box-shadow: 0 12px 34px rgba(184, 155, 255, 0.5);
    outline: none;
  }

  #gameover {
    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
    background: radial-gradient(ellipse at center, rgba(80, 30, 50, 0.9), rgba(14, 18, 34, 0.96));
    display: none; flex-direction: column;
    align-items: center; justify-content: center; z-index: 200;
    overflow: hidden;
    opacity: 0;
  }
  /* Rising photon backdrop — lives behind every card on the game-over /
     victory screen. Each .gameover-photon is absolutely-positioned at
     random x near the bottom and uses its own infinite `photon-rise`
     animation. --photon-boost (0-1) is written by a rAF loop in
     src/game-over.js, tapping the music's bass level for tempo-sync.
     The boost scales brightness + pulls speed slightly so the layer
     "breathes" with the track. */
  #gameover #gameover-photons {
    position: absolute; inset: 0; pointer-events: none;
    z-index: 0; overflow: hidden;
    --photon-boost: 0;
  }
  #gameover > * { position: relative; z-index: 1; }
  #gameover > #gameover-photons { z-index: 0; }
  .gameover-photon {
    position: absolute; bottom: -30px;
    border-radius: 50%;
    /* Soft halo via radial gradient — much cheaper than box-shadow because
       it uses the photon's own background, no blur overdraw. */
    background: radial-gradient(circle,
      rgba(255, 240, 200, calc(0.9 * var(--ph-bright, 0.8) + 0.5 * var(--photon-boost, 0))) 0%,
      rgba(255, 200, 120, calc(0.5 * var(--ph-bright, 0.8) + 0.3 * var(--photon-boost, 0))) 45%,
      rgba(255, 180, 90, 0) 75%);
    box-shadow: 0 0 calc(6px + 10px * var(--photon-boost, 0))
                rgba(255, 210, 140, calc(0.4 + 0.4 * var(--photon-boost, 0)));
    opacity: 0;
    animation: photon-rise linear infinite;
    /* `animation-play-state` + a CSS variable technique lets --photon-boost
       tug the rise a hair faster on the beat. Calc on duration is not
       standard, so we leave duration as a fixed inline style and let the
       boost amplify brightness only. */
  }
  /* Tier-tinted photons for win vs loss. */
  #gameover.survived .gameover-photon {
    background: radial-gradient(circle,
      rgba(200, 255, 200, calc(0.9 * var(--ph-bright, 0.8) + 0.5 * var(--photon-boost, 0))) 0%,
      rgba(120, 240, 180, calc(0.5 * var(--ph-bright, 0.8) + 0.3 * var(--photon-boost, 0))) 45%,
      rgba(90, 200, 160, 0) 75%);
    box-shadow: 0 0 calc(6px + 10px * var(--photon-boost, 0))
                rgba(140, 240, 170, calc(0.4 + 0.4 * var(--photon-boost, 0)));
  }
  #gameover.defeated .gameover-photon {
    background: radial-gradient(circle,
      rgba(255, 200, 200, calc(0.9 * var(--ph-bright, 0.8) + 0.5 * var(--photon-boost, 0))) 0%,
      rgba(255, 110, 130, calc(0.5 * var(--ph-bright, 0.8) + 0.3 * var(--photon-boost, 0))) 45%,
      rgba(200, 60, 80, 0) 75%);
    box-shadow: 0 0 calc(6px + 10px * var(--photon-boost, 0))
                rgba(255, 140, 150, calc(0.4 + 0.4 * var(--photon-boost, 0)));
  }
  @keyframes photon-rise {
    0%   { transform: translate3d(0, 0, 0) scale(0.3); opacity: 0; }
    8%   { opacity: 1; transform: translate3d(0, -10vh, 0) scale(0.6); }
    50%  { opacity: 1; transform: translate3d(0, -55vh, 0) scale(1); }
    92%  { opacity: 0.8; transform: translate3d(0, -105vh, 0) scale(1.1); }
    100% { opacity: 0; transform: translate3d(0, -115vh, 0) scale(1.1); }
  }
  @media (prefers-reduced-motion: reduce) {
    .gameover-photon { animation: none; opacity: 0; }
  }
  /* Survived (victory) — gold/green radial with a nebula shimmer. */
  #gameover.survived {
    background:
      radial-gradient(ellipse at center, rgba(255, 211, 110, 0.18), transparent 55%),
      radial-gradient(ellipse at 40% 30%, rgba(159, 232, 112, 0.20), transparent 50%),
      radial-gradient(ellipse at center, rgba(28, 38, 30, 0.92), rgba(10, 14, 22, 0.96));
  }
  /* Defeated — crimson-on-black with a subtle vignette. */
  #gameover.defeated {
    background:
      radial-gradient(ellipse at center, rgba(255, 60, 80, 0.14), transparent 50%),
      radial-gradient(ellipse at center, rgba(60, 20, 28, 0.94), rgba(10, 14, 22, 0.98));
  }
  /* One-shot container reveal — scale-pop for victory, impact-shake for loss.
     Each child block gets its own delay so the reveal cascades top-down:
     title → hero score → stat chips → earned row → CTAs → secondary links. */
  #gameover.revealing { animation: goFadeIn 280ms ease-out forwards; }
  #gameover.survived.revealing > h1,
  #gameover.survived.revealing > #gameover-hero,
  #gameover.survived.revealing > #gameover-stats,
  #gameover.survived.revealing > #gameover-earned,
  #gameover.survived.revealing > #gameover-card-preview,
  #gameover.survived.revealing > #gameover-actions,
  #gameover.survived.revealing > #gameover-secondary { animation-name: goChildPopIn; }
  #gameover.defeated.revealing > h1 { animation: goTitleSlam 620ms cubic-bezier(0.3, 0.1, 0.2, 1) both; }
  #gameover.defeated.revealing > #gameover-hero,
  #gameover.defeated.revealing > #gameover-stats,
  #gameover.defeated.revealing > #gameover-earned,
  #gameover.defeated.revealing > #gameover-card-preview,
  #gameover.defeated.revealing > #gameover-actions,
  #gameover.defeated.revealing > #gameover-secondary { animation-name: goChildFadeUp; }
  #gameover.revealing > h1                       { animation-duration: 620ms; animation-fill-mode: both; }
  #gameover.revealing > #gameover-hero           { animation-duration: 560ms; animation-fill-mode: both; animation-delay: 180ms; }
  #gameover.revealing > #gameover-stats          { animation-duration: 520ms; animation-fill-mode: both; animation-delay: 320ms; }
  #gameover.revealing > #gameover-earned         { animation-duration: 500ms; animation-fill-mode: both; animation-delay: 440ms; }
  #gameover.revealing > #gameover-card-preview   { animation-duration: 520ms; animation-fill-mode: both; animation-delay: 560ms; }
  #gameover.revealing > #gameover-actions        { animation-duration: 520ms; animation-fill-mode: both; animation-delay: 720ms; }
  #gameover.revealing > #gameover-secondary      { animation-duration: 480ms; animation-fill-mode: both; animation-delay: 860ms; }

  @keyframes goFadeIn   { from { opacity: 0; } to { opacity: 1; } }
  @keyframes goChildPopIn {
    0%   { opacity: 0; transform: translateY(22px) scale(0.82); }
    60%  { opacity: 1; transform: translateY(-4px) scale(1.06); }
    100% { opacity: 1; transform: translateY(0)    scale(1);    }
  }
  @keyframes goChildFadeUp {
    0%   { opacity: 0; transform: translateY(14px); }
    100% { opacity: 1; transform: translateY(0);    }
  }
  @keyframes goTitleSlam {
    0%   { opacity: 0; transform: scale(1.8); letter-spacing: 14px; filter: blur(6px); }
    55%  { opacity: 1; transform: scale(0.96); letter-spacing: 0px;  filter: blur(0);    }
    62%  { transform: translateX(-8px); }
    68%  { transform: translateX(8px);  }
    74%  { transform: translateX(-4px); }
    80%  { transform: translateX(4px);  }
    100% { transform: scale(1) translateX(0); filter: blur(0); }
  }
  /* Keep content above the confetti layer (position:absolute shards). */
  #gameover > h1,
  #gameover > #gameover-hero,
  #gameover > #gameover-stats,
  #gameover > #gameover-earned,
  #gameover > #gameover-card-preview,
  #gameover > #gameover-actions,
  #gameover > #gameover-secondary { position: relative; z-index: 2; }

  /* Auto-rendered share-card preview slot. Sits between the earned row and
     the action buttons — the player sees the artifact they can share before
     they see SHARE/PLAY AGAIN. Fixed aspect ratio matches the 1200×630 the
     card is rendered at so the card never distorts when the image resolves.
     Empty state collapses; filled state constrains by width. */
  #gameover-card-preview {
    width: min(420px, 80vw);
    aspect-ratio: 1200 / 630;
    margin: 0 auto 18px;
    border-radius: 14px;
    overflow: hidden;
    background: rgba(12, 16, 32, 0.55);
    border: 1px solid rgba(184, 155, 255, 0.22);
    box-shadow: 0 18px 44px rgba(10, 12, 26, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.08);
    display: none;
  }
  #gameover-card-preview.has-card,
  #gameover-card-preview.rendering { display: block; }
  #gameover-card-preview img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 320ms ease-out;
  }
  #gameover-card-preview.has-card img { opacity: 1; }
  /* Shimmer placeholder while the canvas renders. */
  #gameover-card-preview.rendering:not(.has-card) {
    background:
      linear-gradient(110deg,
        rgba(184, 155, 255, 0.06) 0%,
        rgba(184, 155, 255, 0.18) 45%,
        rgba(184, 155, 255, 0.06) 70%) 0 0 / 220% 100% no-repeat,
      rgba(12, 16, 32, 0.55);
    animation: cardPreviewShimmer 1.2s linear infinite;
  }
  @keyframes cardPreviewShimmer {
    from { background-position: 120% 0, 0 0; }
    to   { background-position: -20% 0,  0 0; }
  }
  @media (prefers-reduced-motion: reduce) {
    #gameover-card-preview.rendering:not(.has-card) { animation: none; }
    #gameover-card-preview img { transition: none; }
  }
  #gameover h1 {
    font-size: 60px; margin-bottom: 18px; font-weight: 800; letter-spacing: 1px;
    background: linear-gradient(120deg, #ff8a8a, #ffa866);
    -webkit-background-clip: text; background-clip: text; color: transparent;
  }
  #gameover.survived h1 {
    background: linear-gradient(120deg, #ffd36e, #9fe870 55%, #7ee2ff);
    -webkit-background-clip: text; background-clip: text; color: transparent;
    filter: drop-shadow(0 6px 28px rgba(255, 211, 110, 0.5));
  }
  #gameover.defeated h1 {
    background: linear-gradient(120deg, #ff6b6b, #ff9e7a);
    -webkit-background-clip: text; background-clip: text; color: transparent;
    filter: drop-shadow(0 4px 22px rgba(255, 60, 80, 0.45));
  }
  /* Hero — single big score right under the title. The score is the one
     saturated number on the card; every other stat is neutral-text dim. */
  #gameover-hero {
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    margin: 0 0 20px;
  }
  #gameover-hero .go-score-label {
    font-size: 10.5px; letter-spacing: 2.4px; text-transform: uppercase;
    color: var(--text-dim);
  }
  #gameover-hero .go-score {
    font-size: 64px; font-weight: 800; letter-spacing: 0.5px;
    line-height: 1; font-variant-numeric: tabular-nums;
    color: #ffd36e;
    filter: drop-shadow(0 6px 26px rgba(255, 211, 110, 0.32));
  }
  #gameover.defeated #gameover-hero .go-score {
    color: #ffa866;
    filter: drop-shadow(0 6px 26px rgba(255, 138, 138, 0.32));
  }
  #gameover-hero .go-badges {
    display: flex; flex-wrap: wrap; justify-content: center; gap: 8px;
    margin-top: 4px;
  }
  /* #gameover scoping beats the blanket `#gameover button { ... }` rule so
     the badges don't inherit the PLAY AGAIN gradient / padding / font-size. */
  #gameover .go-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 12px; border-radius: var(--radius-pill);
    font-family: inherit;
    font-size: 11px; font-weight: 800; letter-spacing: 1.3px;
    color: var(--text-dim);
    background: rgba(36, 22, 48, 0.44);
    border: 1px solid rgba(255, 255, 255, 0.14);
    box-shadow: none;
    /* Button-cancel: undo the blanket #gameover button transform/hover. */
    transform: none;
  }
  #gameover .go-badge.best {
    color: #ffd36e;
    border-color: rgba(255, 211, 110, 0.55);
    background: rgba(60, 44, 14, 0.55);
  }
  #gameover button.go-badge.rank {
    color: #d1aeff;
    border-color: rgba(209, 174, 255, 0.45);
    background: rgba(48, 30, 64, 0.55);
    cursor: pointer;
    transition: background 0.18s, border-color 0.18s, transform 0.18s;
  }
  #gameover button.go-badge.rank:hover,
  #gameover button.go-badge.rank:focus-visible {
    background: rgba(68, 46, 88, 0.7);
    border-color: rgba(209, 174, 255, 0.8);
    transform: translateY(-1px);
    box-shadow: none;
    outline: none;
  }
  /* Ensure [hidden] wins against any other rule (defensive — the blanket
     #gameover button styling has burned us on similar overrides before). */
  #gameover .go-badge[hidden] { display: none; }
  /* Hide the whole badges row when it has no visible children so it doesn't
     eat vertical space. Uses :has() where available, harmless otherwise. */
  #gameover-hero .go-badges:not(:has(> :not([hidden]))) { display: none; }

  /* Chip strip — three compact stats (Wave / Kills / Time) stacked as
     label-under-value. Overrides the previous text-block styling of
     #gameover-stats. */
  #gameover-stats {
    color: var(--text-dim); margin: 0 0 16px;
    text-align: center; line-height: 1.2;
  }
  .go-stat-strip {
    display: flex; justify-content: center; gap: 24px; flex-wrap: wrap;
  }
  .go-chip {
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    min-width: 64px;
  }
  .go-chip .go-chip-v {
    font-size: 22px; font-weight: 700; color: var(--text);
    font-variant-numeric: tabular-nums; line-height: 1;
  }
  .go-chip .go-chip-l {
    font-size: 10px; letter-spacing: 1.8px; text-transform: uppercase;
    color: var(--text-dim);
  }

  /* Earned meta — quiet single row: LVL, combo (≥3 only), relics/prev-best. */
  #gameover-earned {
    display: flex; flex-wrap: wrap; justify-content: center; gap: 14px;
    font-size: 12px; letter-spacing: 0.5px; color: var(--text-dim);
    margin: 0 0 24px;
  }
  #gameover-earned:empty { display: none; }
  #gameover-earned .go-earned-relic { color: #7ee2ff; font-weight: 700; }
  #gameover-earned .go-earned-combo { color: #ffd36e; font-weight: 700; }

  #gameover button {
    background: linear-gradient(135deg, #ff8a8a, #ffa866);
    color: #2a1010;
    box-shadow: 0 8px 26px rgba(255, 138, 138, 0.35);
  }
  #gameover button:hover, #gameover button:focus-visible {
    transform: translateY(-2px) scale(1.02);
    box-shadow: 0 12px 34px rgba(255, 138, 138, 0.5);
    outline: none;
  }
  /* PLAY AGAIN is demoted to a ghost button — translucent fill, bordered in
     the survived/defeated accent colour. Keeps it obviously clickable without
     competing with SHARE for attention. */
  #gameover #restart-btn {
    background: rgba(255, 168, 102, 0.08) !important;
    color: #ffc9a8 !important;
    border: 1.5px solid rgba(255, 138, 138, 0.55) !important;
    box-shadow: none !important;
  }
  #gameover #restart-btn:hover,
  #gameover #restart-btn:focus-visible {
    background: rgba(255, 168, 102, 0.16) !important;
    border-color: rgba(255, 138, 138, 0.85) !important;
  }
  #gameover.survived #restart-btn {
    background: rgba(159, 232, 112, 0.08) !important;
    color: #c4eea8 !important;
    border: 1.5px solid rgba(159, 232, 112, 0.55) !important;
    box-shadow: none !important;
  }
  #gameover.survived #restart-btn:hover,
  #gameover.survived #restart-btn:focus-visible {
    background: rgba(159, 232, 112, 0.16) !important;
    border-color: rgba(159, 232, 112, 0.85) !important;
  }
  /* Primary CTAs — SHARE (solid) + PLAY AGAIN (ghost), equal width, side by
     side. SHARE leads visually so the social loop is the loud call. On mobile
     they stack vertically and SHARE sits on top. */
  #gameover-actions {
    display: flex; gap: 24px; flex-wrap: wrap; justify-content: center;
    width: min(440px, 92vw); margin: 0 auto 14px;
    position: relative; z-index: 2;
  }
  #gameover-actions > button {
    flex: 1 1 160px; min-width: 150px;
    display: inline-flex; align-items: center; justify-content: center;
    gap: 8px;
  }

  /* Secondary row — text-only links for View Rankings + Title Screen. Demoted
     from full buttons so they recede behind the two primary CTAs. */
  #gameover-secondary {
    display: flex; justify-content: center; gap: 18px; flex-wrap: wrap;
  }
  #gameover-secondary button {
    background: transparent !important;
    border: none !important;
    color: var(--text-dim) !important;
    font-size: 12px; font-weight: 600; letter-spacing: 0.8px;
    padding: 6px 10px !important;
    box-shadow: none !important;
    cursor: pointer;
    transition: color 0.15s ease;
  }
  #gameover-secondary button:hover,
  #gameover-secondary button:focus-visible {
    color: #d1aeff !important;
    transform: none !important;
    box-shadow: none !important;
    outline: none;
  }

  /* SHARE button on game-over. Primary CTA — solid gradient, pulsing aura,
     stronger shadow than PLAY AGAIN (which is a ghost) so the visual weight
     unambiguously favours the social loop. The .busy state dims the button
     while navigator.share is open, and .share-flash is a transient "SHARED"
     pill that fades in when the share resolves OK. */
  #share-run-btn {
    position: relative; overflow: visible;
    background: linear-gradient(135deg, #7ee2ff 0%, #b89bff 100%) !important;
    color: #0b0e17 !important;
    border: 1.5px solid rgba(255, 255, 255, 0.4) !important;
    box-shadow: 0 10px 30px rgba(126, 226, 255, 0.5), 0 0 0 0 rgba(126, 226, 255, 0);
    font-weight: 800;
    letter-spacing: 0.8px;
    transition: transform 0.15s ease, box-shadow 0.25s ease, filter 0.15s ease;
    animation: shareIdlePulse 2.4s ease-in-out infinite;
  }
  #share-run-btn:hover,
  #share-run-btn:focus-visible {
    transform: translateY(-1px);
    box-shadow: 0 12px 36px rgba(126, 226, 255, 0.65), 0 0 28px rgba(184, 155, 255, 0.7);
  }
  #share-run-btn.busy { filter: saturate(0.85) brightness(0.95); pointer-events: none; animation: none; }
  #share-run-btn .share-btn-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 20px; height: 20px; color: currentColor;
  }
  /* Busy/idle label swap — the button always reserves the idle label's space
     (so it doesn't collapse while rendering), and CSS toggles which label is
     visible based on .busy. Keeps a single button box without layout thrash. */
  #share-run-btn .share-btn-content,
  #share-run-btn .share-btn-busy {
    display: inline-flex; align-items: center; justify-content: center;
    gap: 8px;
  }
  #share-run-btn .share-btn-busy { display: none; }
  #share-run-btn.busy .share-btn-content { display: none; }
  #share-run-btn.busy .share-btn-busy { display: inline-flex; }
  #share-run-btn .share-btn-spinner {
    width: 14px; height: 14px; border-radius: 50%;
    border: 2px solid rgba(11, 14, 23, 0.25);
    border-top-color: #0b0e17;
    animation: shareBtnSpin 0.75s linear infinite;
  }
  @keyframes shareBtnSpin { to { transform: rotate(360deg); } }
  .share-flash {
    position: absolute; left: 50%; top: -14px;
    transform: translate(-50%, -4px);
    font-size: 11px; font-weight: 700; letter-spacing: 1.2px;
    color: #0b0e17;
    background: #ffd36e;
    padding: 3px 10px; border-radius: 999px;
    box-shadow: 0 4px 12px rgba(255, 211, 110, 0.55);
    animation: shareFlashIn 1.6s ease forwards;
    pointer-events: none;
  }
  @keyframes shareIdlePulse {
    0%, 100% { box-shadow: 0 10px 30px rgba(126, 226, 255, 0.5),  0 0 0 0 rgba(126, 226, 255, 0); }
    50%      { box-shadow: 0 10px 30px rgba(126, 226, 255, 0.62), 0 0 0 8px rgba(126, 226, 255, 0.18); }
  }
  @keyframes shareFlashIn {
    0%   { opacity: 0; transform: translate(-50%, 4px); }
    15%  { opacity: 1; transform: translate(-50%, -14px); }
    80%  { opacity: 1; transform: translate(-50%, -14px); }
    100% { opacity: 0; transform: translate(-50%, -22px); }
  }

  /* Taunt banner — rendered on the landing page when ?s=… query params are
     present (shared-run link). Floats over the top of the landing overlay
     and dismisses either via the × button or by clicking PLAY. */
  #taunt-banner {
    position: fixed; top: 16px; left: 50%;
    transform: translate(-50%, -110%);
    max-width: min(540px, 92vw);
    padding: 10px 16px 10px 20px;
    background: linear-gradient(135deg, rgba(126, 226, 255, 0.18), rgba(184, 155, 255, 0.18));
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 14px;
    box-shadow: 0 10px 30px rgba(10, 14, 30, 0.55);
    color: #e8ecff;
    font-size: 14px;
    letter-spacing: 0.2px;
    z-index: 250;
    transition: transform 0.42s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.32s ease;
    opacity: 0;
  }
  #taunt-banner.visible { transform: translate(-50%, 0); opacity: 1; }
  #taunt-banner.hiding  { transform: translate(-50%, -110%); opacity: 0; }
  #taunt-banner .taunt-inner {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  }
  #taunt-banner .taunt-who   { font-weight: 700; color: #ffd36e; }
  #taunt-banner .taunt-score { color: #7ee2ff; font-variant-numeric: tabular-nums; }
  #taunt-banner .taunt-stage { color: #b89bff; }
  #taunt-banner .taunt-cta   { color: #9fe870; font-weight: 700; margin-left: 4px; }
  #taunt-banner .taunt-dismiss {
    margin-left: auto;
    background: transparent; border: none; color: #808a9d;
    font-size: 20px; line-height: 1; width: 24px; height: 24px;
    cursor: pointer; padding: 0;
    transition: color 0.15s ease;
  }
  #taunt-banner .taunt-dismiss:hover { color: #fff; }
  @media (max-width: 520px) {
    #taunt-banner { top: 10px; font-size: 13px; padding: 9px 12px 9px 14px; }
    #taunt-banner .taunt-inner { gap: 6px; }
  }

  /* Victory confetti — spawned by _spawnVictoryConfetti. Purely decorative,
     pointer-events disabled so clicks still pass through to the buttons. */
  .gameover-confetti {
    position: absolute; top: -28px;
    width: 10px; height: 14px; border-radius: 2px;
    opacity: 0.9; pointer-events: none; z-index: 1;
    animation-name: confettiFall;
    animation-timing-function: cubic-bezier(0.3, 0.7, 0.4, 1);
    animation-fill-mode: forwards;
    will-change: transform, opacity;
  }
  @keyframes confettiFall {
    0%   { transform: translateY(-12vh) rotate(0deg);  opacity: 0; }
    12%  { opacity: 0.95; }
    100% { transform: translateY(112vh) rotate(720deg); opacity: 0; }
  }

  #minimap {
    position: fixed; bottom: 84px; right: 22px;
    width: 128px; height: 128px;
    background: rgba(18, 24, 44, 0.55);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid var(--card-border);
    border-radius: var(--radius-lg); overflow: hidden; pointer-events: none;
    box-shadow: 0 4px 18px rgba(10, 14, 30, 0.35);
  }
  #minimap canvas { width: 100%; height: 100%; }

  .active-pu {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: baseline;
    gap: 10px;
    min-width: 180px;
    background: var(--card-bg);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid rgba(184, 155, 255, 0.35);
    border-radius: var(--radius-pill);
    padding: 5px 14px; color: var(--lilac);
    font-size: 11.5px; font-weight: 600; letter-spacing: 0.4px;
    text-align: left;
  }
  .active-pu .pu-label { color: rgba(230, 224, 255, 0.7); font-weight: 500; text-transform: uppercase; font-size: 10px; letter-spacing: 0.8px; }
  .active-pu .pu-val   { color: var(--lilac); }
  .active-pu .pu-n     { color: rgba(184, 155, 255, 0.65); font-size: 10.5px; }
  .active-pu.maxed {
    border-color: rgba(255, 215, 0, 0.65);
    background: rgba(255, 215, 0, 0.08);
  }
  .active-pu.maxed .pu-label { color: rgba(255, 215, 0, 0.75); }
  .active-pu.maxed .pu-val,
  .active-pu.maxed .pu-n { color: #ffd700; }

  /* Pause — sits in the ability bar alongside Dash / Overdrive / Shield.
     Clickable even on desktop (overrides #ability-bar's pointer-events:none). */
  #pause-btn {
    pointer-events: auto;
    cursor: pointer;
    touch-action: manipulation;
    transition: background 0.15s, border-color 0.15s, transform 0.08s;
    display: none;
    font: inherit;
  }
  #pause-btn:hover  { background: rgba(255,255,255,0.06); border-color: rgba(184, 155, 255, 0.6); }
  #pause-btn:active { transform: scale(0.96); }
  body.playing #pause-btn { display: flex; }

  /* Pause-screen controls panel — replaces the always-on #controls-hint */
  #pause-controls {
    margin: 0 -4px 18px;
    padding: 12px 14px;
    background: rgba(10, 14, 30, 0.35);
    border: 1px solid rgba(184, 155, 255, 0.2);
    border-radius: var(--radius-md);
    color: rgba(246, 248, 254, 0.82);
    font-size: 12px; line-height: 1.7; text-align: left;
  }
  #pause-controls .pc-title {
    text-transform: uppercase; letter-spacing: 1.2px;
    font-size: 10px; color: rgba(246, 248, 254, 0.55);
    margin-bottom: 6px;
  }
  #pause-controls kbd {
    display: inline-block; padding: 1px 6px; margin: 0 2px;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(184, 155, 255, 0.3);
    border-radius: 4px; font-family: inherit; font-size: 11px;
    color: var(--lilac);
  }
  .pc-desktop { display: block; }
  .pc-touch   { display: none; }
  body.touch .pc-desktop { display: none; }
  body.touch .pc-touch   { display: block; }

  /* Stage loading overlay — shown during the synchronous teardown + rebuild
     of setSelectedStage / restartGame. Uses z-index above every other overlay
     so it covers the old scene while the new one builds. z-index must beat
     the landing #overlay (200), leaderboard modals (270), and the progress-
     sync card (280) — this loader is the topmost thing in the app. */
  #stage-loading {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    background: rgba(6, 4, 20, 0.88);
    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
    z-index: 9999;
    padding: 24px;
  }
  #stage-loading.visible { display: flex; }
  #stage-loading .sl-card {
    max-width: 340px; width: 100%;
    background: rgba(20, 18, 46, 0.9);
    border: 1px solid rgba(184, 155, 255, 0.35);
    border-radius: 18px;
    padding: 30px 28px;
    color: var(--lilac);
    text-align: center;
    display: flex; flex-direction: column; align-items: center; gap: 14px;
  }
  #stage-loading .sl-spinner {
    width: 44px; height: 44px; border-radius: 50%;
    border: 3px solid rgba(184, 155, 255, 0.25);
    border-top-color: var(--lilac);
    animation: sl-spin 0.9s linear infinite;
  }
  @keyframes sl-spin { to { transform: rotate(360deg); } }
  #stage-loading .sl-title {
    font-size: 11px; letter-spacing: 2.2px;
    text-transform: uppercase;
    color: rgba(246, 248, 254, 0.62);
  }
  #stage-loading .sl-stage {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 22px; font-weight: 800;
    letter-spacing: 1.2px;
    color: #f6f8fe;
  }

  /* Pause overlay — PAUSED card with acquired powerups */
  #pause-screen {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    background: rgba(8, 6, 24, 0.82);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    z-index: 90;
    padding: 24px;
  }
  #pause-screen.visible { display: flex; }
  #pause-screen .pause-card {
    max-width: 400px; width: 100%;
    max-height: 82vh; overflow-y: auto;
    background: rgba(20, 18, 46, 0.82);
    border: 1px solid rgba(184, 155, 255, 0.35);
    border-radius: 18px;
    padding: 26px 28px; color: var(--lilac);
    text-align: center;
  }
  #pause-screen h2 {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 32px; margin: 0 0 4px; letter-spacing: 2px;
    color: #f6f8fe;
  }
  #pause-screen .pause-sub {
    color: rgba(246,248,254,0.64);
    font-size: 11px; margin-bottom: 18px;
    letter-spacing: 1.5px; text-transform: uppercase;
  }
  #pause-audio {
    display: flex; align-items: center; gap: 12px;
    margin-bottom: 18px;
    padding: 10px 14px;
    border-radius: 12px;
    background: rgba(255,255,255,0.03);
    border: 1px solid rgba(255,255,255,0.06);
  }
  #pause-audio #mute-btn {
    background: transparent; color: var(--text); border: none;
    font-size: 18px; cursor: pointer; width: 34px; height: 34px;
    border-radius: 50%; display: flex; align-items: center; justify-content: center;
    transition: background 0.15s;
  }
  #pause-audio #mute-btn:hover { background: rgba(255,255,255,0.08); }
  #pause-audio #mute-btn[aria-pressed="true"] { color: #808a9d; }
  #pause-audio #volume-slider {
    flex: 1; accent-color: #5dd6c8;
  }
  #pause-audio #volume-readout {
    min-width: 42px; text-align: right; color: var(--text-dim);
    font-variant-numeric: tabular-nums; font-size: 12px;
  }
  #pause-powerups { display: flex; flex-direction: column; gap: 7px; margin-bottom: 22px; }
  #pause-powerups:empty::before {
    content: 'No powerups yet.';
    color: rgba(246,248,254,0.5);
    font-size: 13px; letter-spacing: 0.5px;
  }
  #pause-powerups .active-pu { min-width: 0; }
  #pause-resume {
    background: var(--lilac); color: #120e2a;
    border: none; border-radius: 999px;
    padding: 10px 32px; font-weight: 700; letter-spacing: 1.2px;
    text-transform: uppercase; cursor: pointer; font-size: 13px;
    touch-action: manipulation;
  }
  #pause-resume:hover { filter: brightness(1.1); }

  /* Ability bar — bottom-left, tucked under controls hint */
  #ability-bar {
    position: fixed; bottom: 18px; left: 18px;
    display: flex; gap: 10px; pointer-events: none;
  }
  /* Ability tiles — per-skill accent color drives the gradient, icon hue,
     border glow and pressed-state ripple. Each tile is a small "game card":
     dark translucent base, subtle inner-bevel highlight, accent-colored
     top sheen, and a cooldown wedge that sweeps clockwise. The :active
     ripple feeds back on click/tap so the button feels clicky even when
     its action isn't available yet (greyed out via .cd class). */
  .ability {
    --acc:        var(--lilac);
    --acc-rgb:    184, 155, 255;
    --acc-tint:   rgba(var(--acc-rgb), 0.22);
    position: relative; width: 72px; height: 72px;
    background:
      radial-gradient(120% 90% at 50% -20%, rgba(var(--acc-rgb), 0.28) 0%, rgba(var(--acc-rgb), 0) 55%),
      linear-gradient(180deg, rgba(26, 32, 54, 0.82) 0%, rgba(14, 18, 34, 0.88) 100%);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(var(--acc-rgb), 0.55);
    border-radius: var(--radius-md);
    box-shadow:
      0 6px 18px rgba(0, 0, 0, 0.35),
      0 0 0 0 rgba(var(--acc-rgb), 0),
      inset 0 1px 0 rgba(255, 255, 255, 0.14),
      inset 0 -12px 24px rgba(0, 0, 0, 0.28);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    color: rgba(246, 248, 254, 0.92); font-weight: 700; font-size: 10px; letter-spacing: 0.6px;
    text-transform: uppercase;
    overflow: hidden;
    transition: transform 0.12s ease, box-shadow 0.2s ease, border-color 0.2s ease, filter 0.15s ease;
    cursor: pointer;
  }
  /* Glossy sheen across the top edge — fakes the beveled plastic look of
     classic RPG hotbar icons without an extra element. */
  .ability::before {
    content: '';
    position: absolute; left: 6%; right: 6%; top: 4px; height: 26%;
    border-radius: 12px 12px 22px 22px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0) 100%);
    pointer-events: none;
    opacity: 0.8;
  }
  .ability:hover {
    transform: translateY(-1px);
    border-color: rgba(var(--acc-rgb), 0.8);
    box-shadow:
      0 10px 24px rgba(0, 0, 0, 0.45),
      0 0 22px rgba(var(--acc-rgb), 0.35),
      inset 0 1px 0 rgba(255, 255, 255, 0.18),
      inset 0 -12px 24px rgba(0, 0, 0, 0.25);
    filter: brightness(1.08);
  }
  .ability:active { transform: translateY(0) scale(0.96); filter: brightness(1.15); }
  .ability .ab-ico  {
    color: var(--acc); line-height: 1;
    font-size: 24px; margin-bottom: 2px;
    display: inline-flex; align-items: center; justify-content: center;
    filter:
      drop-shadow(0 0 8px rgba(var(--acc-rgb), 0.65))
      drop-shadow(0 2px 0 rgba(0, 0, 0, 0.35));
  }
  .ability .ab-name {
    font-size: 9.5px; opacity: 0.92;
    color: rgba(255, 255, 255, 0.94);
    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
  }
  .ability .ab-key {
    position: absolute; top: 5px; right: 6px;
    font-size: 9px; font-weight: 700; letter-spacing: 0.5px;
    padding: 1px 5px; border-radius: 6px;
    color: rgba(255, 255, 255, 0.78);
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(var(--acc-rgb), 0.28);
    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
  }

  /* Per-ability accent palette. --acc drives the color-related vars above. */
  .ability.ability-dash   { --acc: #5de3ff; --acc-rgb: 93, 227, 255; }  /* motion cyan */
  .ability.ability-over   { --acc: #ffb454; --acc-rgb: 255, 180, 84; }  /* fire amber */
  .ability.ability-shield { --acc: #b89bff; --acc-rgb: 184, 155, 255; } /* defense lilac */
  .ability.ability-pause  { --acc: #c6cee0; --acc-rgb: 198, 206, 224; } /* neutral */
  /* Radial cooldown indicator — conic-gradient sweeps from 0° (12 o'clock)
     clockwise, covering the ability icon with a darkened wedge that shrinks
     as --cd-frac ticks from 1 → 0. Numeric countdown stays centred on top. */
  .ability .ab-cd   {
    --cd-frac: 0;
    position: absolute; inset: 0;
    background: conic-gradient(from 0deg, rgba(10,14,30,0.68) calc(var(--cd-frac) * 360deg), transparent 0);
    border-radius: inherit;
    display: none; align-items: center; justify-content: center;
    font-size: 18px; font-weight: 700; color: #fff;
    pointer-events: none;
  }
  .ability.cooldown .ab-cd { display: flex; }
  /* Active = ability is firing right now (brief flash). Pulse uses the tile's
     own accent color so each ability glows its own hue. */
  .ability.active {
    border-color: var(--acc);
    box-shadow:
      0 0 0 2px var(--acc),
      0 0 22px rgba(var(--acc-rgb), 0.7),
      inset 0 1px 0 rgba(255, 255, 255, 0.22);
    filter: brightness(1.2);
  }
  /* Faded state while on cooldown — the wedge itself is drawn by .ab-cd. */
  .ability.cooldown { filter: saturate(0.55) brightness(0.85); }
  /* Ready = off-cooldown and idle. Subtle accent outline so the player can
     read "this is usable" at a glance without three strobing HUD tiles. */
  .ability.ready {
    border-color: rgba(var(--acc-rgb), 0.55);
    box-shadow:
      0 0 10px rgba(var(--acc-rgb), 0.25),
      inset 0 1px 0 rgba(255, 255, 255, 0.18);
  }
  /* Triggered once when a cooldown finishes — replays via class toggle in
     updateAbilityCooldowns. Keep it short so it doesn't compete with the
     'active' pulse fired the moment the player spends it. */
  @keyframes ab-just-ready {
    0%   { box-shadow: 0 0 0 0 rgba(var(--acc-rgb), 0.9),  0 0 22px rgba(var(--acc-rgb), 0.85); }
    70%  { box-shadow: 0 0 0 10px rgba(var(--acc-rgb), 0), 0 0 22px rgba(var(--acc-rgb), 0.35); }
    100% { box-shadow: 0 0 0 0 rgba(var(--acc-rgb), 0),    0 0 10px rgba(var(--acc-rgb), 0.25); }
  }
  .ability.ready.just-ready { animation: ab-just-ready 0.65s ease-out 1; }
  @media (prefers-reduced-motion: reduce) {
    .ability.ready.just-ready { animation: none; }
  }

  /* CSS-drawn pause glyph — avoids emoji presentation of U+23F8. Inherits
     ab-ico's lilac via currentColor, scales with ab-ico font-size via em. */
  .ab-ico-pause { display: inline-flex; gap: 0.16em; align-items: center; justify-content: center; }
  .ab-ico-pause span { width: 0.22em; height: 0.7em; background: currentColor; border-radius: 1px; }

  /* Pause is a system control, not an ability — separate it from the cluster. */
  #ability-bar #pause-btn { margin-left: 18px; }

  /* Limit break meter — sits above the health bar, full-width of the HUD
     cluster. Gold-magenta gradient picks up the landing-page accent palette.
     Hidden (.hidden) when no skill equipped; pulses (.ready) at 100% charge. */
  #limit-break-meter {
    position: fixed; bottom: 112px; left: 50%; transform: translateX(-50%);
    width: 260px;
    display: flex; align-items: center; gap: 8px;
    padding: 4px 10px;
    background: rgba(18, 20, 32, 0.55);
    border: 1px solid rgba(255, 215, 102, 0.28);
    border-radius: 999px;
    backdrop-filter: blur(3px) saturate(1.1);
    /* Non-interactive while charging — we don't want stray clicks stealing
       pointer-lock focus on desktop. .ready swaps this to `auto` below so
       touch players can tap to fire. */
    pointer-events: none;
    /* touch-action manipulation skips iOS's 300ms synthetic-click delay AND
       prevents double-tap zoom. Essential — without it, tapping an animated
       element on mobile can be ignored entirely. */
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    cursor: pointer;
    font-family: 'Space Grotesk', sans-serif;
    font-size: 11px; font-weight: 700; letter-spacing: 1.5px;
    color: rgba(246, 248, 254, 0.78);
    transition: box-shadow 0.25s, border-color 0.25s, transform 0.2s;
    z-index: 9;
  }
  /* When ready, pop ABOVE the virtual joystick (z:15) and cam-pad (z:14)
     so touches actually land on the meter on mobile. Only raises on ready —
     while charging, the meter stays under the touch-zones so the player
     can freely joystick across its footprint. */
  #limit-break-meter.ready {
    pointer-events: auto;
    z-index: 16;
  }
  #limit-break-meter.hidden { display: none; }
  #limit-break-meter #limit-break-icon {
    /* Holds an SVG icon from LIMIT_ICONS (src/meta-icons.js) — sized via
       the child svg rule below. font-size stays as the fallback. */
    width: 20px; height: 20px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 15px; line-height: 1;
    filter: drop-shadow(0 0 4px rgba(255, 215, 102, 0.5));
  }
  #limit-break-meter #limit-break-icon svg { width: 100%; height: 100%; display: block; }
  #limit-break-meter .lb-bar {
    flex: 1; height: 7px;
    background: rgba(40, 30, 50, 0.75);
    border-radius: 999px;
    overflow: hidden;
  }
  #limit-break-meter .lb-fill {
    height: 100%; width: 0;
    background: linear-gradient(90deg, #ffd166 0%, #ff6b9f 60%, #b16bff 100%);
    box-shadow: 0 0 8px rgba(255, 200, 120, 0.5);
    border-radius: 999px;
    transition: width 0.18s ease-out;
  }
  #limit-break-meter .lb-label { white-space: nowrap; }
  /* Ready state — brighter border + glow pulse. Intentionally does NOT
     animate the parent's `transform` so the hit-box stays stable for
     mouse + touch clicks (Playwright reported "element is not stable"
     and real touch devices have the same problem with transform-animated
     click targets). Pulse lives on box-shadow + the icon child instead. */
  #limit-break-meter.ready {
    border-color: rgba(255, 215, 102, 0.85);
    box-shadow: 0 0 22px rgba(255, 180, 80, 0.5), 0 0 6px rgba(255, 215, 102, 0.6) inset;
    animation: lb-glow 1.1s ease-in-out infinite;
  }
  #limit-break-meter.ready .lb-label {
    color: #ffd166;
    text-shadow: 0 0 6px rgba(255, 215, 102, 0.7);
  }
  /* Scale pulse lives on the icon child — a small, non-hit-critical element
     so its movement can't affect tap registration. */
  #limit-break-meter.ready #limit-break-icon {
    animation: lb-icon-pulse 1.1s ease-in-out infinite;
  }
  @keyframes lb-glow {
    0%, 100% {
      box-shadow: 0 0 18px rgba(255, 180, 80, 0.45),
                  0 0 6px rgba(255, 215, 102, 0.55) inset;
      filter: brightness(1);
    }
    50%      {
      box-shadow: 0 0 30px rgba(255, 195, 110, 0.75),
                  0 0 9px rgba(255, 220, 120, 0.8) inset;
      filter: brightness(1.08);
    }
  }
  @keyframes lb-icon-pulse {
    0%, 100% { transform: scale(1.00); }
    50%      { transform: scale(1.18); }
  }
  @media (max-width: 720px) {
    #limit-break-meter { width: 220px; font-size: 10px; bottom: 96px; }
  }

  /* Wave modifier — small tag under wave notice */
  #wave-modifier {
    position: fixed; top: 128px; left: 50%; transform: translateX(-50%);
    font-size: 13px; font-weight: 600; letter-spacing: 1.2px; text-transform: uppercase;
    color: #ff88cc;
    background: rgba(255,46,165,0.12);
    border: 1px solid rgba(255,46,165,0.45);
    border-radius: var(--radius-pill);
    padding: 4px 14px;
    pointer-events: none; opacity: 0; transition: opacity 0.3s;
  }
  #wave-modifier.visible { opacity: 1; }

  /* Stack toast — fires when a powerup is picked for the Nth time (N>=2).
     Sits above the HUD center, flashes in with a scale overshoot, holds,
     slides down and away. Re-triggered by toggling .visible. */
  #stack-toast {
    position: fixed; top: 18%; left: 50%; transform: translate(-50%, -40%) scale(0.6);
    display: flex; align-items: center; gap: 10px;
    font-family: 'Space Grotesk', sans-serif;
    font-size: 22px; font-weight: 700; letter-spacing: 1px;
    color: #fff;
    background: linear-gradient(135deg, rgba(24,28,54,0.92), rgba(64,42,96,0.92));
    border: 1px solid rgba(255,215,102,0.55);
    border-radius: 999px;
    padding: 10px 22px;
    box-shadow: 0 8px 28px rgba(0,0,0,0.4), 0 0 24px rgba(255,215,102,0.28);
    pointer-events: none;
    opacity: 0;
    z-index: 38;
    white-space: nowrap;
  }
  #stack-toast .st-icon {
    /* SVG icon from POWERUP_ICONS or MERCHANT_ICONS. */
    width: 34px; height: 34px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 26px; line-height: 1; flex-shrink: 0;
  }
  #stack-toast .st-icon svg { width: 100%; height: 100%; display: block; }
  #stack-toast .st-count {
    color: #ffd166;
    font-size: 28px;
    text-shadow: 0 0 10px rgba(255,215,102,0.65);
    margin-left: 2px;
  }
  #stack-toast.visible {
    animation: st-pop 1.6s cubic-bezier(.2,.9,.25,1.15) forwards;
  }
  @keyframes st-pop {
    0%   { transform: translate(-50%, -70%) scale(0.4); opacity: 0; }
    14%  { transform: translate(-50%, -40%) scale(1.08); opacity: 1; }
    24%  { transform: translate(-50%, -40%) scale(1);    opacity: 1; }
    80%  { transform: translate(-50%, -40%) scale(1);    opacity: 1; }
    100% { transform: translate(-50%, 0%)   scale(0.9);  opacity: 0; }
  }
  @media (max-width: 720px) {
    #stack-toast { font-size: 17px; padding: 8px 16px; top: 16%; }
    #stack-toast .st-icon { width: 26px; height: 26px; font-size: 20px; }
    #stack-toast .st-count { font-size: 22px; }
  }

  /* Death ceremony — "Epitaph" redesign.
     Full-opacity void so the world is visibly gone. Cinzel serif for the
     YOU DIED letters gives a carved-stone / epitaph feel instead of a
     clinical geometric read. A crimson blood-stain bloom blooms behind
     the text as the letters arrive; a second italic Garamond line ("all
     returns to the pattern") fades in after the title settles; and a
     handful of amber embers drift up through the void for atmosphere.
     The title landing leaves a tiny heart-failing tremble on the letters
     for ~1s before the whole overlay fades into the game-over card. */
  #death-ceremony {
    position: fixed;
    top: 0; left: 0;
    width: 100vw; height: 100vh;
    display: flex; align-items: center; justify-content: center;
    /* Fully opaque void — the previous 0.72 radial let the game UI bleed
       through. Warm-crimson edge vignette layered on top of deep black so
       the frame carries the violence, not just the centre. */
    background:
      radial-gradient(ellipse 70% 60% at 50% 60%,
        rgba(60, 0, 6, 0.62) 0%,
        rgba(20, 0, 2, 0.9) 45%,
        #000 90%),
      #000;
    opacity: 0;
    pointer-events: none;
    /* Above everything — game-over card (z:200), leaderboard prompt
       (z:260-280), stage-select (z:250) all sit below this for the 2.8s
       ceremony window. */
    z-index: 400;
    overflow: hidden;
  }
  #death-ceremony.active { animation: dc-fade 5.6s ease-out forwards; }
  @keyframes dc-fade {
    0%   { opacity: 0; }
    12%  { opacity: 1; }
    78%  { opacity: 1; }
    100% { opacity: 0; }
  }
  #death-ceremony .dc-center {
    position: relative;
    display: flex; flex-direction: column; align-items: center; gap: 18px;
    z-index: 2;
  }
  /* Blood-stain bloom — sits behind the title and grows from nothing into
     a dark crimson halo as the letters land. Multiply-blended so it
     deepens the backdrop's edge vignette instead of overpainting. */
  #death-ceremony .dc-stain {
    position: absolute; top: 50%; left: 50%;
    width: 900px; height: 380px;
    margin-left: -450px; margin-top: -220px;
    /* Gradient softened at the edges (0.75 → 0.45 → 0 alpha) so the halo
       already reads as a hazy bloom. Earlier `filter: blur(12px)` +
       `mix-blend-mode: screen` forced an offscreen composite of a
       342,000px² raster every frame for 2.4s — the single biggest GPU
       cost on the YOU DIED ceremony. Both dropped. */
    background: radial-gradient(ellipse 50% 50% at 50% 50%,
      rgba(180, 10, 28, 0.75) 0%,
      rgba(90, 0, 10, 0.45) 35%,
      rgba(40, 0, 4, 0) 70%);
    transform: scale(0.2);
    opacity: 0;
    z-index: 1;
  }
  #death-ceremony.active .dc-stain {
    animation: dc-stain 2.4s cubic-bezier(.25,.8,.3,1) forwards;
  }
  @keyframes dc-stain {
    0%   { transform: scale(0.2); opacity: 0; }
    18%  { transform: scale(0.6); opacity: 0.55; }
    55%  { transform: scale(1.05); opacity: 0.92; }
    100% { transform: scale(1.25); opacity: 0.88; }
  }
  /* YOU DIED title — Cinzel serif, bright-blood gradient fill with a
     warm highlight running through the middle so the letters look wet /
     lit-from-within rather than painted flat. */
  #death-ceremony .dc-title {
    display: flex;
    gap: 4px;
    font-family: 'Cinzel', 'Times New Roman', serif;
    font-size: 108px;
    font-weight: 900;
    letter-spacing: 6px;
    line-height: 1;
    /* Bright blood-red with layered text-shadows for depth. Dropped
       `background-clip: text` gradient because `filter: drop-shadow`
       interacts badly with it (blurs the gradient-clipped pixels away)
       and `text-shadow` on gradient-clipped text is invisible (shadow
       uses the transparent fill color). Layered shadows give the same
       "wet, lit, carved" read — inner highlight, hard drop, soft glow,
       ambient halo — on a solid-fill glyph that renders reliably. */
    color: #ff3a4a;
    /* Text-shadow trimmed from 6 layers to 3 — the 2.2s tremble
       animation re-rasterized every layer (including the 42px and 90px
       blur halos) on each frame across 7 letters, a significant GPU
       cost at the 5.6s ceremony length. Kept: inner highlight for the
       "wet" read, hard drop for carving, single 24px red glow. The 90px
       ambient halo is replaced by the void's existing radial vignette. */
    text-shadow:
      0 1px 0 rgba(255, 220, 190, 0.55),
      0 5px 0 rgba(80, 0, 6, 0.95),
      0 0 24px rgba(255, 50, 70, 0.85);
  }
  #death-ceremony .dc-gap { width: 34px; }
  #death-ceremony .dc-title span {
    display: inline-block;
    opacity: 0;
    transform: translateY(26px) scale(0.82);
  }
  #death-ceremony.active .dc-title span {
    /* Heavier easing than before — letters fall and settle with weight. */
    animation: dc-letter 0.7s cubic-bezier(.22,.7,.25,1.0) forwards,
               dc-tremble 2.2s ease-in-out 0.9s forwards;
  }
  #death-ceremony.active .dc-title span:nth-child(1) { animation-delay: 0.20s, 0.90s; }
  #death-ceremony.active .dc-title span:nth-child(2) { animation-delay: 0.32s, 1.02s; }
  #death-ceremony.active .dc-title span:nth-child(3) { animation-delay: 0.44s, 1.14s; }
  #death-ceremony.active .dc-title span:nth-child(5) { animation-delay: 0.66s, 1.36s; }
  #death-ceremony.active .dc-title span:nth-child(6) { animation-delay: 0.78s, 1.48s; }
  #death-ceremony.active .dc-title span:nth-child(7) { animation-delay: 0.90s, 1.60s; }
  #death-ceremony.active .dc-title span:nth-child(8) { animation-delay: 1.02s, 1.72s; }
  @keyframes dc-letter {
    /* Removed `filter: blur()` — it was the main GPU cost on the YOU DIED
       cascade (7 letters × 0.7s × per-frame blur raster). The translate +
       scale + rotate already sell the "heavy letter dropping" feel;
       dropping blur fixes the lag reported at the 5.6s ceremony length. */
    0%   { opacity: 0; transform: translateY(40px) scale(0.55) rotate(-3deg); }
    65%  { opacity: 1; transform: translateY(-3px) scale(1.06) rotate(0.5deg); }
    100% { opacity: 1; transform: translateY(0) scale(1) rotate(0deg); }
  }
  /* Heart-failing tremble — sub-pixel random jitter driven by cubic
     easing so it feels organic. Fades out at the end of the ceremony. */
  @keyframes dc-tremble {
    0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
    8%       { transform: translate3d(0.8px, -0.4px, 0) rotate(0.15deg); }
    22%      { transform: translate3d(-0.6px, 0.5px, 0) rotate(-0.12deg); }
    38%      { transform: translate3d(0.4px, 0.7px, 0) rotate(0.1deg); }
    54%      { transform: translate3d(-0.5px, -0.3px, 0) rotate(-0.08deg); }
    72%      { transform: translate3d(0.3px, 0.4px, 0) rotate(0.06deg); }
    86%      { transform: translate3d(-0.2px, -0.2px, 0) rotate(-0.04deg); }
  }
  /* Italic Garamond epitaph that fades in after the title has landed.
     Lowercase + tracked letters + low-opacity for a "whispered" quality. */
  #death-ceremony .dc-epitaph {
    font-family: 'EB Garamond', 'Times New Roman', serif;
    font-style: italic;
    font-size: 20px;
    letter-spacing: 4px;
    color: rgba(255, 200, 180, 0.42);
    text-transform: lowercase;
    opacity: 0;
    transform: translateY(10px);
  }
  #death-ceremony.active .dc-epitaph {
    animation: dc-epitaph 2.0s ease-out 1.4s forwards;
  }
  @keyframes dc-epitaph {
    0%   { opacity: 0; transform: translateY(10px); letter-spacing: 4px; }
    40%  { opacity: 1; }
    100% { opacity: 1; transform: translateY(0); letter-spacing: 10px; }
  }
  /* Rising embers — ten amber pinpoints drifting up through the void. CSS-
     only; each span has a unique animation-delay + left position to break
     up the rhythm. Sit below the centre content so they never hide the
     title. */
  #death-ceremony .dc-embers {
    position: absolute; inset: 0; z-index: 0;
    pointer-events: none;
  }
  #death-ceremony .dc-embers span {
    position: absolute; bottom: -30px;
    width: 3px; height: 3px; border-radius: 50%;
    background: radial-gradient(circle,
      rgba(255, 220, 160, 0.95) 0%,
      rgba(255, 140, 60, 0.6) 40%,
      rgba(200, 40, 20, 0) 75%);
    box-shadow: 0 0 6px rgba(255, 160, 90, 0.6);
    opacity: 0;
  }
  #death-ceremony.active .dc-embers span {
    animation: dc-ember 2.6s linear forwards;
  }
  #death-ceremony .dc-embers span:nth-child(1)  { left: 12%; width: 2px; height: 2px; animation-delay: 0.30s; }
  #death-ceremony .dc-embers span:nth-child(2)  { left: 22%; width: 4px; height: 4px; animation-delay: 0.55s; }
  #death-ceremony .dc-embers span:nth-child(3)  { left: 35%; width: 2px; height: 2px; animation-delay: 0.15s; }
  #death-ceremony .dc-embers span:nth-child(4)  { left: 48%; width: 3px; height: 3px; animation-delay: 0.70s; }
  #death-ceremony .dc-embers span:nth-child(5)  { left: 58%; width: 2px; height: 2px; animation-delay: 0.40s; }
  #death-ceremony .dc-embers span:nth-child(6)  { left: 68%; width: 4px; height: 4px; animation-delay: 0.85s; }
  #death-ceremony .dc-embers span:nth-child(7)  { left: 78%; width: 2px; height: 2px; animation-delay: 0.20s; }
  #death-ceremony .dc-embers span:nth-child(8)  { left: 86%; width: 3px; height: 3px; animation-delay: 0.95s; }
  #death-ceremony .dc-embers span:nth-child(9)  { left: 92%; width: 2px; height: 2px; animation-delay: 0.50s; }
  #death-ceremony .dc-embers span:nth-child(10) { left: 8%;  width: 3px; height: 3px; animation-delay: 1.00s; }
  @keyframes dc-ember {
    0%   { opacity: 0; transform: translate3d(0, 0, 0) scale(0.7); }
    15%  { opacity: 0.9; }
    50%  { transform: translate3d(6px, -45vh, 0) scale(1); }
    85%  { opacity: 0.6; }
    100% { opacity: 0; transform: translate3d(-8px, -100vh, 0) scale(0.9); }
  }
  /* Canvas filter removed — the 5-function filter stack
     (saturate + brightness + contrast + sepia + hue-rotate) re-rasterized
     the entire viewport every frame for the full 5.6s ceremony, which
     was the main remaining cause of the YOU DIED lag. The ceremony
     overlay reaches opacity 1 in 672ms and covers the canvas through a
     near-opaque radial void after that, so the scene is visually
     occluded without the filter. */
  /* Pause photon animations while the death/victory ceremony is on top.
     The ceremony overlay at z:400 fully occludes them, but browsers still
     composite the animating photons — which was a real perf cost during
     the extended 5.6s window. animation-play-state: paused freezes them
     without tearing down the DOM. */
  body.dying .landing-photon,
  body.victorious .landing-photon,
  body.dying .gameover-photon,
  body.victorious .gameover-photon {
    animation-play-state: paused !important;
  }
  /* ─── VICTORY VARIANT — "the light endures" ─────────────────────────────
     Shares the structure of the death ceremony (fade timeline, letter
     cascade shape, stain bloom, embers, epitaph) but re-themes every
     colour-bearing surface to gold + green. Triggered by adding .victory
     to #death-ceremony on the survived path. Letters are replaced by JS
     to VICTORY (7 chars, no gap) so the animation-delay rules here
     target nth-child(1..7) instead of the death 1/2/3/5/6/7/8. */
  #death-ceremony.victory {
    background:
      radial-gradient(ellipse 70% 60% at 50% 60%,
        rgba(60, 40, 0, 0.6) 0%,
        rgba(12, 22, 6, 0.88) 48%,
        #040804 92%),
      #040804;
  }
  #death-ceremony.victory .dc-stain {
    background: radial-gradient(ellipse 50% 50% at 50% 50%,
      rgba(255, 200, 90, 0.75) 0%,
      rgba(200, 140, 30, 0.45) 35%,
      rgba(40, 80, 30, 0) 70%);
  }
  #death-ceremony.victory .dc-title {
    /* Pure uppercase Cinzel, gold-honey core with green-tinted shadows
       instead of crimson. Same layered text-shadow recipe: inner warm
       highlight, dark drop-carve, close warm glow, wider halo, far
       emerald bleed so the victory feels both regal AND natural. */
    color: #ffe07a;
    text-shadow:
      0 1px 0 rgba(255, 250, 220, 0.7),
      0 5px 0 rgba(80, 50, 0, 0.95),
      0 7px 2px rgba(20, 18, 0, 0.85),
      0 0 14px rgba(255, 200, 90, 0.95),
      0 0 42px rgba(255, 170, 60, 0.7),
      0 0 90px rgba(140, 220, 120, 0.55);
  }
  #death-ceremony.victory.active .dc-title span {
    /* VICTORY is 7 letters with no gap — stagger each by 0.12s starting
       at 0.2s, same rhythm as the death cascade without the gap-span
       skip. The tremble runs 0.7s after each letter lands; feels like
       pride-shiver rather than death-shake because colour carries it. */
    animation: dc-letter 0.7s cubic-bezier(.22,.7,.25,1.0) forwards,
               dc-tremble 2.2s ease-in-out forwards;
  }
  #death-ceremony.victory.active .dc-title span:nth-child(1) { animation-delay: 0.20s, 0.90s; }
  #death-ceremony.victory.active .dc-title span:nth-child(2) { animation-delay: 0.32s, 1.02s; }
  #death-ceremony.victory.active .dc-title span:nth-child(3) { animation-delay: 0.44s, 1.14s; }
  #death-ceremony.victory.active .dc-title span:nth-child(4) { animation-delay: 0.56s, 1.26s; }
  #death-ceremony.victory.active .dc-title span:nth-child(5) { animation-delay: 0.68s, 1.38s; }
  #death-ceremony.victory.active .dc-title span:nth-child(6) { animation-delay: 0.80s, 1.50s; }
  #death-ceremony.victory.active .dc-title span:nth-child(7) { animation-delay: 0.92s, 1.62s; }
  #death-ceremony.victory .dc-epitaph {
    color: rgba(255, 235, 180, 0.5);
  }
  /* Gold-green embers — warmer + a touch of emerald at the halo. */
  #death-ceremony.victory .dc-embers span {
    background: radial-gradient(circle,
      rgba(255, 245, 200, 0.95) 0%,
      rgba(220, 200, 90, 0.6) 40%,
      rgba(100, 180, 80, 0) 75%);
    box-shadow: 0 0 6px rgba(240, 220, 130, 0.6);
  }
  /* Canvas filter removed for the same reason as body.dying #canvas —
     the 5-function filter stack rasterized the entire viewport each
     frame for 5.6s. Victory overlay covers the canvas via its own
     opacity ramp + radial void. */
  @media (max-width: 720px) {
    /* 10% smaller than the previous mobile size — the old 56px +
       letter-spacing 3px was edge-to-edge on narrow phones. Desktop
       (.dc-title font-size: 108px) is left at its original size. */
    #death-ceremony .dc-title { font-size: 50px; letter-spacing: 2.5px; }
    #death-ceremony .dc-gap { width: 16px; }
    #death-ceremony .dc-epitaph { font-size: 13px; letter-spacing: 3px; }
    #death-ceremony .dc-stain { width: 520px; margin-left: -260px; height: 260px; margin-top: -130px; }
  }
  @media (prefers-reduced-motion: reduce) {
    #death-ceremony .dc-title span,
    #death-ceremony .dc-epitaph,
    #death-ceremony .dc-embers span,
    #death-ceremony .dc-stain {
      animation: none !important;
      opacity: 1 !important;
      transform: none !important;
    }
  }

  /* Shop screen */
  #shop-screen {
    position: fixed; inset: 0; z-index: 50;
    display: none; flex-direction: column; align-items: center; justify-content: center;
    background: rgba(10,14,30,0.88);
    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  }
  #shop-screen.visible { display: flex; }
  #shop-screen h2 { font-family: 'Space Grotesk', sans-serif; font-size: 42px; color: #ffd700; margin: 0; letter-spacing: 2px; }
  #shop-screen p { color: rgba(246,248,254,0.72); margin: 6px 0 22px; font-size: 14px; }
  #shop-screen #shop-score { color: #ffd700; font-weight: 700; }
  #shop-choices {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px;
    max-width: 820px; padding: 0 24px;
  }
  .shop-card {
    background: var(--card-bg);
    border: 1px solid rgba(255,215,0,0.4);
    border-radius: var(--radius-lg);
    padding: 18px; min-width: 220px;
    cursor: pointer; transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s;
    color: var(--fg);
  }
  .shop-card:hover { transform: translateY(-3px); border-color: #ffd700; box-shadow: 0 10px 30px rgba(255,215,0,0.2); }
  .shop-card.locked { opacity: 0.4; cursor: not-allowed; }
  .shop-card .sc-icon  {
    width: 42px; height: 42px;
    margin: 0 auto 6px;
    font-size: 32px; line-height: 42px;
    display: flex; align-items: center; justify-content: center;
  }
  .shop-card .sc-icon svg { width: 100%; height: 100%; display: block; }
  .shop-card .sc-name  { font-weight: 700; font-size: 15px; letter-spacing: 0.6px; }
  .shop-card .sc-desc  { font-size: 12px; opacity: 0.75; margin: 4px 0 10px; line-height: 1.4; }
  .shop-card .sc-cost  { color: #ffd700; font-weight: 700; font-size: 13px; }
  #shop-skip {
    margin-top: 22px;
    background: transparent; border: 1px solid var(--card-border); color: var(--fg);
    padding: 10px 22px; border-radius: var(--radius-pill);
    font-family: inherit; font-size: 13px; font-weight: 600; letter-spacing: 1.2px;
    cursor: pointer;
  }
  #shop-skip:hover { background: var(--card-bg); }

  /* Reroll button inside powerup screen */
  #reroll-btn {
    margin-top: 14px; background: transparent;
    border: 1px solid rgba(184,155,255,0.5); color: var(--lilac);
    padding: 8px 18px; border-radius: var(--radius-pill);
    font-family: inherit; font-size: 12px; font-weight: 600; letter-spacing: 1.2px;
    cursor: pointer;
  }
  #reroll-btn:disabled { opacity: 0.4; cursor: not-allowed; }
  #reroll-btn:hover:not(:disabled) { background: rgba(184,155,255,0.1); }

  /* Pickup orbs pulse (applied via CSS custom prop on pickups — actually 3D, no-op here) */

  /* ─── TOUCH / MOBILE ─────────────────────────────────────────────────── */
  #joystick, #cam-pad { display: none; }

  body.touch #joystick {
    display: block;
    position: fixed; left: 0; top: 0; width: 50vw; height: 100%;
    touch-action: none; pointer-events: auto; z-index: 15;
    user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  }
  body.touch #joystick .j-base {
    position: absolute; width: 120px; height: 120px; border-radius: 50%;
    background: rgba(18, 24, 44, 0.35);
    border: 1.5px solid rgba(184, 155, 255, 0.55);
    box-shadow: 0 4px 18px rgba(10, 14, 30, 0.35);
    display: none; pointer-events: none;
    transform: translate(-50%, -50%);
  }
  body.touch #joystick.active .j-base { display: block; }
  body.touch #joystick .j-knob {
    position: absolute; left: 50%; top: 50%;
    width: 52px; height: 52px; border-radius: 50%;
    background: rgba(184, 155, 255, 0.85);
    box-shadow: 0 2px 12px rgba(184, 155, 255, 0.5);
    transform: translate(-50%, -50%);
  }

  body.touch #cam-pad {
    display: block;
    position: fixed; right: 0; top: 0; width: 50vw; height: 100%;
    touch-action: none; pointer-events: auto; z-index: 14;
    user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  }

  body.touch #minimap { display: none; }

  body.touch #pause-screen .pause-card { padding: 22px 20px; max-height: 80vh; }
  body.touch #pause-screen h2 { font-size: 26px; }

  body.touch #top-bar { gap: 4px; top: calc(8px + env(safe-area-inset-top)); }
  body.touch .stat-block { padding: 3px 8px; min-width: 0; gap: 5px; }
  body.touch #kills-block { display: none; }
  body.touch .stat-block .value { font-size: 13px; }
  body.touch .stat-block .label { font-size: 8.5px; letter-spacing: 0.9px; }
  /* Extra-tight enraged pill on mobile — the top-bar's 4-block row leaves
     only ~90px per pill on a 375px screen, and "☠ ENRAGED" clips/overflows
     at the desktop 13px size. Drop to 10.5px + 0 letter-spacing so the
     skull and text stay inside the pill. */
  body.touch .stat-block.enraged .value {
    font-size: 10.5px; letter-spacing: 0;
  }

  /* HP + XP bars split the top row on mobile — HP left, XP right */
  body.touch #health-bar-wrap,
  body.touch #xp-bar-wrap {
    top: calc(40px + env(safe-area-inset-top));
    bottom: auto; transform: none;
    width: calc(50vw - 12px);
  }
  body.touch #health-bar-wrap { left: 8px;  right: auto; }
  body.touch #xp-bar-wrap     { left: auto; right: 8px; }
  body.touch .bar-label { font-size: 9.5px; letter-spacing: 0.9px; margin-bottom: 3px; }
  body.touch .bar-bg    { height: 10px; }

  body.touch #ability-bar {
    pointer-events: none; z-index: 16;
    bottom: calc(16px + env(safe-area-inset-bottom));
    top: auto; left: 50%; right: auto;
    transform: translateX(-50%);
    flex-direction: row; gap: 12px;
  }
  body.touch .ability { width: 56px; height: 56px; pointer-events: auto; touch-action: manipulation; padding: 0 4px; box-sizing: border-box; }
  body.touch .ability .ab-key  { display: none; }
  body.touch .ability .ab-ico  { font-size: 20px; }
  body.touch .ability .ab-name { font-size: 7.5px; letter-spacing: 0; text-transform: none; white-space: nowrap; }

  body.touch #requirements       { display: none; }
  body.touch #touch-requirements { display: inline-flex; }

  /* Level-up modal — portrait */
  body.touch #powerup-screen h2  { font-size: 24px; margin-bottom: 6px; }
  body.touch #powerup-screen p   { font-size: 12px; margin-bottom: 16px; padding: 0 18px; }
  body.touch #powerup-choices    { gap: 10px; max-width: 100vw; padding: 0 10px; }
  body.touch .powerup-card       { width: 46vw; max-width: 170px; padding: 14px 12px; }
  body.touch .powerup-card .icon { width: 42px; height: 42px; margin: 0 auto 6px; font-size: 28px; line-height: 42px; }
  body.touch .powerup-card .name { font-size: 13px; }
  body.touch .powerup-card .desc { font-size: 11px; }
  body.touch #reroll-btn         { padding: 8px 14px; font-size: 11px; }

  /* Shop — portrait */
  body.touch #shop-screen h2 { font-size: 26px; }
  body.touch #shop-screen p  { font-size: 12px; margin-bottom: 12px; }
  body.touch #shop-choices   { grid-template-columns: 1fr; gap: 10px; max-width: 100vw; padding: 0 16px; }
  body.touch .shop-card      { min-width: 0; padding: 12px 14px; }
  body.touch .shop-card .sc-icon { width: 34px; height: 34px; font-size: 24px; line-height: 34px; margin: 0 auto 4px; }
  body.touch .shop-card .sc-name { font-size: 13px; }
  body.touch .shop-card .sc-desc { font-size: 11px; margin: 2px 0 6px; }
  body.touch #shop-skip      { margin-top: 14px; padding: 10px 18px; }

  /* Gameover — portrait */
  body.touch #gameover h1    { font-size: 40px; margin-bottom: 12px; }
  body.touch #gameover-stats { margin-bottom: 14px; padding: 0 18px; }

  /* Phones / narrow windows — shrink hero typography and stack the primary
     CTAs vertically so SHARE + PLAY AGAIN both sit above the fold. */
  @media (max-width: 520px) {
    #gameover h1 { font-size: 34px; letter-spacing: 0.5px; margin-bottom: 10px; }
    #gameover-hero { margin-bottom: 14px; gap: 4px; }
    #gameover-hero .go-score { font-size: 44px; }
    #gameover-hero .go-score-label { font-size: 9.5px; letter-spacing: 2px; }
    #gameover .go-badge { font-size: 10px; padding: 4px 10px; letter-spacing: 1.1px; }
    .go-stat-strip { gap: 16px; }
    .go-chip { min-width: 54px; }
    .go-chip .go-chip-v { font-size: 19px; }
    .go-chip .go-chip-l { font-size: 9.5px; letter-spacing: 1.4px; }
    #gameover-earned { gap: 10px; font-size: 11.5px; margin-bottom: 18px; }
    #gameover-actions {
      flex-direction: column; gap: 20px;
      width: min(360px, 86vw); margin-bottom: 12px;
    }
    /* Column flex — reset basis to auto so buttons size to content height
       instead of claiming 160px of vertical space each. */
    #gameover-actions > button {
      flex: 0 0 auto;
      width: 100%; min-width: 0;
      padding: 13px 22px; font-size: 15px;
    }
    #gameover-secondary { gap: 10px; }
  }

  /* ─── Leaderboard modal ──────────────────────────────────────────────────
     Full-screen overlay with a single rounded card: title + period tabs +
     stage filter pills + top-10 list. Opens from the landing RANKINGS pill
     or the game-over "View Rankings" link. Purely client-side; fetches
     /api/leaderboard in src/leaderboard.js. */
  #leaderboard-modal {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    z-index: 270;  /* above relic-shop (250) + progress-sync (260) */
    background: rgba(6, 8, 18, 0.82);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
  }
  #leaderboard-modal.visible { display: flex; }
  #leaderboard-modal .lb-backdrop { position: absolute; inset: 0; cursor: pointer; }
  #leaderboard-modal .lb-card {
    position: relative;
    width: min(760px, 92vw);
    max-height: 88vh;
    display: flex; flex-direction: column;
    background: linear-gradient(160deg, rgba(30, 36, 60, 0.96), rgba(22, 18, 40, 0.96));
    border: 1px solid rgba(209, 174, 255, 0.25);
    border-radius: 18px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
    overflow: hidden;
  }
  .lb-head {
    display: flex; align-items: center;
    padding: 18px 22px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  .lb-head h2 {
    font-size: 22px; font-weight: 800; letter-spacing: 1.2px; color: var(--text);
    flex: 1;
  }
  .lb-close {
    background: transparent; color: var(--text-dim);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 50%;
    width: 32px; height: 32px;
    font-size: 18px; font-family: inherit;
    cursor: pointer; line-height: 1;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
  }
  .lb-close:hover {
    color: var(--text); border-color: rgba(255, 255, 255, 0.28);
    background: rgba(255, 255, 255, 0.06);
  }
  .lb-period-tabs {
    display: flex; gap: 8px;
    padding: 14px 22px 6px;
  }
  .lb-period {
    flex: 1;
    padding: 9px 12px;
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-dim);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--radius-pill);
    font-size: 11px; font-weight: 700; letter-spacing: 0.9px;
    font-family: inherit; cursor: pointer;
    transition: color 0.15s, background 0.15s, border-color 0.15s, transform 0.15s;
  }
  .lb-period:hover { color: var(--text); background: rgba(255, 255, 255, 0.08); }
  .lb-period.active {
    color: #1a0a2a;
    background: linear-gradient(135deg, #9b7bff 0%, #d1aeff 100%);
    border-color: transparent;
    box-shadow: 0 3px 10px rgba(155, 123, 255, 0.35);
  }
  .lb-stage-pills {
    display: flex; flex-wrap: wrap; gap: 6px;
    padding: 10px 22px 6px;
    max-height: 86px; overflow-y: auto;
  }
  .lb-stage {
    padding: 5px 12px;
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-dim);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--radius-pill);
    font-size: 11px; font-weight: 600; letter-spacing: 0.5px;
    font-family: inherit; cursor: pointer;
    transition: color 0.15s, background 0.15s, border-color 0.15s;
  }
  .lb-stage:hover { color: var(--text); background: rgba(255, 255, 255, 0.08); }
  .lb-stage.active {
    color: var(--text);
    background: rgba(126, 226, 255, 0.16);
    border-color: rgba(126, 226, 255, 0.45);
  }
  .lb-list {
    list-style: none;
    margin: 10px 0 0;
    padding: 6px 14px 14px;
    overflow-y: auto;
    flex: 1;
  }
  .lb-row {
    display: grid;
    grid-template-columns: 34px 1fr auto;
    grid-template-areas: "rank name score" "rank meta meta";
    align-items: center; column-gap: 12px; row-gap: 2px;
    padding: 10px 14px;
    border-radius: 12px;
    border: 1px solid transparent;
    transition: background 0.15s;
  }
  .lb-row:hover { background: rgba(255, 255, 255, 0.03); }
  .lb-row + .lb-row { margin-top: 4px; }
  .lb-row:nth-child(1) .lb-rank { color: #ffd36e; }
  .lb-row:nth-child(2) .lb-rank { color: #d1d5e2; }
  .lb-row:nth-child(3) .lb-rank { color: #cd8a5f; }
  .lb-rank {
    grid-area: rank;
    font-size: 18px; font-weight: 800;
    color: var(--text-dim);
    text-align: center;
  }
  .lb-name {
    grid-area: name;
    font-size: 14px; font-weight: 700;
    color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .lb-score {
    grid-area: score;
    font-size: 15px; font-weight: 800;
    color: var(--gold);
    letter-spacing: 0.3px;
  }
  .lb-meta {
    grid-area: meta;
    font-size: 11px; color: var(--text-dim);
    letter-spacing: 0.2px;
  }
  .lb-loading, .lb-empty, .lb-error {
    list-style: none;
    text-align: center; padding: 24px 12px;
    color: var(--text-dim);
    font-size: 13px;
  }
  .lb-error { color: #ff9a9a; }
  .lb-skeleton .lb-rank,
  .lb-skeleton .lb-name,
  .lb-skeleton .lb-score,
  .lb-skeleton .lb-meta {
    background: linear-gradient(90deg,
      rgba(255, 255, 255, 0.04) 0%,
      rgba(255, 255, 255, 0.09) 50%,
      rgba(255, 255, 255, 0.04) 100%);
    background-size: 200% 100%;
    border-radius: 6px;
    color: transparent;
    min-height: 1em;
    animation: lb-shimmer 1.2s ease-in-out infinite;
  }
  .lb-skeleton .lb-rank  { width: 22px; margin: 0 auto; height: 16px; }
  .lb-skeleton .lb-name  { width: 55%;  height: 14px; }
  .lb-skeleton .lb-score { width: 64px; height: 15px; justify-self: end; }
  .lb-skeleton .lb-meta  { width: 70%;  height: 11px; margin-top: 2px; }
  .lb-skeleton:hover { background: transparent; }
  @keyframes lb-shimmer {
    0%   { background-position:  100% 0; }
    100% { background-position: -100% 0; }
  }
  @media (prefers-reduced-motion: reduce) {
    .lb-skeleton .lb-rank,
    .lb-skeleton .lb-name,
    .lb-skeleton .lb-score,
    .lb-skeleton .lb-meta { animation: none; }
  }
  .lb-foot {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 22px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    font-size: 12px; color: var(--text-dim);
  }
  .lb-foot .lb-identity { flex: 1; }
  .lb-foot .lb-my-name { color: var(--text); font-weight: 700; }
  .lb-change-name {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-dim);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: var(--radius-pill);
    padding: 6px 14px; font-size: 11px; font-weight: 600;
    letter-spacing: 0.5px; cursor: pointer;
    font-family: inherit;
    transition: color 0.15s, background 0.15s, border-color 0.15s;
  }
  .lb-change-name:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.24);
  }

  /* Inline name-entry dialog — presented once on the first submission, and
     again from the leaderboard footer's "Change name" button. Styled to
     match the leaderboard card's dark-violet palette. */
  .lb-name-modal {
    position: fixed; inset: 0;
    display: flex; align-items: center; justify-content: center;
    z-index: 280;  /* above #leaderboard-modal (270) */
  }
  .lb-name-backdrop {
    position: absolute; inset: 0;
    background: rgba(6, 8, 18, 0.82);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    cursor: pointer;
  }
  .lb-name-card {
    position: relative;
    width: min(420px, 92vw);
    background: linear-gradient(160deg, rgba(30, 36, 60, 0.98), rgba(22, 18, 40, 0.98));
    border: 1px solid rgba(209, 174, 255, 0.3);
    border-radius: 16px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
    padding: 22px 24px;
  }
  .lb-name-card h3 {
    font-size: 16px; font-weight: 800; letter-spacing: 1px;
    color: var(--text); margin-bottom: 6px;
  }
  .lb-name-help {
    font-size: 12px; color: var(--text-dim);
    margin-bottom: 14px; line-height: 1.5;
  }
  .lb-name-input {
    width: 100%;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 10px;
    color: var(--text);
    font-family: inherit;
    font-size: 16px; font-weight: 600;
    padding: 10px 12px;
    outline: none;
    transition: border-color 0.15s, background 0.15s;
  }
  .lb-name-input:focus {
    border-color: rgba(209, 174, 255, 0.55);
    background: rgba(0, 0, 0, 0.42);
  }
  .lb-name-actions {
    display: flex; justify-content: flex-end; gap: 10px;
    margin-top: 16px;
  }
  .lb-name-cancel, .lb-name-save {
    font-family: inherit; font-weight: 700; letter-spacing: 0.6px;
    font-size: 12px; padding: 9px 18px;
    border-radius: var(--radius-pill);
    cursor: pointer;
  }
  .lb-name-cancel {
    background: transparent; color: var(--text-dim);
    border: 1px solid rgba(255, 255, 255, 0.14);
    transition: color 0.15s, border-color 0.15s;
  }
  .lb-name-cancel:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.3); }
  .lb-name-save {
    background: linear-gradient(135deg, #9b7bff 0%, #d1aeff 100%);
    color: #1a0a2a;
    border: none;
    box-shadow: 0 4px 14px rgba(155, 123, 255, 0.38);
    transition: transform 0.15s, box-shadow 0.18s, filter 0.15s;
  }
  .lb-name-save:hover {
    transform: translateY(-1px);
    box-shadow: 0 8px 22px rgba(155, 123, 255, 0.55);
    filter: brightness(1.08);
  }

  /* Leaderboard — portrait */
  @media (max-width: 600px) {
    #leaderboard-modal .lb-card { width: 94vw; max-height: 92vh; border-radius: 14px; }
    .lb-head { padding: 14px 16px; }
    .lb-head h2 { font-size: 18px; letter-spacing: 0.9px; }
    .lb-period-tabs { padding: 10px 14px 4px; gap: 6px; }
    .lb-period { font-size: 10px; padding: 8px 6px; letter-spacing: 0.6px; }
    .lb-stage-pills { padding: 8px 14px 4px; max-height: 74px; }
    .lb-stage { font-size: 10.5px; padding: 4px 10px; }
    .lb-list { padding: 4px 10px 10px; }
    .lb-row { grid-template-columns: 28px 1fr auto; padding: 8px 10px; column-gap: 10px; }
    .lb-name { font-size: 13px; }
    .lb-score { font-size: 14px; }
    .lb-meta { font-size: 10px; }
    .lb-foot { padding: 10px 16px; gap: 8px; }
    .lb-foot .lb-identity { font-size: 11px; }
    .lb-change-name { padding: 5px 10px; font-size: 10.5px; }
  }

  /* ─── ACHIEVEMENTS ─────────────────────────────────────────────────────── */
  /* Toast — mirrors the #stack-toast pattern but lives on its own DOM node so
     timing doesn't fight an in-flight stack-toast animation. Tier color
     drives the border + glow palette. .summary variant is used once on
     landing for the "X unlocked from your prior runs" backfill notice. */
  #achievement-toast {
    position: fixed; top: 22%; left: 50%; transform: translate(-50%, -40%) scale(0.6);
    display: flex; align-items: center; gap: 12px;
    font-family: 'Space Grotesk', sans-serif;
    color: #fff;
    background: linear-gradient(135deg, rgba(20,22,40,0.94), rgba(40,30,80,0.94));
    border: 1px solid rgba(255,215,102,0.55);
    border-radius: 14px;
    padding: 10px 16px 10px 12px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 24px rgba(255,215,102,0.25);
    pointer-events: none;
    opacity: 0;
    /* Toast must render above every gameplay overlay (powerup-screen 100,
       wave-modifier 140, pause 150, modals 160, victory/gameover 200,
       relic-shop 250, sub-modals 260) so an unlock popping mid-victory or
       mid-shop is still visible. Stays under stage-transition (9999) so the
       scene-swap curtain still covers it during a stage change. */
    z-index: 9000;
    max-width: 360px;
    white-space: normal;
  }
  #achievement-toast .ach-toast-icon {
    width: 44px; height: 44px;
    flex-shrink: 0;
  }
  #achievement-toast .ach-toast-icon svg { width: 100%; height: 100%; display: block; }
  #achievement-toast .ach-toast-eyebrow {
    font-size: 10.5px; letter-spacing: 2px; font-weight: 700;
    color: rgba(255,215,102,0.85); text-transform: uppercase;
    margin-bottom: 2px;
  }
  #achievement-toast .ach-toast-name {
    font-size: 16px; font-weight: 800; letter-spacing: 0.6px;
    line-height: 1.15;
  }
  #achievement-toast .ach-toast-desc {
    font-size: 12px; opacity: 0.78; margin-top: 3px; line-height: 1.35;
  }
  #achievement-toast.tier-bronze {
    border-color: rgba(245,176,106,0.65);
    box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 24px rgba(245,176,106,0.30);
  }
  #achievement-toast.tier-bronze .ach-toast-eyebrow { color: #ffd9a8; }
  #achievement-toast.tier-silver {
    border-color: rgba(220,228,240,0.65);
    box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 24px rgba(220,228,240,0.28);
  }
  #achievement-toast.tier-silver .ach-toast-eyebrow { color: #e6ecf6; }
  #achievement-toast.tier-gold {
    border-color: rgba(255,226,122,0.75);
    box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 28px rgba(255,215,102,0.40);
  }
  #achievement-toast.tier-gold .ach-toast-eyebrow { color: #ffe27a; }
  #achievement-toast.summary { border-color: rgba(126,226,255,0.55); }
  #achievement-toast.summary .ach-toast-eyebrow { color: #7ee2ff; }
  #achievement-toast.visible {
    animation: ach-toast-pop 1.55s cubic-bezier(.2,.9,.25,1.15) forwards;
  }
  @keyframes ach-toast-pop {
    0%   { transform: translate(-50%, -70%) scale(0.55); opacity: 0; }
    14%  { transform: translate(-50%, -40%) scale(1.04); opacity: 1; }
    24%  { transform: translate(-50%, -40%) scale(1);    opacity: 1; }
    78%  { transform: translate(-50%, -40%) scale(1);    opacity: 1; }
    100% { transform: translate(-50%, -10%) scale(0.94); opacity: 0; }
  }
  @media (max-width: 720px) {
    #achievement-toast { font-size: 14px; padding: 8px 14px 8px 10px; max-width: 92vw; }
    #achievement-toast .ach-toast-icon { width: 36px; height: 36px; }
    #achievement-toast .ach-toast-name { font-size: 14px; }
    #achievement-toast .ach-toast-desc { font-size: 11px; }
  }

  /* Landing-page badge — small pill that mirrors the relic-bar pill style.
     Lives inside #landing-utility-row alongside Relics + Rankings; spacing
     comes from the row's flex `gap` so no margin needed here.
     Double-id (#overlay #achievements-badge) beats the blanket
     `#overlay button` warm-gradient rule below so the gold pill background
     stays gold. */
  #overlay #achievements-badge {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 10px 16px;
    border-radius: var(--radius-pill);
    background: rgba(255, 215, 102, 0.10);
    border: 1px solid rgba(255, 215, 102, 0.35);
    color: #ffe9aa; font-weight: 700; letter-spacing: 0.5px;
    font-family: inherit; font-size: 13px;
    cursor: pointer; pointer-events: auto;
    margin: 0;
    box-shadow: none;
    transition: background 0.15s, border-color 0.15s, transform 0.15s;
  }
  #overlay #achievements-badge:hover,
  #overlay #achievements-badge:focus-visible {
    background: rgba(255, 215, 102, 0.18);
    border-color: rgba(255, 215, 102, 0.55);
    transform: translateY(-1px);
    box-shadow: none;
    outline: none;
  }
  #achievements-badge .ach-badge-icon {
    width: 18px; height: 18px; display: inline-flex;
  }
  #achievements-badge .ach-badge-icon svg { width: 100%; height: 100%; display: block; }

  /* Collection modal — mirrors #relic-shop layout. */
  #achievements-modal {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    z-index: 250;
    background: rgba(6, 8, 18, 0.82);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
  }
  #achievements-modal.visible { display: flex; }
  #achievements-panel {
    width: min(960px, 92vw);
    max-height: 88vh;
    display: flex; flex-direction: column;
    background: linear-gradient(160deg, rgba(30, 36, 60, 0.96), rgba(22, 18, 40, 0.96));
    border: 1px solid rgba(255, 215, 102, 0.25);
    border-radius: 18px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
    overflow: hidden;
  }
  #achievements-header {
    display: flex; align-items: center; gap: 18px;
    padding: 18px 22px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  #achievements-title {
    font-size: 22px; font-weight: 800; letter-spacing: 1.2px; color: var(--text);
  }
  #achievements-subtitle {
    font-size: 12px; color: var(--text-dim); margin-top: 2px;
  }
  #achievements-progress {
    margin-left: auto;
    display: flex; align-items: center; gap: 8px;
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    background: rgba(255, 215, 102, 0.12);
    border: 1px solid rgba(255, 215, 102, 0.35);
    font-weight: 700; font-size: 14px;
    color: #ffe9aa;
  }
  #achievements-close {
    background: none; border: none; color: var(--text-dim);
    font-size: 28px; line-height: 1; cursor: pointer;
    padding: 0 4px; font-family: inherit;
    transition: color 0.15s, transform 0.15s;
  }
  #achievements-close:hover { color: var(--text); transform: scale(1.1); }
  #achievements-body {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 14px 22px 22px;
  }
  #achievements-grid {
    display: grid; gap: 12px;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  }
  .achievement-card {
    background: rgba(16, 22, 40, 0.7);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: var(--radius-md);
    padding: 14px 14px 12px;
    display: flex; flex-direction: column; gap: 6px;
    transition: border-color 0.18s, transform 0.18s, box-shadow 0.18s;
  }
  .achievement-card .ach-card-icon {
    width: 56px; height: 56px;
    margin: 0 auto 4px;
  }
  .achievement-card .ach-card-icon svg { width: 100%; height: 100%; display: block; }
  .achievement-card .ach-card-name {
    text-align: center;
    font-size: 14px; font-weight: 800; letter-spacing: 0.4px;
    color: var(--text);
  }
  .achievement-card .ach-card-desc {
    text-align: center;
    font-size: 12px; color: var(--text-dim); line-height: 1.4;
    min-height: 2.8em;
  }
  .achievement-card .ach-card-foot {
    margin-top: auto;
    display: flex; align-items: center; justify-content: center; gap: 6px;
    font-size: 10.5px; letter-spacing: 0.4px;
  }
  .achievement-card .ach-card-locked   { color: var(--text-dim); opacity: 0.7; }
  .achievement-card .ach-card-unlocked { color: #cfd6e8; opacity: 0.9; }
  .achievement-card .ach-card-retro {
    color: #1a2240; background: rgba(126,226,255,0.85);
    padding: 1px 6px; border-radius: 999px; font-weight: 800;
  }
  .achievement-card.locked {
    opacity: 0.62;
    filter: grayscale(0.7);
  }
  .achievement-card.unlocked.tier-bronze { border-color: rgba(245,176,106,0.45); }
  .achievement-card.unlocked.tier-silver { border-color: rgba(220,228,240,0.45); }
  .achievement-card.unlocked.tier-gold {
    border-color: rgba(255,215,102,0.55);
    box-shadow: 0 0 18px rgba(255,180,80,0.16), 0 0 4px rgba(255,215,102,0.30) inset;
  }
  @media (max-width: 720px) {
    #achievements-panel { max-height: 92vh; border-radius: 0; }
    /* Force exactly 2 columns on mobile so cards stay legible at narrow
       widths instead of squeezing to 1 column or shrinking past readability. */
    #achievements-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
    #achievements-body { padding: 12px 14px 18px; }
    /* Header gets cramped under 720px — let the title/subtitle cluster shrink
       and the progress pill float to the side. Title size drops; subtitle gets
       a touch tighter so the row doesn't overflow on small phones. */
    #achievements-header { gap: 10px; padding: 14px 16px; flex-wrap: wrap; }
    #achievements-title { font-size: 18px; }
    #achievements-subtitle { font-size: 11px; }
    #achievements-progress { padding: 5px 10px; font-size: 12px; }
    #achievements-close { font-size: 26px; }
    .achievement-card { padding: 12px 10px 10px; }
    .achievement-card .ach-card-icon { width: 52px; height: 52px; }
    .achievement-card .ach-card-name { font-size: 13px; letter-spacing: 0.3px; }
    .achievement-card .ach-card-desc { font-size: 11.5px; min-height: 3.2em; }
    .achievement-card .ach-card-foot { font-size: 10px; gap: 4px; }
    .achievement-card .ach-card-retro { padding: 1px 5px; }
  }
  /* Very narrow phones (≤380px) — tighten further but stay 2 columns. */
  @media (max-width: 380px) {
    #achievements-grid { gap: 8px; }
    #achievements-body { padding: 10px 10px 14px; }
    .achievement-card { padding: 10px 8px 8px; }
    .achievement-card .ach-card-icon { width: 44px; height: 44px; }
    .achievement-card .ach-card-name { font-size: 12px; }
    .achievement-card .ach-card-desc { font-size: 11px; min-height: 3.4em; }
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      transition-duration: 0.01ms !important;
      animation-duration: 0.01ms !important;
    }
  }
