/* ============================================================================
   Playit — everything the PLAYED GAME's page looks like, for every engine.

   The two screens the platform owns around a game are here: the START screen
   (cover, title, PLAY — which becomes the LOADING gauge on tap — high scores) and
   the GAME OVER screen (score, the board, the name box, PLAY AGAIN). The game
   itself owns only the canvas between them.

   ONE file for every engine. The engines used to each carry their
   own near-identical copy of the start screen, which drifted apart into
   slightly different looks — different backdrops, different veils. Now they share
   this, and each engine file sets only `--frame-backdrop`, the one thing that
   should differ.

   It is built from kit.css — the SAME fonts, colour tokens and .btn system the
   rest of the app uses (the BFF injects both files into every served game page).
   Nothing here re-invents a button or hardcodes a brand colour: if it looks like
   the site, that is because it IS the site's kit.

   The visual idea is an ARCADE CABINET, matching the game card's modal
   (game_grid.css `.cabinet`): a marquee band with a lit edge, a CRT screen behind
   glass, and chunky buttons on a panel.
   ============================================================================ */

/* ---- the page + the canvas the game draws on ---- */

/* The page always fills whatever screen it opens on — phone, laptop, 4K. The game picks its own
   fixed base resolution (game.js sizes the renderer — landscape or portrait) and boot.js sizes the
   canvas ELEMENT to the largest same-aspect box that fits, which the flex body centres on black —
   the letterbox. So game code designs against its own numbers and never has to think about the
   real screen size. The element's box IS the drawn picture, on purpose: every input system (Three's
   raycaster, a Cartoon game's own clientX math) maps a tap from the element's box,
   so a CSS-only letterbox (`object-fit: contain` on a 100%×100% element) put the picture and the
   hit boxes ~70px apart on any non-16:9 screen — the corner mute button couldn't be tapped. */
html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: #000; overflow: hidden; }
body { display: flex; align-items: center; justify-content: center; }
canvas {
  display: block;
  /* Touch games steer by dragging on the canvas, so the browser's own gestures (double-tap zoom,
     text selection, the long-press callout) have to be off or they fight the controls. A game can't
     add this itself — it only writes JS modules; index.html is locked. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

/* ---- shared cabinet shell ----
   The panel both screens are built from: the exact shell of the game card's modal
   (game_grid.css `.modal-dialog.cabinet`) — hard ledge, thick ink border, a lit
   top edge. `--frame-backdrop` is the only per-engine value (see cartoon.css); it is
   read with a FALLBACK rather than declared with a default here, so an engine's
   sheet wins no matter which order the two files happen to load in. */

#playit-start-inner,
.pb-panel {
  width: 100%;
  overflow: hidden;                       /* clip the marquee to the rounded shell */
  text-align: center;
  background: linear-gradient(180deg, #241f47, #14112e);
  border: 3px solid var(--color-ink);
  border-radius: 16px 16px 10px 10px;
  box-shadow: 0 10px 0 rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .07);
}

/* The marquee: a flat ink band with the name in arcade yellow and a bright rule
   lighting its bottom edge — the cabinet's light strip. */
#playit-title,
.pb-title {
  margin: 0;
  padding: 13px 14px;
  background: var(--color-ink);
  border-bottom: 3px solid var(--color-cyan);
  font-family: var(--font-display);
  font-size: 13px;
  line-height: 1.5;
  letter-spacing: 1px;
  color: var(--color-title-yellow);
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .6);
  overflow-wrap: anywhere;                /* a long game title wraps, never overflows */
}

/* ---- the START screen ---- */

#playit-start {
  position: fixed; inset: 0; z-index: 999; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; box-sizing: border-box;
  background: var(--frame-backdrop, #0b0a24);
  font-family: var(--font-body);
}

/* The game's cover fills the screen behind everything. boot.js hides it if it fails
   to load, so the backdrop above shows through as the fallback. */
#playit-cover {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
}

/* Layer 1 — the SCRIM. A flat 40% ink wash over the cover: enough to sit the
   cabinet on without hiding the art the player just had made for them. It is flat
   on purpose (a gradient would darken one edge and leave the other bright, and a
   generated cover can put its detail anywhere). */
#playit-start::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: rgba(11, 10, 36, .4);
}

/* Layer 2 — the CRT GLASS: scanlines over a vignette, the same recipe as the game
   cards and the cabinet modal (tv_card.css `.tv-scan` / `.tv-vign`). This is what
   makes it read as a screen you are looking INTO rather than a photo with text on
   top.
   The vignette is DELIBERATELY lighter than the cards' (.35 to their .4, starting
   further out): here it stacks on top of the 40% scrim above, and the cover is art
   the player had made for their own game — the pair should frame it, not bury it.
   Neither layer has to carry text contrast, because the text sits on the cabinet's
   own opaque panel. */
#playit-start::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, .28) 0 2px, transparent 2px 4px),
    radial-gradient(125% 115% at 50% 44%, transparent 60%, rgba(0, 0, 0, .35) 100%);
}

#playit-start-inner {
  position: relative; z-index: 3;
  max-width: 380px;
  max-height: 100%;
  overflow-y: auto;                       /* a short screen scrolls rather than clipping PLAY */
}

/* The start screen's own PLAY button. The scaffold's markup is LOCKED (it ships in
   every game's index.html and predates the kit), so it can't be given the .btn
   classes — instead it takes the .btn + .btn-green + .btn-lg recipe by id. Keep
   these values in step with kit.css; they are the same button, not a look-alike. */
#playit-play {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  margin: 22px 0 4px;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: 1px;
  padding: 20px 40px;
  color: var(--color-ink);
  background: var(--color-green);
  border: 3px solid var(--color-ink);
  --btn-shadow-color: var(--color-green-shadow);
  box-shadow: 0 6px 0 var(--btn-shadow-color);
  transition: transform .1s, box-shadow .1s;
}
#playit-play:hover  { transform: translateY(-2px); box-shadow: 0 10px 0 var(--btn-shadow-color); }
#playit-play:active { transform: translateY(6px);  box-shadow: 0 2px 0 var(--btn-shadow-color); }
#playit-play:focus-visible { outline: 3px solid var(--color-cyan); outline-offset: 3px; }

/* A game with NO scores yet has no board under the button, which leaves PLAY as the
   last thing in the cabinet — and its 6px ledge sitting right on the panel's bottom
   edge. `:last-child` gives it the body padding the board would otherwise have
   provided, so the cabinet closes properly either way. */
#playit-play:last-child { margin-bottom: 26px; }

/* ---- the LOADING gauge ----
   boot.js swaps the PLAY button for this the moment it is tapped, while the game's
   pictures finish downloading (see its header comment). Same cabinet, same spot,
   so nothing jumps: a small label and a big percentage on one line, a chunky
   segmented bar under them — an arcade power gauge filling up — and one friendly
   line of copy below. It shares PLAY's margins (including the `:last-child` body
   padding when no score teaser follows), so the cabinet closes the same either way. */

#playit-load { margin: 22px 18px 4px; text-align: left; }
#playit-load:last-child { margin-bottom: 26px; }

.pl-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  margin: 0 0 10px;
}
.pl-label {
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 2px;
  color: var(--color-muted);
}
/* The number is the thing the player watches, so it gets the display font and the
   "good news" green the score uses. Tabular figures + a reserved width keep it from
   jittering as the digits change. */
.pl-pct {
  min-width: 4ch;
  text-align: right;
  font-family: var(--font-display);
  font-size: 16px;
  color: var(--color-green);
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .55);
  font-variant-numeric: tabular-nums;
}

/* The bar: an ink well with the site's line border (the same recipe as the wizard's
   coin bar), and a segment grid drawn over the fill so it reads as ten chunky cells
   lighting up left to right rather than a smooth web progress bar. */
.pl-bar {
  position: relative;
  height: 18px;
  background: var(--color-ink);
  border: 2px solid var(--color-line-2);
  overflow: hidden;
}
.pl-bar::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg, transparent 0 calc(10% - 2px), var(--color-ink) calc(10% - 2px) 10%);
}
.pl-fill {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--color-cyan), var(--color-green));
  box-shadow: inset 0 3px 0 rgba(255, 255, 255, .28);
  transition: width .25s ease-out;
}
/* A soft glow that breathes while we wait, and settles once the bar is full. */
.pl-fill { animation: plGlow 1.4s ease-in-out infinite; }
.pl-done .pl-fill { animation: none; box-shadow: inset 0 3px 0 rgba(255, 255, 255, .28), 0 0 12px var(--color-green); }
@keyframes plGlow {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.25); }
}

/* The one line of copy. A reserved height so swapping the text never moves the bar. */
.pl-hint {
  margin: 10px 0 0;
  min-height: 1.3em;
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.3;
  color: var(--color-text-dim);
}
.pl-done .pl-hint { color: var(--color-green); }

/* ---- the GAME OVER screen ---- */

#playit-over {
  position: fixed; inset: 0; z-index: 1000;
  /* `align-items: flex-start` + `margin: auto` on the panel, NOT `align-items:
     center`: a centred flex child that grows taller than its container gets its
     top AND bottom clipped with no way to scroll to them. This pair centres the
     cabinet while it fits and scrolls cleanly once it doesn't. */
  display: flex; align-items: flex-start; justify-content: center;
  padding: 20px; box-sizing: border-box; overflow-y: auto;
  /* Darker than the site's own modal backdrop: there is a lit, moving game behind
     this one, and the run is over — it should stop competing. */
  background: rgba(5, 4, 15, .88);
  backdrop-filter: blur(3px);
  font-family: var(--font-body);
  -webkit-user-select: none; user-select: none;
}
.pb-panel { max-width: 380px; margin: auto; }

/* Everything under the marquee shares the cabinet's body padding. */
.pb-body { padding: 18px; }

/* The run's score — the one number the player is here for, so it gets the display
   font and the app's "good news" green. */
.pb-your-score {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 2px;
  color: var(--color-muted);
}
.pb-your-score b {
  display: block;
  margin-top: 10px;
  font-size: 24px;
  letter-spacing: 1px;
  color: var(--color-green);
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .55);
}

/* ---- the name box ---- */

.pb-entry { margin: 0 0 16px; }
.pb-label {
  display: block; margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 8px;
  letter-spacing: 2px;
  color: var(--color-muted);
}
/* The site's field, exactly (auth.css `.auth-input`) — ink well, line border, body
   font, cyan caret — just centred and tracked out, because it holds one short name. No
   uppercasing: a name keeps its capitals when saved, so the box shows exactly what goes on
   the board. */
.pb-input {
  width: 100%;
  box-sizing: border-box;
  margin: 0 0 12px;
  padding: 12px;
  background: var(--color-ink);
  border: 2px solid var(--color-line-2);
  outline: none;
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 22px;
  line-height: 1.2;
  letter-spacing: 4px;
  text-align: center;
  caret-color: var(--color-cyan);
}
.pb-input:focus { border-color: var(--color-cyan); }
.pb-input::placeholder { color: var(--color-faint); letter-spacing: 4px; }

/* Both buttons are the real .btn (the markup adds the classes), so they only need
   their width here — never a second copy of the button styling. */
.pb-save, .pb-again { width: 100%; }
.pb-again { margin-top: 14px; }

/* ---- the board ---- */

/* The start screen's teaser sits under PLAY inside the same cabinet, so it needs no
   shell of its own — just a lit divider above it. */
.pb-teaser { margin: 18px 0 0; padding: 14px 16px; border-top: 2px solid var(--color-line); }
.pb-teaser-title,
.pb-board-title {
  margin: 0 0 12px;
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 2px;
  color: var(--color-gold);
}
.pb-board-title { margin-top: 4px; }

.pb-list { margin: 0; padding: 0; list-style: none; }
/* A full board is ten rows, which is taller than a laptop's play area once the
   score, the name box and both buttons are above and below it. Capping the LIST
   (rather than letting the whole cabinet grow) keeps SAVE SCORE and PLAY AGAIN on
   screen — the two things the player still needs — and lets the board scroll on
   its own. On a tall screen the cap is never reached and nothing scrolls. */
#playit-over .pb-list { max-height: 38vh; overflow-y: auto; }
.pb-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 6px;
  border-bottom: 1px solid var(--color-line);
  font-family: var(--font-body);
  font-size: 20px;
  line-height: 1.2;
  color: var(--color-text);
}
.pb-row:last-child { border-bottom: 0; }
.pb-place {
  flex: 0 0 20px; text-align: right;
  font-size: 16px;
  color: var(--color-muted-2);
}
/* A name is whatever another player typed, so it truncates rather than pushing the
   score off the row. */
.pb-name {
  flex: 1 1 auto; text-align: left; letter-spacing: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Tabular figures keep the score column aligned down the board. */
.pb-score { flex: 0 0 auto; color: var(--color-green); font-variant-numeric: tabular-nums; }

/* The row the player just earned — found at a glance, and marked by a bright left
   edge as well as colour (colour alone is never the only signal). */
.pb-row-mine {
  background: rgba(62, 240, 139, .12);
  box-shadow: inset 3px 0 0 var(--color-green);
}
.pb-row-mine .pb-place, .pb-row-mine .pb-name { color: var(--color-green); }

.pb-rank, .pb-saved {
  margin: 14px 0 0;
  font-family: var(--font-display);
  font-size: 9px;
  letter-spacing: 2px;
  color: var(--color-green);
}
.pb-saved { margin: 0 0 14px; }

/* The one failure line. Empty (and invisible) until something actually goes wrong. */
.pb-note {
  margin: 12px 0 0;
  font-size: 17px; line-height: 1.35;
  color: var(--color-gold);
}
.pb-note:empty { display: none; }

/* ---- the TOUCH PAD ----
   playit_pad.js (the shared kit) puts the platform's on-screen buttons here when a game declares
   them with playitPad(...). Drawn and wired by the PLATFORM — never by game code — so pointer
   capture, multi-touch and the show/hide rules live in exactly one file.

   The layer covers the frame but never eats a tap: pointer-events is OFF on the layer and back ON
   only on the button clusters, so canvas gestures (drag to steer, tap to fire) keep working right
   through it. It hugs the FRAME's corners, not the canvas — the letterboxed canvas floats centred,
   and thumbs live at the screen's edges (on a letterboxed game, the black border).

   Hidden until a touch is actually seen, hidden again when a keyboard is used (the kit toggles
   `.is-touch`) — a desktop player never sees it, and the api's headless play-test (no touch) never has
   it in the way. Sits at z 800: over the canvas, under the rotate hint (900) and both platform
   screens — the full z-order contract is written at the ROTATE hint below. */
/* The HUD LAYER (boot.js `playitHud`): a box kept exactly over the canvas, holding the game's own
   buttons (mute) — the score and banners are drawn on the sheet below — so they sit ON the picture on
   every screen, never in the letterbox. boot.js sets its box and its font-size (the game's own
   scale). Taps pass through to the canvas — a drag-to-steer game steers under its own score —
   except on the HUD's buttons. Sits at z 10: over
   the canvas, under the touch pad (800), the rotate hint (900) and both platform screens. */
#playit-hud {
  position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: 10;
  pointer-events: none; overflow: hidden;
}
#playit-hud button { pointer-events: auto; }
/* A new-UI game builds its whole interface in this box — a name box, sliders, menus that scroll —
   so its fields and the panels it marks data-playit-panel take taps too (a panel then blocks taps
   to the canvas behind it, which is what a menu wants). New-UI pages only: an older game's box is
   untouched. The page-and-box part sits in :where(), so it weighs nothing — only the field or
   the panel mark itself counts — and a game's own rule for the same element (its styles come
   after this sheet) wins: a menu fading out can switch its own taps off. */
:where(html[data-playit-ui="game"] #playit-hud) input,
:where(html[data-playit-ui="game"] #playit-hud) select,
:where(html[data-playit-ui="game"] #playit-hud) textarea,
:where(html[data-playit-ui="game"] #playit-hud) label,
:where(html[data-playit-ui="game"] #playit-hud) [data-playit-panel] { pointer-events: auto; }
/* The HUD's drawing sheet (boot.js `playitHudCtx`): the button box's sibling, kept on the same box
   by boot.js — z 10 too, and earlier in the page, so the game's buttons sit on top of it. */
#playit-hud-sheet { position: fixed; left: 0; top: 0; z-index: 10; pointer-events: none; }

#playit-pad { display: none; }
#playit-pad.is-touch {
  display: block;
  position: fixed; inset: 0; z-index: 800;
  pointer-events: none;
  font-family: var(--font-display);
}
.pad-move, .pad-actions {
  position: absolute; bottom: 18px;
  display: flex; align-items: center; gap: 14px;
  pointer-events: auto;
}
.pad-move { left: 18px; }
.pad-actions { right: 18px; }

/* One thumb-sized disc (72px, well past the app's 44px floor), in the mobile-game convention:
   TRANSLUCENT WHITE — a frosted ring and glyph that float over ANY game's art, bright or dark
   (the PUBG-style overlay look, picked by the owner). Deliberately neutral whites instead of the
   kit's brand tokens: this layer sits over every game ever generated, and a brand-coloured button
   clashes with art it knows nothing about — the same reason the scrims in this file are plain
   ink washes. */
.pad-btn {
  width: 72px; height: 72px;
  padding: 0;
  display: flex; align-items: center; justify-content: center;
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .55);
  background: rgba(255, 255, 255, .14);
  color: rgba(255, 255, 255, .92);
  font-family: var(--font-display);
  /* The size the kit computed for THIS label (playit_pad.js fits it to the disc, 6–11).
     The stylesheet owns the px so the small-screen rule below scales text with the disc. */
  font-size: calc(var(--pad-fs, 11) * 1px);
  /* the pixel font fills its em box — 1.5 keeps real air between a two-line label's rows */
  line-height: 1.5;
  letter-spacing: 1px;
  white-space: pre-line;   /* the kit writes a two-line label with a \n between the words */
  text-align: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .5);   /* labels stay readable over bright skies */
  overflow: hidden;
  cursor: pointer;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
/* Held, not clicked: the disc lights up while the finger is down. Deliberately NOT the kit's .btn
   lift/sink ledge — that animation says "click me once", and these are HELD controls. */
.pad-btn.is-down {
  background: rgba(255, 255, 255, .38);
  border-color: rgba(255, 255, 255, .95);
}
/* The movement arrows are DRAWN, not typed — the pixel font renders a ◀ glyph small and uneven
   inside the disc, while a clip-path triangle is crisp at any size and scales with the button. */
.pad-arrow-left::before,
.pad-arrow-right::before {
  content: "";
  width: 36%;
  height: 42%;
  background: rgba(255, 255, 255, .92);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45));
}
.pad-arrow-left::before  { clip-path: polygon(100% 0, 100% 100%, 0 50%); }
.pad-arrow-right::before { clip-path: polygon(0 0, 0 100%, 100% 50%); }
.pad-arrow-up::before,
.pad-arrow-down::before {
  content: "";
  width: 42%;
  height: 36%;
  background: rgba(255, 255, 255, .92);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45));
}
.pad-arrow-up::before   { clip-path: polygon(0 100%, 100% 100%, 50% 0); }
.pad-arrow-down::before { clip-path: polygon(0 0, 100% 0, 50% 100%); }

/* The FOUR-WAY pad (a game seen from above): the arrows in a cross, each in its own cell, the
   middle left empty. Smaller discs than the two-arrow row (60px, past the app's 44px floor) so
   the whole cross stays one thumb's reach and clear of the action buttons. */
.pad-cross {
  display: grid;
  grid-template-columns: repeat(3, auto);
  grid-template-areas: ". up ." "left . right" ". down .";
  gap: 2px;
}
.pad-cross .pad-btn { width: 60px; height: 60px; }
.pad-cross .pad-arrow-up    { grid-area: up; }
.pad-cross .pad-arrow-left  { grid-area: left; }
.pad-cross .pad-arrow-right { grid-area: right; }
.pad-cross .pad-arrow-down  { grid-area: down; }

/* ---- the ROTATE hint ----
   boot.js raises this layer when the game's fixed shape and the screen's shape disagree (a wide
   game on an upright phone) — a turn glyph, one line of copy, and PLAY ANYWAY (a real kit .btn),
   so the advice never traps anyone.

   It fires for ONE pairing only — a landscape game in a portrait viewport (boot.js explains why
   the mirror case is deliberately absent: a pillarboxed portrait game on a monitor is correct,
   and a monitor cannot rotate).

   The z-order here is a CONTRACT, not a coincidence:
       canvas  <  HUD (10)  <  touch pad (800)  <  rotate hint (900)  <  #playit-start (999)  <  #playit-over (1000)
   The hint must sit UNDER the start screen: the api's play-test clicks #playit-play, and a layer
   over that button would swallow the click, so the play-test would never see the game boot.
   (The play-test's fixed landscape viewport never triggers the hint at all now — the ordering
   stays as the structural belt, and the platform's own screens must outrank an advisory layer
   anyway.) And the bare rule below must stay `display: none`: an invisible-but-hit-testable layer
   intercepts taps just the same. */
#playit-rotate { display: none; }
#playit-rotate.is-on {
  position: fixed; inset: 0; z-index: 900;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; padding: 24px; box-sizing: border-box;
  text-align: center;
  background: rgba(5, 4, 15, .85);   /* the game-over scrim's recipe — a lit game waits behind it */
  font-family: var(--font-body);
}
.pr-glyph { font-size: 54px; line-height: 1; }
.pr-msg {
  margin: 0;
  max-width: 420px;
  font-size: 22px;
  line-height: 1.4;
  color: var(--color-text);
}

/* ---- small screens ----
   A phone in landscape has almost no vertical room: the cabinet keeps the title,
   PLAY and the score, and drops the start-screen teaser rather than pushing the
   button off the bottom (the full board is one tap away at game over anyway). */
@media (max-height: 520px) {
  .pb-teaser { display: none; }
  #playit-play { margin-top: 14px; padding: 16px 30px; font-size: 15px; }
  #playit-load { margin-top: 14px; }
  #playit-title, .pb-title { font-size: 11px; padding: 10px 12px; }
  /* The pad steps down with the screen but stays well past the 44px tap floor;
     the label scales with the disc (64/72 ≈ 0.89) so its clearance holds. */
  .pad-btn { width: 64px; height: 64px; font-size: calc(var(--pad-fs, 11) * 0.89px); }
}

/* ---- narrow screens: the pad in PORTRAIT ----
   A phone held upright has plenty of height and almost no WIDTH: at full size the two arrows
   (158px from the left) and four action discs (330px from the right) overlapped in the middle of
   a 390px screen, and the actions — added to the DOM last — sat on top and ate the right arrow's
   taps. Smaller discs (56px, still past the app's 44px floor) and a two-column stack for the
   third and fourth action button keep both clusters at their own edge with clear water between
   them: the worst real case (four actions on a 320px phone) now measures 122px + 122px + insets.
   wrap-reverse, not wrap: a game declares its MAIN actions first, and wrap-reverse fills the
   bottom row first, so those stay closest to the resting thumb; extras stack upward. */
@media (max-width: 480px) {
  .pad-btn { width: 56px; height: 56px; font-size: calc(var(--pad-fs, 11) * 0.78px); }
  .pad-move, .pad-actions { gap: 10px; bottom: 16px; }
  .pad-move { left: 12px; }
  /* The cross at 50px: 3 x 50 + gaps + inset = 166px, which with the two-column action stack
     (122px + inset) still leaves water between them on a 320px phone. */
  .pad-cross { gap: 2px; }   /* the phone rule above widens every pad's gap; a cross keeps its own */
  .pad-cross .pad-btn { width: 50px; height: 50px; }
  .pad-actions {
    right: 12px;
    max-width: 122px;              /* two 56px discs + one gap — the wrap point */
    flex-wrap: wrap-reverse;
    justify-content: flex-end;
  }
}

/* Motion-sensitive users: the buttons keep their ledge and their colour, they just
   stop moving — same rule the kit applies site-wide. The gauge still fills, it just
   snaps instead of sliding and doesn't breathe. */
@media (prefers-reduced-motion: reduce) {
  #playit-play { transition: none; }
  #playit-play:hover, #playit-play:active { transform: none; }
  .pl-fill { transition: none; animation: none; }
}
