/* Presentation shell (user-requested UX pass, 2026-07-15).
   Two concerns, one file, loaded LAST (after main/hud/vfx) so its rules win
   by source order:

     1. LIGHT / DARK THEME — every themeable colour is a :root custom
        property; :root[data-theme="dark"] overrides them. js/theme_controller.js
        flips the data-theme attribute; nothing here is toggled inline. Tiles
        keep the base palette in both themes (the beige 2/4 chips read fine on
        dark; the coloured 8+ tiles are the game's fixed colour language and
        stay identical). Realizes ADR-0015's theme.css / :root seam
        (Proposed — promotion ratifies the extended light/dark scope).

     2. RESPONSIVE FIT-TO-VIEWPORT — the board sizes from a single --board
        variable (min of width budget, height budget, and a desktop cap) and
        every cell / gap / tile / tile-position derives from it via calc().
        HTMLActuator positions tiles by CSS class only (no inline transforms),
        so overriding the geometry rules here is sufficient — no JS change.
        VFXController re-measures .grid-cell rects at resize/scroll, so the
        canvas overlay tracks whatever size the board renders at.

   The default (no data-theme attribute) resolves to the light :root values,
   so the page paints light even before ThemeController runs — no flash. */

/* ===================================================================== *
 * 1. THEME TOKENS                                                        *
 * ===================================================================== */

:root {
  --c-bg: #faf8ef;
  --c-text: #776e65;
  --c-board: #bbada0;
  --c-cell: rgba(238, 228, 218, 0.35);
  --c-score-bg: #bbada0;
  --c-score-label: #eee4da;
  --c-btn: #8f7a66;
  --c-btn-text: #f9f6f2;
  --c-hud-text: #776e65;
  --c-track: #bbada0;
  --c-toggle-bg: #eee4da;
  --c-toggle-border: #bbada0;
  --c-toggle-text: #776e65;
  --c-msg-overlay: rgba(238, 228, 218, 0.5);
  --c-dialog-bg: #faf8ef;
  --c-dialog-border: #e6ddcd;
  --c-modal-scrim: rgba(61, 50, 40, 0.55);
  --c-hr: #d8d4d0;
  --c-link: #776e65;
  /* Accent (2026-07-18 UI pass): primary-action colour drawn from the
     game's own tile ramp (the 8/16/32 orange family) so CTAs read as part
     of the board's colour language, not foreign chrome. --c-accent-glow is
     the matching shadow/focus tint. */
  --c-accent: #f65e3b;
  --c-accent-hi: #f2913d;
  --c-accent-text: #ffffff;
  --c-accent-glow: rgba(246, 94, 59, 0.35);
  /* Darker accent for the "pressed 3D" bottom edge on primary buttons
     (2026-07-23 popup redesign). */
  --c-accent-edge: #c74a22;
}

/* Dark = the warm-brown arcade look (2026-07-23 full-UI pass, user
   reference screenshots): near-black brown page, brown card surfaces with
   warm 1px borders, the orange accent family unchanged. */
:root[data-theme="dark"] {
  --c-bg: #1b1310;
  --c-text: #efe6d9;
  --c-board: #241a14;
  --c-cell: rgba(255, 226, 190, 0.06);
  --c-score-bg: #241a14;
  --c-score-label: #b3a493;
  --c-btn: #33261c;
  --c-btn-text: #f6efe4;
  --c-hud-text: #c9bcab;
  --c-track: #33261c;
  --c-toggle-bg: #241a14;
  --c-toggle-border: #3d2f24;
  --c-toggle-text: #e5dccd;
  --c-msg-overlay: rgba(16, 11, 7, 0.78);
  /* Dialog surfaces go WARM in the dark theme (2026-07-23 popup redesign,
     redesign reference): deep brown card + warm border reads as one family
     with the orange accent and the dark host page. */
  --c-dialog-bg: #251c15;
  --c-dialog-border: #40332a;
  --c-modal-scrim: rgba(12, 8, 5, 0.78);
  --c-hr: #33281f;
  --c-link: #c9b8a4;
  /* Dark keeps the same warm accent family, slightly lifted so it holds
     its own against the dark board without vibrating. */
  --c-accent: #f6703b;
  --c-accent-hi: #f79a4d;
  --c-accent-text: #ffffff;
  --c-accent-glow: rgba(246, 112, 59, 0.4);
}

/* --- typography (full-UI restyle; font per user pick 2026-07-24) ---
   Poppins (SIL OFL), self-hosted woff2 subsets in /fonts: latin + latin-ext.
   Three weights (400 body/UI, 600 buttons/labels, 700 tile numbers) so weight
   steps render with real glyphs instead of browser synthesis. No non-Latin
   subset is shipped: this build targets a Latin-script (US) audience, so the
   Vietnamese face was dropped. font-display swap: the system stack paints
   first, no invisible text. */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/poppins-latin-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/poppins-latin-ext-400.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/poppins-latin-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/poppins-latin-ext-600.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/poppins-latin-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/poppins-latin-ext-700.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

body {
  font-family: 'Poppins', 'Clear Sans', 'Helvetica Neue', Arial,
    sans-serif;
}

/* Form controls do not inherit the body family by default. */
button, input, select, textarea {
  font-family: inherit;
}

/* Mobile feel: the game owns its gestures — the browser must never try to
   scroll/zoom from a swipe (the declarative twin of the input manager's
   preventDefault, honoured before any JS runs). Chrome controls drop the
   double-tap-zoom wait so taps land instantly.
   2026-07-24: declared on every play-area BAND, not just the board. With only
   .game-container set, a swipe that began in the margin around the board (the
   HUD above, the power-ups below, the side strips) was still 'auto' there, so
   iOS Safari treated it as a page scroll — it janked the page a frame (the
   board appeared to jump) and then stole the gesture, so the move never fired
   ("swipe outside the board isn't responsive", user report). Listing the bands
   individually (rather than their .container parent) is deliberate: the modals
   are descendants of .container too, and touch-action:none on an ANCESTOR
   would block scrolling INSIDE the leaderboard / country-picker lists (the
   intersection rule — none on any ancestor wins). The modals sit between these
   bands in the DOM and keep the default, so they still scroll; the swipe
   listener itself bails while a modal is open. */
/* NB: .above-game is deliberately ABSENT from this list. It holds the board-
   size <select>, and touch-action:none on an ANCESTOR intersects down onto
   the control (a descendant can't re-enable it) — which stops iOS Safari from
   opening the native <select> picker at all (user report 2026-07-24: "can't
   tap #board-size"). The controls row is a thin band at the top; a swipe that
   starts there is still owned by the JS handler's preventDefault, so dropping
   its declarative touch-action costs nothing. */
.heading,
.hud-panel,
.game-container,
.powerups,
.powerup-hint,
.game-explanation,
.game-about,
hr {
  touch-action: none;
}

button, select, .restart-button {
  touch-action: manipulation;
}

/* Scroll hint (2026-07-24 mobile-feel pass) — a floating round button that
   moves the player between the swipe-capturing game and the page content
   below it. Base state is hidden: js/scroll_hint.js removes .hud-hidden only
   when the page can actually scroll, and the mobile media query below is what
   flips it to a flex button (desktop scrolls normally and needs no hint). */
.scroll-hint {
  display: none;
}
.scroll-hint.hud-hidden {
  display: none !important;
}
@media (max-width: 760px) {
  /* Bottom-LEFT on purpose: the embed host ships its own to-top button in the
     bottom-right corner, so keeping this down-hint on the opposite side avoids
     two stacked circles. Pinned to the very bottom edge: the board now spans
     ~98vw and reaches to ~90px above the viewport bottom, so the earlier
     bottom:84px floated the button ON TOP of the board's corner (2026-07-24
     user report). At bottom ~10px it sits in the power-ups band instead —
     horizontally clear too: even the 4-button PLUS row starts at x=54 on a
     360px phone, and this 40px circle at left 8 ends at 48. */
  .scroll-hint {
    position: fixed;
    left: max(8px, env(safe-area-inset-left));
    bottom: max(10px, env(safe-area-inset-bottom));
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--c-accent);
    color: var(--c-accent-text);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.28);
    opacity: 0.92;
    cursor: pointer;
    transition: transform 160ms ease, opacity 160ms ease;
  }
  .scroll-hint .icon {
    width: 24px;
    height: 24px;
  }
  .scroll-hint:active {
    transform: scale(0.92);
  }
}

/* --- inline SVG icons (Lucide/Feather-style strokes, 2026-07-18) --- */

.icon {
  display: block;
  width: 20px;
  height: 20px;
}

/* Theme toggle: both glyphs ship in the markup; exactly one shows. The
   default (no data-theme attribute) paints light, so the moon must show
   there too — hence :not([data-theme="dark"]) rather than ="light". */
.theme-toggle .icon-when-light,
.theme-toggle .icon-when-dark {
  display: none;
  line-height: 0;
}

:root:not([data-theme="dark"]) .theme-toggle .icon-when-light {
  display: block;
}

:root[data-theme="dark"] .theme-toggle .icon-when-dark {
  display: block;
}

/* Smooth the theme switch (but never the reduced-motion path). */
html, body,
.game-container, .grid-cell,
.score-container, .best-container,
.mode-toggle-btn, .theme-toggle {
  transition: background-color 0.25s ease, color 0.25s ease,
    border-color 0.25s ease;
}

.hud-reduced-motion, :root[data-theme] .hud-reduced-motion {
  transition: none;
}

/* --- themeable surfaces (route base colours through the tokens) --- */

html, body {
  background: var(--c-bg);
  color: var(--c-text);
}

a {
  color: var(--c-link);
}

hr {
  border-bottom-color: var(--c-hr);
}

/* Score / Best as bordered cards (bordered-card look). */
.score-container, .best-container {
  background: var(--c-score-bg);
  border: 1px solid var(--c-toggle-border);
  border-radius: 14px;
  box-sizing: border-box;
}

.score-container:after, .best-container:after {
  color: var(--c-score-label);
}

/* New Game: quiet outline card (orange stays reserved for the
   sign-in/competition CTAs; the board action stays chrome). */
.restart-button {
  background: var(--c-toggle-bg);
  border: 1px solid var(--c-toggle-border);
  border-radius: 12px;
  color: var(--c-toggle-text);
  box-sizing: border-box;
  transition: border-color 0.15s ease, transform 0.1s ease;
}

.restart-button:hover {
  border-color: var(--c-accent);
}

.restart-button:active {
  transform: scale(0.96);
}

.game-container {
  background: var(--c-board);
}

.grid-cell {
  background: var(--c-cell);
}

/* Tile 2 / 4 numbers: pin the dark brown so they stay legible on the light
   beige chips in BOTH themes. In dark mode they would otherwise inherit the
   light body text colour and wash out (user report 2026-07-15). Tiles keep
   their base palette; only these two low-value chips lack their own colour. */
.tile.tile-2 .tile-inner,
.tile.tile-4 .tile-inner {
  color: #776e65;
}

/* Subtle depth on every tile (learned from the play2048.co rewrite, whose
   board tiles carry a soft SVG glow filter). A faint drop shadow + a 1px
   inner highlight give the DOM tiles the same lifted, rounded feel without a
   canvas. Kept low-alpha so it reads on both themes and never muddies the
   value colours. The super-tile rules above set their own stronger glow and
   still win (they target the same element later / more specifically). */
.tile .tile-inner,
.tile.tile-2 .tile-inner,
.tile.tile-4 .tile-inner {
  box-shadow: 0 2px 6px rgba(50, 40, 30, 0.14),
    inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

/* The base sheet styles the won state at HIGHER specificity
   (.game-container .game-message.game-won — golden rgba(237,194,46,.5) +
   white text), out-ranking a bare .game-message token rule. On the dark
   theme / dark host pages the golden wash left the overlay text sunk into
   the tiles (2026-07-23 user report on the embedded host site). List every overlay
   state here so the theme token always wins; text follows --c-text so each
   theme keeps a readable pairing. */
.game-container .game-message,
.game-container .game-message.game-over,
.game-container .game-message.game-won {
  background: var(--c-msg-overlay);
  color: var(--c-text);
}

.game-container .game-message a {
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  box-shadow: 0 3px 10px var(--c-accent-glow);
}

/* Run-end rank card (vfx.css pins #776e65 for the light overlay — on the
   dark overlay that reads as near-invisible; route it through the theme
   token instead. Light resolves to the same #776e65, dark to #d7dae2.
   User report 2026-07-18: SCORE/BEST unreadable on dark game-over.) */
.rank-summary {
  color: var(--c-text);
}

/* HUD (hud.css) surfaces. The multiplier-bar tier colours are the game's
   fixed colour language (mirrored by the VFX glow) and are deliberately NOT
   re-themed here — they read on both backgrounds and ADR-0015 owns the neon
   ramp. Only the neutral chrome is tokenized. */
.hud-panel {
  color: var(--c-hud-text);
}

.multiplier-bar-track {
  background: var(--c-track);
}

.mode-toggle-btn {
  background: var(--c-toggle-bg);
  border-color: var(--c-toggle-border);
  color: var(--c-toggle-text);
}

.mode-toggle-btn.mode-toggle-active {
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  border-color: transparent;
  color: var(--c-accent-text);
}

#mode-switch-modal {
  background: var(--c-modal-scrim);
}

.mode-switch-dialog {
  background: var(--c-dialog-bg);
  color: var(--c-text);
  border: 1px solid var(--c-dialog-border);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
}

.mode-switch-dialog p {
  margin: 4px 0 14px;
  font-size: 15px;
}

.mode-switch-dialog button {
  border-radius: 12px;
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  box-shadow: 0 3px 0 var(--c-accent-edge);
}

.mode-switch-dialog button:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--c-accent-edge);
}

/* The SAFE action (keep playing) is the quiet outline one; only the
   destructive confirm carries the accent (2026-07-23 popup redesign). */
#mode-switch-no, #board-size-no {
  background: transparent;
  border: 1px solid var(--c-dialog-border);
  color: var(--c-text);
  box-shadow: none;
}

#mode-switch-no:active, #board-size-no:active {
  transform: scale(0.97);
  box-shadow: none;
}

#mode-switch-no:hover, #board-size-no:hover {
  border-color: var(--c-accent);
}

/* Board-size confirm modal — full overlay styling (hud.css only covers the
   mode-switch modal); reuses the .mode-switch-dialog look inside. */
#board-size-modal {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--c-modal-scrim);
  z-index: 100;
}

/* Confirm dialogs: flex-center within the safe area instead of hud.css's
   `margin: 30vh auto 0`, which pushed the buttons off-screen on short
   landscape phones. Same open/close mechanics (.hud-hidden). */
#mode-switch-modal:not(.hud-hidden),
#board-size-modal:not(.hud-hidden) {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
  box-sizing: border-box;
}

.mode-switch-dialog {
  margin: 0;
  width: min(90%, 320px);
  border-radius: 16px;
}

/* .hud-hidden must stay authoritative. theme.css loads AFTER hud.css, so a
   bare `.hud-hidden { display: none }` (hud.css) loses by source order to any
   same-specificity display rule declared below (e.g. .account-chip's
   inline-flex, .auth-panel's flex) — which would leave "hidden" elements
   visible. Force the hide utility to win so those elements actually hide when
   the JS toggles the class (the account chip when online is disabled, the
   inactive auth panel, etc.). */
.hud-hidden {
  display: none !important;
}

/* Online layer (ADR-0017): account chip, Leaderboard button, and the account +
   leaderboard modals. Themed via the same :root/[data-theme] tokens. */
.account-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.account-name {
  font-weight: bold;
  color: var(--c-hud-text);
}

.account-btn, .lb-open-btn {
  flex: 0 0 auto;
  min-height: 44px;
  /* ≥44px touch target (technical-preferences.md) */
  padding: 6px 12px;
  border: 1px solid var(--c-toggle-border);
  border-radius: 12px;
  background: var(--c-toggle-bg);
  color: var(--c-toggle-text);
  font-weight: bold;
  cursor: pointer;
}

/* Leaderboard opener is icon-only (SVG trophy) — square it up + center. */
.lb-open-btn {
  min-width: 44px;
  padding: 6px 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.account-btn:hover, .lb-open-btn:hover {
  border-color: var(--c-accent);
}

.account-btn:focus-visible, .lb-open-btn:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

.account-btn:active, .lb-open-btn:active {
  transform: scale(0.95);
}

.online-modal {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--c-modal-scrim);
  /* MUST beat the run-end overlay (.game-message, z-index 100, which sits
     LATER in the DOM so equal z-index loses) and the power-up tooltip (200):
     the guest signs in FROM the game-over screen (2026-07-18 user report —
     the dialog rendered underneath the "Game over!" text). */
  z-index: 300;
}

.online-modal:not(.hud-hidden) {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
  box-sizing: border-box;
}

/* Dialog card language (2026-07-23 redesign): big radius,
   warm 1px border, deep soft shadow, title + muted subtitle, ✕ close. */
.online-dialog {
  position: relative;
  /* .dialog-close anchor */
  background: var(--c-dialog-bg);
  color: var(--c-text);
  width: 92%;
  max-width: 360px;
  padding: 22px;
  border-radius: 20px;
  border: 1px solid var(--c-dialog-border);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  box-sizing: border-box;
  animation: dialog-pop 0.18s ease-out;
  /* Mobile-first: a tall card (the profile card with stats + name + flag +
     two actions) must scroll INSIDE the dialog rather than push the whole
     modal off a short phone screen. The scrim padding already reserves the
     safe-area gutters, so 100% of the padded area is available here. */
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

@keyframes dialog-pop {
  from {
    opacity: 0;
    transform: scale(0.96);
  }

  to {
    opacity: 1;
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .online-dialog {
    animation: none;
  }
}

.dialog-title {
  margin: 0 28px 4px 0;
  /* clear the ✕ */
  font-size: 21px;
  line-height: 1.2;
  color: var(--c-text);
}

.dialog-sub {
  margin: 0 24px 16px 0;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--c-hud-text);
}

.dialog-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--c-hud-text);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.dialog-close:hover {
  color: var(--c-accent);
  background: var(--c-cell);
}

.dialog-close:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* Segmented Sign in / Create account switch; the active side carries the
   accent so the current step is unmistakable. */
.auth-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
  padding: 4px;
  border: 1px solid var(--c-dialog-border);
  border-radius: 14px;
}

.auth-tab {
  flex: 1 1 0;
  padding: 8px;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--c-hud-text);
  font-weight: bold;
  font-size: 14px;
  cursor: pointer;
}

.auth-tab:hover {
  color: var(--c-text);
}

.auth-tab.auth-tab-active {
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
}

.auth-panel, .auth-verify {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.auth-verify {
  margin-top: 6px;
}

.auth-label {
  margin: 2px 0 -4px;
  font-size: 12.5px;
  font-weight: bold;
  color: var(--c-hud-text);
}

.auth-input {
  padding: 12px 14px;
  border: 1px solid var(--c-dialog-border);
  border-radius: 12px;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: inherit;
  font-size: 16px;
  /* <16px triggers iOS's input-focus auto-zoom */
  box-sizing: border-box;
}

.auth-input::placeholder {
  color: var(--c-hud-text);
  opacity: 0.7;
}

.auth-input:focus {
  outline: none;
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px var(--c-accent-glow);
}

.auth-submit {
  margin-top: 4px;
  padding: 13px;
  border: none;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  font-family: inherit;
  font-size: 15px;
  font-weight: bold;
  cursor: pointer;
  box-shadow: 0 3px 0 var(--c-accent-edge);
}

.auth-submit:hover {
  filter: brightness(1.05);
}

.auth-submit:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--c-accent-edge);
}

.auth-msg {
  margin: 12px 0 0;
  min-height: 1.2em;
  font-size: 14px;
}

/* Secondary action (Sign out inside the profile card) — quiet outline. */
.auth-secondary {
  margin-top: 4px;
  padding: 11px;
  border: 1px solid var(--c-dialog-border);
  border-radius: 12px;
  background: transparent;
  color: var(--c-text);
  font-family: inherit;
  font-size: 14px;
  font-weight: bold;
  cursor: pointer;
}

.auth-secondary:hover {
  border-color: var(--c-accent);
}

/* Display-name editing row (dino parity): input + inline Save. Wraps
   gracefully; targets stay >=44px tall on mobile. */
.auth-name-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
}

.auth-name-input {
  flex: 1 1 auto;
  min-width: 0;
}

.auth-name-save {
  flex: 0 0 auto;
  min-width: 64px;
  padding: 0 16px;
  border: none;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  font-family: inherit;
  font-size: 14px;
  font-weight: bold;
  cursor: pointer;
  box-shadow: 0 3px 0 var(--c-accent-edge);
}

.auth-name-save:hover {
  filter: brightness(1.05);
}

.auth-name-save:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--c-accent-edge);
}

/* Inline field error (name validation). Muted-red, small, tucked under the
   field it belongs to. */
.auth-field-error {
  margin: -4px 0 0;
  font-size: 12.5px;
  line-height: 1.3;
  color: var(--c-danger, #e5654a);
}

.auth-field-error.hud-hidden {
  display: none;
}

.auth-input-invalid {
  border-color: var(--c-danger, #e5654a) !important;
}

/* Session-expired dialog (keepalive): compact, actions stacked. */
.session-expired-dialog {
  max-width: 340px;
}

.session-expired-dialog .dialog-sub {
  margin: 6px 0 16px;
  font-size: 14px;
  color: var(--c-hud-text);
  line-height: 1.45;
}

.session-expired-dialog .auth-submit,
.session-expired-dialog .auth-secondary {
  width: 100%;
}

/* Password field + show/hide eye (2026-07-23). The input reserves room on
   the right for the toggle; the eye is an inline-SVG mask so it themes with
   currentColor and swaps to the slashed variant when the password shows. */
.auth-pass-wrap {
  position: relative;
  display: flex;
}

.auth-pass-wrap .auth-pass {
  flex: 1 1 auto;
  padding-right: 44px;
}

.auth-pass-toggle {
  position: absolute;
  top: 0;
  right: 0;
  width: 42px;
  height: 100%;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--c-hud-text);
  cursor: pointer;
}

.auth-pass-toggle::before {
  content: "";
  display: block;
  width: 20px;
  height: 20px;
  margin: 0 auto;
  background: currentColor;
  -webkit-mask: var(--eye) center / 20px 20px no-repeat;
  mask: var(--eye) center / 20px 20px no-repeat;
  --eye: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E");
}

.auth-pass-toggle:hover {
  color: var(--c-accent);
}

.auth-pass-toggle.is-shown::before {
  --eye: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24'/%3E%3Cline x1='1' y1='1' x2='23' y2='23'/%3E%3C/svg%3E");
}

/* Signed-in identity card at the top of the profile panel. */
.auth-id-card {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 4px;
  padding: 12px 14px;
  border: 1px solid var(--c-dialog-border);
  border-radius: 14px;
  background: var(--c-cell);
}

.auth-id-flag {
  display: inline-flex;
  flex: 0 0 auto;
  line-height: 0;
  font-size: 12px;
  font-weight: bold;
  color: var(--c-hud-text);
}

.auth-id-flag .auth-id-flag-svg {
  width: 40px;
  height: 27px;
  border-radius: 4px;
  box-shadow: 0 0 0 1px var(--c-toggle-border);
}

.auth-id-name {
  font-size: 17px;
  font-weight: bold;
  color: var(--c-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The display-name chip is now a clickable account entry point. */
.account-name {
  cursor: pointer;
}

.account-name:hover {
  color: var(--c-accent);
}

/* Country picker (2026-07-23). The select reuses .auth-input tokens; only
   the pieces a <select> adds are here. The chip flag button holds either a
   bare code ("US") or an <svg><use> flag from the shared sprite. */
.auth-select {
  width: 100%;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  /* neutral chevron that reads on both themes */
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23918a80' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 38px;
}

.auth-profile-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 4px;
  font-weight: bold;
  color: var(--c-text);
}

.auth-profile-flag {
  display: inline-flex;
  line-height: 0;
}

.auth-profile-flag .auth-flag {
  width: 27px;
  height: 18px;
  border-radius: 2px;
  box-shadow: 0 0 0 1px var(--c-toggle-border);
}

/* Country trigger buttons: the flag + name row that opens the grid. */
.auth-country-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  font-weight: bold;
  color: var(--c-text);
}

.auth-flag-slot {
  display: inline-flex;
  flex: 0 0 auto;
  min-width: 27px;
  line-height: 0;
  font-size: 11px;
  color: var(--c-hud-text);
}

.auth-flag-slot .auth-flag {
  width: 27px;
  height: 18px;
  border-radius: 2px;
  box-shadow: 0 0 0 1px var(--c-toggle-border);
}

.auth-country-name {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The flag grid (js/country_picker.js) — its own centered modal (2026-07-23:
   was a cramped absolute overlay inside the 360px account card that scrolled
   horizontally). Wider card, taller grid, vertical scroll only. */
.country-picker-dialog {
  width: 94%;
  max-width: 460px;
  display: flex;
  flex-direction: column;
  max-height: min(80vh, 620px);
}

.country-picker-dialog .dialog-title {
  margin-bottom: 14px;
}

.country-picker-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* minmax(0,..) lets a
     long name ellipsis instead of forcing horizontal overflow */
  gap: 8px;
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  padding: 2px;
  -webkit-overflow-scrolling: touch;
}

@media (min-width: 460px) {
  .country-picker-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.country-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
  /* allow the name to shrink + ellipsis */
  padding: 10px 4px;
  border: 1px solid transparent;
  border-radius: 12px;
  background: transparent;
  color: var(--c-text);
  font-family: inherit;
  font-size: 12px;
  line-height: 1.25;
  text-align: center;
  cursor: pointer;
}

.country-item:hover {
  background: var(--c-cell);
}

.country-item-active {
  border-color: var(--c-accent);
  background: var(--c-cell);
}

.country-item-flag,
.country-item .country-item-code {
  width: 40px;
  height: 27px;
  border-radius: 4px;
  box-shadow: 0 0 0 1px var(--c-toggle-border);
}

.country-item .country-item-code {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--c-hud-text);
}

.country-item-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
  color: var(--c-hud-text);
}

/* Signed-in identity chip = avatar (initial) + name (2026-07-23). */
.account-name {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 4px 12px 4px 4px;
  border: 1px solid var(--c-toggle-border);
  border-radius: 999px;
  background: var(--c-toggle-bg);
  color: var(--c-toggle-text);
  font-family: inherit;
  font-weight: bold;
  cursor: pointer;
}

.account-name:hover {
  border-color: var(--c-accent);
}

.account-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  font-size: 16px;
  font-weight: bold;
  line-height: 1;
}

.account-name-text {
  max-width: 12ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Player stats block in the account card (games played + best per mode). */
.auth-stats {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 2px 0 4px;
  border: 1px solid var(--c-dialog-border);
  border-radius: 12px;
  overflow: hidden;
}

.auth-stat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 14px;
  background: var(--c-cell);
  font-size: 13.5px;
}

.auth-stat-label {
  color: var(--c-hud-text);
}

.auth-stat-value {
  font-weight: bold;
  color: var(--c-text);
}

/* Leaderboard PANEL styles moved to style/leaderboard.css (2026-07-23
   redesign — tabs, board selector, pagination, YOUR BEST card). Only the
   pieces that live OUTSIDE the panel stay here: the opener button above and
   the run-end rank line + guest nudge below. */
.lb-run-rank {
  margin-top: 8px;
  font-size: 14px;
  color: var(--c-hud-text);
}

/* Guest run-end nudge — an actionable button (opens the sign-in modal),
   not dead text (2026-07-18 guest-flow fix). */
.lb-run-rank .lb-signin-nudge {
  border: none;
  border-radius: 8px;
  min-height: 36px;
  padding: 8px 14px;
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  font-family: inherit;
  font-size: 13px;
  font-weight: bold;
  cursor: pointer;
  box-shadow: 0 3px 10px var(--c-accent-glow);
}

.lb-run-rank .lb-signin-nudge:active {
  transform: scale(0.96);
}

/* Guest sign-in toast (js/signin_prompt.js, 2026-07-23) — a floating strip
   under the header, never a modal. Fixed so it survives scroll; sits below
   the online modals (z 300) since it must never cover a dialog. */
.signin-prompt {
  position: fixed;
  /* Docked to the BOTTOM (mobile-first): at the top it covered the logo and
     the mode tabs on a phone. A bottom strip clears the board and header on
     every width. */
  bottom: max(12px, env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  z-index: 250;
  display: flex;
  align-items: center;
  gap: 10px;
  width: max-content;
  max-width: min(92vw, 440px);
  padding: 10px 12px;
  border-radius: 14px;
  background: var(--c-dialog-bg);
  color: var(--c-text);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);
  border: 1px solid var(--c-dialog-border);
}

.signin-prompt.hud-hidden {
  display: none;
}

.signin-prompt-text {
  font-size: 13px;
  line-height: 1.35;
}

.signin-prompt-go {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 6px 14px;
  border: none;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  font-family: inherit;
  font-size: 13px;
  font-weight: bold;
  cursor: pointer;
  box-shadow: 0 3px 10px var(--c-accent-glow);
}

.signin-prompt-go:active {
  transform: scale(0.96);
}

.signin-prompt-dismiss {
  flex: 0 0 auto;
  min-width: 32px;
  min-height: 32px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--c-hud-text);
  font-size: 14px;
  cursor: pointer;
}

.signin-prompt-dismiss:hover {
  color: var(--c-accent);
}

/* Light/dark toggle button (index.html #theme-toggle) — SVG moon/sun. */
.theme-toggle {
  flex: 0 0 auto;
  min-width: 44px;
  min-height: 44px;
  /* ≥44px touch target */
  padding: 6px 12px;
  border: 1px solid var(--c-toggle-border);
  border-radius: 12px;
  background: var(--c-toggle-bg);
  color: var(--c-toggle-text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  cursor: pointer;
}

.theme-toggle:hover {
  border-color: var(--c-accent);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

.theme-toggle:active {
  transform: scale(0.95);
}

/* Board-size selector (index.html #board-size). Compact; themed. */
.board-size-select {
  flex: 0 0 auto;
  min-height: 44px;
  /* ≥44px touch target */
  padding: 6px 26px 6px 10px;
  border: 1px solid var(--c-toggle-border);
  border-radius: 12px;
  background: var(--c-toggle-bg);
  color: var(--c-toggle-text);
  font-weight: bold;
  font-family: inherit;
  cursor: pointer;
  /* custom chevron, matching .auth-select (full-UI restyle 2026-07-23) */
  -webkit-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23918a80' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
}

.board-size-select:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

.board-size-select:hover {
  border-color: var(--c-accent);
}

/* ===================================================================== *
 * 2. RESPONSIVE FIT-TO-VIEWPORT                                          *
 * ===================================================================== */

/* --board = the largest square that fits. DESKTOP/TABLET ONLY (>520px):
   bounded by the width budget (94vw), the height budget (viewport minus the
   fixed chrome above/below the board), and a cap so the tiles never grow
   absurd. The height budget uses svh, not dvh (dvh grows as a mobile bar
   auto-hides, which visibly rescaled the board mid-scroll — 2026-07-24).
   PHONES (<=520px) size the board in ONE place only: the width-first rule in
   the final media block at the end of this sheet. These desktop rules are
   deliberately fenced behind min-width so that on a phone exactly ONE
   --board declaration exists in the whole cascade — a live-site debugging
   session (2026-07-24) found iOS Safari applying an earlier svh-budget rule
   despite a later same-specificity override, and removing every competing
   declaration is the only arrangement that cannot lose that fight. */
:root {
  /* --chrome reserves room for the heading + controls + HUD + the power-ups
     toolbar/hint below the board, so the whole app fits without scrolling.
     Consumed only by the desktop --board formulas below. */
  --chrome: 372px;
}

@media (min-width: 521px) {
  :root {
    --board: max(220px, min(560px, 94vw, calc(100vh - var(--chrome))));
  }

  @supports (height: 100svh) {
    :root {
      --board: max(220px, min(560px, 94vw, calc(100svh - var(--chrome))));
    }
  }
}

/* Icon/label swap for compact buttons (New Game, Sign in). Desktop shows the
   text label; the icon is hidden. The ≤520px block below flips this (source
   order: this base MUST precede the media block so the media rule wins). */
.btn-icon {
  display: none;
  width: 20px;
  height: 20px;
}

.restart-button .btn-label, .account-btn .btn-label {
  display: inline;
}

@media (max-width: 520px) {

  /* Phones: heading + the two control rows + the HUD strip live ABOVE the
     board, and the power-ups toolbar lives BELOW it — all must stay on the
     first screen. Measured chrome (390x780) is ~360px; the old 330 pushed the
     power-ups row past the fold (2026-07-24 user report). Below ~360px height
     the board floor (220px) takes over anyway. The how-to line may fall below
     the fold — it is the only exempt element. */
  /* (--chrome overrides that used to live here are gone: phones size the
     board width-first in the final media block, so no phone --board formula
     consumes --chrome any more.) */

  /* Compact the main controls so mode + size + New Game fit one 360px row
     (~330px of content) — targets stay ≥44px tall throughout. Verified via
     headless screenshot 2026-07-15: at 15px/12px paddings the row still
     wrapped to three lines, so this is deliberately tighter. */
  .above-game, .controls-main, .controls-utility {
    gap: 6px;
  }

  .mode-toggle-btn {
    padding: 10px 8px;
    font-size: 13px;
  }

  .board-size-select {
    padding: 6px 2px 6px 6px;
    font-size: 13px;
    min-height: 44px;
    background-image: none;
  }

  .restart-button {
    font-size: 14px;
  }

  .above-game, .controls-main, .controls-utility {
    gap: 5px;
  }

  /* Tighten the vertical gaps around the board so the power-ups row clears the
     fold without shrinking the board more than necessary (2026-07-24).
     NOTE: several of these are overridden by same-specificity BASE rules that
     appear LATER in this file (.game-container at its shell rule, .powerups at
     its toolbar rule) — the authoritative phone compaction lives in the final
     media block at the END of this sheet, which wins on source order. */
  .above-game {
    margin-bottom: 8px;
  }

  .hud-panel {
    margin: 6px 0;
  }

  /* Trim the shell's side padding on phones so the board can span nearly the
     full width — the old 18px/side + the board's 94vw cap together left a big
     dead margin on a 6x6 (2026-07-24 user report: board too small, doesn't
     stretch). */
  .container {
    padding-left: max(8px, env(safe-area-inset-left));
    padding-right: max(8px, env(safe-area-inset-right));
  }

  /* (phone --board formulas that used to live here are gone — the ONE phone
     rule is the width-first declaration in the final media block, see the
     desktop fence comment above for why competitors were removed.) */

  /* Collapse the controls to ONE row (2026-07-24 user report: two rows ate
     the screen and shrank the board). New Game + Sign in become square icon
     buttons; their text labels hide. Everything then fits beside the mode
     toggle + size select on a single line, giving the board more height. */
  .btn-icon {
    display: block;
  }

  .restart-button .btn-label, .account-btn .btn-label {
    display: none;
  }

  .restart-button, .account-btn {
    width: 44px;
    min-width: 44px;
    padding: 0;
    justify-content: center;
  }

  .account-btn {
    display: inline-flex;
    align-items: center;
  }

  /* Flatten both wrappers into the above-game flex line so every button wraps
     individually (not the whole utility block at once), and center the row so
     a wrapped last button looks intentional (2026-07-24). */
  .controls-main, .controls-utility {
    display: contents;
  }

  .above-game {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 6px;
  }

  /* Utility buttons (theme / leaderboard / account) as compact 40px icons so
     the control cluster stays short whether it lands on one row or wraps. */
  .theme-toggle, .lb-open-btn, .account-btn, .restart-button {
    width: 40px;
    min-width: 40px;
    height: 40px;
    min-height: 40px;
  }
}

/* (The short-viewport --board shrink blocks that used to live here are gone:
   phones are width-first now — a short viewport scrolls down to the
   power-ups instead of shrinking the board.) */

:root {
  /* --n = board dimension (BoardSizeControl sets it per size, default 4).
     Cell/step derive from --n so ONE N x N geometry covers 3..8: content =
     board - 2*pad; N cells + (N-1) gaps fill it. With pad=gap=board*0.03 the
     4x4 case reduces to the original 0.2125*board cell exactly. */
  --n: 4;
  --pad: calc(var(--board) * 0.03);
  --gap: calc(var(--board) * 0.03);
  --cell: calc((var(--board) - (var(--n) + 1) * var(--gap)) / var(--n));
  --step: calc(var(--cell) + var(--gap));
}

/* --- app shell: one flex column, sized to the viewport, centered --- */

html {
  height: 100%;
}

/* Shell min-heights use svh, NOT dvh (2026-07-24, second mobile report): dvh
   GROWS in real time as the browser toolbar collapses mid-scroll, so a
   dvh-sized shell stretched ~80px while the player scrolled — the whole game
   section visibly "breathed" and shoved the host content below it. svh is the
   static small viewport: fills the screen exactly at load and never moves;
   hiding the toolbar just reveals the content below sooner. (100vh stays as
   the pre-svh fallback — static too, merely a little oversized.) */
body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  min-height: 100svh;
  overflow-x: hidden;
}

.container {
  width: auto;
  max-width: 560px;
  margin: 0 auto;
  padding: clamp(10px, 2.4vh, 20px) clamp(10px, 3vw, 18px);
  /* Notched phones (viewport-fit=cover): never let the shell sit under the
     status bar / home indicator / display cutouts. */
  padding-top: max(clamp(10px, 2.4vh, 20px), env(safe-area-inset-top));
  padding-right: max(clamp(10px, 3vw, 18px), env(safe-area-inset-right));
  padding-bottom: max(clamp(10px, 2.4vh, 20px), env(safe-area-inset-bottom));
  padding-left: max(clamp(10px, 3vw, 18px), env(safe-area-inset-left));
  box-sizing: border-box;
  min-height: 100vh;
  min-height: 100svh; /* svh not dvh — see the body comment above */
  display: flex;
  flex-direction: column;
}

h1.title {
  font-size: clamp(32px, 9vw, 56px);
  line-height: 1;
}

.heading {
  margin-bottom: 6px;
}

/* Controls bar (mobile-first redesign 2026-07-15, per the reference
   sites): two deliberate groups. .controls-main = game controls (mode /
   size / New Game) and never wraps internally; .controls-utility = chrome
   (theme / leaderboard / account) and drops to its own right-aligned row
   under ~520px — a deterministic 2-row stack instead of the old ragged
   6-item wrap. width:auto on .restart-button overrides the base
   max-width:520px 42%-width rule that otherwise fights the flex sizing. */
.above-game {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  flex-wrap: wrap;
}

.above-game:after {
  display: none;
}

.controls-main {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
  /* graceful degrade under ~330px: New Game drops below */
}

.controls-utility {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  flex: 0 0 auto;
}

/* Segmented mode toggle: one pill, two halves (narrower than two spaced
   buttons — buys back ~20px on a 360px screen). Overrides hud.css's
   per-button margins/radii by source order. */
#mode-toggle {
  margin: 0;
  display: inline-flex;
  flex: 0 0 auto;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--c-toggle-border);
}

.mode-toggle-btn {
  border-radius: 0;
  border: 0;
}

.mode-toggle-btn+.mode-toggle-btn {
  margin-left: 0;
  border-left-width: 0;
}

.restart-button {
  float: none;
  /* Sits right after the size select as part of the main group — the old
     margin-left:auto pushed it to the far edge and left an ugly gap between
     it and the size select on a phone (2026-07-24 user report). */
  margin: 0;
  flex: 0 0 auto;
  width: auto;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  /* base's max-width:520px block zeroes the padding —
                            keep the tap target ≥44px regardless */
  padding: 0 16px;
  box-sizing: border-box;
  border-radius: 12px;
  /* match mode-toggle + size select (unified pills) */
}

.hud-panel {
  margin: 8px 0;
}

.game-container {
  width: var(--board);
  height: var(--board);
  padding: var(--pad);
  margin: clamp(8px, 1.6vh, 18px) auto 0;
  box-sizing: border-box;
  border-radius: 16px;
  /* softer card look (reference look); VFX overlay
                            measures cell rects, radius doesn't affect it */
  /* Lift the board off the page like the reference site — a soft drop shadow
     gives it the same "raised panel" depth (2026-07-24). */
  box-shadow: 0 10px 30px rgba(50, 40, 30, 0.18),
    0 2px 6px rgba(50, 40, 30, 0.12);
}

.grid-cell, .tile .tile-inner {
  border-radius: 8px;
}

.score-container, .best-container {
  border-radius: 8px;
}

.grid-row {
  margin-bottom: var(--gap);
}

.grid-row:last-child {
  margin-bottom: 0;
}

.grid-cell {
  width: var(--cell);
  height: var(--cell);
  margin-right: var(--gap);
}

.grid-cell:last-child {
  margin-right: 0;
}

.tile, .tile .tile-inner {
  width: var(--cell);
  height: var(--cell);
  line-height: var(--cell);
}

.tile .tile-inner {
  font-size: calc(var(--cell) * 0.5);
}

.tile.tile-128 .tile-inner,
.tile.tile-256 .tile-inner,
.tile.tile-512 .tile-inner {
  font-size: calc(var(--cell) * 0.42);
}

.tile.tile-1024 .tile-inner,
.tile.tile-2048 .tile-inner {
  font-size: calc(var(--cell) * 0.34);
}

.tile.tile-super .tile-inner {
  font-size: calc(var(--cell) * 0.28);
}

/* Super-tile colours (2026-07-23 user report: 4096 rendered as the base's
   near-black .tile-super). The base only styles up to 2048; give the
   milestone tiles a hot ramp that reads on both themes (tile colours are
   theme-invariant, like the base's own palette), with the 1024/2048-style
   glow scaled up.

   ORDER MATTERS: .tile-super sits on the SAME element as .tile-4096 etc.
   (js/html_actuator.js pushes both) at equal specificity — the generic
   purple fallback must come FIRST so the value-specific rules below win
   for 4096/8192/16384, leaving purple to 32768 and beyond. */
.tile.tile-super .tile-inner {
  color: #f9f6f2;
  background: #7c4fe0;
  /* 32768+: purple, never unreadable near-black */
  box-shadow: 0 0 30px 10px rgba(124, 79, 224, 0.55),
    inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

.tile.tile-4096 .tile-inner {
  color: #f9f6f2;
  background: #f2603c;
  /* fiery orange-red — hotter than 2048's gold */
  box-shadow: 0 0 30px 10px rgba(246, 112, 59, 0.55),
    inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

.tile.tile-8192 .tile-inner {
  color: #f9f6f2;
  background: #ee4c54;
  /* red — the next heat step */
  box-shadow: 0 0 30px 10px rgba(238, 76, 84, 0.6),
    inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

.tile.tile-16384 .tile-inner {
  color: #f9f6f2;
  background: #c93ad1;
  /* magenta — beyond the fire */
  box-shadow: 0 0 30px 10px rgba(201, 58, 209, 0.6),
    inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

/* ===================================================================== *
 * Tile motion — refined from the play2048.co rewrite (2026-07-24).
 * That build animates tiles with a spring (stiffness:200, damping:7,
 * mass:0.3) → a gentle OVERSHOOT rather than a flat ease. We keep the DOM
 * renderer but borrow the feel: an easeOutBack curve on the merge "pop" and
 * the spawn "appear", and a slightly longer, springier slide. Overrides the
 * base main.css timings by source order.
 * ===================================================================== */

/* Slide: a touch longer than the base's 100ms + a bezier with a hair of
   overshoot so a moved tile settles instead of stopping dead. */
.tile {
  transition: transform 140ms cubic-bezier(0.34, 1.2, 0.64, 1);
}

/* Spawn: scale up from small WITH overshoot (easeOutBack), not the base's
   linear scale(0)->scale(1). */
@keyframes appear-back {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }

  70% {
    opacity: 1;
    transform: scale(1.08);
  }

  100% {
    opacity: 1;
    transform: scale(1);
  }
}

.tile-new .tile-inner {
  animation: appear-back 220ms cubic-bezier(0.34, 1.56, 0.64, 1) 60ms;
  animation-fill-mode: backwards;
}

/* Merge pop: the signature bounce. The base pops to 1.2 linearly; the
   rewrite's spring overshoots then settles, so use easeOutBack and a hold at
   the 1.22 peak before settling to 1. */
@keyframes pop-back {
  0% {
    transform: scale(0.4);
  }

  55% {
    transform: scale(1.22);
  }

  100% {
    transform: scale(1);
  }
}

.tile-merged .tile-inner {
  z-index: 20;
  animation: pop-back 240ms cubic-bezier(0.34, 1.56, 0.64, 1) 60ms;
  animation-fill-mode: backwards;
}

/* Motion-off honours the OS setting: no overshoot, just a quick fade/appear
   (mirrors the base's reduced-motion intent). */
@media (prefers-reduced-motion: reduce) {
  .tile {
    transition: none;
  }

  .tile-new .tile-inner,
  .tile-merged .tile-inner {
    animation: none;
  }
}

/* Fluid tile positions — translate by whole --step multiples. First index is
   the column (X), second is the row (Y): tile-position-{x}-{y}. Overrides
   both the desktop and the max-width:520px pixel transforms in main.css. */
.tile.tile-position-1-1 {
  transform: translate(0, 0);
}

.tile.tile-position-1-2 {
  transform: translate(0, var(--step));
}

.tile.tile-position-1-3 {
  transform: translate(0, calc(var(--step) * 2));
}

.tile.tile-position-1-4 {
  transform: translate(0, calc(var(--step) * 3));
}

.tile.tile-position-1-5 {
  transform: translate(0, calc(var(--step) * 4));
}

.tile.tile-position-1-6 {
  transform: translate(0, calc(var(--step) * 5));
}

.tile.tile-position-1-7 {
  transform: translate(0, calc(var(--step) * 6));
}

.tile.tile-position-1-8 {
  transform: translate(0, calc(var(--step) * 7));
}

.tile.tile-position-2-1 {
  transform: translate(var(--step), 0);
}

.tile.tile-position-2-2 {
  transform: translate(var(--step), var(--step));
}

.tile.tile-position-2-3 {
  transform: translate(var(--step), calc(var(--step) * 2));
}

.tile.tile-position-2-4 {
  transform: translate(var(--step), calc(var(--step) * 3));
}

.tile.tile-position-2-5 {
  transform: translate(var(--step), calc(var(--step) * 4));
}

.tile.tile-position-2-6 {
  transform: translate(var(--step), calc(var(--step) * 5));
}

.tile.tile-position-2-7 {
  transform: translate(var(--step), calc(var(--step) * 6));
}

.tile.tile-position-2-8 {
  transform: translate(var(--step), calc(var(--step) * 7));
}

.tile.tile-position-3-1 {
  transform: translate(calc(var(--step) * 2), 0);
}

.tile.tile-position-3-2 {
  transform: translate(calc(var(--step) * 2), var(--step));
}

.tile.tile-position-3-3 {
  transform: translate(calc(var(--step) * 2), calc(var(--step) * 2));
}

.tile.tile-position-3-4 {
  transform: translate(calc(var(--step) * 2), calc(var(--step) * 3));
}

.tile.tile-position-3-5 {
  transform: translate(calc(var(--step) * 2), calc(var(--step) * 4));
}

.tile.tile-position-3-6 {
  transform: translate(calc(var(--step) * 2), calc(var(--step) * 5));
}

.tile.tile-position-3-7 {
  transform: translate(calc(var(--step) * 2), calc(var(--step) * 6));
}

.tile.tile-position-3-8 {
  transform: translate(calc(var(--step) * 2), calc(var(--step) * 7));
}

.tile.tile-position-4-1 {
  transform: translate(calc(var(--step) * 3), 0);
}

.tile.tile-position-4-2 {
  transform: translate(calc(var(--step) * 3), var(--step));
}

.tile.tile-position-4-3 {
  transform: translate(calc(var(--step) * 3), calc(var(--step) * 2));
}

.tile.tile-position-4-4 {
  transform: translate(calc(var(--step) * 3), calc(var(--step) * 3));
}

.tile.tile-position-4-5 {
  transform: translate(calc(var(--step) * 3), calc(var(--step) * 4));
}

.tile.tile-position-4-6 {
  transform: translate(calc(var(--step) * 3), calc(var(--step) * 5));
}

.tile.tile-position-4-7 {
  transform: translate(calc(var(--step) * 3), calc(var(--step) * 6));
}

.tile.tile-position-4-8 {
  transform: translate(calc(var(--step) * 3), calc(var(--step) * 7));
}

.tile.tile-position-5-1 {
  transform: translate(calc(var(--step) * 4), 0);
}

.tile.tile-position-5-2 {
  transform: translate(calc(var(--step) * 4), var(--step));
}

.tile.tile-position-5-3 {
  transform: translate(calc(var(--step) * 4), calc(var(--step) * 2));
}

.tile.tile-position-5-4 {
  transform: translate(calc(var(--step) * 4), calc(var(--step) * 3));
}

.tile.tile-position-5-5 {
  transform: translate(calc(var(--step) * 4), calc(var(--step) * 4));
}

.tile.tile-position-5-6 {
  transform: translate(calc(var(--step) * 4), calc(var(--step) * 5));
}

.tile.tile-position-5-7 {
  transform: translate(calc(var(--step) * 4), calc(var(--step) * 6));
}

.tile.tile-position-5-8 {
  transform: translate(calc(var(--step) * 4), calc(var(--step) * 7));
}

.tile.tile-position-6-1 {
  transform: translate(calc(var(--step) * 5), 0);
}

.tile.tile-position-6-2 {
  transform: translate(calc(var(--step) * 5), var(--step));
}

.tile.tile-position-6-3 {
  transform: translate(calc(var(--step) * 5), calc(var(--step) * 2));
}

.tile.tile-position-6-4 {
  transform: translate(calc(var(--step) * 5), calc(var(--step) * 3));
}

.tile.tile-position-6-5 {
  transform: translate(calc(var(--step) * 5), calc(var(--step) * 4));
}

.tile.tile-position-6-6 {
  transform: translate(calc(var(--step) * 5), calc(var(--step) * 5));
}

.tile.tile-position-6-7 {
  transform: translate(calc(var(--step) * 5), calc(var(--step) * 6));
}

.tile.tile-position-6-8 {
  transform: translate(calc(var(--step) * 5), calc(var(--step) * 7));
}

.tile.tile-position-7-1 {
  transform: translate(calc(var(--step) * 6), 0);
}

.tile.tile-position-7-2 {
  transform: translate(calc(var(--step) * 6), var(--step));
}

.tile.tile-position-7-3 {
  transform: translate(calc(var(--step) * 6), calc(var(--step) * 2));
}

.tile.tile-position-7-4 {
  transform: translate(calc(var(--step) * 6), calc(var(--step) * 3));
}

.tile.tile-position-7-5 {
  transform: translate(calc(var(--step) * 6), calc(var(--step) * 4));
}

.tile.tile-position-7-6 {
  transform: translate(calc(var(--step) * 6), calc(var(--step) * 5));
}

.tile.tile-position-7-7 {
  transform: translate(calc(var(--step) * 6), calc(var(--step) * 6));
}

.tile.tile-position-7-8 {
  transform: translate(calc(var(--step) * 6), calc(var(--step) * 7));
}

.tile.tile-position-8-1 {
  transform: translate(calc(var(--step) * 7), 0);
}

.tile.tile-position-8-2 {
  transform: translate(calc(var(--step) * 7), var(--step));
}

.tile.tile-position-8-3 {
  transform: translate(calc(var(--step) * 7), calc(var(--step) * 2));
}

.tile.tile-position-8-4 {
  transform: translate(calc(var(--step) * 7), calc(var(--step) * 3));
}

.tile.tile-position-8-5 {
  transform: translate(calc(var(--step) * 7), calc(var(--step) * 4));
}

.tile.tile-position-8-6 {
  transform: translate(calc(var(--step) * 7), calc(var(--step) * 5));
}

.tile.tile-position-8-7 {
  transform: translate(calc(var(--step) * 7), calc(var(--step) * 6));
}

.tile.tile-position-8-8 {
  transform: translate(calc(var(--step) * 7), calc(var(--step) * 7));
}

/* End-of-run overlay: flex-center the message + rank card + buttons so the
   whole card fits any board size (replaces the base's fixed 222px margins,
   which overflowed a shrunk board). !important beats main.css's
   max-width:520px !important rules on .game-message p. */
.game-container .game-message.game-over,
.game-container .game-message.game-won {
  display: flex !important;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(var(--board) * 0.02);
  padding: calc(var(--board) * 0.05);
  box-sizing: border-box;
}

.game-container .game-message p {
  margin: 0 !important;
  height: auto !important;
  font-size: calc(var(--board) * 0.11) !important;
  line-height: 1.1 !important;
}

.game-container .game-message .lower {
  margin: 0 !important;
}

/* Marketing / credits text is not gameplay — hide it below the fold on any
   viewport short enough that keeping it would force a scroll (covers virtually
   all laptops and phones). It still shows on tall desktop displays.
   .game-explanation is EXEMPT: it is the only how-to-play guidance and mobile
   players (the primary audience) must see it. */
.game-explanation {
  margin-top: 14px;
  font-size: 0.82em;
  text-align: center;
}

@media (max-height: 900px) {

  .container>p:not(.game-explanation),
  .container>hr {
    display: none;
  }
}

/* ===================================================================== *
 * 3. POWER-UPS TOOLBAR (Undo / Swap / Delete)                           *
 * ===================================================================== */

.powerups {
  display: flex;
  justify-content: center;
  gap: 14px;
  margin: 14px auto 0;
}

.powerup-btn {
  position: relative;
  /* tooltip + uses-badge anchor */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  padding: 0;
  border: 1px solid var(--c-toggle-border);
  border-radius: 14px;
  background: var(--c-toggle-bg);
  color: var(--c-toggle-text);
  font-weight: bold;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    opacity 0.15s ease, transform 0.1s ease,
    box-shadow 0.15s ease;
}

.powerup-btn:hover:not(:disabled) {
  border-color: var(--c-accent);
  box-shadow: 0 3px 10px var(--c-accent-glow);
}

.powerup-btn:active:not(:disabled) {
  transform: scale(0.94);
}

/* Hover / focus tooltip (data-tooltip). Gated behind @media (hover: hover) so
   it only appears on devices with a real pointer (a mouse). On touch, :hover
   sticks after a tap/swipe and the big tooltip box covers the board (2026-07-22
   user report) — there the #powerup-hint line narrates the action on tap and
   each button's aria-label carries the same copy for screen readers, so
   nothing is lost by suppressing the visual bubble. */
@media (hover: hover) {

  /* A DIMMED button (opacity < 1 — the unusable state) is its own stacking
     context, which traps the ::after tooltip's z-index INSIDE it at the
     button's own level 0 — and .tile-container (z:2) then paints tiles
     over the bubble (2026-07-23 user report: clipped tooltip). Lifting the
     hovered/focused button itself above the board frees the bubble no
     matter how the button is painted. */
  .powerup-btn:hover,
  .powerup-btn:focus-visible {
    z-index: 210;
  }

  .powerup-btn[data-tooltip]:hover::after,
  .powerup-btn[data-tooltip]:focus-visible::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%);
    max-width: 220px;
    width: max-content;
    padding: 6px 10px;
    border-radius: 6px;
    background: var(--c-text);
    color: var(--c-bg);
    font-size: 12px;
    font-weight: normal;
    line-height: 1.3;
    text-align: center;
    white-space: normal;
    pointer-events: none;
    z-index: 200;
  }
}

.powerup-btn:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

.powerup-icon {
  display: block;
  line-height: 0;
}

.powerup-icon .icon {
  width: 24px;
  height: 24px;
}

/* Remaining-uses counter as a notification-style badge riding the corner. */
.powerup-uses {
  position: absolute;
  top: -7px;
  right: -7px;
  min-width: 21px;
  height: 21px;
  padding: 0 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  color: var(--c-accent-text);
  font-size: 11px;
  font-weight: bold;
  line-height: 1;
  border: 2px solid var(--c-bg);
  box-sizing: border-box;
}

.powerup-btn.powerup-active {
  background: linear-gradient(135deg, var(--c-accent-hi), var(--c-accent));
  border-color: transparent;
  color: var(--c-accent-text);
  box-shadow: 0 0 0 3px var(--c-accent-glow);
}

.powerup-btn:disabled,
.powerup-btn.powerup-disabled {
  opacity: 0.35;
  cursor: default;
}

.powerup-btn:disabled .powerup-uses,
.powerup-btn.powerup-disabled .powerup-uses {
  background: var(--c-toggle-border);
  color: var(--c-bg);
}

/* Attention nudge (2026-07-18): pulsing accent ring on power-ups that could
   save a near-full board or a just-ended run. Set/cleared by
   PowerUps.renderAttention(). */
.powerup-btn.powerup-attention {
  border-color: var(--c-accent);
  animation: powerup-attention-pulse 1.2s ease-in-out infinite;
}

@keyframes powerup-attention-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 var(--c-accent-glow);
  }

  50% {
    box-shadow: 0 0 0 8px rgba(0, 0, 0, 0), 0 0 14px var(--c-accent-glow);
  }
}

@media (prefers-reduced-motion: reduce) {
  .powerup-btn.powerup-attention {
    animation: none;
    box-shadow: 0 0 0 3px var(--c-accent-glow);
  }
}

/* Game-over recovery button inside the overlay (index.html #undo-run-button).
   Base .game-message a styling + accent gradient already apply; it only
   needs to sit apart from Try again. .hud-hidden (display:none !important)
   keeps it out of the row until PowerUps shows it. */
.game-container .game-message .undo-run-button {
  margin-right: 8px;
}

/* Social share bar inside the run-end overlay (index.html #share-score,
   js/share_score.js). Round icon buttons in the toggle-chip language so they
   read as secondary chrome, not a CTA competing with Try again. The base
   ".game-message a" gradient rule targets <a> only — these <button>s are
   unaffected. 44px hits the tap-target floor (technical-preferences.md).
   #share-native ships .hud-hidden; ShareScore unhides it where the Web
   Share API exists. */
.share-score {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.share-score-label {
  font-size: 13px;
  font-weight: bold;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--c-text);
  opacity: 0.7;
}

.share-score-buttons {
  display: flex;
  gap: 10px;
}

.share-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--c-toggle-border);
  border-radius: 999px;
  background: var(--c-toggle-bg);
  color: var(--c-toggle-text);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease,
    transform 0.1s ease;
}

.share-btn:hover {
  border-color: var(--c-accent);
  box-shadow: 0 3px 10px var(--c-accent-glow);
}

.share-btn:active {
  transform: scale(0.92);
}

.share-btn:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

.share-btn .icon {
  width: 22px;
  height: 22px;
}

/* Transient "Copied!" feedback (aria-live) — reserves its line so the
   overlay doesn't reflow when it appears. */
.share-copied {
  min-height: 1em;
  font-size: 12px;
  font-weight: bold;
  color: var(--c-text);
  opacity: 0.8;
}

.powerup-hint {
  min-height: 1.2em;
  margin: 6px 0 0;
  text-align: center;
  font-size: 0.82em;
  color: var(--c-text);
  opacity: 0.85;
}

/* Tile marked during a swap selection. */
.tile.powerup-selected .tile-inner {
  outline: 3px solid var(--c-accent);
  outline-offset: -3px;
  box-shadow: 0 0 12px var(--c-accent-glow);
}

/* ===================================================================== *
 * 4. PLAY2048.CO RESKIN (2026-07-22 user request) — CSS-only             *
 *                                                                        *
 * A clean, centered layout faithful to the reference site. No HTML/JS      *
 * every id/class, the tests, and the 49-agent architecture are untouched.*
 * The palette already mirrored the reference verbatim; this fixes the     *
 * float-based header + fixed-padding score chips that overflowed on      *
 * mobile, folds the controls into groups that WRAP instead of overflow,  *
 * returns the primary button to the reference solid brown, and evens the  *
 * vertical rhythm. Loaded last, so these win by source order over base   *
 * main.css and the earlier theme.css sections (equal specificity → later *
 * declaration wins).                                                     *
 * ===================================================================== */

/* --- Shell: pin the column to the board's own width so the header, the
   controls, and the board share one width and one set of edge gutters
   (was: 560px box with inner padding, which let the board — sized off 94vw —
   stick out past the padded header on the right). min(560px, 94vw) is the
   board's width cap, so on phones the column IS 94vw with tidy 3vw gutters,
   and on desktop it is a centered 560px column, reference-style. --- */
.container {
  width: min(560px, 94vw);
  max-width: none;
  margin: 0 auto;
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* The board already centers itself (margin: … auto 0) and never exceeds
   min(560px, 94vw), so it fills the column when width-bound and centers
   within it when the viewport-height budget makes it smaller. */
.game-container {
  margin-left: auto;
  margin-right: auto;
}

/* --- Header: title left, scores right, via flex. Floats were the mobile-
   overflow root (title float:left + scores float:right + non-shrinking
   score chips); space-between mirrors the reference title/score split. --- */
.heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 14px;
  margin: 0 0 14px;
}

.heading::after {
  content: none;
  display: none;
}

/* drop the float clearfix */

h1.title {
  float: none;
  margin: 0;
  font-size: clamp(40px, 12vw, 72px);
  line-height: 1;
  letter-spacing: -1px;
  flex: 0 1 auto;
  min-width: 0;
}

/* Score / Best chips — the reference stacked "LABEL over number" boxes, made
   fluid so both always sit beside the title, down to 320px. */
.scores-container {
  float: none;
  display: flex;
  align-items: stretch;
  gap: 8px;
  flex: 0 0 auto;
}

.score-container, .best-container {
  float: none;
  margin: 0;
  min-width: clamp(50px, 15vw, 76px);
  height: auto;
  padding: clamp(16px, 4.4vw, 20px) clamp(10px, 3vw, 22px) clamp(6px, 1.6vw, 9px);
  font-size: clamp(16px, 4.6vw, 24px);
  line-height: 1;
  box-sizing: border-box;
}

.score-container::after, .best-container::after {
  top: clamp(5px, 1.6vw, 8px);
  font-size: clamp(9px, 2.6vw, 12px);
}

/* Score "+N" float — the base main.css pins it at right:30px, which on a
   narrow chip (min-width 50px) lands OUTSIDE the box and overlaps the score
   digits. Center it over the fluid chip instead and scale the text down. */
.score-container .score-addition, .best-container .score-addition {
  right: 0;
  left: 0;
  text-align: center;
  font-size: clamp(13px, 3.4vw, 20px);
  color: var(--c-accent);
}

/* --- Controls: two groups that WRAP rather than overflow. Main = mode /
   size / New Game; utility = theme / leaderboard / account, hugging the
   right and dropping to its own row when the width runs out. --- */
.above-game {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* flex-start (not space-between): space-between shoved the two groups to
     opposite edges, leaving a lopsided gap on a phone. Left-aligned + a shared
     gap reads as one tidy control cluster; the utility group still hugs the
     right on a wide row via its margin-left:auto, and drops to its own
     left-aligned row when the width runs out. */
  justify-content: flex-start;
  gap: 8px;
  margin: 0 0 12px;
}

.above-game::after {
  content: none;
  display: none;
}

.controls-main {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  /* shrink-to-content (was flex:1 1 auto, which stretched the group and blew
     a gap between the mode toggle and New Game). */
  flex: 0 1 auto;
  min-width: 0;
}

.controls-utility {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
  /* hug the right when sharing a row with main */
  flex: 0 0 auto;
}

/* Primary action back to the reference solid brown — the accent-gradient New
   Game read as foreign chrome. The mode-active pill and power-up glow keep
   the accent, so the orange still lives on the board's own controls. */
.restart-button {
  float: none;
  width: auto;
  margin: 0;
  min-height: 44px;
  padding: 0 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  font-weight: bold;
  border-radius: 8px;
  background: var(--c-btn);
  color: var(--c-btn-text);
  box-shadow: none;
}

.restart-button:hover {
  filter: brightness(1.05);
  box-shadow: none;
}

.restart-button:active {
  transform: scale(0.96);
}

/* --- FUSE HUD: ×value and the fill bar on one tidy row (was value stacked
   over bar, which read as two stray strips above the board). --- */
.hud-panel {
  margin: 0 0 10px;
}

#multiplier-container {
  display: flex;
  align-items: center;
  gap: 10px;
}

#multiplier-value {
  flex: 0 0 auto;
  min-width: 3.4em;
  font-size: 15px;
}

.multiplier-bar-track {
  flex: 1 1 auto;
  margin-top: 0;
}

#heat-strip-container {
  margin-top: 7px;
  font-size: 13px;
  opacity: 0.85;
}

/* How-to line: a quiet, centered footer note (the reference site keeps its intro
   terse and out of the way). */
.game-explanation {
  margin-top: 16px;
  opacity: 0.9;
}
/* ============================================================================
   PHONE VERTICAL COMPACTION — the authoritative ≤520px overrides.
   MUST STAY THE LAST BLOCK IN THIS SHEET: everything here shares specificity
   with base rules ABOVE (.game-container shell rule, .powerups toolbar rule,
   h1.title, .heading), so only source order makes these win. An earlier
   attempt placed them in the mid-sheet media block and they were silently
   dead (2026-07-24).
   Purpose: every vertical crumb feeds --board directly (its height budget is
   100svh - --chrome). Cutting the margins, compacting the heading and
   collapsing the idle hint line funds the --chrome drop below, which lets the
   board reach its 98vw width cap on phones ("board is small on mobile",
   post-svh-lock report). */
@media (max-width: 520px) {
  .heading {
    margin-bottom: 2px;
  }

  h1.title {
    font-size: clamp(26px, 7.6vw, 40px);
  }

  .above-game {
    margin-bottom: 6px;
  }

  .hud-panel {
    margin: 4px 0;
  }

  .game-container {
    margin-top: 6px;
  }

  .powerups {
    margin-top: 6px;
  }

  /* The hint line reserves its 1.2em ONLY while it has text (swap/delete
     selection mode). Idle it collapses to nothing — that empty strip was
     ~21px of permanently wasted board budget. When it appears it pushes only
     the below-board text down; the board itself never moves. */
  .powerup-hint:empty {
    min-height: 0;
    margin: 0;
  }

  .container {
    padding-top: max(6px, env(safe-area-inset-top));
    padding-bottom: max(6px, env(safe-area-inset-bottom));
  }

  /* --- Mode toggle -> dropdown (phones, 2026-07-24) -----------------------
     The 3-segment control was the widest item in the controls row: on a
     375px phone it forced the row to wrap to TWO lines, eating a board-height
     row and pushing the utility cluster off the main line. Collapsed, only
     the ACTIVE mode shows (with a caret); tapping it opens the other modes as
     an overlay panel (.mode-dd-open, js/mode_toggle.js). The panel items are
     absolutely positioned so opening never reflows the row. */
  #mode-toggle {
    position: relative;
    overflow: visible;
    z-index: 90;
    border-radius: 12px;
  }

  #mode-toggle .mode-toggle-btn:not(.mode-toggle-active) {
    display: none;
  }

  .mode-toggle-btn.mode-toggle-active::after {
    content: "";
    width: 0;
    height: 0;
    display: inline-block;
    margin-left: 6px;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid currentColor;
  }

  #mode-toggle.mode-dd-open .mode-toggle-btn:not(.mode-toggle-active) {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    left: 0;
    top: calc(100% + 4px);
    min-width: 100%;
    width: max-content;
    height: 44px;
    box-sizing: border-box;
    background: var(--c-toggle-bg);
    border: 1px solid var(--c-toggle-border);
    border-radius: 10px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
    white-space: nowrap;
  }

  /* The SECOND hidden mode drops one slot lower (whichever pair is inactive:
     the general-sibling selector matches the later of the two). */
  #mode-toggle.mode-dd-open
    .mode-toggle-btn:not(.mode-toggle-active)
    ~ .mode-toggle-btn:not(.mode-toggle-active) {
    top: calc(100% + 52px);
  }

  /* One controls line, always: with the toggle collapsed everything fits a
     360px phone (~310px total), so the utility cluster shares the main row
     instead of wrapping under it. */
  .above-game {
    flex-wrap: nowrap;
    justify-content: space-between;
  }

  /* --- Width-first board (phones) ----------------------------------------
     The user rule: the board must stretch with the width, always — EDGE TO
     EDGE (2026-07-24: 98vw still left a ~4px gap per side plus the shell's
     8px padding, and the question came back as "why doesn't it reach both
     edges"). 100vw is the full viewport; the height budget is gone — on a
     short viewport the page scrolls down to the power-ups instead of
     shrinking the board (the scroll-hint button covers that path). Being the
     LAST --board declaration for <=520px, this overrides every earlier
     formula on source order. */
  :root {
    --board: max(220px, min(560px, 100vw));
  }

  /* Full-bleed: the board lives inside the centered shell (width 94vw), so
     pull it out with the classic full-bleed formula — 50% of the shell's
     content box minus half the viewport lands the 100vw board exactly at
     x=0 regardless of the shell's actual inset. The previous fixed -8px
     pull assumed an 8px shell padding and left a ~4-7px right overflow
     (horizontal scroll) once the shell became 94vw-centered (2026-07-24
     production report: board tràn phải). (margin-top comes from the
     compaction rule earlier in this block.) */
  .game-container {
    margin-left: calc(50% - 50vw);
  }
}
