/* ncnlinh.com homepage — restrained neon. One bold color (magenta), the rest
   bone-white on black. Homepage-only; never touches style.css. */

:root {
  --black:   #07070d;
  --magenta: #ff2e97;   /* the one hero color */
  --bone:    #ece9f5;   /* primary text — 16.8:1 */
  --dim:     #9aa0c8;   /* secondary text — 7.9:1 (WCAG AAA) */
  --line:    rgba(236, 233, 245, 0.10);
}

/* Honor the OS "increase contrast" setting: bump to WCAG AAA (7:1+). */
@media (prefers-contrast: more) {
  :root {
    --magenta: #ff8cc2;  /* 9.4:1 */
    --dim:     #b0b5d6;  /* 10.0:1 */
    --line:    rgba(236, 233, 245, 0.28);
  }
}

* { box-sizing: border-box; }
/* This is a single-screen attract layout — it never scrolls. Clip overflow at
   the root so the falling rain / off-screen effect particles (position:fixed,
   translated past the viewport) can't extend the page or spawn a scrollbar.
   `clip` is preferred over `hidden`: it doesn't create a scroll container. */
html {
  background: var(--black);
  overflow: clip;
}

/* No raw browser ring. Inline links show focus via the animated underline +
   magenta glow (see the link rules below); the menu row has its own wash. */
a, a:focus, a:focus-visible { outline: none; }

body {
  margin: 0;
  min-height: 100vh;
  /* No accidental text highlighting on double-click; it's an attract screen. */
  -webkit-user-select: none;
  user-select: none;
  padding: 0 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "VT323", ui-monospace, "SF Mono", Menlo, monospace;
  /* Fluid base: scales with the viewport between phone and large desktop. */
  font-size: clamp(20px, 1.6vw + 12px, 30px);
  line-height: 1.5;          /* WCAG 1.4.8: paragraph line spacing >= 1.5 */
  color: var(--bone);
  background:
    radial-gradient(70% 50% at 50% 38%, rgba(255, 46, 151, 0.07), transparent 70%),
    var(--black);
  background-attachment: fixed;
  cursor: none;
  overflow: clip;     /* root also clips; this keeps body from ever scrolling */
}

/* Faint scanlines + vignette — atmosphere, kept very low so it reads as a
   screen without muddying the type. */
body::before,
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 50;
}
body::before {
  background: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.022) 0 1px,
    transparent 1px 3px
  );
}
body::after {
  background: radial-gradient(130% 130% at 50% 45%, transparent 60%, rgba(0,0,0,0.6) 100%);
}

/* The held game-mode zoom is .wrap's BASE scale (a registered custom property so
   the transition interpolates it smoothly). The screen-effect animations
   (shake/drop/rumble) run via the Web Animations API with composite:"add", so
   they layer their transforms ON TOP of this base scale — the zoom holds while a
   rumble pulses or a shake jitters. (See animateWrap in fx.js.) */
@property --fx-zoom { syntax: "<number>"; inherits: false; initial-value: 1; }
.wrap {
  width: 100%;
  max-width: 34em;          /* grows with the fluid base, not a fixed px box */
  position: relative;
  z-index: 1;
  transform-origin: 50% 50%;
  scale: var(--fx-zoom);
  /* Leaving game mode springs --fx-zoom back to 1 fast — 500ms ease-IN, matching
     the glass's quick exit (this resting-state transition governs the return). */
  transition: --fx-zoom .5s cubic-bezier(.4,0,1,1);
}
/* Entering game mode pushes the page "back into the cabinet": --fx-zoom eases
   down over 1.5s ease-OUT, in step with the glass zooming in. */
body.fx-ingame .wrap {
  --fx-zoom: .88;
  transition: --fx-zoom 1.5s cubic-bezier(.16,.84,.3,1);
}
@media (prefers-reduced-motion: reduce) {
  .wrap, body.fx-ingame .wrap { transition: none; }
  body.fx-ingame .wrap { --fx-zoom: 1; }      /* no zoom under reduced motion */
}

/* ── Marquee — the one bold thing ──────────────────────────────────── */
.intro { margin: 0 0 4em; }

h1 {
  font-family: "Press Start 2P", "VT323", monospace;
  font-weight: 400;
  font-size: clamp(28px, 4.2vw + 8px, 60px);
  line-height: 1.3;
  letter-spacing: 0.01em;
  margin: 0;
  color: #fff;
  /* crisp sign: sharp white core, one tight magenta halo, one soft ambient. */
  text-shadow:
    0 0 1px rgba(255, 255, 255, 0.9),
    0 0 7px var(--magenta),
    0 0 30px rgba(255, 46, 151, 0.45);
}

.tagline {
  margin: 0.85em 0 0;
  color: var(--dim);
  font-size: 1em;
}
.tagline a { color: var(--bone); }
.tagline .dot { opacity: .4; margin: 0 8px; }

/* ── Menu (projects) — a select list, no boxes ─────────────────────── */
.menu { margin: 0; }

.menu-title {
  font-family: "Jersey 10", monospace;
  font-weight: 400;
  font-size: 0.85em;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--magenta);
  margin: 0 0 1.2em;
  opacity: 0.9;
}

.menu-item {
  display: block;
  position: relative;
  padding: 0.8em 0 0.8em 1.3em;
  text-decoration: none;
  color: inherit;
  border-top: 1px solid var(--line);
  transition: background .14s, box-shadow .14s;
}
.menu-item:last-child { border-bottom: 1px solid var(--line); }

/* Keyboard focus: a magenta wash + a thick ring drawn as an inset border
   (WCAG 2.4.13 — 3px, magenta 7.8:1 on black). The ::before/::after pseudo
   elements are taken by the ▸ marker and the divider, so the ring is an
   inset box-shadow that fades in. */
.menu-item:focus-visible {
  outline: none;
  background: linear-gradient(90deg, rgba(255, 46, 151, 0.12), transparent 75%);
  box-shadow:
    inset 0 0 0 3px var(--magenta),
    0 0 14px rgba(255, 46, 151, 0.4);
  border-radius: 6px;
}

/* the ▸ selector — invisible until the row is active */
.menu-item::before {
  content: "\25B8";
  position: absolute;
  left: 0.15em;
  top: 0.8em;
  color: var(--magenta);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity .14s, transform .14s;
}
.menu-item:hover::before,
.menu-item:focus-visible::before { opacity: 1; transform: translateX(0); }

.menu-item .name {
  display: block;
  font-size: 1.1em;
  color: var(--bone);
  transition: color .14s, text-shadow .14s;
}
.menu-item:hover .name,
.menu-item:focus-visible .name {
  color: #fff;
  text-shadow: 0 0 8px rgba(255, 46, 151, 0.5);
}
.menu-item .desc { display: block; color: var(--dim); font-size: 0.85em; margin-top: 1px; }

/* ── Contact + links: a quiet line under the subheader ─────────────── */
.coda { color: var(--dim); font-size: 0.85em; margin: 0.9em 0 0; }
.coda a { color: var(--dim); }
.coda .sep { opacity: .35; margin: 0 10px; }

/* ── Inline link states ────────────────────────────────────────────── */
/* Hover  → magenta underline wipes in from the left, tiny lift.
   Focus  → a thick magenta ring snaps in around the link (distinct from
            hover, and meeting WCAG 2.4.13 focus appearance: 2px+ border,
            magenta on black is 7.8:1). */
.tagline a,
.coda a {
  position: relative;
  text-decoration: none;
  display: inline-block;
  transition: color .16s, transform .16s, text-shadow .16s;
}

/* hover underline */
.tagline a::after,
.coda a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0.12em;
  width: 100%;
  height: 1px;
  background: var(--magenta);
  box-shadow: 0 0 6px var(--magenta);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .26s cubic-bezier(.2, .7, .3, 1);
}
.tagline a:hover::after,
.coda a:hover::after { transform: scaleX(1); }

.tagline a:hover,
.coda a:hover {
  color: var(--magenta);
  transform: translateY(-1px);
  text-shadow: 0 0 8px rgba(255, 46, 151, 0.45);
}

/* focus ring — animates in from a slightly expanded, faded state */
.tagline a::before,
.coda a::before {
  content: "";
  position: absolute;
  inset: -0.18em -0.4em;
  border: 3px solid var(--magenta);
  border-radius: 6px;
  box-shadow: 0 0 10px rgba(255, 46, 151, 0.55);
  opacity: 0;
  transform: scale(1.18);
  pointer-events: none;
  transition: opacity .14s ease-out, transform .14s cubic-bezier(.2, .8, .25, 1);
}
.tagline a:focus-visible,
.coda a:focus-visible {
  color: var(--magenta);
  text-shadow: 0 0 8px rgba(255, 46, 151, 0.5);
}
.tagline a:focus-visible::before,
.coda a:focus-visible::before {
  opacity: 1;
  transform: scale(1);
}

/* ── Cursor sprite + trail (the one live splash of green) ──────────── */
/* z-index above every panel/toast (max is the toast at 90) so the sprite cursor
   and its sparks always ride ON TOP of the shop/feedback dialogs — otherwise the
   custom cursor would vanish behind a panel you're hovering, which reads broken. */
#cursor, .spark {
  position: fixed;
  top: 0; left: 0;
  pointer-events: none;
  z-index: 100;
  will-change: transform;
}

/* Touch / no-fine-pointer devices have no real cursor to replace, so the custom
   sprite is pointless (and would just sit frozen mid-screen). Hide it and let the
   OS handle pointing. The JS also bails on these devices (see index.html). */
@media (hover: none), (pointer: coarse) {
  body { cursor: auto; }
  #cursor, .spark { display: none; }
}
#cursor {
  width: 10px; height: 10px;
  margin: -5px 0 0 -5px;
  background: #fff;
  box-shadow: 0 0 0 2px var(--black), 0 0 8px 1px var(--magenta), 0 0 16px 3px rgba(255,46,151,.5);
  transition: transform .04s linear;
}
.spark {
  width: 4px; height: 4px;
  margin: -2px 0 0 -2px;
  background: var(--magenta);
  box-shadow: 0 0 6px 1px var(--magenta);
}

/* ══════════════════════════════════════════════════════════════════════
   CLICK EFFECTS — the cabinet reacts to being whacked.
   Double-click → one random Tier-1 effect. Triple-click → a Tier-2 effect.
   Rapid continuous clicking → FEVER MODE (Tier-3), escalating.
   All JS-driven via classes/elements on <body>; all gated by reduced motion.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Tier 1: screen shake ──────────────────────────────────────────── */
/* Effects animate the INDEPENDENT translate/rotate/scale properties (not the
   combined `transform`). The page-movement effects (shake, drop, rumble,
   fever-shake) now run via the Web Animations API with composite:"add" (see
   animateWrap in fx.js) so they SUM with each other and the zoom — they're no
   longer CSS here. crt keeps its CSS transform (it needs skew). */

/* ── Tier 1: chromatic split on the headline ───────────────────────── */
/* A BIG RGB-split: the cyan/magenta/blue ghosts jump far off the headline
   (±18–24px) and ricochet through several positions before settling — a heavy
   "signal tearing apart" read, not a subtle aberration. */
@keyframes fx-chroma {
  0%   { text-shadow: 0 0 1px #fff, 0 0 7px var(--magenta), 0 0 30px rgba(255,46,151,.45); }
  18%  { text-shadow: -20px 0 0 #ff2e97, 20px 0 0 #2effd0, 0 0 36px rgba(255,46,151,.5); }
  38%  { text-shadow: 22px 0 0 #2e8bff, -22px 0 0 #ff2e97, 0 0 36px rgba(255,46,151,.5); }
  58%  { text-shadow: -16px 0 0 #ff2e97, 16px 0 0 #2effd0, 0 0 36px rgba(255,46,151,.5); }
  78%  { text-shadow: 10px 0 0 #2e8bff, -10px 0 0 #2effd0, 0 0 36px rgba(255,46,151,.5); }
  100% { text-shadow: 0 0 1px #fff, 0 0 7px var(--magenta), 0 0 30px rgba(255,46,151,.45); }
}
.fx-chroma h1 { animation: fx-chroma .6s steps(5) 1; }

/* ── Tier 1: headline jiggle (per-letter wrappers added by JS) ─────── */
@keyframes fx-jiggle {
  0%,100% { transform: translateY(0) rotate(0); }
  25% { transform: translateY(-10px) rotate(-6deg); }
  50% { transform: translateY(4px) rotate(5deg); }
  75% { transform: translateY(-5px) rotate(-3deg); }
}
.fx-letter { display: inline-block; will-change: transform; }
.fx-jiggle .fx-letter { animation: fx-jiggle .5s ease-in-out; }

/* ── Tier 1: scanline sweep ────────────────────────────────────────── */
.fx-sweep {
  position: fixed; left: 0; right: 0; top: 0; height: 4px; z-index: 70;
  pointer-events: none;
  background: linear-gradient(transparent, rgba(255,255,255,.85), transparent);
  box-shadow: 0 0 18px 6px rgba(255,46,151,.6);
  animation: fx-sweep-move .7s linear forwards;
}
@keyframes fx-sweep-move { from { top: -6px; opacity: 1; } to { top: 100vh; opacity: .4; } }

/* ── Tier 1: shockwave ring (spawned at the click point) ───────────── */
/* An expanding neon ring that scales up and fades, then JS reaps it (see
   shockwave() in fx.js). A spawned element — composes with every .wrap transform
   effect because it never touches .wrap. */
.fx-shockwave {
  position: fixed; z-index: 71; pointer-events: none;
  width: 24px; height: 24px; margin: -12px 0 0 -12px;  /* center on (x,y) */
  border: 3px solid var(--magenta); border-radius: 50%;
  box-shadow: 0 0 12px 2px var(--magenta), inset 0 0 8px rgba(255,46,151,.5);
  animation: fx-shockwave-grow .6s cubic-bezier(.2,.7,.3,1) forwards;
}
@keyframes fx-shockwave-grow {
  0%   { opacity: .9; transform: scale(.2); }
  100% { opacity: 0;  transform: scale(6); }
}

/* ── Tier 1: palette flip (magenta → cyan/green and back) ──────────── */
/* A held flip, not a blip: snap to the rotated palette, sit there, snap back.
   Steps keep the swap reading as a deliberate palette change, not a fade. It's a
   `filter` on .wrap — its own channel, so it COMPOSES with the transform effects
   (shake/spin/rumble). Scoped to .wrap, NOT body, so the HUD and cabinet glass
   keep their real colors while only the page content recolors. */
@keyframes fx-flip-anim {
  0%   { filter: hue-rotate(0deg) saturate(1); }
  12%  { filter: hue-rotate(180deg) saturate(1.5); }
  88%  { filter: hue-rotate(180deg) saturate(1.5); }
  100% { filter: hue-rotate(0deg) saturate(1); }
}
.fx-flip .wrap { animation: fx-flip-anim .9s steps(1, end); }

/* ── Tier 1: invert flash (single blink) ───────────────────────────── */
/* One brief invert+hue blink on .wrap — a single flash, never repeated, so it
   stays inside the WCAG 2.3.1 flash budget. A `filter` (own channel) scoped to
   .wrap, so it composes with the transforms and leaves the HUD/glass untinted. */
@keyframes fx-invert-blink {
  0%, 100% { filter: none; }
  40%, 60% { filter: invert(1) hue-rotate(90deg); }
}
.fx-invert .wrap { animation: fx-invert-blink .24s steps(2, end); }

/* ── Tier 1: neon edge flash (arcade "hit" vignette) ───────────────── */
/* A spawned full-screen overlay that flares a magenta inset glow at the rim and
   fades. Composes freely — it's its own fixed element, never touches .wrap. */
.fx-edge-flash {
  position: fixed; inset: 0; z-index: 73; pointer-events: none;
  box-shadow: inset 0 0 90px 12px var(--magenta), inset 0 0 30px 4px rgba(255,46,151,.6);
  animation: fx-edge-flash-fade .42s ease-out forwards;
}
@keyframes fx-edge-flash-fade {
  0%   { opacity: .85; }
  100% { opacity: 0; }
}

/* ── Tier 2: warp streaks (hyperspace lines from the click point) ──── */
/* Thin neon lines that shoot outward and fade (JS sets the travel transform +
   reaps them). Spawned elements, so they layer with every .wrap effect. */
.fx-streak {
  position: fixed; z-index: 71; pointer-events: none;
  width: 46px; height: 2px; margin: -1px 0 0 0;
  transform-origin: 0 50%;
  background: linear-gradient(90deg, rgba(255,46,151,0), var(--magenta));
  box-shadow: 0 0 6px 1px rgba(255,46,151,.7);
}

/* ── Tier 1: floating pixel text (coin / combo / fever readouts) ───── */
.fx-float {
  position: fixed; z-index: 72; pointer-events: none;
  font-family: "Press Start 2P", "VT323", monospace;
  font-size: 16px; color: #ffe14d;
  text-shadow: 0 0 8px rgba(255,225,77,.8), 2px 2px 0 #b85c00;
  white-space: nowrap;
  transform: translate(-50%, -50%);
  animation: fx-float-up 1s ease-out forwards;
}
@keyframes fx-float-up {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.6); }
  20%  { opacity: 1; transform: translate(-50%, -90%) scale(1.1); }
  100% { opacity: 0; transform: translate(-50%, -240%) scale(1); }
}

/* ── Tier 1: 8-bit coin pop ("+1 CREDIT") ──────────────────────────── */
/* A real 8-frame coin spin (each frame is a distinct drawing of the coin
   rotating about its vertical axis), drawn as box-shadow pixel art on a 20px
   grid. JS cycles the .cf1…cf8 frame classes; the whole 200px source coin is
   scaled down to fit the floating pop. The wrapper floats coin + label away. */
/* Anchored so the COIN sits on the cursor: the pop is a zero-size point at
   (x,y); the coin is centered on it and the label hangs below, absolutely
   positioned so it doesn't pull the anchor off the cursor. */
.fx-coin-pop {
  position: fixed; z-index: 72; pointer-events: none;
  width: 0; height: 0;
  animation: fx-coin-float 1.1s ease-out forwards;
}
/* Pixel-based float (the wrapper is a zero-size point, so % translate is a
   no-op here — must move in px). */
@keyframes fx-coin-float {
  0%   { opacity: 0; transform: translateY(0) scale(.6); }
  18%  { opacity: 1; transform: translateY(-14px) scale(1.1); }
  100% { opacity: 0; transform: translateY(-70px) scale(1); }
}
.fx-coin-pop .fx-coin {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
}
.fx-coin-label {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, 14px);   /* just below the 24px coin */
  font-family: "Press Start 2P", "VT323", monospace;
  font-size: 13px; color: #ffe14d;
  text-shadow: 0 0 8px rgba(255,225,77,.8), 2px 2px 0 #b85c00;
  white-space: nowrap;
}
/* The 200x200 source grid is scaled down to fit a small box. The <i> pixel
   sits at the box's top-left; scaling about (0,0) by 0.22 maps the grid's
   200px extent to 44px, and its center (100,100) to (22,22) — the box center.
   So every frame shares the same pivot and the spin stays put. */
.fx-coin {
  position: relative;
  width: 24px; height: 24px;
  filter: drop-shadow(0 0 6px rgba(255,207,46,.55));
}
.fx-coin > i {
  position: absolute; top: 0; left: 0;
  width: 20px; height: 20px;          /* one "pixel" on the source grid */
  transform: scale(0.12);             /* 200px grid → 24px; center → (12,12) */
  transform-origin: 0 0;
}
/* Frame 1 — widest face */
.fx-coin > i.cf1 { box-shadow:
  40px 0 0 #FFF000,60px 0 0 #FFF000,80px 0 0 #FFF000,100px 0 0 #FFF000,120px 0 0 #FFF000,140px 0 0 #FFF000,
  20px 20px 0 #FFF000,40px 20px 0 #FFD800,60px 20px 0 #FFD800,80px 20px 0 #FFD800,100px 20px 0 #FFD800,120px 20px 0 #FFD800,140px 20px 0 #FFD800,160px 20px 0 #FFD800,
  0 40px 0 #FFF000,20px 40px 0 #FFD800,40px 40px 0 #FFD800,60px 40px 0 #FFF,80px 40px 0 #FFAE00,100px 40px 0 #FFAE00,120px 40px 0 #FFAE00,140px 40px 0 #FFD800,160px 40px 0 #FFD800,180px 40px 0 #FFD800,
  0 60px 0 #FFD800,20px 60px 0 #FFD800,40px 60px 0 #FFF,60px 60px 0 #FFF,80px 60px 0 #FFF,100px 60px 0 #FFD800,120px 60px 0 #FFD800,140px 60px 0 #FFF000,160px 60px 0 #FFD800,180px 60px 0 #FFD800,
  0 80px 0 #FFD800,20px 80px 0 #FFD800,40px 80px 0 #FFAE00,60px 80px 0 #FFF,80px 80px 0 #FFD800,100px 80px 0 #FFD800,120px 80px 0 #FFD800,140px 80px 0 #FFF000,160px 80px 0 #FFD800,180px 80px 0 #FFD800,
  0 100px 0 #FFD800,20px 100px 0 #FFD800,40px 100px 0 #FFAE00,60px 100px 0 #FFD800,80px 100px 0 #FFD800,100px 100px 0 #FFD800,120px 100px 0 #FFD800,140px 100px 0 #FFF000,160px 100px 0 #FFD800,180px 100px 0 #FFD800,
  0 120px 0 #FFD800,20px 120px 0 #FFD800,40px 120px 0 #FFAE00,60px 120px 0 #FFD800,80px 120px 0 #FFD800,100px 120px 0 #FFD800,120px 120px 0 #FFD800,140px 120px 0 #FFF000,160px 120px 0 #FFD800,180px 120px 0 #FFD800,
  0 140px 0 #FFD800,20px 140px 0 #FFD800,40px 140px 0 #FFD800,60px 140px 0 #FFF000,80px 140px 0 #FFF000,100px 140px 0 #FFF000,120px 140px 0 #FFF000,140px 140px 0 #FFD800,160px 140px 0 #FFD800,180px 140px 0 #FFAE00,
  20px 160px 0 #FFD800,40px 160px 0 #FFD800,60px 160px 0 #FFD800,80px 160px 0 #FFD800,100px 160px 0 #FFD800,120px 160px 0 #FFD800,140px 160px 0 #FFD800,160px 160px 0 #FFAE00,
  40px 180px 0 #FFAE00,60px 180px 0 #FFAE00,80px 180px 0 #FFAE00,100px 180px 0 #FFAE00,120px 180px 0 #FFAE00,140px 180px 0 #FFAE00; }
/* Frame 2 */
.fx-coin > i.cf2 { box-shadow:
  50px 0 0 #FFF000,70px 0 0 #FFF000,90px 0 0 #FFF000,110px 0 0 #FFF000,130px 0 0 #FFF000,
  30px 20px 0 #FFF000,50px 20px 0 #FFD800,70px 20px 0 #FFD800,90px 20px 0 #FFD800,110px 20px 0 #FFD800,130px 20px 0 #FFD800,150px 20px 0 #FFD800,
  10px 40px 0 #FFF000,20px 40px 0 #FFD800,40px 40px 0 #FFD800,60px 40px 0 #FFD800,80px 40px 0 #FFAE00,90px 40px 0 #FFAE00,110px 40px 0 #FFAE00,130px 40px 0 #FFD800,150px 40px 0 #FFD800,170px 40px 0 #FFD800,
  10px 60px 0 #FFD800,20px 60px 0 #FFD800,40px 60px 0 #FFD800,60px 60px 0 #FFF,80px 60px 0 #FFD800,90px 60px 0 #FFD800,110px 60px 0 #FFD800,130px 60px 0 #FFF000,150px 60px 0 #FFD800,170px 60px 0 #FFD800,
  10px 80px 0 #FFD800,20px 80px 0 #FFD800,40px 80px 0 #FFD800,60px 80px 0 #FFAE00,80px 80px 0 #FFD800,90px 80px 0 #FFD800,110px 80px 0 #FFD800,130px 80px 0 #FFF000,150px 80px 0 #FFD800,170px 80px 0 #FFD800,
  10px 100px 0 #FFD800,20px 100px 0 #FFD800,40px 100px 0 #FFD800,60px 100px 0 #FFAE00,80px 100px 0 #FFD800,90px 100px 0 #FFD800,110px 100px 0 #FFD800,130px 100px 0 #FFF000,150px 100px 0 #FFD800,170px 100px 0 #FFD800,
  10px 120px 0 #FFD800,20px 120px 0 #FFD800,40px 120px 0 #FFD800,60px 120px 0 #FFAE00,80px 120px 0 #FFD800,90px 120px 0 #FFD800,110px 120px 0 #FFD800,130px 120px 0 #FFF000,150px 120px 0 #FFD800,170px 120px 0 #FFD800,
  10px 140px 0 #FFD800,30px 140px 0 #FFD800,50px 140px 0 #FFD800,60px 140px 0 #FFD800,80px 140px 0 #FFF000,100px 140px 0 #FFF000,110px 140px 0 #FFF000,130px 140px 0 #FFD800,150px 140px 0 #FFD800,170px 140px 0 #FFAE00,
  30px 160px 0 #FFD800,50px 160px 0 #FFD800,70px 160px 0 #FFD800,90px 160px 0 #FFD800,110px 160px 0 #FFD800,130px 160px 0 #FFD800,150px 160px 0 #FFAE00,
  50px 180px 0 #FFAE00,60px 180px 0 #FFAE00,80px 180px 0 #FFAE00,100px 180px 0 #FFAE00,120px 180px 0 #FFAE00,130px 180px 0 #FFAE00; }
/* Frame 3 */
.fx-coin > i.cf3 { box-shadow:
  60px 0 0 #FFF000,80px 0 0 #FFF000,100px 0 0 #FFF000,120px 0 0 #FFF000,
  40px 20px 0 #FFF000,60px 20px 0 #FFD800,80px 20px 0 #FFD800,100px 20px 0 #FFD800,120px 20px 0 #FFD800,140px 20px 0 #FFD800,
  20px 40px 0 #FFF000,40px 40px 0 #FFD800,60px 40px 0 #FFD800,80px 40px 0 #FFD800,100px 40px 0 #FFAE00,120px 40px 0 #FFD800,140px 40px 0 #FFD800,160px 40px 0 #FFD800,
  20px 60px 0 #FFD800,40px 60px 0 #FFD800,60px 60px 0 #FFD800,80px 60px 0 #FFAE00,100px 60px 0 #FFD800,120px 60px 0 #FFF000,140px 60px 0 #FFD800,160px 60px 0 #FFD800,
  20px 80px 0 #FFD800,40px 80px 0 #FFD800,60px 80px 0 #FFD800,80px 80px 0 #FFAE00,100px 80px 0 #FFD800,120px 80px 0 #FFF000,140px 80px 0 #FFD800,160px 80px 0 #FFD800,
  20px 100px 0 #FFD800,40px 100px 0 #FFD800,60px 100px 0 #FFD800,80px 100px 0 #FFAE00,100px 100px 0 #FFD800,120px 100px 0 #FFF000,140px 100px 0 #FFD800,160px 100px 0 #FFD800,
  20px 120px 0 #FFD800,40px 120px 0 #FFD800,60px 120px 0 #FFD800,80px 120px 0 #FFAE00,100px 120px 0 #FFD800,120px 120px 0 #FFF000,140px 120px 0 #FFD800,160px 120px 0 #FFD800,
  20px 140px 0 #FFD800,40px 140px 0 #FFD800,60px 140px 0 #FFD800,80px 140px 0 #FFD800,100px 140px 0 #FFF000,120px 140px 0 #FFD800,140px 140px 0 #FFD800,160px 140px 0 #FFAE00,
  40px 160px 0 #FFD800,60px 160px 0 #FFD800,80px 160px 0 #FFD800,100px 160px 0 #FFD800,120px 160px 0 #FFD800,140px 160px 0 #FFAE00,
  60px 180px 0 #FFAE00,80px 180px 0 #FFAE00,100px 180px 0 #FFAE00,120px 180px 0 #FFAE00; }
/* Frame 4 — thin */
.fx-coin > i.cf4 { box-shadow:
  70px 0 0 #FFF000,90px 0 0 #FFF000,110px 0 0 #FFF000,
  50px 20px 0 #FFF000,70px 20px 0 #FFD800,90px 20px 0 #FFD800,110px 20px 0 #FFD800,130px 20px 0 #FFD800,
  50px 40px 0 #FFD800,70px 40px 0 #FFD800,90px 40px 0 #FFD800,110px 40px 0 #FFAE00,130px 40px 0 #FFD800,
  50px 60px 0 #FFD800,70px 60px 0 #FFD800,90px 60px 0 #FFD800,110px 60px 0 #FFAE00,130px 60px 0 #FFD800,
  50px 80px 0 #FFD800,70px 80px 0 #FFD800,90px 80px 0 #FFD800,110px 80px 0 #FFAE00,130px 80px 0 #FFD800,
  50px 100px 0 #FFD800,70px 100px 0 #FFD800,90px 100px 0 #FFD800,110px 100px 0 #FFAE00,130px 100px 0 #FFD800,
  50px 120px 0 #FFD800,70px 120px 0 #FFD800,90px 120px 0 #FFD800,110px 120px 0 #FFAE00,130px 120px 0 #FFD800,
  50px 140px 0 #FFD800,70px 140px 0 #FFD800,90px 140px 0 #FFD800,110px 140px 0 #FFAE00,130px 140px 0 #FFD800,
  50px 160px 0 #FFD800,70px 160px 0 #FFD800,90px 160px 0 #FFD800,110px 160px 0 #FFD800,130px 160px 0 #FFD800,
  70px 180px 0 #FFAE00,90px 180px 0 #FFAE00,110px 180px 0 #FFAE00; }
/* Frame 5 — edge-on (thinnest) */
.fx-coin > i.cf5 { box-shadow:
  70px 0 0 #FFF000,90px 0 0 #FFF000,110px 0 0 #FFF000,
  70px 20px 0 #FFD800,90px 20px 0 #FFD800,110px 20px 0 #FFD800,
  70px 40px 0 #FFD800,90px 40px 0 #FFD800,110px 40px 0 #FFD800,
  70px 60px 0 #FFD800,90px 60px 0 #FFD800,110px 60px 0 #FFD800,
  70px 80px 0 #FFD800,90px 80px 0 #FFD800,110px 80px 0 #FFD800,
  70px 100px 0 #FFD800,90px 100px 0 #FFD800,110px 100px 0 #FFD800,
  70px 120px 0 #FFD800,90px 120px 0 #FFD800,110px 120px 0 #FFD800,
  70px 140px 0 #FFD800,90px 140px 0 #FFD800,110px 140px 0 #FFD800,
  70px 160px 0 #FFD800,90px 160px 0 #FFD800,110px 160px 0 #FFD800,
  70px 180px 0 #FFAE00,90px 180px 0 #FFAE00,110px 180px 0 #FFAE00; }
/* Frame 6 — widening (mirror of 4) */
.fx-coin > i.cf6 { box-shadow:
  70px 0 0 #FFF000,90px 0 0 #FFF000,110px 0 0 #FFF000,
  50px 20px 0 #FFF000,70px 20px 0 #FFD800,90px 20px 0 #FFD800,110px 20px 0 #FFD800,130px 20px 0 #FFD800,
  50px 40px 0 #FFD800,70px 40px 0 #FFAE00,90px 40px 0 #FFD800,110px 40px 0 #FFD800,130px 40px 0 #FFD800,
  50px 60px 0 #FFD800,70px 60px 0 #FFAE00,90px 60px 0 #FFD800,110px 60px 0 #FFD800,130px 60px 0 #FFD800,
  50px 80px 0 #FFD800,70px 80px 0 #FFAE00,90px 80px 0 #FFD800,110px 80px 0 #FFD800,130px 80px 0 #FFD800,
  50px 100px 0 #FFD800,70px 100px 0 #FFAE00,90px 100px 0 #FFD800,110px 100px 0 #FFD800,130px 100px 0 #FFD800,
  50px 120px 0 #FFD800,70px 120px 0 #FFAE00,90px 120px 0 #FFD800,110px 120px 0 #FFD800,130px 120px 0 #FFD800,
  50px 140px 0 #FFD800,70px 140px 0 #FFAE00,90px 140px 0 #FFD800,110px 140px 0 #FFD800,130px 140px 0 #FFD800,
  50px 160px 0 #FFD800,70px 160px 0 #FFD800,90px 160px 0 #FFD800,110px 160px 0 #FFD800,130px 160px 0 #FFD800,
  70px 180px 0 #FFAE00,90px 180px 0 #FFAE00,110px 180px 0 #FFAE00; }
/* Frame 7 (mirror of 3) */
.fx-coin > i.cf7 { box-shadow:
  60px 0 0 #FFF000,80px 0 0 #FFF000,100px 0 0 #FFF000,120px 0 0 #FFF000,
  40px 20px 0 #FFF000,60px 20px 0 #FFD800,80px 20px 0 #FFD800,100px 20px 0 #FFD800,120px 20px 0 #FFD800,140px 20px 0 #FFD800,
  20px 40px 0 #FFF000,40px 40px 0 #FFD800,60px 40px 0 #FFD800,80px 40px 0 #FFAE00,100px 40px 0 #FFD800,120px 40px 0 #FFD800,140px 40px 0 #FFD800,160px 40px 0 #FFD800,
  20px 60px 0 #FFD800,40px 60px 0 #FFD800,60px 60px 0 #FFAE00,80px 60px 0 #FFD800,100px 60px 0 #FFF000,120px 60px 0 #FFD800,140px 60px 0 #FFD800,160px 60px 0 #FFD800,
  20px 80px 0 #FFD800,40px 80px 0 #FFD800,60px 80px 0 #FFAE00,80px 80px 0 #FFD800,100px 80px 0 #FFF000,120px 80px 0 #FFD800,140px 80px 0 #FFD800,160px 80px 0 #FFD800,
  20px 100px 0 #FFD800,40px 100px 0 #FFD800,60px 100px 0 #FFAE00,80px 100px 0 #FFD800,100px 100px 0 #FFF000,120px 100px 0 #FFD800,140px 100px 0 #FFD800,160px 100px 0 #FFD800,
  20px 120px 0 #FFD800,40px 120px 0 #FFD800,60px 120px 0 #FFAE00,80px 120px 0 #FFD800,100px 120px 0 #FFF000,120px 120px 0 #FFD800,140px 120px 0 #FFD800,160px 120px 0 #FFD800,
  20px 140px 0 #FFD800,40px 140px 0 #FFD800,60px 140px 0 #FFD800,80px 140px 0 #FFF000,100px 140px 0 #FFD800,120px 140px 0 #FFD800,140px 140px 0 #FFD800,160px 140px 0 #FFAE00,
  40px 160px 0 #FFD800,60px 160px 0 #FFD800,80px 160px 0 #FFD800,100px 160px 0 #FFD800,120px 160px 0 #FFD800,140px 160px 0 #FFAE00,
  60px 180px 0 #FFAE00,80px 180px 0 #FFAE00,100px 180px 0 #FFAE00,120px 180px 0 #FFAE00; }
/* Frame 8 (mirror of 2) */
.fx-coin > i.cf8 { box-shadow:
  50px 0 0 #FFF000,70px 0 0 #FFF000,90px 0 0 #FFF000,110px 0 0 #FFF000,130px 0 0 #FFF000,
  30px 20px 0 #FFF000,50px 20px 0 #FFD800,70px 20px 0 #FFD800,90px 20px 0 #FFD800,110px 20px 0 #FFD800,130px 20px 0 #FFD800,150px 20px 0 #FFD800,
  10px 40px 0 #FFF000,30px 40px 0 #FFD800,50px 40px 0 #FFD800,70px 40px 0 #FFAE00,80px 40px 0 #FFAE00,90px 40px 0 #FFAE00,100px 40px 0 #FFAE00,110px 40px 0 #FFD800,130px 40px 0 #FFD800,150px 40px 0 #FFD800,170px 40px 0 #FFD800,
  10px 60px 0 #FFD800,30px 60px 0 #FFD800,50px 60px 0 #FFF,70px 60px 0 #FFD800,90px 60px 0 #FFD800,100px 60px 0 #FFD800,120px 60px 0 #FFF000,140px 60px 0 #FFD800,160px 60px 0 #FFD800,170px 60px 0 #FFD800,
  10px 80px 0 #FFD800,30px 80px 0 #FFD800,50px 80px 0 #FFAE00,70px 80px 0 #FFD800,90px 80px 0 #FFD800,100px 80px 0 #FFD800,120px 80px 0 #FFF000,140px 80px 0 #FFD800,160px 80px 0 #FFD800,170px 80px 0 #FFD800,
  10px 100px 0 #FFD800,30px 100px 0 #FFD800,50px 100px 0 #FFAE00,70px 100px 0 #FFD800,90px 100px 0 #FFD800,100px 100px 0 #FFD800,120px 100px 0 #FFF000,140px 100px 0 #FFD800,160px 100px 0 #FFD800,170px 100px 0 #FFD800,
  10px 120px 0 #FFD800,30px 120px 0 #FFD800,50px 120px 0 #FFAE00,70px 120px 0 #FFD800,90px 120px 0 #FFD800,100px 120px 0 #FFD800,120px 120px 0 #FFF000,140px 120px 0 #FFD800,160px 120px 0 #FFD800,170px 120px 0 #FFD800,
  10px 140px 0 #FFD800,30px 140px 0 #FFD800,50px 140px 0 #FFD800,70px 140px 0 #FFF000,90px 140px 0 #FFF000,100px 140px 0 #FFF000,120px 140px 0 #FFD800,140px 140px 0 #FFD800,150px 140px 0 #FFD800,170px 140px 0 #FFAE00,
  30px 160px 0 #FFD800,50px 160px 0 #FFD800,70px 160px 0 #FFD800,90px 160px 0 #FFD800,110px 160px 0 #FFD800,130px 160px 0 #FFD800,150px 160px 0 #FFAE00,
  50px 180px 0 #FFAE00,60px 180px 0 #FFAE00,80px 180px 0 #FFAE00,100px 180px 0 #FFAE00,120px 180px 0 #FFAE00,130px 180px 0 #FFAE00; }

/* ── Tier 1/3: spark + confetti burst particles (JS-positioned) ────── */
.fx-burst {
  position: fixed; z-index: 71; pointer-events: none;
  width: 6px; height: 6px; will-change: transform, opacity;
}

/* Tier 2 gravity drop + CRT "lose signal" glitch were removed: both owned
   .wrap's transform (drop translated it offscreen, crt collapsed it to a line on
   the Y axis), so neither composed with the additive WAAPI effects. Tier 2 now
   relies on spin/rumble/confetti/invaders/matrix, which all layer cleanly. */

/* ── Tier 2: invaders / sprite marching across ─────────────────────── */
.fx-sprite {
  position: fixed; left: 0; z-index: 71;
  /* Clickable: each invader is a coin you can shoot (see marchSprites). The
     box is sized to the glyph itself (an inline-block sized to its content),
     so the hit-area matches exactly what you see — no dead space around it,
     no oversized invisible padding. */
  pointer-events: auto; cursor: pointer;
  display: inline-block;
  font-size: 56px; line-height: 1; will-change: transform;
  text-shadow: 0 0 10px var(--magenta);
  animation: fx-march var(--dur, 2.4s) linear forwards, fx-bob .4s steps(2) infinite;
}
@keyframes fx-march { from { transform: translateX(-60px); } to { transform: translateX(calc(100vw + 60px)); } }
@keyframes fx-bob { 0% { margin-top: 0; } 100% { margin-top: 8px; } }
/* MIDAS TOUCH upgrade: a gold glow + gentle sheen pulse marks the 5-coin ones. */
.fx-sprite.golden {
  text-shadow: 0 0 10px #ffe14d, 0 0 22px #ffb800;
  animation: fx-march var(--dur, 2.4s) linear forwards, fx-bob .4s steps(2) infinite, fx-gold 0.7s ease-in-out infinite alternate;
}
@keyframes fx-gold { from { filter: brightness(1); } to { filter: brightness(1.5) saturate(1.4); } }

/* ── Tier 2: matrix rain column ────────────────────────────────────── */
.fx-rain {
  position: fixed; top: -20vh; z-index: 65; pointer-events: none;
  font-family: "VT323", monospace; font-size: 22px; line-height: 1;
  color: #2effa0; text-shadow: 0 0 8px #2effa0;
  writing-mode: vertical-rl; white-space: nowrap;
  animation: fx-rain-fall var(--dur, 1.6s) linear forwards;
}
/* The buried word: brighter white-green with a stronger glow, so the hidden
   message subtly reveals itself within the noise. */
.fx-rain b {
  font-weight: 400;
  color: #ddffe9;
  text-shadow: 0 0 6px #aaffcc, 0 0 14px #2effa0;
}
@keyframes fx-rain-fall { from { transform: translateY(0); opacity: .9; } to { transform: translateY(140vh); opacity: 0; } }
/* A column frozen by the BULLET TIME upgrade: held in place (animation paused in
   JS) and lit a touch brighter so it reads as "caught" while you decode it. */
.fx-rain.frozen { opacity: 1; filter: brightness(1.25); }

/* ── Tier 2: big rumble + zoom pulse ───────────────────────────────── */
/* Tier 2 rumble (page scale-pulse + jitter) now runs via animateWrap (WAAPI
   composite:"add") — see tiers.js — layering with the zoom and other effects. */

/* ── Tier 3: FEVER MODE ────────────────────────────────────────────── */
@keyframes fx-fever-bg {
  0%   { filter: hue-rotate(0deg) saturate(1.4); }
  100% { filter: hue-rotate(360deg) saturate(1.4); }
}
/* Background hue-cycle is slow (one full sweep / 3s ≈ gentle, no harsh flashing).
   The continuous .wrap jitter and the h1 scale-pulse are now WAAPI animations
   (see startFever) — the h1 pulse moved off CSS so it stops overwriting chroma's
   text-shadow glitch, which shares the h1's single CSS `animation` slot. */
body.fx-fever { animation: fx-fever-bg 3s linear infinite; }

/* combo counter readout, pinned corner */
.fx-combo {
  position: fixed; top: 18px; right: 20px; z-index: 75; pointer-events: none;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 18px;
  color: #fff; text-shadow: 0 0 10px var(--magenta), 0 0 22px var(--magenta);
  transform-origin: right top;
}
.fx-combo .n { color: #ffe14d; }
@keyframes fx-combo-bump { 0% { transform: scale(1.5); } 100% { transform: scale(1); } }
.fx-combo.bump { animation: fx-combo-bump .18s ease-out; }

/* STOP button — shown under the combo during the code-fever CHEAT (the combo
   auto-ticks; tapping STOP ends the fever and cashes out). Pinned just below the
   combo readout, same top-right column. Magenta neon, on-brand with the HUD. */
.fx-combo-stop {
  position: fixed; top: 46px; right: 20px; z-index: 76; cursor: pointer;
  padding: 6px 14px;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 12px;
  color: #fff; background: rgba(255, 46, 151, 0.18);
  border: 2px solid var(--magenta);
  text-shadow: 0 0 8px var(--magenta);
  box-shadow: 0 0 8px 1px var(--magenta), 0 0 18px 4px rgba(255,46,151,.45);
  -webkit-tap-highlight-color: transparent;
  animation: fx-start-pulse 0.9s ease-in-out infinite;
}
.fx-combo-stop:hover { background: rgba(255, 46, 151, 0.32); }
.fx-combo-stop:focus-visible { outline: none; box-shadow: 0 0 0 3px #fff, 0 0 10px 2px var(--magenta); }

/* fever banners (GAME OVER / triumphant reset etc.) */
.fx-banner {
  position: fixed; inset: 0; z-index: 76; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  font-family: "Press Start 2P", "VT323", monospace;
  font-size: clamp(24px, 7vw, 72px); text-align: center;
  color: #fff; text-shadow: 0 0 16px var(--magenta), 0 0 40px var(--magenta);
  animation: fx-banner-in .9s ease-out forwards;
}
@keyframes fx-banner-in {
  0%   { opacity: 0; transform: scale(2.4); letter-spacing: .4em; }
  25%  { opacity: 1; transform: scale(1); letter-spacing: .02em; }
  80%  { opacity: 1; }
  100% { opacity: 0; }
}
/* Sticky banner (code-started "FEVER!"): pop in, then HOLD with a gentle pulse
   until JS removes it. Stays at the default centered position. */
.fx-banner-sticky {
  animation: fx-banner-pop .45s cubic-bezier(.2,.9,.3,1.3) both,
             fx-banner-pulse 1s ease-in-out .45s infinite;
}
@keyframes fx-banner-pop {
  0%   { opacity: 0; transform: scale(2.4); letter-spacing: .4em; }
  70%  { opacity: 1; transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); letter-spacing: .02em; }
}
@keyframes fx-banner-pulse { 0%,100% { opacity: 1; } 50% { opacity: .6; } }

/* ── Konami code HUD ───────────────────────────────────────────────── */
/* As the code is entered, its glyphs appear one-by-one in the top-right.
   A wrong key shatters them (see fx-konami-break + the JS particle burst).
   Sits just below where the combo counter would be, so the two don't fight. */
.fx-konami {
  position: fixed; top: 52px; right: 20px; z-index: 75; pointer-events: none;
  display: flex; gap: 8px; align-items: center;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 18px;
}
.fx-konami-key {
  color: #fff;
  text-shadow: 0 0 8px var(--magenta), 0 0 18px rgba(255,46,151,.6);
  animation: fx-konami-pop .22s cubic-bezier(.2,.9,.3,1.3);
}
/* Press Start 2P lacks ←/→, so left/right are the ↑ glyph rotated 90° via an
   inner <i> (rotation set inline by JS). The glow lives on the <i> so it
   rotates WITH the glyph. A small translate nudge corrects the rotated arrows'
   vertical alignment against ↑/↓ (the glyph ink isn't centered in its em). */
.fx-konami-key i {
  display: inline-block;
  font-style: normal;        /* <i> defaults to italic — that slants the arrow */
  transform-origin: 50% 50%;
  color: #fff;
  text-shadow: 0 0 8px var(--magenta), 0 0 18px rgba(255,46,151,.6);
}
/* The span's own glow is redundant once the glyph lives in <i>; drop it there
   so we don't double-glow (which also looked ghosted). */
.fx-konami-key:has(i) { text-shadow: none; }
@keyframes fx-konami-pop {
  0%   { opacity: 0; transform: translateY(-8px) scale(.4); }
  60%  { opacity: 1; transform: translateY(0) scale(1.25); }
  100% { transform: scale(1); }
}
/* Break: the glyphs flash white and crumble away (JS also sprays particles). */
.fx-konami.breaking .fx-konami-key {
  animation: fx-konami-shatter .32s ease-in forwards;
}
@keyframes fx-konami-shatter {
  0%   { opacity: 1; transform: scale(1); color: #fff; text-shadow: 0 0 12px #fff; }
  100% { opacity: 0; transform: translateY(10px) scale(.3) rotate(12deg); }
}
/* The arcade "PRESS START" beat: once the full code is entered, a pulsing neon
   START button appears after the A. Clicking it (or pressing Enter/Space while
   it's focused) launches fever. A real <button> so it's keyboard/SR-accessible. */
.fx-konami-start {
  margin: 0 0 0 4px; padding: 6px 12px; cursor: pointer;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 13px;
  color: #fff; background: rgba(255, 46, 151, 0.18);
  border: 2px solid var(--magenta);
  text-shadow: 0 0 8px var(--magenta);
  box-shadow: 0 0 8px 1px var(--magenta), 0 0 18px 4px rgba(255,46,151,.45);
  animation: fx-start-pulse 0.9s ease-in-out infinite;
}
.fx-konami-start:hover { background: rgba(255, 46, 151, 0.32); color: #fff; }
.fx-konami-start:focus-visible { outline: none; box-shadow: 0 0 0 3px #fff, 0 0 10px 2px var(--magenta); }
@keyframes fx-start-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 8px 1px var(--magenta), 0 0 18px 4px rgba(255,46,151,.45); }
  50%      { opacity: .72; box-shadow: 0 0 14px 3px var(--magenta), 0 0 28px 8px rgba(255,46,151,.7); }
}

/* ── Touch B/A pad ─────────────────────────────────────────────────── */
/* Phones can't press B/A, so after the eight swipes land we raise a centered
   controller card — A and B side by side (laid out like the old Google Play
   Games overlay), tapped in order B → A, then a START button drops in below to
   launch fever. Kept on-brand: magenta neon, not the reference's blue/red, so
   it matches the site's one-hero-color discipline. */
.fx-konami-pad {
  position: fixed; z-index: 90; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: grid; grid-template-columns: auto auto; gap: 18px 28px;
  justify-items: center; align-items: center;
  padding: 26px 30px;
  background: rgba(7, 7, 13, 0.92);
  border: 2px solid var(--magenta); border-radius: 16px;
  box-shadow: 0 0 18px 2px rgba(255,46,151,.45), 0 10px 40px rgba(0,0,0,.6);
  animation: fx-quiet-fade-in .2s ease-out;
}
.fx-konami-pad-btn {
  width: 84px; height: 84px; border-radius: 50%; cursor: pointer;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 30px;
  color: #fff; background: rgba(255, 46, 151, 0.12);
  border: 3px solid var(--magenta);
  text-shadow: 0 0 8px var(--magenta);
  box-shadow: 0 0 10px 1px rgba(255,46,151,.4), inset 0 0 12px rgba(255,46,151,.25);
  -webkit-tap-highlight-color: transparent;
  transition: transform .08s ease, background .12s ease;
}
.fx-konami-pad-btn:active { transform: scale(.92); }
/* A correctly-tapped button stays lit so the entered run reads as complete. */
.fx-konami-pad-btn.done {
  background: rgba(255, 46, 151, 0.4); color: #fff;
  box-shadow: 0 0 16px 3px var(--magenta), inset 0 0 14px rgba(255,46,151,.5);
}
/* START drops in below the A/B row, spanning both columns — the "PRESS START"
   beat from the keyboard path, here inside the same card. */
.fx-konami-pad .fx-konami-start {
  grid-column: 1 / -1; margin: 6px 0 0; padding: 12px 26px; font-size: 16px;
}

/* fx-quiet-fade-in: a calm fade used by the B/A pad overlay (and elsewhere). */
@keyframes fx-quiet-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ══════════════════════════════════════════════════════════════════════
   GAME HUD — the persistent shell that turns the effects into a game.
   Credits readout bottom-center, ✕ (hide) top-left, feedback bubble
   bottom-right. Top-right stays reserved for the combo / Konami HUD. The
   container is click-through (pointer-events: none); only the buttons opt
   back in, so the HUD never eats game clicks meant for the page.
   ══════════════════════════════════════════════════════════════════════ */
.fx-hud { pointer-events: none; }

/* ── Cabinet glass ───────────────────────────────────────────────────
   Once the HUD is up the page becomes the game board: this scrim is the glass of
   an arcade cabinet laid over it. It sits ABOVE the page content (z1) but BELOW
   the HUD controls (z75+) and every effect/sprite (z65+), so coins, invaders and
   buttons all read through/over it. It captures pointer events so a stray tap
   plays a hit instead of hitting a link (the page is also made `inert` in JS).
   Three flat layers, no per-frame motion: a dim veil, a radial vignette (darker
   at the edges, like a curved tube), and faint static scanlines. */
.fx-hud-scrim {
  position: fixed; inset: 0; z-index: 2; pointer-events: auto;
  transform-origin: 50% 50%;
  background:
    repeating-linear-gradient(
      to bottom, rgba(0,0,0,0) 0, rgba(0,0,0,0) 2px,
      rgba(0,0,0,.18) 2px, rgba(0,0,0,.18) 3px),                 /* scanlines */
    radial-gradient(120% 120% at 50% 50%,
      rgba(7,7,13,.18) 38%, rgba(7,7,13,.66) 100%),              /* vignette */
    rgba(7,7,13,.46);                                            /* dim veil */
  /* A soft inner shadow fakes the bezel curving away at the rim. */
  box-shadow: inset 0 0 140px 30px rgba(7,7,13,.7);
  /* The cabinet powering up: the glass zooms OUT from a tight, over-warped
     center and SETTLES into its resting frame over 1.5s — an ease-OUT curve
     (fast start, gentle settle) reads as "locking into place". */
  animation: fx-scrim-in 1.5s cubic-bezier(.16,.84,.3,1) both;
}
/* Leaving game mode: the glass snaps back out fast — a short 500ms ease-IN
   (slow start, accelerating away) so it clears quickly rather than lingering.
   JS adds .leaving, waits for it to end, then removes the node (see setGameMode). */
.fx-hud-scrim.leaving {
  animation: fx-scrim-out .5s cubic-bezier(.4,0,1,1) both;
  pointer-events: none;   /* exit is non-interactive; clicks fall through */
}
@keyframes fx-scrim-in {
  0%   { opacity: 0; transform: scale(1.6); filter: blur(8px);
         box-shadow: inset 0 0 300px 90px rgba(7,7,13,.85); }
  55%  { opacity: 1; filter: blur(0); }
  100% { opacity: 1; transform: scale(1); filter: blur(0);
         box-shadow: inset 0 0 140px 30px rgba(7,7,13,.7); }
}
@keyframes fx-scrim-out {
  0%   { opacity: 1; transform: scale(1); filter: blur(0);
         box-shadow: inset 0 0 140px 30px rgba(7,7,13,.7); }
  100% { opacity: 0; transform: scale(1.6); filter: blur(8px);
         box-shadow: inset 0 0 300px 90px rgba(7,7,13,.85); }
}
/* Reduced motion: the 2s zoom/warp is exactly the large-scale movement that can
   cause discomfort, so drop it entirely — the glass just appears/disappears.
   .leaving still tags the node; JS removes it on the (instant) animationend, and
   a 0s no-op animation here guarantees that event still fires. */
@media (prefers-reduced-motion: reduce) {
  .fx-hud-scrim, .fx-hud-scrim.leaving {
    animation: fx-quiet-fade-in .01s linear both; transform: none; filter: none;
  }
}
/* High-contrast users: thin the veil so any page text glimpsed through the glass
   stays legible (the scrim is decoration, never the thing being read). */
@media (prefers-contrast: more) {
  .fx-hud-scrim { background: rgba(7,7,13,.18); box-shadow: none; }
}

/* Running total, bottom-center. */
.fx-hud-credits {
  position: fixed; left: 0; right: 0; bottom: 18px; z-index: 75;
  text-align: center; pointer-events: none;
  font-family: "Press Start 2P", "VT323", monospace; font-size: clamp(12px, 2.2vw, 16px);
  color: #fff; text-shadow: 0 0 10px var(--magenta), 0 0 22px rgba(255,46,151,.6);
}
.fx-hud-credits-n { display: inline-block; color: #ffe14d; }
@keyframes fx-hud-bump { 0% { transform: scale(1.5); } 100% { transform: scale(1); } }
.fx-hud-credits-n.bump { animation: fx-hud-bump .18s ease-out; }

/* Shared button reset for the HUD controls — strip native chrome, keep them
   keyboard-operable with a clear magenta focus ring (matches the menu rows). */
.fx-hud-close, .fx-hud-feedback, .fx-hud-shop {
  position: fixed; z-index: 76; pointer-events: auto;
  margin: 0; border: 0; background: transparent; cursor: pointer;
  color: var(--bone); line-height: 1;
  -webkit-tap-highlight-color: transparent;
  transition: color .14s, text-shadow .14s, transform .14s;
}
.fx-hud-close:focus-visible, .fx-hud-feedback:focus-visible, .fx-hud-shop:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--magenta), 0 0 14px rgba(255,46,151,.45);
  border-radius: 8px;
}

/* 8-bit icons — both the ✕ and the chat glyph are drawn as box-shadow pixel
   art rather than a font glyph/emoji, so they match the cabinet. Each <i> is a
   single 3px "pixel" at the grid origin (top-left); box-shadow offsets paint the
   rest of the sprite. A drop-shadow gives the magenta neon glow. */
.fx-hud-close i, .fx-hud-feedback i {
  display: block; position: relative;
  color: var(--bone);
}

/* The 8-bit ✕ — a 5×5 pixel X drawn from the origin pixel. Self-contained so it
   works anywhere (the HUD hide button AND the dialog close button).
   The origin pixel sits top-left and box-shadow paints the rest of the sprite
   down-and-right, spanning 15px×15px total. The <i> is sized to that full
   footprint (not just the 3px origin pixel) so flex-centering centers the
   VISIBLE glyph inside its button — otherwise the box-shadow overflows the
   hit-area to the bottom-right. ::before carries the origin pixel + shadows so
   the element's own box is pure layout. */
.fx-px-x {
  display: block; position: relative;
  width: 15px; height: 15px;       /* the sprite's true footprint (5 pixels × 3px) */
  filter: drop-shadow(0 0 4px var(--magenta));
}
.fx-px-x::before {
  content: ""; position: absolute; top: 0; left: 0;
  width: 3px; height: 3px;         /* one pixel unit (the shadows use multiples) */
  background: currentColor;
  color: inherit;
  box-shadow:
    0    0    0 currentColor, 12px 0    0 currentColor,
    3px  3px  0 currentColor, 9px  3px  0 currentColor,
    6px  6px  0 currentColor,
    3px  9px  0 currentColor, 9px  9px  0 currentColor,
    0    12px 0 currentColor, 12px 12px 0 currentColor;
}

/* ✕ hide button, top-left. */
.fx-hud-close {
  top: 14px; left: 10px; padding: 9px;
  display: flex; align-items: center; justify-content: center;
}
.fx-hud-close:hover i { color: #fff; }

/* 💬 feedback bubble, bottom-right — an 8-bit speech bubble drawn entirely as
   box-shadow pixel art (same technique as the coin), so the rounded corners and
   the tail are real pixels, not a clip. The button is just a hit-area; the
   .fx-px-chat <i> is one 4px "pixel" at the grid origin and box-shadow offsets
   paint the rest: the rounded outline, then the three chat dots inside. */
.fx-hud-feedback {
  bottom: 16px; right: 16px;
  width: 48px; height: 48px;
  display: flex; align-items: flex-start; justify-content: flex-start;
  background: none; border: 0; box-shadow: none;
}
/* The bubble outline + tail + dots, all in one box-shadow sprite. Grid is 4px
   pixels. Outline = a 9×6 rounded rectangle (corner pixels omitted) with a
   2-pixel tail stepping off the bottom; dots = three pixels centered inside.
   The origin pixel itself (top-left of the outline's first row) is at col 1,row
   0 — we offset everything from there. White outline, magenta glow. */
.fx-hud-feedback i.fx-px-chat {
  margin: 6px 6px 0 6px;        /* inset the sprite within the hit-area */
  width: 4px; height: 4px;      /* one 4px grid pixel; this <i> IS (col1,row0) */
  background: var(--bone);
  color: var(--bone);           /* the dots use currentColor → magenta on hover */
  filter: drop-shadow(0 0 3px rgba(255,46,151,.45));
  /* 9 cols (0..8) × 6 rows (0..5) rounded rectangle: the four corner pixels
     (0,0)(8,0)(0,5)(8,5) are omitted so the corners read as rounded. The <i>'s
     own background pixel is (col1,row0); all offsets below are from there. The
     OUTLINE is always white (var(--bone)); only the DOTS use currentColor so a
     single color swap on hover/activate tints just them magenta. */
  box-shadow:
    /* top row — cols 2..7 (col1 is the <i> itself) */
    4px 0 0 var(--bone), 8px 0 0 var(--bone), 12px 0 0 var(--bone), 16px 0 0 var(--bone), 20px 0 0 var(--bone), 24px 0 0 var(--bone),
    /* left wall col0 + right wall col8, rows 1..4 */
    -4px 4px 0 var(--bone), 28px 4px 0 var(--bone),
    -4px 8px 0 var(--bone), 28px 8px 0 var(--bone),
    -4px 12px 0 var(--bone), 28px 12px 0 var(--bone),
    -4px 16px 0 var(--bone), 28px 16px 0 var(--bone),
    /* bottom row — cols 1..7, gap at col4 where the tail joins (col4 = 12px) */
    0 20px 0 var(--bone), 4px 20px 0 var(--bone), 8px 20px 0 var(--bone), 16px 20px 0 var(--bone), 20px 20px 0 var(--bone), 24px 20px 0 var(--bone),
    /* tail: two pixels stepping down-left from the bottom gap */
    12px 24px 0 var(--bone), 8px 28px 0 var(--bone),
    /* three chat dots inside — row 2 (8px), cols 2/4/6; currentColor (white by
       default, magenta on hover) */
    4px 8px 0 currentColor, 12px 8px 0 currentColor, 20px 8px 0 currentColor;
}
/* Hover/focus: the dots light up magenta (the accent), glow intensifies — no
   movement. The outline stays white. */
.fx-hud-feedback:hover i, .fx-hud-feedback:focus-visible i {
  color: var(--magenta);
  filter: drop-shadow(0 0 6px var(--magenta));
}

/* Fever bank readout — top-right, stacked under the combo (18px) and, during the
   code-cheat, the STOP button (46px); clears both at 84px. */
.fx-bank {
  position: fixed; top: 84px; right: 20px; z-index: 75; pointer-events: none;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 13px;
  color: #fff; text-shadow: 0 0 8px var(--magenta), 0 0 18px rgba(255,46,151,.6);
  transform-origin: right top;
}
.fx-bank .n { color: #ffe14d; }
.fx-bank.bump { animation: fx-combo-bump .18s ease-out; }

/* Feedback panel — the message box the bubble opens. Square corners (no radius)
   to keep the 8-bit cabinet look. */
.fx-feedback-panel {
  position: fixed; z-index: 78; right: 16px; bottom: 64px;
  width: min(320px, calc(100vw - 32px));
  pointer-events: auto;
  background: rgba(10, 10, 20, 0.96);   /* opaque fallback (see @supports below) */
  border: 2px solid var(--bone);   /* white frame; magenta is reserved for focus */
  /* magenta neon glow matched to the cursor's profile (a tight full-strength
     magenta ring + a softer spread), over a deep drop-shadow for separation */
  box-shadow:
    0 0 8px 1px var(--magenta),
    0 0 16px 3px rgba(255,46,151,.5),
    0 12px 40px rgba(0,0,0,.65);
  padding: 18px 16px 14px;
}
.fx-feedback-panel[hidden] { display: none; }
.fx-feedback-close {
  position: absolute; top: 10px; right: 10px;
  margin: 0; border: 0; background: transparent; cursor: pointer;
  padding: 8px;
  display: flex; align-items: center; justify-content: center;
}
.fx-feedback-close i { color: var(--bone); }
.fx-feedback-close:hover i { color: #fff; }
.fx-feedback-close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--magenta);
}
.fx-feedback-form { display: flex; flex-direction: column; }
.fx-feedback-label {
  font-family: "Jersey 10", monospace; font-size: 0.7em; letter-spacing: .12em;
  text-transform: uppercase; color: var(--bone);
  margin: 8px 0 4px;
}
.fx-feedback-label:first-child { margin-top: 0; }
/* NES-style pixel border drawn from a tiny SVG via border-image — gives the
   inputs a chunky 8-bit frame with no anti-aliased rounding. The SVG is hosted
   at /pixel-border.svg (a 5×5 corner motif, stretched). */
.fx-feedback-text, .fx-feedback-email {
  font-family: "VT323", ui-monospace, monospace; font-size: 18px;
  color: var(--bone); background: rgba(255,255,255,0.04);
  border-style: solid;
  border-width: 2px;           /* match the outer panel's 2px frame */
  border-image-source: url("/pixel-border.svg");
  border-image-slice: 2;
  border-image-width: 2;
  border-image-repeat: stretch;
  border-image-outset: 0;
  background-clip: padding-box;
  padding: 8px 10px; width: 100%;
  -webkit-user-select: text; user-select: text;
}
.fx-feedback-text { resize: vertical; min-height: 64px; }
/* Focus recolors the pixel border itself to magenta (swap the border-image to
   the magenta SVG) — no separate outline, no glow. */
.fx-feedback-text:focus, .fx-feedback-email:focus {
  outline: none;
  border-image-source: url("/pixel-border-magenta.svg");
}
.fx-feedback-text::placeholder, .fx-feedback-email::placeholder { color: var(--dim); opacity: .7; }
/* NES-style primary button, ported from the NES.css .nes-btn.is-primary. Shared
   by the feedback SEND button and the shop's BUY/EQUIP buttons (.fx-nes-btn).
   The pixel frame is an inlined SVG border-image (magenta corner motif); the
   ::after draws the hard inset bevel (darker magenta, bottom-right) that deepens
   on hover and flips to top-left on press — the classic NES push. border-image-
   outset:2 and the ::after's -4px inset are paired exactly as in NES.css. */
.fx-nes-btn {
  border-image-slice: 2;
  border-image-width: 2;
  border-image-repeat: stretch;
  border-image-source: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" ?><svg version="1.1" width="5" height="5" xmlns="http://www.w3.org/2000/svg"><path d="M2 1 h1 v1 h-1 z M1 2 h1 v1 h-1 z M3 2 h1 v1 h-1 z M2 3 h1 v1 h-1 z" fill="rgb(255,46,151)" /></svg>');
  border-image-outset: 2;
  position: relative;
  display: inline-block;
  padding: 10px 12px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  -webkit-user-select: none; user-select: none;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 12px;
  text-shadow: none;
  color: #fff;
  background-color: var(--magenta);
}
.fx-feedback-send { margin-top: 18px; }   /* the only thing SEND adds to the base */
.fx-nes-btn::after {
  position: absolute;
  top: -4px; right: -4px; bottom: -4px; left: -4px;
  content: "";
  box-shadow: inset -4px -4px #b3105f;
}
.fx-nes-btn:hover:not(:disabled) {
  text-decoration: none;
  background-color: #ff52ab;
}
.fx-nes-btn:hover:not(:disabled)::after { box-shadow: inset -6px -6px #b3105f; }
.fx-nes-btn:active:not(:disabled)::after { box-shadow: inset 4px 4px #b3105f; }
.fx-nes-btn:focus { outline: 0; box-shadow: 0 0 0 6px rgba(255,46,151,.3); }
.fx-nes-btn:disabled {
  cursor: not-allowed;
  background-color: #8a2a5f;
  box-shadow: inset -4px -4px #5e1740;
  opacity: .6;
}
/* The shop's EQUIP variant is a quieter "ghost" — owned but not the active flex.
   Keeps the magenta frame, drops the filled background so EQUIPPED/BUY stand out. */
.fx-nes-btn.is-ghost {
  background-color: transparent;
  color: var(--bone);
}
.fx-nes-btn.is-ghost:hover:not(:disabled) { background-color: rgba(255,46,151,.18); }
.fx-feedback-status {
  margin: 8px 0 0; min-height: 1.2em;
  font-family: "VT323", monospace; font-size: 16px; color: var(--dim);
}
.fx-feedback-status.ok  { color: #2effa0; }
.fx-feedback-status.err { color: #ff7ac0; }

/* ── Shop ───────────────────────────────────────────────────────────── */
/* SHOP button — a text pill in the bottom row, left of the feedback bubble.
   Shares the HUD-control reset/focus-ring (see .fx-hud-close, .fx-hud-feedback);
   here we just place it and give it the cabinet font. */
.fx-hud-shop {
  bottom: 22px; right: 74px;        /* sits left of the 48px feedback bubble */
  padding: 8px 10px;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 11px;
  color: var(--bone);
  text-shadow: 0 0 8px var(--magenta), 0 0 18px rgba(255,46,151,.5);
}
.fx-hud-shop:hover { color: #fff; }

/* Shop panel — same cabinet frame as the feedback panel, a touch wider. */
.fx-shop-panel {
  position: fixed; z-index: 79; right: 16px; bottom: 64px;
  width: min(540px, calc(100vw - 32px));
  pointer-events: auto;
  background: rgba(10, 10, 20, 0.97);   /* opaque fallback (see @supports below) */
  border: 2px solid var(--bone);
  box-shadow:
    0 0 8px 1px var(--magenta),
    0 0 16px 3px rgba(255,46,151,.5),
    0 12px 40px rgba(0,0,0,.65);
  padding: 16px 14px 14px;
}
.fx-shop-panel[hidden] { display: none; }

/* Frosted-glass dialogs: where the browser supports backdrop-filter, drop the
   panel opacity and blur what's behind. Over this page's near-black field the
   sparse rain/sprites blur into a faint CRT-scanline texture — it reads as a
   translucent terminal overlay, deepening the arcade feel rather than the iOS
   "frosted glass" look (which only appears with heavier blur over a bright bg).
   The opaque rgba() in each panel rule stays the fallback for non-supporting
   browsers. This block sits AFTER both panel rules so its background wins the
   cascade (equal specificity → later source order). Honored: a
   prefers-reduced-transparency user keeps the solid panels. */
@supports ((backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px))) {
  @media (prefers-reduced-transparency: no-preference) {
    .fx-shop-panel, .fx-feedback-panel {
      background: rgba(10, 10, 20, 0.78);
      -webkit-backdrop-filter: blur(8px);
      backdrop-filter: blur(8px);
    }
  }
}
.fx-shop-close {
  position: absolute; top: 10px; right: 10px;
  margin: 0; border: 0; background: transparent; cursor: pointer;
  padding: 8px; display: flex; align-items: center; justify-content: center;
}
.fx-shop-close i { color: var(--bone); }
.fx-shop-close:hover i { color: #fff; }
.fx-shop-close:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--magenta); }

.fx-shop-head {
  font-family: "Press Start 2P", "VT323", monospace; font-size: 13px; color: #fff;
  text-shadow: 0 0 8px var(--magenta); margin: 0 0 12px;
}
.fx-shop-credits { color: #ffe14d; margin-left: 8px; }

.fx-shop-tabs { display: flex; gap: 6px; margin-bottom: 12px; }
.fx-shop-tab {
  flex: 1; margin: 0; border: 0; cursor: pointer;
  background: transparent; color: var(--dim);
  padding: 6px 4px 8px;
  font-family: "Press Start 2P", "VT323", monospace; font-size: 9px;
  border-bottom: 2px solid transparent;
}
.fx-shop-tab[aria-pressed="true"] { color: #fff; border-bottom-color: var(--magenta); }
.fx-shop-tab:hover { color: var(--bone); }
.fx-shop-tab:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--magenta); border-radius: 4px; }

/* The tab body fills a fixed vertical range and scrolls if its list outgrows it. */
.fx-shop-body { min-height: 50vh; max-height: 80vh; overflow-y: auto; }
.fx-shop-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.08);
}
.fx-shop-row:last-child { border-bottom: 0; }
.fx-shop-info { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fx-shop-name {
  font-family: "Press Start 2P", "VT323", monospace; font-size: 10px; color: var(--bone);
}
.fx-shop-sub { font-family: "VT323", monospace; font-size: 15px; color: var(--dim); }
.fx-shop-buy { font-size: 10px; padding: 8px 10px; flex: 0 0 auto; }

.fx-shop-armed {
  font-family: "VT323", monospace; font-size: 15px; color: #2effd0;
  margin: 0 0 8px;
}
.fx-shop-ach.is-got .fx-shop-name { color: #2effa0; }
.fx-shop-ach:not(.is-got) { opacity: .55; }
/* A gated, not-yet-unlocked item: masked "???" like a locked achievement. */
.fx-shop-locked { opacity: .55; }
.fx-shop-reward {
  font-family: "Press Start 2P", "VT323", monospace; font-size: 10px; color: #ffe14d;
  flex: 0 0 auto;
}

/* ── Achievement toast ──────────────────────────────────────────────── */
/* Pops in top-center, holds briefly, then JS removes it. Pure flourish; the
   unlock is also announced to assistive tech via announce(). */
.fx-toast {
  position: fixed; left: 50%; top: 16%; z-index: 90;
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  background: rgba(10, 10, 20, 0.97);
  border: 2px solid var(--bone);
  box-shadow: 0 0 8px 1px var(--magenta), 0 0 18px 4px rgba(255,46,151,.5), 0 10px 30px rgba(0,0,0,.6);
  padding: 10px 14px;
  animation: fx-toast-in .35s cubic-bezier(.2,.9,.3,1.3) both;
}
.fx-toast-badge { color: #ffe14d; font-size: 20px; filter: drop-shadow(0 0 6px #ffe14d); }
.fx-toast-body {
  display: flex; flex-direction: column; gap: 3px;
  font-family: "VT323", monospace; font-size: 16px; color: var(--dim);
}
.fx-toast-body b {
  font-family: "Press Start 2P", "VT323", monospace; font-size: 11px; color: #fff;
  text-shadow: 0 0 8px var(--magenta);
}
@keyframes fx-toast-in {
  from { transform: translate(-50%, -14px); opacity: 0; }
  to   { transform: translate(-50%, 0); opacity: 1; }
}

/* ── Quality floor ─────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .coda .blink { animation: none; }
  body { cursor: auto; }
  #cursor, .spark { display: none; }
  /* Hover underline + focus ring still appear, just without the
     wipe/scale-in motion. */
  .tagline a, .coda a,
  .tagline a::after, .coda a::after,
  .tagline a::before, .coda a::before { transition: none; }
  .tagline a:hover, .coda a:hover { transform: none; }
  .tagline a:focus-visible::before,
  .coda a:focus-visible::before { transform: scale(1); }
  /* Motion is killed here. The JS also skips the motion primitives (shake,
     burst, sprites, rain, fever particles), so this is belt-and-suspenders.
     The playful READOUTS (coin, combo, banner) are kept — see .fx-reduced
     below — they just appear and fade instead of flying/spinning. */
  /* shake/rumble/spin/fever-shake AND the h1 fever-pulse are WAAPI now and
     self-gate under reduced motion (animateEl no-ops), so they're not listed
     here — only the remaining CSS-class effects are. */
  .fx-chroma h1, .fx-jiggle .fx-letter, .fx-flip .wrap, .fx-invert .wrap,
  .fx-edge-flash, .fx-streak,
  .fx-sprite, .fx-rain, .fx-sweep,
  .fx-burst, .fx-float, .fx-coin-pop, .fx-coin, .fx-banner,
  body.fx-fever,
  .fx-konami-key, .fx-konami.breaking .fx-konami-key,
  .fx-konami-start, .fx-konami-pad, .fx-konami-pad-btn:active,
  .fx-combo-stop, .fx-combo.bump,
  .fx-hud-credits-n.bump, .fx-bank.bump, .fx-toast { animation: none !important; }
  /* The achievement toast keeps its centering transform; only the slide-in is
     swapped for a calm opacity fade so unlocks are still PERCEIVABLE. */
  .fx-reduced .fx-toast { animation: fx-quiet-fade 2.6s ease-out forwards !important; }
  /* The HUD itself is static chrome, not motion — it stays fully visible and
     usable under reduced motion (only its bump pulses above are disabled). */

  /* Reduced-motion readouts: a calm cross-fade in place of motion, so the
     coin "+1 CREDIT", combo counter, and banners are still PERCEIVABLE — just
     without movement that could trigger vestibular discomfort. */
  .fx-reduced .fx-coin-pop,
  .fx-reduced .fx-banner:not(.fx-banner-sticky) { animation: fx-quiet-fade 1.1s ease-out forwards !important; }
  /* The sticky FEVER banner must stay visible for the whole hold — show it
     statically (no fade, no pulse) until JS removes it. */
  .fx-reduced .fx-banner-sticky { animation: none !important; opacity: 1 !important; }
  .fx-reduced .fx-coin-pop .fx-coin { transform: translate(-50%, -50%) !important; }
  .fx-reduced .fx-combo { opacity: 1; }
}

/* Calm fade used for reduced-motion readouts (no transform/movement). */
@keyframes fx-quiet-fade {
  0%   { opacity: 0; }
  15%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { opacity: 0; }
}

/* Visually-hidden live region — invisible on screen, read by screen readers.
   The standard clip pattern; not display:none (that would mute it). */
.fx-sr-only {
  position: fixed !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap;
}
