  #minimap {
    position: fixed; bottom: 84px; right: 22px;
    width: 128px; height: 128px;
    background: rgba(18, 24, 44, 0.55);
    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);
    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:not(.tablet) .pc-desktop { display: none; }
  body.touch:not(.tablet) .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);
    z-index: var(--z-critical);
    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: spin-360 0.9s linear infinite;
  }
  #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 — storybook parchment redesign (Figma 3348-2) ──────────
     PAUSED scroll banner over an ornate gold-framed parchment panel: a fixed
     5-slot LOADOUT row (gun · subweapon · shield · drone · superweapon) above a
     POWERUPS grid of in-run picks, with wood (RESUME) + stone (BACK TO TITLE)
     plaque buttons. Reuses the shop/gear/inventory art kit — super-scroll.png,
     parchment.png, the gold-corner border-image, EB Garamond / Cinzel. */
  #pause-screen {
    --pause-gold: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='none'%3E%3ClinearGradient id='g' x1='0' x2='0' y1='0' y2='1'%3E%3Cstop offset='0' stop-color='%23f4d98e'/%3E%3Cstop offset='.45' stop-color='%23cda455'/%3E%3Cstop offset='1' stop-color='%238a6326'/%3E%3C/linearGradient%3E%3Crect x='7' y='7' width='86' height='86' rx='12' stroke='url(%23g)' stroke-width='5'/%3E%3Cpath d='M7 32 V18 Q7 7 18 7 H32' stroke='url(%23g)' stroke-width='13' stroke-linecap='round'/%3E%3Cpath d='M68 7 H82 Q93 7 93 18 V32' stroke='url(%23g)' stroke-width='13' stroke-linecap='round'/%3E%3Cpath d='M93 68 V82 Q93 93 82 93 H68' stroke='url(%23g)' stroke-width='13' stroke-linecap='round'/%3E%3Cpath d='M32 93 H18 Q7 93 7 82 V68' stroke='url(%23g)' stroke-width='13' stroke-linecap='round'/%3E%3C/svg%3E");
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    background:
      radial-gradient(125% 120% at 50% 28%, rgba(70, 42, 16, 0.55) 0%, rgba(18, 10, 3, 0.94) 72%),
      #130b04;
    z-index: 90;
    padding: clamp(12px, 3vw, 34px);
  }
  #pause-screen.visible { display: flex; }

  /* Ornate panel — dark-wood frame; gold-corner border-image overlay (::after). */
  #pause-screen .pause-card {
    position: relative;
    width: min(840px, 100%);
    box-sizing: border-box;
    border-radius: 22px;
    padding: 14px;
    background: linear-gradient(180deg, #4d301a 0%, #3a2310 52%, #2f1c0c 100%);
    box-shadow: inset 0 2px 2px rgba(255, 226, 180, 0.16), 0 18px 52px rgba(8, 5, 2, 0.62);
  }
  #pause-screen .pause-card::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    border: 14px solid transparent;
    border-image: var(--pause-gold) 33 / 14px stretch;
    border-radius: 22px;
  }

  /* PAUSED title — curled parchment scroll straddling the top frame edge. */
  #pause-screen h2 {
    position: relative; z-index: 3;
    margin: -2px auto -14px;
    width: min(440px, 84%); min-height: 80px;
    display: flex; align-items: center; justify-content: center;
    border-style: solid; border-color: transparent; border-width: 0 36px 0 39px;
    border-image: url('../assets/gear/super-scroll.png') 0 83 0 90 fill / 0 36px 0 39px / 0 stretch;
    filter: drop-shadow(0 5px 10px rgba(18, 10, 2, 0.55));
    font-family: 'Cinzel', serif; font-weight: 800;
    font-size: clamp(25px, 4vw, 40px); letter-spacing: 3px;
    color: #6e4a1e; text-shadow: 0 1px 0 rgba(255, 245, 216, 0.6);
  }

  /* Controls help + redundant sub-header hidden to match the mock. */
  #pause-screen #pause-controls,
  #pause-screen .pause-sub { display: none; }

  /* Parchment interior panel — scrolls if the build overflows; the gold frame
     (::after) stays put above its top/side edges. */
  #pause-screen .pause-inner {
    max-height: min(76vh, 760px); overflow-y: auto;
    border-radius: 13px;
    padding: clamp(28px, 3.2vw, 40px) clamp(14px, 2.6vw, 30px) clamp(16px, 2.2vw, 24px);
    background:
      radial-gradient(135% 78% at 50% 0%, rgba(255, 247, 222, 0.62) 0%, rgba(228, 206, 162, 0) 60%),
      linear-gradient(180deg, #efe1bf 0%, #e4d1a4 60%, #dcc69a 100%),
      url('../assets/gear/parchment.png') center / cover no-repeat, #e7d4a8;
    box-shadow: inset 0 0 30px rgba(120, 84, 38, 0.26), inset 0 2px 5px rgba(255, 251, 238, 0.45);
    color: #5a3f22; text-align: center;
    scrollbar-width: thin; scrollbar-color: rgba(120, 86, 46, 0.5) transparent;
  }
  #pause-screen .pause-inner::-webkit-scrollbar { width: 8px; }
  #pause-screen .pause-inner::-webkit-scrollbar-thumb { background: rgba(120, 86, 46, 0.5); border-radius: 4px; }

  /* Build column — LOADOUT scroll + 5-tile row, POWERUPS scroll + pick grid. */
  #pause-powerups {
    display: flex; flex-direction: column; align-items: stretch;
    gap: 2px; margin: 0 0 16px;
  }
  /* Section headers — the same curled scroll, compact. */
  #pause-powerups .pu-grp {
    align-self: center;
    width: min(300px, 72%); min-height: 50px;
    display: flex; align-items: center; justify-content: center;
    margin: 12px auto 9px;
    border-style: solid; border-color: transparent; border-width: 0 30px 0 32px;
    border-image: url('../assets/gear/super-scroll.png') 0 83 0 90 fill / 0 30px 0 32px / 0 stretch;
    filter: drop-shadow(0 3px 6px rgba(28, 14, 0, 0.34));
    font-family: 'Cinzel', serif; font-weight: 700;
    font-size: clamp(13px, 1.7vw, 17px); letter-spacing: 2.4px;
    text-transform: uppercase; color: #6e4a1e;
    text-shadow: 0 1px 0 rgba(255, 245, 216, 0.55);
  }
  #pause-powerups .pu-grp:first-child { margin-top: 2px; }

  /* LOADOUT row — always exactly 5 columns. POWERUPS grid — auto-fill wrap. */
  #pause-powerups .pu-loadrow {
    display: grid; grid-template-columns: repeat(5, 1fr); gap: clamp(6px, 1vw, 11px);
  }
  #pause-powerups .pu-pugrid {
    display: grid; grid-template-columns: repeat(5, 1fr);
    gap: clamp(6px, 1vw, 11px);
  }

  /* Parchment tile — cream card with tan edge + emboss (mock). */
  #pause-powerups .pu-tile {
    position: relative;
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    min-width: 0; padding: 11px 6px 9px; text-align: center;
    border-radius: 11px; border: 1.5px solid #b58f54;
    background:
      linear-gradient(180deg, rgba(255, 251, 238, 0.5) 0%, rgba(150, 118, 72, 0.16) 100%),
      url('../assets/gear/parchment.png') center / cover no-repeat, #f1e3c2;
    box-shadow: inset 0 1px 0 rgba(255, 251, 238, 0.7), inset 0 -3px 6px rgba(120, 88, 42, 0.22), 0 2px 4px rgba(40, 22, 8, 0.3);
    color: #5a3f22;
  }
  #pause-powerups .pu-tile .pu-ic {
    width: 46px; height: 46px;
    display: flex; align-items: center; justify-content: center; color: #5a3f22;
  }
  #pause-powerups .pu-tile .pu-ic svg { width: 40px; height: 40px; display: block; filter: drop-shadow(0 1px 1px rgba(80, 52, 20, 0.3)); }
  /* Real GLB thumbnail (loadout gear) — fills the icon box, drop-shadow for pop. */
  #pause-powerups .pu-tile .pu-ic .pu-thumb {
    width: 100%; height: 100%; object-fit: contain; display: block;
    filter: drop-shadow(0 2px 3px rgba(40, 22, 8, 0.45));
  }

  /* Player affliction strip — small pills (icon + label/stacks) under the top
     HUD when the player is debuffed (slow, burn, …). Enemy afflictions render
     as in-world badges above their HP bars (src/affliction-badge.js). */
  #player-afflictions {
    position: fixed; left: 50%; top: 88px; transform: translateX(-50%);
    display: none; gap: 6px; z-index: 6; pointer-events: none;
  }
  body.touch.portrait #player-afflictions { top: 72px; }
  #player-afflictions .pa-tile {
    display: flex; align-items: center; gap: 5px;
    padding: 4px 10px; border-radius: 999px;
    background: rgba(8, 8, 14, 0.78);
    border: 1.5px solid var(--pac, #9fe8ff);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  }
  #player-afflictions .pa-ic { font-size: 15px; line-height: 1; }
  #player-afflictions .pa-lb { font: 800 11px/1 system-ui, sans-serif; letter-spacing: 0.6px; color: var(--pac, #9fe8ff); }
  #pause-powerups .pu-tile .pu-name {
    font-family: 'Cinzel', serif; font-weight: 700;
    font-size: 10px; letter-spacing: 0.5px; line-height: 1.18;
    text-transform: uppercase; color: #6b4a26;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  #pause-powerups .pu-tile .pu-meta { display: flex; align-items: baseline; gap: 5px; }
  #pause-powerups .pu-tile .pu-val { font-family: 'EB Garamond', serif; font-weight: 700; color: #4f3414; font-size: 15px; line-height: 1; }
  #pause-powerups .pu-tile .pu-n   { font-family: 'EB Garamond', serif; font-weight: 700; color: #9a6b2e; font-size: 12px; }
  /* Maxed powerup — amber/gold wash (levelup "capped" treatment). */
  #pause-powerups .pu-tile.maxed {
    border-color: #d9a93a;
    background:
      linear-gradient(180deg, rgba(255, 236, 176, 0.72) 0%, rgba(200, 150, 40, 0.3) 100%),
      url('../assets/gear/parchment.png') center / cover no-repeat, #f6e6b4;
    box-shadow: inset 0 1px 0 rgba(255, 251, 238, 0.8), 0 0 12px rgba(220, 170, 60, 0.42), 0 2px 4px rgba(40, 22, 8, 0.3);
  }
  #pause-powerups .pu-tile.maxed .pu-val,
  #pause-powerups .pu-tile.maxed .pu-name { color: #8a5a14; }
  /* Empty loadout slot — recessed dashed parchment (no weapon owned). */
  #pause-powerups .pu-tile.pu-empty {
    border-style: dashed; border-color: rgba(120, 86, 46, 0.55);
    background: linear-gradient(180deg, rgba(120, 88, 46, 0.12), rgba(80, 54, 24, 0.18));
    box-shadow: inset 0 2px 6px rgba(60, 36, 14, 0.32);
    opacity: 0.7;
  }
  #pause-powerups .pu-tile.pu-empty .pu-ic { opacity: 0.5; }
  #pause-powerups .pu-tile.pu-empty .pu-val { color: #8a6a3e; }
  /* Powerups empty-state hint. */
  #pause-powerups .pu-empty-hint {
    padding: 12px; text-align: center;
    font-family: 'EB Garamond', serif; font-weight: 700; font-size: 14px;
    color: #8a6a3e; letter-spacing: 0.4px;
  }

  /* Actions — RESUME (wood) + BACK TO TITLE (stone) ornate plaques, side by
     side (the mock); both share the gold-corner frame + red side-gems. */
  #pause-actions {
    display: flex; flex-direction: row; justify-content: center;
    gap: clamp(10px, 2vw, 22px); margin-top: 4px; flex-wrap: wrap;
  }
  #pause-screen #pause-resume,
  #pause-screen #pause-back-to-title {
    position: relative;
    flex: 1 1 0; min-width: 150px; max-width: 330px;
    box-sizing: border-box;
    border: 12px solid transparent;
    border-image: var(--pause-gold) 33 / 12px stretch;
    border-radius: 15px;
    padding: 11px 16px;
    font-family: 'Cinzel', serif; font-weight: 800;
    font-size: clamp(15px, 2vw, 20px); letter-spacing: 2px; text-transform: uppercase;
    cursor: pointer; touch-action: manipulation;
    transition: filter 140ms ease, transform 110ms ease;
  }
  #pause-screen #pause-resume {
    background: linear-gradient(180deg, #b6824a 0%, #8a5a2e 52%, #6e451f 100%);
    color: #f6e6c4; text-shadow: 0 1px 2px rgba(40, 20, 4, 0.55);
    box-shadow: inset 0 2px 3px rgba(255, 224, 170, 0.3), inset 0 -4px 8px rgba(40, 22, 8, 0.42), 0 6px 14px rgba(16, 9, 3, 0.45);
  }
  #pause-screen #pause-back-to-title {
    background: linear-gradient(180deg, #b6b4ac 0%, #8d8b83 52%, #6f6d66 100%);
    color: #3f3d38; text-shadow: 0 1px 1px rgba(255, 255, 255, 0.35);
    box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.35), inset 0 -4px 8px rgba(40, 40, 36, 0.4), 0 6px 14px rgba(16, 9, 3, 0.45);
  }
  #pause-screen #pause-resume:hover,
  #pause-screen #pause-back-to-title:hover { filter: brightness(1.08); }
  #pause-screen #pause-resume:active,
  #pause-screen #pause-back-to-title:active { transform: translateY(1px); }
  /* Red side-gems on the plaque buttons (mock). */
  #pause-screen #pause-resume::before, #pause-screen #pause-resume::after,
  #pause-screen #pause-back-to-title::before, #pause-screen #pause-back-to-title::after {
    content: ""; position: absolute; top: 50%; width: 13px; height: 13px;
    transform: translateY(-50%) rotate(45deg);
    background: radial-gradient(circle at 38% 32%, #ff8a9a 0%, #e0455f 45%, #a8132e 100%);
    border: 1.5px solid #6e0c1e; border-radius: 2px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  }
  #pause-screen #pause-resume::before, #pause-screen #pause-back-to-title::before { left: -7px; }
  #pause-screen #pause-resume::after,  #pause-screen #pause-back-to-title::after  { right: -7px; }

  /* Portrait / narrow — shrink loadout internals so 5 boxes still fit a row. */
  @media (max-width: 600px) {
    #pause-screen .pause-card { width: 100%; }
    #pause-powerups .pu-loadrow { gap: 5px; }
    #pause-powerups .pu-loadrow .pu-tile { padding: 8px 3px 7px; border-radius: 9px; }
    #pause-powerups .pu-loadrow .pu-tile .pu-ic { width: 34px; height: 34px; }
    #pause-powerups .pu-loadrow .pu-tile .pu-ic svg { width: 28px; height: 28px; }
    #pause-powerups .pu-loadrow .pu-tile .pu-name { font-size: 8px; letter-spacing: 0.2px; }
    #pause-powerups .pu-loadrow .pu-tile .pu-val { font-size: 12px; }
    #pause-powerups .pu-pugrid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }
  }

  /* 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:
      /* Accent tint covering the full button — bright at top, holding
         through the bottom so the button reads as one coherent colored
         tile instead of a half-glossy / half-dark split. */
      linear-gradient(180deg, rgba(var(--acc-rgb), 0.32) 0%, rgba(var(--acc-rgb), 0.18) 55%, rgba(var(--acc-rgb), 0.12) 100%),
      linear-gradient(180deg, rgba(26, 32, 54, 0.82) 0%, rgba(14, 18, 34, 0.88) 100%);
    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);
      /* Removed inset 0 -12px 24px rgba(0,0,0,0.28) — that bottom
         vignette was eating the lower half of the colored tint and
         making the button look like only the top half had the glow. */
    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: '';
    /* Gloss/sheen — was previously inset 6% from sides + 4px from top
       to read like a separate "highlight insert", but visually the
       inset frame around the gloss looked like the colored fill was
       only covering ~95% of the button (a darker rim around an inner
       glossy core). Pulling it edge-to-edge so the whole button reads
       as one solid glossy tile. The parent's overflow:hidden + matching
       border-radius keep the corners clean. */
    position: absolute; left: 0; right: 0; top: 0; height: 38%;
    border-radius: inherit;
    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: var(--lilac); --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;
    /* Desktop default — sits above the health/XP bars. Mobile overrides
       this to track the action bar via the @media (max-width: 720px)
       block further down. */
    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: var(--radius-pill);
    /* 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;
    /* Inherit Outfit from body so the LIMIT BREAK label visually matches
       the SCORE / WAVE / LEVEL / HEALTH HUD pills above it. (Was Space
       Grotesk — visibly more angular than the rest of the HUD.) */
    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: var(--radius-pill);
    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: var(--radius-pill);
    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); }
  }
  /* ── FF7-style "charged!" one-shot ──────────────────────────────────────
     Fires the instant the meter hits 100%, via .just-ready (added on the
     rising edge in src/hud.js, mirroring .ability.ready.just-ready). Grounded
     in how FF7 signals a ready Limit: the gauge FLASHES, then the fill cycles
     an IRIDESCENT rainbow shimmer while the label FLASHES pink↔gold↔white.
     The one-shot lives on a ::before flash + the fill + the label so it never
     overrides the steady .ready `lb-glow` animation on the meter element. */
  #limit-break-meter.just-ready::before {
    content: ''; position: absolute; inset: -1px;
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, 0.95);
    mix-blend-mode: screen; pointer-events: none;
    opacity: 0;                         /* base 0 so it's invisible after the flash */
    animation: lb-ready-flash 0.5s ease-out 1;
  }
  #limit-break-meter.just-ready .lb-fill { animation: lb-ready-irid 1.6s linear 1; }
  #limit-break-meter.just-ready .lb-label { animation: lb-ready-label 1.1s steps(1, end) 1; }
  @keyframes lb-ready-flash { 0% { opacity: 0; } 18% { opacity: 1; } 100% { opacity: 0; } }
  @keyframes lb-ready-irid {
    0%   { filter: hue-rotate(0deg)   saturate(1.5) brightness(1.4); }
    100% { filter: hue-rotate(360deg) saturate(1.1) brightness(1); }
  }
  @keyframes lb-ready-label {
    0% { color: #fff; } 20% { color: #ff6b9f; } 40% { color: #ffd166; }
    60% { color: #ff6b9f; } 80% { color: #fff; } 100% { color: #ffd166; }
  }
  @media (prefers-reduced-motion: reduce) {
    #limit-break-meter.just-ready::before,
    #limit-break-meter.just-ready .lb-fill,
    #limit-break-meter.just-ready .lb-label { animation: none; }
  }
  @media (max-width: 720px) {
    #limit-break-meter {
      width: 220px; font-size: 10px;
      /* Original mobile value was a fixed 96px which sat directly under
         the action bar once Android 15 safe-area insets pushed the bar
         up. Add the inset back so the meter tracks the action bar. */
      bottom: calc(96px + env(safe-area-inset-bottom, 0px));
    }
  }

  /* 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: var(--radius-pill);
    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;
  }
