/* Palette sampled directly from the physical card backs (leather-bound
   book cover: deep brown, darker brown spine, bright antique gold trim
   and lettering, a muted tan for the dashed stitching border) — see
   GAME_DATA.md's "UI color scheme — matches the card backs" section.
   --bg/--panel are deepened well past the card's actual brown (#603a22)
   on purpose: that brown is gorgeous as a single small card face, but
   used as a full-screen background behind a game board and paragraphs of
   text for a whole play session it fights with --text for attention and
   gets fatiguing fast. Darkening it while keeping the same warm hue (and
   using the literal sampled gold/tan for every accent) keeps the "leather
   book" identity without the readability cost. --south/--north/--forest
   are gameplay-functional (team-side and terrain colors on the board
   canvas), not part of this "book" palette, and are deliberately left as
   they were — GAME_DATA.md again, same section, for why. */
:root {
  --bg: #17100a;
  --panel: #2a1a10;
  --panel-alt: #341f13;
  --panel-border: #5a3a22;
  --text: #f1e6d3;
  --text-dim: #b89a72;
  --accent: #f0c446;
  --accent-dim: #cea870;
  --south: #c1553d;
  --north: #3d7ec1;
  --forest: #16321c;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  /* A faint warm vignette instead of a flat fill — echoes the cover's
     depth/shadowing at its edges without any image asset. */
  background: radial-gradient(ellipse at 50% 0%, #241608 0%, var(--bg) 60%);
  background-color: var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: 'Trebuchet MS', 'Segoe UI', sans-serif;
  height: 100%;
}

#app {
  display: flex;
  flex-direction: column;
  /* calc(var(--vh-unit) * 100), not plain 100vh -- see .card's max-height
     comment further down for why (the zoom-based Text Size setting). This
     one is a min-height, so at plain 100vh it wouldn't clip/hide anything
     under zoom (the page would just need an extra bit of ordinary page
     scroll), but keeping it consistent with the other two fixed spots
     costs nothing and avoids that minor inconvenience too. */
  min-height: calc(var(--vh-unit, 1vh) * 100);
}

header {
  padding: 10px 16px;
  background: var(--panel);
  border-bottom: 2px solid var(--panel-border);
  display: flex;
  align-items: baseline;
  gap: 12px;
}

header h1 {
  font-size: 18px;
  margin: 0;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  font-weight: 800;
}
header .subtitle { font-size: 12px; color: var(--text-dim); }

.sound-controls {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  /* The header is a plain static-flow element, but #overlay (every menu
     screen: Main Menu, Shop, Collection, Team, the auth gate — z-index 20),
     #welcome-cta (22), and #tutorial-banner (26) are all `position: fixed`
     and paint above it regardless of DOM order. Without its own stacking
     context above all three, this control would only ever be clickable
     during live match play (the one time #overlay is hidden) — exactly
     backwards, since muting before/at the very start is a common case too. */
  position: relative;
  z-index: 40;
}
.sound-controls button {
  background: transparent;
  border: 1px solid var(--panel-border);
  color: var(--text);
  font-weight: normal;
  padding: 3px 7px;
  font-size: 14px;
  border-radius: 6px;
}
.sound-controls input[type="range"] {
  width: 70px;
  accent-color: var(--accent);
}

#layout {
  flex: 1;
  display: flex;
  gap: 12px;
  padding: 12px;
  min-height: 0;
}

#board-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  position: relative;
}

canvas#board {
  background: var(--forest);
  border: 2px solid var(--panel-border);
  border-radius: 6px;
  max-width: 100%;
  max-height: 100%;
  /* The canvas's width/height HTML attributes (900x640) set its intrinsic
     display size, not just its drawing-buffer resolution — without an
     explicit height here, max-width:100% alone would shrink the width on a
     narrow screen while the height stayed pinned at 640px, squashing the
     board into a stretched sliver. height:auto lets the browser scale the
     height to match, preserving the 900:640 aspect ratio at any width. */
  height: auto;
  /* Taps on the board shouldn't trigger the double-tap-to-zoom gesture or
     text-selection long-press menu mobile browsers apply by default —
     manipulation disables those but still allows normal page scrolling
     past the canvas (relevant once #layout stacks vertically below). */
  touch-action: manipulation;
}

#sidebar {
  width: 300px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
}

.panel {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 10px 12px;
}

#turn-banner {
  font-size: 14px;
  font-weight: bold;
}
#turn-banner.south { color: var(--south); }
#turn-banner.north { color: var(--north); }

#hint {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 4px;
  min-height: 32px;
}

.bench-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}

.bench-slot {
  width: 52px;
  height: 62px;
  border-radius: 6px;
  border: 2px solid var(--panel-border);
  background: #1e1208;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  cursor: pointer;
  padding: 2px;
  text-align: center;
  line-height: 1.15;
}
.bench-slot.empty { opacity: 0.35; cursor: default; }
.bench-slot.locked { opacity: 0.35; cursor: not-allowed; border-style: dashed; }
.bench-slot.selected { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent) inset; }
.bench-slot .elem { font-size: 9px; color: var(--text-dim); }
.bench-sprite {
  width: 24px;
  height: 24px;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

#minion-card-popup {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 30;
  pointer-events: none;
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 10px 12px;
  width: 220px;
  font-size: 12px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.5);
}
#minion-card-popup[hidden] { display: none; }
#minion-card-popup h3 { margin: 0 0 4px; font-size: 13px; color: var(--accent); }
#minion-card-popup .stats { color: var(--text-dim); font-size: 11px; }
#minion-card-popup .kw { color: var(--accent); font-size: 10px; font-weight: normal; }
#minion-card-popup .ability { color: var(--text-dim); font-size: 10px; margin-top: 6px; line-height: 1.3; }

/* Type effectiveness chart -- static reference table toggled by
   #type-chart-btn during a match (see ensureTypeChartRendered()/
   toggleTypeChart() in main.js). Fixed + centered like a lightweight
   modal, but with no backdrop, so the board stays visible around it and
   toggling it off never disrupts anything mid-turn. */
#type-chart-panel {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 32;
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 10px;
  padding: 16px 18px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
  /* Same zoom-vs-viewport-units fix as .card's own max-height/width (see
     that rule's comment and settings.js's updateViewportUnitVars()) --
     plain vh/vw would silently oversize this fixed-position panel at a
     non-100% Text Size. overflow: auto is the last-resort safety net if
     the grid still doesn't fit (a very short/zoomed screen). */
  max-width: calc(var(--vw-unit, 1vw) * 94);
  max-height: calc(var(--vh-unit, 1vh) * 90);
  overflow: auto;
}
#type-chart-panel[hidden] { display: none; }
#type-chart-close-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  padding: 0;
  line-height: 1;
  font-size: 13px;
}
#type-chart-panel h3 {
  margin: 0 0 10px;
  text-align: center;
  color: var(--accent);
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 1px;
}
/* Fixed at 9 columns (8 Elements + 1 header) here in CSS rather than an
   inline style computed from ELEMENTS.length -- that count is a stable
   game constant, and keeping the column count in CSS (not JS) is what
   lets the media query below resize cells without main.js knowing
   anything about screen size. */
.type-chart-grid {
  display: grid;
  grid-template-columns: repeat(9, 34px);
  gap: 4px;
}
.type-chart-cell {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: bold;
  color: #fff;
}
.type-chart-cell.header { font-size: 10px; }
.type-chart-cell.super { background: #4caf6b; }
.type-chart-cell.weak { background: #b8453a; }
.type-chart-cell.neutral { background: #2a2018; color: var(--text-dim); font-weight: normal; }
.type-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  justify-content: center;
  margin-top: 12px;
  font-size: 11px;
  color: var(--text-dim);
}
.type-chart-legend .swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  margin-right: 5px;
  vertical-align: middle;
}
/* Shrinks the grid to comfortably fit a phone screen without needing
   #type-chart-panel's overflow:auto scrollbar as anything but a last
   resort -- same breakpoint the mascot wrapper uses. */
@media (max-width: 860px) {
  #type-chart-panel { padding: 12px 10px; }
  .type-chart-grid { grid-template-columns: repeat(9, 26px); gap: 3px; }
  .type-chart-cell { width: 26px; height: 26px; font-size: 9px; }
  .type-chart-cell.header { font-size: 8px; }
  .type-chart-legend { font-size: 10px; gap: 4px 10px; }
}

/* First-run welcome CTA — large and central, but its backdrop is
   deliberately translucent (not the near-opaque #overlay treatment) so the
   Main Menu is still visible behind it per the intended "big CTA over the
   menu" feel rather than a blocking modal. */
#welcome-cta {
  position: fixed;
  inset: 0;
  z-index: 22;
  background: rgba(15, 9, 5, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
#welcome-cta[hidden] { display: none; }
.welcome-cta-card {
  background: var(--panel);
  border: 2px solid var(--accent);
  outline: 1px dashed var(--accent-dim);
  outline-offset: -8px;
  border-radius: 14px;
  padding: 32px 36px;
  max-width: 440px;
  width: 100%;
  text-align: center;
  box-shadow: 0 12px 40px rgba(0,0,0,0.6);
}
.welcome-cta-card h2 {
  margin: 4px 0 10px;
  font-size: 22px;
  color: var(--accent);
}
.welcome-cta-card p {
  font-size: 14px;
  color: var(--text-dim);
  margin: 0 0 20px;
}
.welcome-cta-card button {
  display: block;
  width: 100%;
  margin-bottom: 10px;
  padding: 14px;
  font-size: 15px;
}
.welcome-cta-card button:last-child { margin-bottom: 0; }

/* Shared tutorial instruction banner — a persistent bar pinned to the top
   of the viewport, visible over both menu screens and live matches.
   `top` sits below the header (via --header-height, measured/kept live by
   updateHeaderHeightVar() in main.js) rather than a flat 12px -- at a
   flat offset this banner's fixed box occupied the same vertical band as
   the header, and since .sound-controls needs z-index 40 to stay
   clickable over this banner (see that rule's own comment), it drew
   right on top of the banner's text instead of beside it. Below the
   header, there's nothing left in that band for it to draw over. */
#tutorial-banner {
  position: fixed;
  top: calc(var(--header-height, 50px) + 12px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 26;
  max-width: 640px;
  width: calc(100% - 32px);
  background: rgba(31, 18, 10, 0.97);
  border: 1px solid var(--accent);
  border-radius: 10px;
  padding: 10px 16px;
  font-size: 13px;
  line-height: 1.4;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: space-between;
  box-shadow: 0 6px 20px rgba(0,0,0,0.5);
}
#tutorial-banner[hidden] { display: none; }
#tutorial-banner span { flex: 1 1 240px; }
#tutorial-banner button { flex: 0 0 auto; padding: 6px 12px; font-size: 12px; }
/* Dismisses just the current box (see renderTutorialBanner() in main.js)
   -- kept visually secondary/quiet so it doesn't compete with the actual
   action button for attention. */
#tutorial-banner-close-btn {
  padding: 6px 9px !important;
  line-height: 1;
  background: transparent;
}

/* Shared by the in-match combat log (#log) and the Trade Room's chat log
   (.scroll-log, e.g. #trade-chat-log) — same re-render-last-N +
   force-scroll-to-bottom JS pattern, so both should look and scroll
   identically instead of a hand-copied second rule block that could drift
   out of sync with this one. */
#log, .scroll-log {
  flex: 1;
  overflow-y: auto;
  font-size: 12px;
  line-height: 1.4;
  min-height: 120px;
  /* #app only declares `min-height: 100vh` (not `height`), so nothing
     above this in the flex chain (#layout, #sidebar, this panel) actually
     enforces a hard ceiling — `flex: 1` + `overflow-y: auto` only produce
     an internal scrollbar once something upstream stops the container
     from just growing to fit. Without this, a long match's combat log
     grows the WHOLE PAGE taller turn after turn instead of staying put
     with its own scrollbar — on desktop just as much as mobile (this used
     to be capped only inside the mobile media query below, which fixed
     the symptom on phones but missed that the exact same flex gap exists
     at every viewport width). See GAME_DATA.md. */
  max-height: 260px;
  /* Default OS/browser scrollbars can be nearly invisible against a dark
     panel (thin OS scrollbar themes, auto-hiding overlay scrollbars) —
     style one explicitly so "this box scrolls" is obvious without having
     to discover it by accident. */
  scrollbar-width: thin;
  scrollbar-color: var(--accent-dim) rgba(0, 0, 0, 0.25);
}
#log::-webkit-scrollbar, .scroll-log::-webkit-scrollbar { width: 9px; }
#log::-webkit-scrollbar-track, .scroll-log::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.25); border-radius: 5px; }
#log::-webkit-scrollbar-thumb, .scroll-log::-webkit-scrollbar-thumb { background: var(--accent-dim); border-radius: 5px; }
#log::-webkit-scrollbar-thumb:hover, .scroll-log::-webkit-scrollbar-thumb:hover { background: var(--accent); }
#log div, .scroll-log div { padding: 2px 0; border-bottom: 1px solid rgba(240,196,70,0.06); }

button {
  background: var(--accent);
  color: #241608;
  border: none;
  border-radius: 6px;
  padding: 8px 14px;
  font-weight: bold;
  cursor: pointer;
  font-size: 13px;
}
button:disabled { opacity: 0.4; cursor: not-allowed; }
button.secondary { background: var(--panel-alt); color: var(--text); border: 1px solid var(--panel-border); }

#overlay {
  position: fixed;
  inset: 0;
  /* Fully opaque -- not just "mostly" (see the removed 0.93 alpha in git
     history). draw() always paints the board's background art + grid onto
     canvas#board even with no match active (it only skips units/highlights
     once `!app.state`, well after the art layers), so any transparency here
     let that idle board bleed through behind every menu screen that uses
     this backdrop (Team, Shop, Collection, Profile, etc. -- anything using
     showOverlay() rather than the separate, always-opaque
     showLandingOverlay()/.landing-host path). */
  background: rgb(15, 9, 5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 20;
}
#overlay[hidden] { display: none; }
.card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  outline: 1px dashed var(--accent-dim);
  outline-offset: -7px;
  border-radius: 10px;
  padding: 24px 28px;
  max-width: 640px;
  width: 92%;
  /* Positioning context for .shop-mel-wrap (Mel the shopkeeper) below --
     doesn't move .card itself, just lets an absolutely-positioned child
     anchor to its corner instead of the whole viewport. */
  position: relative;
  /* calc(var(--vh-unit) * 90), not a plain 90vh -- see settings.js's
     updateViewportUnitVars() header comment for why: under the Settings
     screen's zoom-based Text Size, a plain `vh` value gets computed against
     the true viewport and then rendered at the zoom factor ON TOP of that,
     so the box silently grows past the actual screen (confirmed: the Back
     button became genuinely unreachable, even after scrolling, at 130%
     zoom) -- --vh-unit is pre-divided by the current zoom so the final
     rendered size still matches the real viewport regardless of zoom
     level. Falls back to plain `1vh` if the custom property isn't set yet
     (e.g., extremely early in page load), which is correct at zoom=100%.
  */
  max-height: calc(var(--vh-unit, 1vh) * 90);
  overflow-y: auto;
}
.card h2 {
  margin: 0 0 18px;
  color: var(--accent);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  font-size: 19px;
}
.card h2::after {
  content: '\25c6';
  display: block;
  margin-top: 8px;
  font-size: 9px;
  color: var(--accent-dim);
}
.mode-buttons { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }

/* ---------------------------------------------------------------------
   Mascots (Mel in the Shop, Buggo in a Lobby): float in their screen's
   bottom-right corner, anchored to .card (see that rule's
   `position: relative` above) rather than the viewport, so they stay put
   relative to the panel at any card width -- including .card-wide screens
   like the Lobby, since this only ever anchors to .card's own box.
   `pointer-events: none` on the whole wrapper so a mascot never eats a
   click meant for real UI underneath it -- purely decorative, shouldn't
   compete with real UI for input.
--------------------------------------------------------------------- */
.mascot-wrap {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 300px;
  height: 300px;
  pointer-events: none;
  z-index: 5;
}
/* Anchored via a PERCENTAGE of the wrapper's own height, not a fixed
   pixel top/left guess -- a fixed px guess looked right at one size but
   collided with Mel's head once the wrapper was scaled down for mobile
   (font-size/padding don't shrink at the same ratio as the image). A
   percentage scales with the wrapper at any breakpoint instead.
   `bottom: 100%` (stacked flush above the wrapper's own top edge) was the
   first attempt at that same idea, but both mascot PNGs have a lot of
   transparent canvas above the actual art (Mel's art starts ~15% down
   into its 300x300 square, Buggo's ~17%) -- 100% put the bubble above
   that dead space too, reading as floating way above the character
   instead of next to his head. 87% lands just above where the art
   actually starts for both (measured directly from each PNG's alpha
   channel), with a few px of margin below to spare. */
.mascot-wrap .mascot-speech {
  position: absolute;
  bottom: 87%;
  right: 0;
  margin-bottom: 4px;
  background: var(--panel-alt);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 6px 10px;
  font-size: 11px;
  line-height: 1.3;
  max-width: 190px;
  color: var(--text);
}
.mascot-wrap img {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 300px;
  height: 300px;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5));
}
/* .card is 92% of the viewport width (capped at 640px -- see that rule),
   so on a narrow phone screen the card itself is nowhere near 640px wide.
   A flat 300px mascot doesn't shrink with it, so on mobile it ends up
   crowding whatever's next to it (the Packs section in Shop, the offers
   panel in a Lobby). Scale it down here rather than changing anything
   about the desktop layout above -- still clearly mascot-sized (not the
   original 130px Mel briefly used, which read as too small given how much
   empty space is actually free on a real phone), just smaller than the
   300px desktop version. */
@media (max-width: 860px) {
  .mascot-wrap {
    width: 190px;
    height: 190px;
  }
  .mascot-wrap img {
    width: 190px;
    height: 190px;
  }
  .mascot-wrap .mascot-speech {
    max-width: 140px;
    font-size: 10px;
    padding: 5px 8px;
  }
}
/* Buggo on the Quick Play screen: standing inline next to the Vs. AI/
   Vs. Player buttons (part of the normal document flow, in the same .row
   as that button group) rather than floating over a corner of the card
   like Mel/the other Buggo placement -- this screen's card is too short
   for an absolutely-positioned overlay mascot to have anywhere to sit
   without covering the buttons or the title. `position: relative` here
   makes this element (not the whole .card) the containing block for the
   speech bubble's `bottom: 87%` (same offset as .mascot-wrap -- see that
   rule's comment for why 87%, not 100%: both PNGs have transparent canvas
   above the actual art). */
.mascot-inline {
  position: relative;
  flex: 0 0 auto;
  width: 80px;
  height: 80px;
  pointer-events: none;
}
.mascot-inline img {
  width: 80px;
  height: 80px;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5));
}
.mascot-inline .mascot-speech {
  position: absolute;
  bottom: 87%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 4px;
  background: var(--panel-alt);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 5px 8px;
  font-size: 10px;
  line-height: 1.3;
  max-width: 140px;
  color: var(--text);
  white-space: normal;
}

.squad-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px;
  margin: 14px 0;
}
.squad-card {
  border: 2px solid var(--panel-border);
  border-radius: 8px;
  padding: 8px;
  cursor: pointer;
  font-size: 12px;
  background: #1e1208;
}
.squad-card.picked { border-color: var(--accent); background: #3a2410; }
.squad-card h3 { margin: 0 0 4px; font-size: 13px; }
.squad-card .stats { color: var(--text-dim); font-size: 11px; }
.squad-card .kw { color: var(--accent); font-size: 10px; font-weight: normal; }
.squad-card .ability { color: var(--text-dim); font-size: 10px; margin-top: 6px; line-height: 1.3; }

/* ---------------------------------------------------------------------
   Trade Room: members + chat + trade board need more room than the
   standard .card's 640px cap gives the in-match menus — an opt-in
   modifier (see showTradeRoom()'s classList.add('card-wide')) rather than
   raising .card's own max-width, so every other screen is unaffected.
--------------------------------------------------------------------- */
.card-wide { max-width: 920px; }
.trade-room-layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 12px;
  margin: 14px 0;
}
#trade-chat-panel { min-height: 0; }
@media (max-width: 860px) {
  .trade-room-layout { grid-template-columns: 1fr; }
}
.collection-sprite {
  display: block;
  width: 48px;
  height: 48px;
  margin: 0 auto 4px;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

#dice-toast {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(31,18,10,0.95);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 8px 14px;
  font-size: 12px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s;
}
#dice-toast.show { opacity: 1; }

/* ---------------------------------------------------------------------
   Main Menu landing page
--------------------------------------------------------------------- */
#overlay-content.landing-host {
  background: none;
  border: none;
  padding: 0;
  max-width: none;
  /* calc(var(--vw-unit/--vh-unit) * 100), not plain 100vw/100vh -- same
     zoom-vs-viewport-units mismatch .card's max-height comment explains,
     except worse here: this box has overflow:hidden (not auto), so content
     pushed outside its (silently oversized, under zoom) bounds isn't even
     reachable by scrolling -- it's just gone. */
  width: calc(var(--vw-unit, 1vw) * 100);
  height: calc(var(--vh-unit, 1vh) * 100);
  max-height: calc(var(--vh-unit, 1vh) * 100);
  border-radius: 0;
  overflow: hidden;
}
.landing {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 40px 20px;
  background: radial-gradient(ellipse at 50% 30%, #2a1a0d 0%, #120b06 70%);
  overflow-y: auto;
}
.landing-kicker {
  text-transform: uppercase;
  letter-spacing: 6px;
  font-size: 12px;
  font-weight: bold;
  color: var(--accent-dim);
  margin-bottom: 10px;
}
/* Diamond flourishes flanking the kicker, echoing the card back's
   "◆ OF ◆" treatment around its own subtitle line. */
.landing-kicker::before, .landing-kicker::after {
  content: '\25c6';
  color: var(--accent-dim);
  font-size: 8px;
  margin: 0 10px;
  vertical-align: middle;
}
.landing-title {
  font-size: clamp(32px, 7vw, 54px);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin: 0 0 10px;
  background: linear-gradient(180deg, #ffe9ad, var(--accent) 55%, #c99a35);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  line-height: 1.05;
}
.landing-sub {
  color: var(--text-dim);
  font-size: 14px;
  margin: 0 0 30px;
  max-width: 420px;
}
.landing-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  outline: 1px dashed var(--accent-dim);
  outline-offset: -6px;
  border-radius: 12px;
  padding: 18px 20px;
  width: 320px;
  max-width: 90vw;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.landing-card input[type="text"] {
  flex: 1;
  background: #1e1208;
  border: 1px solid var(--panel-border);
  color: var(--text);
}
.landing-points { font-size: 12px; color: var(--text-dim); }
.landing-points strong { color: var(--accent); }
.landing-nav {
  display: flex;
  flex-wrap: wrap;
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  overflow: hidden;
  margin-top: 22px;
  width: 320px;
  max-width: 90vw;
  background: var(--panel);
}
.landing-nav button {
  flex: 1 1 25%;
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 12px 6px;
  font-size: 12px;
  font-weight: bold;
  color: var(--text-dim);
  border-right: 1px solid var(--panel-border);
}
.landing-nav button:last-child { border-right: none; }
.landing-nav button:hover { background: var(--panel-alt); color: var(--text); }
.landing-cta {
  margin-top: 16px;
  width: 320px;
  max-width: 90vw;
  padding: 15px;
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 1px;
  border-radius: 10px;
  border: none;
  background: linear-gradient(180deg, #f7d876, var(--accent) 60%, #c99a35);
  color: #241608;
  font-weight: bold;
  cursor: pointer;
}
.landing-cta:hover { filter: brightness(1.08); }
.landing-cta:disabled { opacity: 0.5; cursor: not-allowed; filter: none; }
.landing-join-row {
  margin-top: 12px;
  display: flex;
  gap: 8px;
  width: 320px;
  max-width: 90vw;
}
.landing-join-row input {
  flex: 1;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  color: var(--text);
  text-align: center;
  letter-spacing: 2px;
  text-transform: uppercase;
}
.landing-join-row button {
  background: var(--panel-alt);
  border: 1px solid var(--panel-border);
  color: var(--text);
  padding: 10px 16px;
  border-radius: 8px;
}
/* Three small dots at the bottom of the page, echoing the card back's own
   "• • •" footer flourish beneath its title block. */
.landing::after {
  content: '\2022 \2022 \2022';
  display: block;
  margin-top: 22px;
  color: var(--accent-dim);
  letter-spacing: 8px;
  font-size: 12px;
  opacity: 0.7;
}
/* Discord button, sitting below the dots above. `::after` is generated as
   if it were the LAST child of `.landing` (a fixed part of how it's
   specified, regardless of where any real child sits in the markup), so
   an explicit `order` higher than the default 0 every other child (and
   the dots) use is what actually pushes this one after them in the flex
   column -- moving its HTML earlier or later wouldn't do it on its own. */
.landing-discord-link {
  order: 1;
  display: inline-block;
  margin-top: 16px;
  width: 48px;
  height: 48px;
  border-radius: 12px;
  overflow: hidden;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.landing-discord-link img { display: block; width: 100%; height: 100%; }
.landing-discord-link:hover {
  transform: scale(1.08);
  box-shadow: 0 0 0 2px var(--accent-dim);
}

.row { display: flex; gap: 8px; align-items: center; }
label { font-size: 12px; color: var(--text-dim); }
input[type="text"] {
  background: #1e1208;
  border: 1px solid var(--panel-border);
  color: var(--text);
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 13px;
}

/* ---------------------------------------------------------------------
   Mobile / narrow-viewport layout — see GAME_DATA.md's "Mobile web
   support" section for the full story. The desktop #layout is a fixed
   300px sidebar next to a flexed board; on a phone-width screen that
   sidebar alone would eat most of the available width and crush the
   board to a sliver, so below this breakpoint the two stack vertically
   instead (board first — it's the thing being played on IS the point),
   full-width. main.js's long-press-to-inspect handling is what gives
   touch input a substitute for the desktop-only Shift+hover popup; this
   block is purely the layout side of mobile support.
--------------------------------------------------------------------- */
@media (max-width: 860px) {
  header {
    padding: 8px 10px;
    gap: 6px;
    flex-wrap: wrap;
  }
  header h1 { font-size: 14px; letter-spacing: 1px; }
  header .subtitle { font-size: 11px; }
  .sound-controls input[type="range"] { width: 54px; }
  .sound-controls button { padding: 6px 9px; font-size: 15px; }

  #layout {
    flex-direction: column;
    padding: 8px;
    gap: 8px;
  }
  #board-wrap {
    flex: none;
    width: 100%;
  }
  #sidebar {
    width: 100%;
  }
  /* The base #log rule above now caps it everywhere (see its comment) —
     this just shrinks that cap further for a phone's much smaller
     vertical budget, and adds momentum scrolling for touch-dragging. */
  #log {
    max-height: 150px;
    -webkit-overflow-scrolling: touch;
  }
  /* Slightly larger tap targets than the desktop mouse-sized defaults —
     52x62 bench slots and 8px/14px button padding are comfortable for a
     mouse pointer but tight for a fingertip. */
  .bench-slot { width: 58px; height: 68px; }
  button { padding: 11px 16px; font-size: 14px; }
  #tutorial-banner button { padding: 9px 14px; font-size: 13px; }
}
