:root {
  --bg0: #05070d;
  --p1: #ff4c00;   /* orange-red fire wizard */
  --p2: #ffd23f;   /* yellow-gold fire wizard */
  --gold: #ffd23f;
  --ink: #e9eef5;
  /* Flat UI (task #69): the one solid that replaced the old two-tone mint->p1 button fill. Mint
     was that fill's TOP colour — dark #05070d ink on it reads ~13:1 (on the --p1 orange it would
     drop to ~4:1), so the readable colour won. */
  --btn-solid: #7dffb6;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
  width: 100%; height: 100%; overflow: hidden;
  background: var(--bg0); color: var(--ink);
  font-family: "Silkscreen", "Trebuchet MS", monospace, sans-serif;
  -webkit-user-select: none; user-select: none;
}
#game {
  display: block; position: fixed; inset: 0; touch-action: none;
  background: #0a0f1e;   /* flat (was a radial #11182e -> #05070d; only visible behind the canvas) */
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
/* The WebGL post-process output, displayed directly (js/glpost.js inserts it right after #game;
   js/render.js sets its letterbox geometry + show/hide). z-index:auto on purpose: DOM order keeps
   it above the #game backdrop yet below every z-indexed layer (HUD z5, touch z6, overlays z10).
   pointer-events:none so input and the pad cursor's elementFromPoint pass through to #game/UI. */
#glview {
  display: none; position: fixed; left: 0; top: 0; pointer-events: none; touch-action: none;
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.hidden { display: none !important; }

/* ---------- HUD ---------- */
#hud {
  position: fixed; top: 0; left: 0; right: 0; z-index: 5; pointer-events: none;
  display: flex; align-items: flex-start; justify-content: space-between; padding: 12px 22px;
}
.hud-side { display: flex; flex-direction: column; gap: 6px; }
.hud-side.right { align-items: flex-end; }
.wiz-name { font-size: 14px; letter-spacing: 2px; font-weight: 700; }
.hud-side.left .wiz-name { color: var(--p1); text-shadow: 0 0 14px var(--p1-glow); }
.hud-side.right .wiz-name { color: var(--p2); text-shadow: 0 0 14px var(--p2-glow); }
.hp { display: flex; gap: 5px; }
.hud-side.right .hp { flex-direction: row-reverse; }
.seg-hp { width: 46px; height: 16px; border-radius: 0; border: 2px solid rgba(255,255,255,.25); }
.left .seg-hp { background: var(--p1); box-shadow: 0 0 10px var(--p1-glow); }
.right .seg-hp { background: var(--p2); box-shadow: 0 0 10px var(--p2-glow); }
.seg-hp.lost { background: #1a2238; box-shadow: none; }
.hud-mid { text-align: center; color: #8fa3c4; }
.stage-name { font-size: 13px; letter-spacing: 3px; text-transform: uppercase; }
.match-timer {
  font-size: 24px;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.4);
  margin-top: 4px;
  letter-spacing: 1px;
}
.coinline { font-size: 12px; margin-top: 3px; }
.coinline .coin { color: var(--gold); }
.coinline b { color: var(--ink); }
.intensity { width: 160px; height: 4px; margin: 6px auto 0; border-radius: 3px; background: rgba(255,255,255,.08); overflow: hidden; }
.intensity::after { content: ""; display: block; height: 100%; width: var(--lvl, 0%);
  background: var(--gold); transition: width .3s; }

/* ---------- Overlays ---------- */
.overlay {
  position: fixed; inset: 0; z-index: 10;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 16px; padding: 22px;
  background: rgba(5,7,14,.92);   /* flat (was a radial vignette); the backdrop blur keeps contrast */
  backdrop-filter: blur(3px); animation: fade .25s ease; overflow: auto;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.title { font-size: clamp(46px, 11vw, 128px); font-weight: 900; letter-spacing: 6px; line-height: .9;
  text-shadow: 0 0 24px rgba(57,255,136,.45), 0 0 60px rgba(57,255,136,.2); }
.title span { color: var(--p2); text-shadow: 0 0 24px rgba(176,108,255,.6), 0 0 60px rgba(176,108,255,.3); }
.tag { max-width: 560px; color: #9fb1cf; font-size: clamp(14px, 2.4vw, 18px); }
.hint { color: #6c7e9e; font-size: 13px; letter-spacing: 1px; max-width: 660px; }
.setup-title, .winner { font-size: clamp(26px, 6vw, 52px); font-weight: 900; letter-spacing: 4px; }
.winner.p1 { color: var(--p1); text-shadow: 0 0 16px var(--p1-glow); } .winner.p2 { color: var(--p2); text-shadow: 0 0 16px var(--p2-glow); }
.winner.draw { color: #8fa3c4; text-shadow: 0 0 16px rgba(143, 163, 196, 0.5); }
.finalscore { font-size: 18px; color: #aab8d4; letter-spacing: 1px; }

/* ---------- Results screen (task #100) ---------- */
/* One column per player on the over screen, in the player-panel idiom (flat box, --pc accent):
   name, the skin+colour idle preview (drawn by main.js via previewIdleFrame, like the setup
   panels'), then the per-wizard GOALS / KICKS counters. The winning team's columns carry the gold
   .win ring. index.html's compact (max-height:500px) tier flattens these for phone landscape. */
#results { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; max-width: 920px; }
.result-col {
  width: 132px; padding: 10px 8px; display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: rgba(255,255,255,.04); border: 2px solid rgba(255,255,255,.15); border-radius: 0;
}
.result-col { border-color: var(--pc, rgba(255,255,255,.15)); }
.result-col.win { box-shadow: 0 0 0 2px var(--gold), 0 0 22px -6px var(--gold); }
.result-col.win::after { content: "WINNER"; font-size: 9px; letter-spacing: 2px; color: var(--gold); }
.result-name { font-size: 13px; font-weight: 800; letter-spacing: 1px; color: var(--pc, var(--ink));
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.result-prev {
  width: 64px; height: 52px;
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.result-stat { display: flex; justify-content: space-between; align-items: baseline; width: 100%;
  font-size: 11px; letter-spacing: 1px; color: #9fb1cf; }
.result-stat b { font-size: 15px; color: var(--ink); }

.btn {
  padding: 14px 40px; font-size: 18px; font-weight: 800; letter-spacing: 2px;
  color: #05070d; background: var(--btn-solid);
  border: 3px solid #000; border-radius: 0; cursor: pointer; box-shadow: 0 6px 0 #000;
  transition: transform .12s, box-shadow .2s;
}
.btn:active {
  transform: translateY(4px);
  box-shadow: 0 2px 0 #000;
}
.btn:hover { transform: translateY(-2px); }
.btn.ghost { background: rgba(255,255,255,.06); color: var(--ink); border: 1px solid rgba(255,255,255,.16); box-shadow: none; }

/* ---------- Setup ---------- */
.setup-row { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%; max-width: 920px; }
.setup-row.champs { flex-direction: row; justify-content: center; gap: 40px; flex-wrap: wrap; }
.lbl { font-size: 12px; letter-spacing: 3px; color: #8fa3c4; }
.lbl.p1 { color: var(--p1); } .lbl.p2 { color: var(--p2); }
.seg { display: inline-flex; background: rgba(255,255,255,.05); border: 2px solid rgba(255,255,255,.15); border-radius: 0; padding: 4px; gap: 4px; }
.seg-btn { padding: 8px 20px; border: none; background: transparent; color: #aab8d4; font: inherit; font-weight: 700;
  letter-spacing: 1px; border-radius: 0; cursor: pointer; }
.seg-btn small { font-size: 9px; opacity: .7; display: block; }
.seg-btn.active { background: var(--btn-solid); color: #05070d; border: 2px solid #000; }
.card-row { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.champ-col { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.card {
  width: 132px; padding: 10px; border-radius: 0; cursor: pointer; text-align: left;
  background: rgba(255,255,255,.04); border: 2px solid rgba(255,255,255,.15); transition: border-color .15s, transform .1s;
}
.card:hover { transform: translateY(-2px); }
.card.sel { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold); }
.card .cn { font-size: 14px; font-weight: 800; letter-spacing: 1px; }
.card .cd { font-size: 11px; color: #9fb1cf; margin-top: 3px; line-height: 1.3; min-height: 44px; }
.card .ct { font-size: 10px; color: #6c7e9e; margin-top: 4px; }
/* Stage cards (task #77: bigger rectangles, readable names — sized for the 14-stage roster).
   At 160px wide, five cards fill a 920px row, so 14 stages wrap into three tidy rows (5/5/4).
   The per-card blurb stays hidden — name + gimmick tag identify a card; the full description shows
   on the shared #stage-desc line below (and as each card's hover tooltip).
   Task #92: every card is the SAME fixed box. Height used to be content-driven, so a two-line name
   ("The Undercroft", "Resonance Vault") stretched its whole flex row to ~72px while a row of
   one-line names sat at ~57px — visibly mismatched rectangles. The explicit height + pinned
   line-heights make the box identical for every name AND for the Silkscreen/fallback font swap;
   names wrap inside a clipped two-line slot, the gimmick tag is pinned to the card floor, and
   selection stays a border/glow (.card.sel) — never a size change.
   Titles MUST stay inside the card: the longest single Silkscreen word (WIDDERSHINS, 11 glyphs)
   needs ~123px at 12px, and the 160px card's content box is 136px — one line with room to spare.
   The break-word + clip pair stays as the last resort against a future longer name. */
.stage-card { width: 160px; height: 72px; overflow: hidden; display: flex; flex-direction: column; }
.stage-card .cn { font-size: 12px; letter-spacing: 0; overflow-wrap: break-word; word-break: break-word;
  line-height: 15px; max-height: 30px; overflow: hidden; }
.stage-card .cd { display: none; }
.stage-card .ct { font-size: 11px; line-height: 14px; margin-top: auto; }
.stage-desc { font-size: 14px; color: #aab8d4; letter-spacing: .5px; line-height: 1.35;
  min-height: 19px; max-width: 880px; }
.setup-actions { display: flex; gap: 16px; margin-top: 6px; }
/* Task #94: the setup screen's main actions row. As a shrink-wrap flex row each button sized to
   its own label, so START — the primary action with the SHORTEST label — rendered ~40-60px
   narrower than its ghost siblings (identical .btn padding/font; heights already matched via flex
   stretch). Equal-width grid tracks size every button to the widest label (font-proof, unlike a
   hard min-width), so START is always at least as large as TUTORIAL / ⚙ SETTINGS. Direct-child
   only: the MODE toggle and the controls-card rows keep their natural shrink-wrap widths.
   display:none'd buttons (BACK; START + TUTORIAL in online mode) create no track at all. */
#screen-setup > .setup-actions { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }

/* ---------- Stage select screen (#screen-stage; cards built once in js/main.js) ---------- */
/* The cards wrap into rows of five inside the 920px column (room for the 14-stage roster);
   the canvas is a 0.5x-DPI minimap of the 960x420 world. */
#screen-stage #stage-list { max-width: 920px; }
#stage-preview { width: 288px; height: 126px; background: #0a0f1e; border: 2px solid rgba(255,255,255,.15); }

/* ---------- Player panels (Smash-style select) ---------- */
#player-panels { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
.panel {
  width: 150px; padding: 12px 10px; display: flex; flex-direction: column; align-items: center; gap: 9px;
  background: rgba(255,255,255,.04); border: 2px solid rgba(255,255,255,.15); border-radius: 0;
  transition: border-color .15s, box-shadow .15s, opacity .15s;
}
.panel.on { border-color: var(--pc); box-shadow: 0 0 0 2px var(--pc), 0 0 22px -6px var(--pc); }
.panel.off { opacity: .5; }
.panel-head { font-size: 20px; font-weight: 900; letter-spacing: 2px; color: #6c7e9e; }
/* Task #93: the character preview canvas (drawn by main.js from the real sprite atlas, team-
   tinted to the panel colour). The canvas backing store is its CSS size and the art is blitted at
   an integer scale with smoothing off, so the pixelated hint only has to cover the fractional
   DPR remainder — crisp nearest-neighbour pixels everywhere. OFF panels keep the canvas (the four
   panels must stay one shape) but wash it out on top of .panel.off's existing opacity. */
.panel-preview {
  width: 96px; height: 78px;
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.panel.off .panel-preview { opacity: .55; filter: grayscale(.8); }
/* The compact-tier stand-in for the tall preview (task #108): a 26x34 canvas painted by the same
   drawSkinPreview/previewIdleFrame path at a clean 1:1 (the idle frame cell is 24x33), shown ONLY
   in the phone-landscape tier where index.html hides .panel-preview — it sits inline in the slim
   panel row right after the P-number, so the skin+colour read survives on phones. */
.panel-portrait {
  display: none; width: 26px; height: 34px; flex: none;
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.panel.off .panel-portrait { opacity: .55; filter: grayscale(.8); }
/* Mid-height desktop tier: between the phone-landscape compact layout (<=500px, which hides the
   preview entirely — see index.html) and a roomy monitor, the preview-taller panels would push the
   actions row into the fixed #cheats strip (measured: 37px of overlap at 800px height, 100px at
   720px — ~13px of it pre-existing at 720). Halve the preview (the 96x78 backing store CSS-scaled
   by exactly 2/3, so every 3x art pixel lands on a clean 2x2 CSS block under the pixelated hint)
   and trim the screen's tallest space-eaters. Height-gated like the compact tier, never pointer-
   gated, so a desktop window and a tablet of the same size lay out identically. */
@media (min-height: 501px) and (max-height: 899px) {
  .panel-preview { width: 64px; height: 52px; }
  #screen-setup.overlay { gap: 8px; }
  #screen-setup .setup-title { font-size: 24px; }
  .panel { padding: 9px 10px; gap: 7px; }
  #screen-setup .panel-ctl { padding: 7px 6px; }
}
/* The last sliver above the compact gate (a ~740x520 window): even the halved preview tips the
   column past the viewport, so it bows out the same way the compact tier drops it. */
@media (min-height: 501px) and (max-height: 560px) {
  .panel-preview { display: none; }
}
.panel-ctl {
  width: 100%; padding: 9px 6px; font: inherit; font-weight: 800; letter-spacing: 1px; font-size: 13px;
  color: var(--ink); background: rgba(255,255,255,.06); border: 2px solid rgba(255,255,255,.16);
  border-radius: 0; cursor: pointer; transition: transform .1s, border-color .15s, background .15s;
}
.panel-ctl:hover:not(:disabled) { transform: translateY(-1px); }
.panel-ctl:disabled { opacity: .4; cursor: default; }
.panel-type.t-off { color: #7e8aa6; }
.panel-type.t-player { background: var(--btn-solid); color: #05070d; border-color: #000; }
.panel-type.t-cpu { background: rgba(240,180,41,.16); color: var(--gold); border-color: rgba(240,180,41,.55); }
.panel-team { font-size: 12px; }
.panel-color { text-transform: capitalize; }
/* Per-panel input-device line ("⌨ WASD" / "🎮 PAD 2" / "📱 TOUCH"; ⚠ = that pad is unplugged). */
.panel-dev { font-size: 10px; letter-spacing: 1px; color: #8fa3c4; min-height: 13px; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.panel-ctl.focused { outline: 3px solid var(--gold); outline-offset: 2px; }
.setup-hint { color: #8fa3c4; font-size: 12px; letter-spacing: 1px; min-height: 16px; }
.btn.disabled { opacity: .4; pointer-events: none; filter: grayscale(.6); }

kbd { display: inline-block; min-width: 18px; padding: 1px 5px; margin: 0 1px; font: inherit; font-size: 10px;
  color: var(--ink); background: #1b2238; border: 1px solid #2c3654; border-bottom-width: 2px; border-radius: 0; }

/* ---------- Cheats strip ---------- */
#cheats { position: fixed; bottom: 12px; left: 0; right: 0; z-index: 11; display: flex; gap: 28px;
  justify-content: center; font-size: 12px; color: #9fb1cf; pointer-events: none; flex-wrap: wrap; }
.cheat b { letter-spacing: 1px; }
.cheat.p1 b { color: var(--p1); } .cheat.p2 b { color: var(--p2); }

/* ---------- Gamepad Focus indicators ---------- */
.btn.focused, .btn:focus-visible {
  outline: 3px solid var(--gold);
  box-shadow: 0 0 20px var(--gold);
  transform: translateY(-2px);
}
.card.focused {
  outline: 3px solid var(--gold);
  box-shadow: 0 0 20px var(--gold);
  transform: translateY(-2px);
}
.seg-btn.focused {
  outline: 3px solid var(--gold);
  box-shadow: 0 0 10px var(--gold);
}

/* ---------- Melee-style pad cursor (js/padcursor.js) ---------- */
/* z-index 150: above every menu surface it must click (.overlay z10, #cheats z11, #ol-hud z40,
   #fps-hud z41, the z60 net banners) and below the portrait rotate prompt (z200) and the in-page
   test overlay. pointer-events:none so elementFromPoint can never return the cursor itself. */
#pad-cursor {
  position: fixed; left: 0; top: 0; width: 24px; height: 24px;
  z-index: 150; pointer-events: none; will-change: transform;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,.65));
}

/* --- Control-remap overlay (per-player key/button binding) --- */
.panel-controls { font-size: 11px; letter-spacing: .5px; }
.ctl-card {
  background: rgba(12,16,28,.96); border: 2px solid #3a3658; border-radius: 14px;
  padding: 22px 26px; width: min(92vw, 470px);
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  box-shadow: 0 18px 60px rgba(0,0,0,.6);
}
.ctl-card .setup-title { font-size: clamp(20px, 4vw, 30px); }
.ctl-devs { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.ctl-dev {
  padding: 8px 14px; font-size: 13px; font-weight: 700; letter-spacing: .5px; font-family: inherit;
  background: rgba(255,255,255,.05); color: #aeb9d4; border: 1px solid rgba(255,255,255,.16);
  border-radius: 8px; cursor: pointer;
}
.ctl-dev:hover { background: rgba(255,255,255,.1); }
.ctl-dev.on { background: var(--gold); color: #05070d; border-color: var(--gold); }
.ctl-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 18px; width: 100%; }
.ctl-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ctl-act { color: #9fb1cf; font-size: 13px; letter-spacing: .5px; }
.ctl-bind {
  min-width: 80px; padding: 6px 10px; font-size: 13px; font-weight: 700; font-family: inherit;
  background: #10131f; color: #fff; border: 1px solid #5b5580; border-radius: 7px; cursor: pointer;
}
.ctl-bind:hover { border-color: var(--gold); }
.ctl-bind.capturing { background: var(--gold); color: #05070d; border-color: var(--gold); animation: ctlpulse .7s infinite; }
.ctl-bind.dupe { border-color: #ff7a4a; color: #ffb38a; }
.ctl-bind.ctl-dim { opacity: .45; }
@keyframes ctlpulse { 50% { opacity: .5; } }
.ctl-warn { color: #ff9a4d; }
.ctl-tap { display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;
  width: 100%; padding-top: 12px; border-top: 1px solid rgba(255,255,255,.1); }
.ctl-toggle { padding: 7px 14px; font-size: 12px; font-weight: 800; letter-spacing: .5px; font-family: inherit;
  cursor: pointer; border-radius: 8px; background: rgba(255,255,255,.05); color: #aeb9d4; border: 1px solid rgba(255,255,255,.18); }
.ctl-toggle:hover { background: rgba(255,255,255,.1); }
.ctl-toggle.on { background: var(--btn-solid); color: #05070d; border-color: #000; }
/* Touch-profile rows in the remap overlay (js/controls.js): on a 'touch' device the bind grid is
   meaningless, so the card shows these instead — BUTTON SIZE / OPACITY range sliders (live, fed to
   the #touch CSS vars above) and the STICK SIDE toggle. Reuses the settings overlay's .set-row
   shape; the value chip keeps a fixed slot so the slider doesn't shift while dragging. */
.ctl-touch { display: flex; flex-direction: column; gap: 9px; width: 100%;
  padding-top: 12px; border-top: 1px solid rgba(255,255,255,.1); }
.ctl-range { flex: 1 1 auto; min-width: 80px; max-width: 200px; accent-color: var(--gold); }
.ctl-val { font-size: 12px; color: var(--ink); min-width: 44px; text-align: right; }

/* --- Settings overlay (js/settings.js; reuses the .ctl-card / .ctl-toggle remap-overlay look) --- */
.set-rows { display: flex; flex-direction: column; gap: 9px; width: 100%; }
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.set-lbl { color: #9fb1cf; font-size: 13px; letter-spacing: 1px; }
.set-row .ctl-toggle { min-width: 84px; }

/* FPS/debug readout — same chrome as the netcode #ol-hud (js/net/online.js), opposite corner so
   they can coexist online. pointer-events:none keeps it clear of the touch buttons it overlaps. */
#fps-hud {
  position: fixed; right: 10px; bottom: 10px; z-index: 41; pointer-events: none;
  font-family: 'Consolas', 'Courier New', monospace; font-size: 11px; line-height: 1.55;
  color: #9fb3d0; background: rgba(10,14,26,.68); border: 1px solid #2a2640; border-radius: 8px;
  padding: 7px 10px;
}

/* ---------- Mobile touch controls (js/touch.js) ---------- */
#touch {
  position: fixed; inset: 0; z-index: 6; display: none; touch-action: none;
  pointer-events: none;                 /* only the stick zone + buttons capture pointers */
}
#touch.on { display: block; }

/* floating thumbstick — spawns wherever a touch lands in the left zone */
.touch-zone.stick {
  position: absolute; top: 0; bottom: 0; left: 0; width: 46%;
  pointer-events: auto; touch-action: none;
}
#touch-stick {
  position: absolute; width: 128px; height: 128px; margin: -64px 0 0 -64px;
  border-radius: 50%; display: none; pointer-events: none;
  border: 2px solid rgba(255,210,63,.5);
  background: rgba(255,210,63,.06);
  box-shadow: 0 0 24px rgba(255,210,63,.18), inset 0 0 18px rgba(255,210,63,.08);
}
#touch-stick.on { display: block; }
#touch-nub {
  position: absolute; left: 50%; top: 50%; width: 58px; height: 58px; margin: -29px 0 0 -29px;
  border-radius: 50%; will-change: transform;
  background: var(--gold);   /* flat (was a radial sphere shade); the box-shadow keeps the depth read */
  box-shadow: 0 0 16px rgba(255,210,63,.6), 0 2px 6px rgba(0,0,0,.45);
}

/* action-button cluster — bottom-right, clear of notches/home indicator.
   --touch-scale / --touch-alpha are the player's BUTTON SIZE / OPACITY settings (set on #touch by
   js/touch.js applyTouchSettings, persisted under hexball_touch). Scaling the whole cluster from
   its anchored corner keeps every button's relative offset AND its hit target in sync; the button
   handlers only read pointerIds, never coordinates, so a transform can't break input. */
.touch-buttons {
  position: absolute; right: 0; bottom: 0; width: 300px; height: 270px; pointer-events: none;
  margin-right: env(safe-area-inset-right, 0); margin-bottom: env(safe-area-inset-bottom, 0);
  transform: scale(var(--touch-scale, 1)); transform-origin: 100% 100%;
  opacity: var(--touch-alpha, 1);
}
.touch-btn {
  position: absolute; pointer-events: auto; touch-action: none;
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-family: inherit; font-weight: 700; font-size: 12px; letter-spacing: .5px;
  color: #fff; background: rgba(10,14,26,.45); border: 2px solid rgba(255,255,255,.5);
  -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
  transition: transform .05s ease, background .05s, box-shadow .05s;
}
.touch-btn.press { transform: scale(.9); }
.b-jump  { width: 84px; height: 84px; right: 22px;  bottom: 26px;  border-color: #7dffb6;   color: #c8ffe4; box-shadow: 0 0 18px rgba(125,255,182,.25); }
.b-dash  { width: 78px; height: 78px; right: 116px; bottom: 58px;  border-color: var(--gold);color: #ffe9a3; box-shadow: 0 0 18px rgba(255,210,63,.25); }
.b-kick  { width: 78px; height: 78px; right: 36px;  bottom: 126px; border-color: var(--p1);  color: #ffb59a; box-shadow: 0 0 18px rgba(255,76,0,.28); }
.b-hex   { width: 64px; height: 64px; right: 140px; bottom: 158px; border-color: #b06cff;    color: #dcc2ff; box-shadow: 0 0 16px rgba(176,108,255,.28); }
.b-jump.press  { background: rgba(125,255,182,.30); box-shadow: 0 0 24px rgba(125,255,182,.55); }
.b-dash.press  { background: rgba(255,210,63,.30);  box-shadow: 0 0 24px rgba(255,210,63,.55); }
.b-kick.press  { background: rgba(255,76,0,.34);    box-shadow: 0 0 26px rgba(255,76,0,.6); }
.b-hex.press   { background: rgba(176,108,255,.32); box-shadow: 0 0 24px rgba(176,108,255,.55); }

/* STICK SIDE swap (js/touch.js settings, persisted): the floating-stick zone moves to the RIGHT
   half and the button cluster mirrors to the bottom-LEFT — each button keeps its size and its
   distance from the (now left) anchor corner, so the cluster shape is a clean mirror image. The
   stick's opacity follows the buttons' setting; the stick zone itself stays invisible. */
#touch-stick { opacity: var(--touch-alpha, 1); }
#touch.swap .touch-zone.stick { left: auto; right: 0; }
#touch.swap .touch-buttons {
  right: auto; left: 0; transform-origin: 0 100%;
  margin-right: 0; margin-left: env(safe-area-inset-left, 0);
}
#touch.swap .b-jump { right: auto; left: 22px; }
#touch.swap .b-dash { right: auto; left: 116px; }
#touch.swap .b-kick { right: auto; left: 36px; }
#touch.swap .b-hex  { right: auto; left: 140px; }

/* ---------- M3: touch pause button, portrait prompt, touch-scoped UI ---------- */
/* Pause button — top-center of a touch match, opens the existing pause screen. */
/* Top-right edge, below the HP bars — dead-center is owned by the timer, the left 46% is the stick zone. */
#touch-pause {
  position: absolute; top: calc(56px + env(safe-area-inset-top, 0)); right: calc(10px + env(safe-area-inset-right, 0));
  width: 50px; height: 30px; pointer-events: auto; touch-action: none;
  display: flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: 12px; letter-spacing: 2px; color: #cdd8ee;
  background: rgba(10,14,26,.55); border: 1px solid rgba(255,255,255,.28); border-radius: 7px;
  -webkit-tap-highlight-color: transparent; -webkit-user-select: none; user-select: none;
}
#touch-pause.press { background: rgba(255,255,255,.16); }

/* Move the netcode HUD off the bottom-left stick zone on touch (id selector in online.js; this wins on specificity). */
body.touch-on #ol-hud {
  left: 50%; right: auto; bottom: auto; top: calc(58px + env(safe-area-inset-top, 0));
  transform: translateX(-50%); text-align: center;
}

/* Portrait "rotate your device" prompt — only on a touch device held in portrait. */
#rotate-prompt { display: none; }
@media (orientation: portrait) {
  body.touch-on #rotate-prompt {
    display: flex; align-items: center; justify-content: center;
    position: fixed; inset: 0; z-index: 200; text-align: center; padding: 24px;
    background: #0b1022;
  }
  /* hide the gameplay controls under the prompt so stray touches don't leak through */
  body.touch-on #touch { display: none !important; }
}
.rot-icon { font-size: 60px; color: var(--gold); display: inline-block; animation: rotnudge 1.9s ease-in-out infinite; }
.rot-title { margin-top: 16px; font-size: 18px; letter-spacing: 3px; color: var(--ink); }
.rot-sub { margin-top: 8px; font-size: 13px; letter-spacing: 1px; color: #9fb1cf; }
@keyframes rotnudge { 0%, 100% { transform: rotate(-12deg); } 50% { transform: rotate(78deg); } }

/* Bigger tap targets across the menus on any touch device (does not affect desktop/mouse). */
@media (pointer: coarse) {
  .btn { min-height: 48px; }
  .ol-input, .ol-name-input, #ol-code-input { min-height: 46px; }
  .panel-ctl, .seg-btn, .ctl-bind, .ctl-dev, .ctl-toggle, .card { min-height: 44px; }
}

/* On touch the keyboard cheat-sheet is irrelevant — hide it (as online mode already does). */
body.touch-on #cheats { display: none; }

/* Short landscape phones: top-align + scroll the menus and shrink the big headings so nothing clips. */
@media (pointer: coarse) and (max-height: 480px) {
  .overlay { justify-content: flex-start; gap: 10px; padding: 14px 16px; }
  .setup-title { font-size: 24px; margin-bottom: 0; }
  .title { font-size: clamp(32px, 9vw, 60px); }
  .tag, .hint, .setup-hint { font-size: 12px; }
}

/* Short (phone-landscape) viewports — partner block to index.html's compact rules (same max-height
   gate). The stage screen used to keep ONE card row that scrolled inside its own box (5 of 14
   stages visible at 640px); now NOTHING scrolls (tests/e2e/touch-fit.spec.js + stages.spec.js):
   the 14 cards wrap into a full-width 7x2 grid of slim name-only cards (names truncate with an
   ellipsis — each card keeps its full description as the hover tooltip and the selected card's
   blurb shows on the shared #stage-desc line), and the minimap preview drops to 240x105 so cards +
   blurb + preview + actions all fit the 640x300 gate with zero scrolling. Height budget at 300px:
   8+8 overlay padding, 2x32+4 cards, 3x8 gaps, ~28 two-line blurb, 105 preview, 40 actions = 281. */
@media (max-height: 500px) {
  /* The heading is the screen's height driver at 300px — the cards say what this screen is. */
  #screen-stage .setup-title { display: none; }
  #screen-stage #stage-list {
    display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 4px; width: 100%; max-width: 640px;
  }
  /* Task #92 (uniform boxes) holds by construction: every card is a one-line name slot centered
     in the same min-height box; the gimmick tag goes (the blurb line carries the description). */
  #screen-stage #stage-list .stage-card {
    width: auto; height: auto; min-height: 32px; padding: 4px 5px;
    display: flex; flex-direction: column; justify-content: center;
  }
  #screen-stage #stage-list .stage-card .cn {
    font-size: 9px; line-height: 11px; max-height: none;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #screen-stage #stage-list .stage-card .ct { display: none; }
  /* One notch down from desktop's 14px; long blurbs wrap to two lines inside the height budget. */
  #screen-stage .stage-desc { font-size: 11px; min-height: 14px; }
  #screen-stage #stage-preview { width: 240px; height: 105px; }
  #screen-stage .setup-actions { margin-top: 0; }
}

/* ---------- Tutorial overlay (js/tutorial.js, task #84) ---------- */
/* Instruction card pinned top-center over the practice match. The ROOT never eats input — the
   match stays fully playable beneath it; only the QUIT button is clickable. z-index 7: above the
   match HUD (z5) and the touch controls (z6, so the card text wins where the layers would meet),
   below the pause/menu overlays (z10), which therefore dim it correctly while they are open. The
   card hugs the top of the screen and the wizard plays on the floor, so it never covers the player. */
#tutorial { position: fixed; inset: 0; z-index: 7; pointer-events: none; }
#tut-card {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  width: min(78vw, 560px);
  display: flex; flex-direction: column; gap: 7px;
  padding: 10px 14px; text-align: left;
  background: rgba(8, 11, 22, .82); border: 1px solid rgba(255,255,255,.16); border-radius: 10px;
}
#tut-head { display: flex; align-items: center; gap: 10px; }
#tut-title {
  flex: 1 1 auto; font-size: 17px; font-weight: 700; letter-spacing: 3px;
  color: var(--gold); text-shadow: 0 0 12px rgba(255,210,63,.35);
}
#tut-step { font-size: 10px; letter-spacing: 2px; color: #6c7e9e; white-space: nowrap; }
#tut-quit {
  pointer-events: auto; font-family: inherit; font-size: 11px; letter-spacing: 1px;
  color: #cdd8ee; background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.22);
  border-radius: 7px; padding: 5px 10px; min-height: 28px; cursor: pointer; white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}
#tut-quit:hover { background: rgba(255,255,255,.14); }
#tut-text { font-size: 12px; line-height: 1.5; color: #9fb1cf; letter-spacing: .5px; }
#tut-goals { display: flex; flex-direction: column; gap: 4px; }
.tut-goal { display: flex; align-items: center; gap: 9px; font-size: 12px; letter-spacing: 1px; color: #cdd8ee; }
.tut-check {
  flex: none; width: 16px; height: 16px; border: 1px solid rgba(255,255,255,.3); border-radius: 4px;
  font-size: 11px; line-height: 14px; text-align: center; color: transparent;
}
.tut-goal.done { color: #7dffb6; }
.tut-goal.done .tut-check { color: #7dffb6; border-color: #7dffb6; box-shadow: 0 0 8px rgba(125,255,182,.35); }
.tut-what { flex: none; }
.tut-key { color: var(--gold); font-size: 11px; }
#tutorial.complete #tut-title { color: #7dffb6; text-shadow: 0 0 12px rgba(125,255,182,.4); }

/* The online lobby hides the LOCAL action buttons by tagging #btn-fight with ol-hidden
   (js/net/online.js setMode); TUTORIAL is local-only too, so it simply follows its sibling.
   index.html keeps #btn-tutorial right after #btn-fight for exactly this rule. */
#btn-fight.ol-hidden ~ #btn-tutorial { display: none; }

/* Phone-landscape compact fit (the index.html zero-scroll gate, height-only on purpose): trim the
   card so it clears the top-right touch pause button and never crowds the small training yard. */
@media (max-height: 500px) {
  #tut-card { top: 6px; width: min(72vw, 500px); padding: 6px 10px; gap: 4px; }
  #tut-title { font-size: 13px; letter-spacing: 2px; }
  #tut-text { font-size: 10px; line-height: 1.35; }
  .tut-goal { font-size: 10px; gap: 7px; }
  .tut-check { width: 13px; height: 13px; font-size: 9px; line-height: 11px; }
  .tut-key { font-size: 9px; }
  #tut-quit { min-height: 24px; padding: 3px 8px; font-size: 10px; }
}
