/* ===========================================================================
   Infin8 Arcade — shared game page shell

   Every Arcade title renders the same page furniture: a topbar, a 16:9 stage
   with a HUD and overlays over it, a touch control dock, and one information
   rail beside it. Before this file each game shipped its own vocabulary
   (`stage`/`stage-card`/`chip`/`panel` in LoneWolfanCub, `game-stage-card`/
   `hud-chip`/`overlay` in Sk8er, `brawl-layout`/`game-column`/`fight-hud` in
   Brawl) and its own copy of the mobile hardening rules, so the three titles
   drifted apart every time one of them was touched.

   This is now the single source for that furniture. A per-game `style.css`
   should only contain what is genuinely specific to that title (its HUD
   readouts, its touch pad geometry, its own accent), and a new game should be
   able to ship by writing markup against these classes alone.

   Load order on a game page is:
       arcade.css        brand tokens + brand texture
       game-shell.css    this file, the page furniture
       rider-card.css    the shared player card
       style.css         per-game specifics

   Canonical markup:

       <main class="game-page">
         <header class="game-topbar"> .game-brand + .top-actions </header>
         <div class="game-layout">
           <section class="game-column">
             <div class="game-stage" id="game-stage">
               <canvas id="game-canvas"> .hud  .game-overlay  .touch-controls
             </div>
             <p class="game-note">
           </section>
           <aside class="side-column"> .side-card* </aside>
         </div>
       </main>

   Canonical page state: `document.documentElement.dataset.gameState` is one of
   `menu`, `playing`, `paused`, `result`. Games keep their own internal mode
   names and normalize through `Infin8GameShell.setState()` in game-shell.js.
   =========================================================================== */

:root {
  color-scheme: dark;

  /* Shared with arcade.css. Repeated rather than imported so a game page still
     renders correctly if only this file loads. */
  --bg: #050711;
  --panel: rgba(11, 16, 32, 0.88);
  --panel-solid: #080d1c;
  --line: rgba(119, 226, 255, 0.22);
  --cyan: #63e6ff;
  --purple: #b66cff;
  --accent: #b66cff;
  --yellow: #ffe267;
  --danger: #ff5c7c;
  --good: #7cffb3;
  --text: #f5f7ff;
  --muted: #aeb7d2;

  /* Set from the player's saved control profile. */
  --touch-scale: 1;
  --touch-opacity: 0.92;
}

* { box-sizing: border-box; }
html, body { min-height: 100%; margin: 0; }
body {
  position: relative;
  isolation: isolate;
  overflow-x: hidden;
  color: var(--text);
  background:
    radial-gradient(circle at 16% 10%, rgba(128, 65, 211, 0.22), transparent 34rem),
    radial-gradient(circle at 84% 18%, rgba(43, 194, 228, 0.17), transparent 32rem),
    var(--bg);
  font: 15px/1.5 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
button, input, select { font: inherit; }
button { color: inherit; -webkit-tap-highlight-color: transparent; }
a { color: inherit; -webkit-tap-highlight-color: transparent; }
[hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   Shared brand texture

   The same Infin8 nebula `arcade.css` screens over the Arcade home, profile and
   leaderboard pages, so a game page sits on the identical surface as the rest of
   the site instead of on a bare gradient. It is a fixed layer behind the content
   rather than a body background, so it never scrolls, repeats or shifts as
   sections change height. `screen` lifts the dark page toward the texture's
   magentas without ever darkening text.

   `body` isolates, so this sits above the body gradient and below `.game-page`,
   which carries `z-index: 1`. Keep those two facts together — dropping either
   one puts the texture over the page content or hides it entirely.
   --------------------------------------------------------------------------- */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("../../assets/infin8-texture.webp");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  mix-blend-mode: screen;
  opacity: 0.16;
}
/* Reduced transparency preferences and print both want the texture gone. */
@media (prefers-reduced-transparency: reduce) { body::before { opacity: 0.07; } }
@media print { body::before { display: none; } }

/* --- page frame ---------------------------------------------------------- */

.game-page { position: relative; z-index: 1; width: min(1480px, calc(100% - 24px)); margin: 0 auto; padding: 14px 0 26px; }

.game-topbar { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 16px; }
/* The game shell's wordmark matches the Arcade and site headers. The controls
   beside it are deliberately left alone: .icon-button and .text-button are
   controls, not tabs, and painting them the same cyan would erase the one
   distinction the top bar is making. */
.game-brand { display: flex; align-items: center; gap: 12px; color: var(--cyan); text-decoration: none; font-weight: 850; font-size: 1.16rem; transition: color 160ms ease; }
.game-brand:hover { color: var(--purple); }
.game-brand img { width: 38px; height: 38px; object-fit: contain; }
.game-brand .sigil { font-size: 38px; line-height: 1; color: var(--cyan); text-shadow: 0 0 24px var(--purple); }
.game-brand small { display: block; color: rgba(99, 230, 255, 0.72); font-size: 0.68rem; font-weight: 650; letter-spacing: 0.16em; text-transform: uppercase; }

.top-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.icon-button, .text-button {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 38px; padding: 0 13px;
  border: 1px solid var(--line); border-radius: 999px;
  background: rgba(255, 255, 255, 0.045);
  cursor: pointer; text-decoration: none; font-weight: 750;
}
.icon-button:hover, .text-button:hover { border-color: var(--cyan); }
.session-chip { padding: 8px 12px; border: 1px solid rgba(181, 109, 255, 0.32); border-radius: 999px; background: rgba(181, 109, 255, 0.09); color: var(--accent); font-size: 0.78rem; font-weight: 850; }

/* Stage-first layout.
   The information rail used to take a fixed 320px column beside the stage at
   every desktop width, so the game — the only thing on the page anyone came
   for — was rendered into whatever was left. It now runs the full width of the
   page at every size and the panels sit underneath it in one band.

   The width cap is expressed in terms of the *height* available: a 16:9 stage
   given the whole width of a wide, short window would run past the bottom of
   the screen and force the player to scroll to see their own game. Capping
   width at (available height x 16/9) keeps the entire stage on screen, so the
   stage is as wide as it can be while still being fully visible. */
.game-layout { display: block; }
.game-column { min-width: 0; width: min(100%, calc((100svh - 190px) * 16 / 9)); margin-inline: auto; }

/* --- stage --------------------------------------------------------------- */

.game-stage {
  position: relative;
  isolation: isolate;
  width: 100%;
  aspect-ratio: 16 / 9;
  min-height: 360px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: #05070d;
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(177, 108, 255, 0.08);
  touch-action: none;
  user-select: none;
}
.game-stage canvas { display: block; width: 100%; height: 100%; background: #060a13; }
.game-stage:fullscreen { width: 100vw; height: 100vh; aspect-ratio: auto; border: 0; border-radius: 0; }
.game-stage:fullscreen canvas { width: 100vw; height: 100vh; object-fit: contain; }

.game-note { margin: 12px 6px 0; color: var(--muted); font-size: 0.84rem; line-height: 1.55; }

/* --- HUD ----------------------------------------------------------------- */

.hud { position: absolute; z-index: 6; inset: 0; pointer-events: none; }
.hud-row { position: absolute; top: 14px; left: 14px; right: 14px; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.hud-cluster { display: flex; flex-wrap: wrap; gap: 8px; }
.hud-chip {
  min-width: 92px; padding: 8px 11px;
  border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 12px;
  background: rgba(5, 8, 20, 0.7); backdrop-filter: blur(8px);
}
.hud-chip span { display: block; color: var(--muted); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }
.hud-chip strong { display: block; margin-top: 1px; font-size: 1.05rem; }

/* A chip carrying a fill bar (XP, charge meters). */
.hud-chip .meter { display: block; height: 5px; margin-top: 4px; border-radius: 8px; overflow: hidden; background: rgba(2, 5, 13, 0.85); }
.hud-chip .meter i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--cyan), var(--purple)); transition: width 120ms linear; }

/* Fill tracks: run progress, boss health. Plain inline bars by default;
   `.stage-rail` pins one to the bottom edge of the stage. */
.progress-track { height: 8px; overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 999px; background: rgba(5, 8, 20, 0.66); }
.progress-track.stage-rail { position: absolute; left: 14px; right: 14px; bottom: 14px; }
.progress-fill { display: block; height: 100%; width: 0; border-radius: inherit; background: linear-gradient(90deg, var(--cyan), var(--purple), var(--cyan)); transition: width 120ms linear; }

/* The one big centred shout: combo counts, wave banners, level-ups. */
.stage-callout {
  position: absolute; z-index: 8; left: 50%; top: 22%;
  transform: translateX(-50%) scale(0.9); opacity: 0;
  color: var(--yellow); font-size: clamp(1.3rem, 3vw, 2.4rem); font-weight: 950; letter-spacing: -0.03em;
  text-shadow: 0 3px 0 #000, 0 0 22px rgba(255, 226, 103, 0.55);
  transition: opacity 120ms ease, transform 120ms ease;
  pointer-events: none;
}
.stage-callout.show { opacity: 1; transform: translateX(-50%) scale(1); }

/* --- overlays ------------------------------------------------------------ */

.game-overlay {
  position: absolute; z-index: 12; inset: 0;
  display: grid; place-items: center;
  padding: 22px;
  overflow-y: auto; overscroll-behavior: contain;
  background: linear-gradient(rgba(3, 5, 14, 0.58), rgba(5, 7, 19, 0.86));
  backdrop-filter: blur(8px);
}
.overlay-card {
  width: min(760px, 100%); max-height: 100%; overflow-y: auto; scrollbar-gutter: stable;
  padding: clamp(18px, 3vw, 32px);
  border: 1px solid var(--line); border-radius: 22px;
  background: linear-gradient(145deg, rgba(16, 23, 43, 0.95), rgba(8, 13, 28, 0.96));
  box-shadow: 0 30px 100px rgba(0, 0, 0, 0.55);
  text-align: center;
}
.eyebrow { color: var(--accent); font-size: 0.74rem; font-weight: 900; letter-spacing: 0.15em; text-transform: uppercase; }
.overlay-card h1, .overlay-card h2 { margin: 10px 0 12px; line-height: 1; letter-spacing: -0.05em; }
.overlay-card h1 { font-size: clamp(2.7rem, 7vw, 5rem); }
.overlay-card h2 { font-size: clamp(2rem, 5vw, 3.4rem); }
.overlay-card p { color: var(--muted); line-height: 1.55; }
.gradient { color: transparent; background: linear-gradient(90deg, var(--cyan), var(--purple)); background-clip: text; }
.overlay-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 20px; }
.overlay-note { margin: 14px auto 0; max-width: 46rem; color: var(--muted); font-size: 0.84rem; }

.primary-button, .secondary-button {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 0 19px;
  border: 1px solid var(--line); border-radius: 999px;
  cursor: pointer; text-decoration: none; font-weight: 900;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform 120ms ease, background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
}
.primary-button { color: #061016; border-color: transparent; background: linear-gradient(90deg, var(--cyan), #b9f8ff); }
.secondary-button { background: rgba(255, 255, 255, 0.055); }
.secondary-button:hover { border-color: var(--cyan); }
.primary-button:focus-visible, .secondary-button:focus-visible { outline: 2px solid rgba(99, 230, 255, 0.4); outline-offset: 3px; }
.primary-button:active, .secondary-button:active { transform: scale(0.98); }
.primary-button:disabled, .secondary-button:disabled { opacity: 0.55; cursor: not-allowed; }

/* Level/arena pickers on the start and result cards. Any count of buttons
   flows into the same grid, so a game with 7 arenas and one with 10 levels
   read identically. */
.level-actions { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin-top: 20px; }
.level-actions .primary-button, .level-actions .secondary-button { width: 100%; min-width: 0; padding-inline: 10px; font-size: 0.86rem; }

/* Key legend on the start card. */
.controls-inline { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 20px; }
.control-key { padding: 10px 7px; border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 12px; background: rgba(255, 255, 255, 0.035); }
.control-key b { display: block; color: var(--cyan); }
.control-key span { display: block; color: var(--muted); font-size: 0.76rem; }

/* Result readouts. Column count follows the number of children. */
.run-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 8px; margin: 18px 0; }
.run-summary div { padding: 12px 8px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; background: rgba(255, 255, 255, 0.035); }
.run-summary span { display: block; color: var(--muted); font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; }
.run-summary strong { font-size: 1.15rem; }
.score-save-panel { display: grid; justify-items: center; gap: 8px; margin-top: 18px; }
.submit-status { min-height: 22px; margin-top: 8px; color: var(--muted); font-size: 0.84rem; }
.stage-select { display: grid; justify-items: center; gap: 6px; margin: 16px 0 0; }
.stage-select label { color: var(--muted); font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; }
.stage-select select { min-height: 44px; padding: 0 14px; border: 1px solid var(--line); border-radius: 12px; background: var(--panel-solid); color: var(--text); }

/* --- information rail ---------------------------------------------------- */

/* One band under the stage with internal sections, not a stack of floating
   cards and not a sidebar. Every title lays this out identically, so the
   information sits in the same place in the same order whichever game you are
   in; only the contents differ where the game genuinely differs.

   The 1px grid gap over a line-coloured background draws the dividers, which
   is what keeps them correct when the columns wrap onto a second row — a
   border on the cards themselves would double up mid-row and go missing at the
   wrap. */
.side-column {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 264px), 1fr));
  gap: 1px;
  margin-top: 14px;
  overflow: hidden;
  border: 1px solid var(--line); border-radius: 22px;
  background: var(--line);
  box-shadow: 0 18px 45px rgba(0, 0, 0, 0.32);
}
.side-card { padding: 18px; background: var(--panel-solid); }
.side-card h2 { margin: 5px 0 10px; font-size: 1.2rem; line-height: 1.1; }
.side-card p, .side-card li { color: var(--muted); font-size: 0.88rem; line-height: 1.5; }
.side-card ul, .side-card ol { margin: 10px 0 0; padding-left: 18px; }
.side-card li.active { color: var(--text); font-weight: 800; }
.side-link { display: inline-block; margin-top: 12px; color: var(--cyan); font-weight: 800; text-decoration: none; }
.leaderboard-status { color: var(--muted); font-size: 0.78rem; }

/* A labelled key/value row: companions, stats, social links. */
.stat-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
.stat-row:last-child { border-bottom: 0; }
.stat-row span, .stat-row a { color: var(--cyan); text-decoration: none; }
.stat-list { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.stat-list div { padding: 10px; border: 1px solid rgba(255, 255, 255, 0.09); border-radius: 11px; background: rgba(255, 255, 255, 0.03); }
.stat-list span { display: block; color: var(--muted); font-size: 0.72rem; }
.stat-list strong { font-size: 1rem; }

.side-card table { width: 100%; border-collapse: collapse; margin-top: 12px; }
.side-card th, .side-card td { padding: 9px 5px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); text-align: left; font-size: 0.86rem; }
.side-card th:last-child, .side-card td:last-child { text-align: right; }
.side-card th { color: var(--muted); font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; }
.player-cell { display: flex; flex-direction: column; gap: 2px; }
.player-name { font-weight: 700; }
.player-bio { display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: var(--muted); font-size: 0.72rem; line-height: 1.3; }

/* --- touch controls ------------------------------------------------------ */

/* Hidden until a run is actually in progress on every title, so the start card
   is never competing with a control dock. */
.touch-controls {
  display: none;
  position: absolute; z-index: 9;
  inset: auto max(10px, env(safe-area-inset-right)) max(14px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
  justify-content: space-between; align-items: end; gap: 12px;
  pointer-events: none;
  scale: var(--touch-scale); opacity: var(--touch-opacity); transform-origin: bottom center;
}
/* Two conditions, both required: the dock is for touch devices only, and only
   once a run is actually in progress. A desktop player never sees it, and a
   phone player never has it covering a start or result card. */
@media (pointer: coarse), (max-width: 760px) {
  html[data-game-state="playing"] .touch-controls,
  html[data-game-state="paused"] .touch-controls { display: flex; }
}
.touch-pad, .touch-cluster { display: flex; align-items: end; gap: 9px; pointer-events: auto; }
.touch-cluster { margin-left: auto; }
html[data-touch-handedness="left"] .touch-controls { flex-direction: row-reverse; }
html[data-touch-handedness="left"] .touch-cluster { margin-left: 0; margin-right: auto; flex-direction: row-reverse; }
html[data-touch-handedness="left"] .touch-pad { flex-direction: row-reverse; }

.touch-button {
  display: grid; place-items: center;
  width: 62px; height: 62px; min-width: 62px; min-height: 62px;
  border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 50%;
  background: rgba(5, 8, 20, 0.72);
  color: var(--text); font-size: 0.7rem; font-weight: 950; letter-spacing: 0.02em;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.32); backdrop-filter: blur(9px);
  cursor: pointer;
}
.touch-button.primary { border-color: rgba(99, 230, 255, 0.72); color: var(--cyan); }
.touch-button.accent { border-color: rgba(181, 109, 255, 0.65); color: var(--accent); }
.touch-button:active, .touch-button.active { transform: scale(0.92); background: rgba(99, 230, 255, 0.25); }

/* Touch surfaces are controls, never selectable text. */
.game-stage, .game-stage canvas, .touch-controls, .touch-controls * {
  -webkit-user-select: none !important; user-select: none !important;
  -webkit-touch-callout: none !important; -webkit-tap-highlight-color: transparent !important;
}
.touch-controls, .touch-controls button { touch-action: none !important; overscroll-behavior: contain; }
.touch-controls button { outline: none; -webkit-appearance: none; appearance: none; cursor: pointer; }
.touch-controls button::selection { background: transparent; color: inherit; }

/* --- responsive ---------------------------------------------------------- */

/* The band is auto-fitting, so it reflows from one row to two to a single
   column on its own; the old width-stepped column rules are gone with the
   sidebar they were compensating for. */
@media (max-width: 900px) {
  .level-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .controls-inline { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 760px) {
  .game-page { width: min(100% - 10px, 1480px); padding-top: 4px; }
  .game-topbar { min-height: 48px; padding: 4px 6px; }
  .game-topbar { gap: 8px; }
  /* The title is the one thing that must stay on one line: a wrapped brand
     pushes the action buttons into a stack and doubles the topbar height. */
  .game-brand { font-size: 0.98rem; min-width: 0; }
  .game-brand > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .game-brand img { width: 30px; height: 30px; }
  .game-brand small, .top-actions .text-button, .session-chip { display: none; }
  .top-actions { flex-wrap: nowrap; }
  .icon-button { padding: 0 10px; font-size: 0.82rem; }
  /* Below this width the stage sets its own height rather than holding 16:9,
     so the height-derived width cap has nothing to protect and must be lifted
     or it would narrow the stage on a short landscape phone. */
  .game-column { width: 100%; }
  .game-stage { aspect-ratio: auto; height: min(70vh, 610px); min-height: 400px; border-radius: 16px; }
  .hud-row { top: 8px; left: 8px; right: 8px; gap: 6px; }
  .hud-cluster { gap: 5px; }
  .hud-chip { min-width: 72px; padding: 5px 7px; }
  .hud-chip span { font-size: 0.58rem; }
  .hud-chip strong { font-size: 0.86rem; }
  .progress-track.stage-rail { bottom: 8px; }
  .side-column { grid-template-columns: 1fr; }
  .game-note { font-size: 0.78rem; }
}

/* --- Portrait phones -----------------------------------------------------
   Portrait used to get no layout of its own. It inherited the narrow-viewport
   rule above - a stage 70vh TALL - and then floated the control dock on top of
   it, so on a 375x812 phone the pads covered the bottom 256px of a 568px
   stage: 45% of the play area, sitting over a canvas whose aspect had been
   squeezed to 0.64 for games drawn wide. That is the "compressed and unusable
   unless you turn it sideways" report.

   The dock now gets a BAND of its own instead of overlapping. The stage keeps
   a bottom padding the height of that band, the canvas takes only the area
   above it at a real landscape-ish aspect, and the absolutely positioned dock
   sits in the padding. No DOM change, so every title inherits it, and nothing
   about the landscape or desktop layouts moves.

   `--dock-band` is the contract: a game whose pads are taller than the default
   raises it in its own stylesheet. */
@media (pointer: coarse) and (orientation: portrait) {
  .game-stage {
    --dock-band: 236px;
    height: auto;
    min-height: 0;
    aspect-ratio: auto;
    padding-bottom: var(--dock-band);
  }
  /* 3:2 keeps a side-scroller readable: wide enough to see what is coming,
     tall enough that the canvas is not a letterbox slit. */
  .game-stage canvas { width: 100%; height: auto; aspect-ratio: 3 / 2; }
  .touch-controls {
    inset: auto max(8px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));
    /* The band includes the bottom inset, so the dock's own box has to give it
       back - otherwise the dock starts that many pixels high and clips the
       canvas it was moved out of. */
    height: calc(var(--dock-band) - max(10px, env(safe-area-inset-bottom)));
    align-items: center;
    /* In its own band the dock is a control surface, not an overlay, so it
       stops being translucent and stops being shrunk to stay out of the way. */
    scale: 1; opacity: 1;
  }
  /* The HUD belongs to the canvas, not the control band. Start/pause/result
     cards deliberately still cover the whole stage - they are modal. */
  .hud { bottom: var(--dock-band); }
}

/* Phones scroll the start/result card rather than clipping it. */
@media (pointer: coarse) and (orientation: portrait) and (max-width: 860px) {
  .game-overlay { align-items: flex-start; justify-content: center; padding: 10px; -webkit-overflow-scrolling: touch; }
  .overlay-card { width: min(560px, 100%); max-height: calc(100svh - 14px); padding: 16px; }
  .overlay-card h1, .overlay-card h2 { margin-bottom: 10px; }
  .overlay-actions { width: 100%; }
  .level-actions { display: flex; flex-direction: column; gap: 10px; max-height: min(48vh, 300px); overflow-y: auto; padding-right: 4px; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
  .level-actions .primary-button, .level-actions .secondary-button { min-height: 50px; }
  .controls-inline { display: none; }
}

/* Landscape phones play full-bleed: the page furniture gets out of the way. */
@media (pointer: coarse) and (orientation: landscape) and (max-height: 620px) {
  html, body { overflow: hidden; }
  .game-page { width: 100%; max-width: none; padding: 0; }
  .game-topbar, .side-column, .game-note { display: none; }
  .game-layout { display: block; }
  .game-column { width: 100%; }
  .game-stage { width: 100vw; height: 100svh; min-height: 0; aspect-ratio: auto; border: 0; border-radius: 0; }
  .game-stage canvas { object-fit: contain; }
  .game-overlay { padding: 8px; }
  .overlay-card { max-height: calc(100svh - 16px); padding: 14px; }
  .overlay-card h1, .overlay-card h2 { font-size: clamp(1.8rem, 10vh, 3rem); }
  .controls-inline { display: none; }
  .hud-row { top: 6px; left: 6px; right: 6px; }
}

@media (max-width: 860px) and (pointer: coarse) {
  .side-link, .text-button, .side-card a, nav a { display: inline-flex; align-items: center; min-height: 44px; }
}
@media (pointer: coarse) { body { overscroll-behavior: none; } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
}

/* ===========================================================================
   Icons

   Symbols are real vector artwork from assets/infin8-symbols.svg rather than
   typed characters. A glyph like "▶" or "∞" renders in whatever font the
   device supplies, so it changed shape across platforms and could not be
   aligned or sized against the control it labels.

   These rules are required, not cosmetic: an <svg> with no intrinsic size
   lays out at 0x0, so a referenced symbol is invisible without them.
   =========================================================================== */
.icon {
  width: 1em;
  height: 1em;
  flex: none;
  fill: none;
  color: currentColor;
  vertical-align: -0.125em;
}

/* Touch dock glyphs are the control itself, so they fill the button rather
   than sitting inline with text. */
.touch-button .icon { width: 1.9em; height: 1.9em; vertical-align: middle; }

/* The brand mark keeps its own grid: two linked loops, wider than tall. */
.brand-sigil {
  width: 1.8em;
  height: 0.9em;
  flex: none;
  fill: none;
  color: currentColor;
  vertical-align: -0.05em;
}

.game-brand .sigil { display: inline-flex; align-items: center; }

/* ===========================================================================
   Pause

   Pause used to sit inside the main control cluster, one thumb-width from the
   buttons a player is hammering during a run - easy to hit by accident, and it
   stole space from the controls that actually need it. It belongs away from
   the thumbs entirely.

   Top-left of the stage: the top-right is where the music widget lives, and
   the bottom corners are both control clusters.
   =========================================================================== */
.touch-pause {
  display: none;
  position: absolute;
  z-index: 10;
  top: max(10px, env(safe-area-inset-top));
  left: max(10px, env(safe-area-inset-left));
  min-width: 0;
  padding: 8px 14px;
  font-size: 0.62rem;
  opacity: 0.85;
}

@media (pointer: coarse), (max-width: 760px) {
  html[data-game-state="playing"] .touch-pause,
  html[data-game-state="paused"] .touch-pause { display: inline-flex; }
}
