/* ═══════════════════════════════════════════════════════════════════════════
   GameDays — Sorry!  •  styles
   Mobile-first (iOS Safari + Android Chrome). Red-vs-blue Barons heart, the two
   sisters as mascots, premium + characterful. Uses safe-area insets, big touch
   targets, and cheap-to-render effects (no heavy blur on scrolling content).
═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── CYBERPUNK COLOR SYSTEM ────────────────────────────────────────────────
     One night-city palette for the whole shell (splash, Club House, lobby,
     score). Two axes: the Barons red↔blue neon edge, and the gold accent.

     ⚠ PLAYER IDENTITY HUES (--red / --blue / --yellow / --green) are the pawn /
     seat / standings colors. They MUST stay distinct + legible on the Sorry!
     board and Telestrations — they mirror board.js TEAM_COLORS EXACTLY. If you
     retune a player hue here, retune board.js's TEAM_COLORS to match, and re-
     check contrast on the board. The --*-neon variants below are for GLOW/UI
     shell chrome only (edges, text-shadows) — never for pawn fills. */

  /* player identity hues — MUST match board.js TEAM_COLORS EXACTLY.
     Full 4-stop set per color (base · glow · deep · ink) mirrors TEAM_COLORS so
     board zones (Start/Home/Slide/Pawn) and the shell chrome share ONE source of
     truth. If you retune a hue here, retune board.js TEAM_COLORS to match. */
  --red:    #ff3b52;  --red-glow:   #ff8fa0;  --red-deep:   #7d0f1e;  --red-ink:   #2a0308;
  --blue:   #2e8bff;  --blue-glow:  #8fc0ff;  --blue-deep:  #0d3a8f;  --blue-ink:  #041027;
  --yellow: #ffd21e;  --yellow-glow:#fff08a;  --yellow-deep:#8a6a00;  --yellow-ink:#2a2000;
  --green:  #22e06a;  --green-glow: #8ff5b5;  --green-deep: #0a7a3a;  --green-ink: #042314;
  /* darker rails for gradients / pressed states (alias the -deep stops) */
  --red-dk: #7d0f1e;
  --blue-dk:#0d3a8f;
  --yellow-dk:#8a6a00;
  --green-dk:#0a7a3a;
  /* NEON shell-chrome variants (glow / edge only, brighter than the pawn hue) */
  --red-neon:  #ff5e78;
  --blue-neon: #5aa0ff;
  --gold:   #ffd166;
  --gold-neon: #ffe08a;

  /* surfaces — deeper, glossier night-city panels */
  --bg:     #0a0b16;
  --bg-2:   #10121f;
  --panel:  #171b30;
  --panel-2:#1f2440;
  --glass:  rgba(22,26,48,.82);   /* glossy translucent panel fill */
  --ink:    #f2f4ff;
  --ink-dim:#98a1c6;
  --line:   rgba(140,165,255,0.14);   /* faint cool neon hairline */
  --line-hot: rgba(255,94,120,0.30);  /* red edge */
  --line-cold:rgba(90,160,255,0.32);  /* blue edge */
  --radius: 16px;
  --shadow: 0 10px 30px rgba(0,0,0,0.5);
  /* the signature red↔blue neon edge glow, reusable across shell panels */
  --neon-edge: 0 0 0 1px rgba(140,165,255,.14),
               0 0 22px -6px rgba(90,160,255,.5),
               0 0 22px -6px rgba(255,94,120,.4),
               inset 0 1px 0 rgba(255,255,255,.06);
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
  margin: 0; height: 100%; background: var(--bg); color: var(--ink);
  font-family: var(--font); overscroll-behavior: none;
  -webkit-user-select: none; user-select: none;
}
body {
  background:
    radial-gradient(120% 80% at 50% -10%, #1b2140 0%, var(--bg) 55%),
    var(--bg);
}
button { font-family: inherit; cursor: pointer; border: none; color: inherit; }
input { font-family: inherit; }

#app { position: relative; height: 100%; width: 100%; max-width: 640px; margin: 0 auto; overflow: hidden; }

/* ── screen machine ── */
.screen { position: absolute; inset: 0; display: none; flex-direction: column;
  padding: calc(var(--safe-t) + 14px) 16px calc(var(--safe-b) + 14px);
  overflow-y: auto; -webkit-overflow-scrolling: touch; }
#app[data-screen="connect"]      [data-for="connect"],
#app[data-screen="clubhouse"]    [data-for="clubhouse"],
#app[data-screen="lobby"]        [data-for="lobby"],
#app[data-screen="game"]         [data-for="game"],
#app[data-screen="score"]        [data-for="score"],
#app[data-screen="tele-draw"]    [data-for="tele-draw"],
#app[data-screen="tele-guess"]   [data-for="tele-guess"],
#app[data-screen="tele-wait"]    [data-for="tele-wait"],
#app[data-screen="tele-reveal"]  [data-for="tele-reveal"],
#app[data-screen="tele-gallery"] [data-for="tele-gallery"] { display: flex; animation: screenIn .35s ease; }
/* hide brand bar on all full-bleed telestrations screens (they carry their own) */
#app[data-screen="tele-draw"] #brand-bar,
#app[data-screen="tele-guess"] #brand-bar,
#app[data-screen="tele-wait"] #brand-bar,
#app[data-screen="tele-reveal"] #brand-bar,
#app[data-screen="tele-gallery"] #brand-bar { display: none; }
@keyframes screenIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
/* The splash carries a FIXED full-viewport backdrop (.splash-fx). A `transform`
   on its containing .screen would clip that fixed child to the section box for
   the entrance's duration (causing a strip-then-expand flash), so the connect
   screen fades in WITHOUT a transform — its backdrop stays truly full-viewport. */
/* Clubhouse ALSO carries a FIXED full-viewport backdrop (.ch-fx); like the
   splash it must fade in WITHOUT a transform, or the transform would clip that
   fixed child to the section box during the entrance. */
#app[data-screen="connect"]   [data-for="connect"],
#app[data-screen="clubhouse"] [data-for="clubhouse"] { animation: screenFade .35s ease; }
@keyframes screenFade { from { opacity: 0; } to { opacity: 1; } }

.hidden { display: none !important; }

/* ── brand bar ── */
#brand-bar { position: absolute; top: calc(var(--safe-t) + 6px); left: 0; right: 0;
  display: flex; align-items: center; justify-content: center; gap: 8px; z-index: 30;
  pointer-events: none; font-weight: 800; letter-spacing: 2px; font-size: 13px; opacity: .85; }
.brand-word b { color: var(--gold); }
.brand-crest { width: 10px; height: 10px; border-radius: 3px; box-shadow: 0 0 10px currentColor; }
.brand-crest--red { background: var(--red); color: var(--red); }
.brand-crest--blue { background: var(--blue); color: var(--blue); }
#app[data-screen="game"] #brand-bar { display: none; }
/* Brand banner RETIRED (James crossed it out). It becomes the app menu tomorrow;
   for now it's hidden everywhere and the splash reserves an empty top strip (below)
   so tomorrow's menu drops in without shifting the layout. */
#brand-bar { display: none; }

/* ═══════════ CONNECT / SPLASH ═══════════ */
/* Vertical rhythm (top → bottom):
     [reserved app-menu strip] → twins → CLEAR GAP → gamedays.club + form → grid.
   The RESERVED STRIP (--app-menu-h) is empty header space where the app menu
   drops in TOMORROW — accounted for now so the menu won't shift the layout.
   The twins sit just under it; a flexible spacer then pushes the wordmark+form
   DOWN so the form's bottom lands where the neon drift-grid begins, while the
   whole form (name + button + subtext) stays above the fold. */
[data-for="connect"] { --app-menu-h: 46px;
  justify-content: flex-start; gap: 0;
  padding-top: calc(var(--safe-t) + var(--app-menu-h));
  position: relative; }
.hero { text-align: center; margin-top: 2px; position: relative; z-index: 2; }
/* flexible spacer between the twins and the wordmark/form — creates the clear
   separation the annotation asks for, and pushes the form down toward the grid.
   It flexes so the form settles into the lower screen on tall phones but never
   forces anything below the fold on short ones (min-height guarantees a gap).
   Capped so on very tall phones the form lands at the grid START (lower-middle),
   not jammed against the very bottom. */
.splash-spacer { flex: 1 1 auto; min-height: 20px; max-height: 20vh; }
/* small cushion below the form so its bottom sits just AT the grid line rather
   than flush with the safe-area edge — the status line lives in this cushion. */
[data-for="connect"] #connect-status { margin-top: 6px; }

/* ── TOKYO-DRIFT / CYBERPUNK backdrop (splash only) ──
   A night-city, street-racer-neon stage: dual red/blue horizon glows, a
   perspective floor grid receding to a vanishing point, and faint scanlines.
   All GPU-cheap (gradients, one masked repeating-gradient, no blur on scroll).
   Sits behind the hero + form; pointer-events off so it never eats a tap. */
.splash {
  /* The section itself is transparent — the neon stage now lives on the
     FULL-VIEWPORT fixed .splash-fx below, so on desktop (where #app is a
     centered 640px column) the whole screen is the night-city stage instead
     of a trapped strip. The hero + form just float centered on top. */
  background: transparent;
}
/* ── FULL-VIEWPORT cyberpunk stage ──
   .splash-fx is position:FIXED and inset:0, so it fills the entire viewport and
   escapes #app's `overflow:hidden` clip (no transformed ancestor). It carries
   the base night-city gradient + the horizon blooms + the drift-road grid +
   scanlines. Shown only on the connect screen (toggled below), pointer-events
   off so it never eats a tap. On desktop the empty dark sides become neon
   floor; on a phone it fills the screen exactly as before. */
.splash-fx { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  background:
    radial-gradient(120% 66% at 50% -8%, #241a3a 0%, transparent 55%),
    radial-gradient(80% 60% at 10% 6%, rgba(61,139,255,.20) 0%, transparent 60%),
    radial-gradient(80% 60% at 90% 6%, rgba(255,70,85,.20) 0%, transparent 60%),
    linear-gradient(180deg, #08090f 0%, #0d0e1c 55%, #08090f 100%); }
/* The fixed stage only renders while the splash <section> is displayed — its
   parent .screen carries display:none on every other screen, which hides this
   fixed child too. So no explicit per-screen toggle is needed. */
/* neon horizon blooms, gently pulsing (red on one side, blue the other).
   Sized in vw so they scale to a wide desktop viewport, not a 640px column. */
.fx-glow { position: absolute; width: 62vw; height: 62vw; max-width: 720px; max-height: 720px;
  border-radius: 50%; filter: blur(70px); opacity: .5; mix-blend-mode: screen;
  animation: fxPulse 6s ease-in-out infinite; }
.fx-glow--red  { top: -14%; right: -12%; background: radial-gradient(circle, var(--red-neon), transparent 70%); }
.fx-glow--blue { top: -14%; left:  -12%; background: radial-gradient(circle, var(--blue-neon), transparent 70%);
  animation-delay: -3s; }
@keyframes fxPulse { 0%,100% { opacity: .40; transform: scale(1); } 50% { opacity: .62; transform: scale(1.06); } }
/* perspective floor grid receding upward (the "drift" road) — anchored to the
   bottom of the VIEWPORT. Taller + a softer fade so the lower screen reads as
   intentional neon floor (esp. the space below the form on mobile). */
.fx-grid { position: absolute; left: -30%; right: -30%; bottom: 0; height: 62%;
  background-image:
    repeating-linear-gradient(to right, rgba(120,150,255,.18) 0 1px, transparent 1px 46px),
    repeating-linear-gradient(to top,   rgba(120,150,255,.18) 0 1px, transparent 1px 44px);
  transform: perspective(360px) rotateX(68deg); transform-origin: bottom center;
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.5) 68%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.5) 68%, transparent 100%);
  opacity: .85; }
/* MIRRORED CEILING grid — the same drift-road receding to a vanishing point,
   flipped to hang from the TOP of the viewport. Used by the Club House backdrop
   (.ch-fx) to frame the screen top AND bottom. */
.fx-grid--top { bottom: auto; top: 0; height: 52%;
  transform: perspective(360px) rotateX(-68deg); transform-origin: top center;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.5) 66%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.5) 66%, transparent 100%);
  opacity: .55; }
/* faint CRT scanlines over the whole viewport for that screen-glow texture */
.fx-scan { position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,.028) 0 1px, transparent 1px 3px);
  opacity: .6; mix-blend-mode: overlay; }
@media (prefers-reduced-motion: reduce) { .fx-glow { animation: none; } }

/* ══ THE BRAND MARK: the twins bursting out of a circular red-vs-blue badge ══
   A crisp gold-rimmed coin with a blue-left / red-right split glow inside; the
   twins step OUT of it — lower body framed inside the ring, heads/shoulders
   overflowing the TOP rim for a dimensional "popping out of a coin" look, with a
   drop shadow seating the whole badge.

   ── SWAP-IN GUIDE (for a future render, e.g. back-to-back) ──
   Change ONLY these vars on .hero-mascot (and both <img src>s in index.html):
     --badge         : diameter of the coin (drives the whole mark's footprint)
     --mascot-w      : rendered width of the twins art, relative to the badge
     --mascot-y      : vertical nudge — how far the art sits above the coin
     --mascot-x      : horizontal nudge, if a new render isn't centered
   The frame geometry is derived from --badge, so the layout won't need redoing. */
.hero-mascot {
  /* COLLISION-PROOF BY DESIGN: the twins <img> is IN NORMAL FLOW (see
     .hero-mascot-img) and its real height (--mascot-h) is what reserves space, so
     the mark can never overlap the brand bar above or the wordmark/form below. The
     coin sits ABSOLUTELY BEHIND the twins (decorative). To swap a new render later,
     change the <img> src + --mascot-h (and --coin if you want the ring bigger). */
  --mascot-h: min(41vh, 350px);            /* the twins' rendered height — drives the box */
  --badge: calc(var(--mascot-h) * 0.8);    /* coin diameter, behind the twins */
  position: relative;
  width: fit-content; max-width: 90vw;
  margin: 12px auto 10px;                  /* explicit gap above (brand bar) + below (wordmark) */
  animation: mascotFloat 8s ease-in-out infinite;
}

/* the COIN — sits ABSOLUTELY BEHIND the twins, centered on their lower body, so it
   can't affect layout/flow (the in-flow <img> owns the spacing). */
.hero-badge {
  position: absolute; z-index: 1; pointer-events: none;
  width: var(--badge); height: var(--badge);
  left: 50%; bottom: 6%; transform: translateX(-50%);
}
/* the RIM + FACE, built as two stacked layers for a crisp circle on ALL browsers
   (no mask-composite): an outer conic ring, then a solid inner disc inset on top.
   The ring band = the gap between the two circles. */
.hero-badge-disc {
  position: absolute; inset: 0; border-radius: 50%; z-index: 2;
  /* outer ring: gold coin with a blue→red split */
  background:
    conic-gradient(from 210deg,
      var(--blue) 0deg, #6fa8ff 70deg, var(--gold) 155deg,
      #ff7a86 245deg, var(--red) 305deg, var(--blue) 360deg);
  box-shadow:
    0 12px 22px -8px rgba(0,0,0,.6),           /* drop shadow seating the coin */
    0 0 0 1px rgba(0,0,0,.25),                 /* crisp outer edge */
    0 0 10px rgba(255,209,102,.30);            /* soft gold glow */
}
/* the inner FACE disc — sits on the ring, leaving a clean rim band showing */
.hero-badge-disc::before {
  content: ''; position: absolute;
  inset: calc(var(--badge) * 0.055);           /* = rim thickness */
  border-radius: 50%;
  background:
    radial-gradient(circle at 30% 28%, rgba(61,139,255,.55), transparent 62%),
    radial-gradient(circle at 72% 32%, rgba(255,70,85,.5), transparent 62%),
    radial-gradient(circle at 50% 64%, #232a4a, #10132a 80%);
  box-shadow:
    inset 0 2px 10px rgba(255,255,255,.14),    /* top inner sheen */
    inset 0 -14px 24px rgba(0,0,0,.40),        /* bottom inner depth */
    0 0 0 1px rgba(0,0,0,.35);                 /* crisp inner rim edge */
}
/* ── WORLD-LINES motif inside the coin (behind the twins) ──
   A low-opacity globe wireframe with a neon arc linking two points: the two
   sisters, connected across distance. Clipped to the inner face disc so it lives
   INSIDE the coin's gradient; the twins (z-index 2) still pop clearly on top.
   Slowly rotates to feel alive without pulling focus. */
.hero-globe { position: absolute; z-index: 1;
  inset: calc(var(--badge) * 0.055);          /* = the inner face disc, same inset */
  width: auto; height: auto; border-radius: 50%; overflow: hidden;
  opacity: .5; pointer-events: none; }
/* only the wireframe grid rotates (the globe turning); the link + its two nodes
   stay put, so "two fixed points connected" reads clearly. */
.hero-globe-wire { fill: none; stroke: #8fb4ff; stroke-width: .7; opacity: .55;
  transform-origin: 50px 50px; animation: globeSpin 40s linear infinite; }
.hero-globe-wire circle { stroke: #a9c4ff; stroke-width: .9; opacity: .7; }
/* EASTER EGG (§Part C): two sister-points connected by an occasional signal.
   --sig: one full cycle (mostly idle, then one sweep) — every ~9s, not constant.
   The trail is a short lit DASH sweeping the arc from blue → red; the base arc is
   near-invisible; the nodes stay faint and flare only as the signal passes. All
   GPU-cheap (dash-offset + opacity) and OFF under prefers-reduced-motion. */
.hero-globe-signal, .hero-globe-node { --sig: 9s; }
/* the faint always-there arc — a whisper, so the connection isn't fully hidden */
.hero-globe-arc { fill: none; stroke: #7f9fe0; stroke-width: .9; opacity: .12; }
/* the traveling SIGNAL: a short lit segment (12 of 100) that sweeps the path once
   per cycle, then rests off-screen the rest of the time. */
.hero-globe-signal { fill: none; stroke-width: 1.6; stroke-linecap: round;
  filter: drop-shadow(0 0 2.5px rgba(150,180,255,.8));
  stroke-dasharray: 12 100; stroke-dashoffset: 24; opacity: 0;
  animation: sigTravel var(--sig) ease-in-out infinite; }
.hero-globe-node { filter: drop-shadow(0 0 3px currentColor); opacity: .14; }
.hero-globe-node--blue { fill: #6fa8ff; color: #6fa8ff;
  animation: nodeFlareBlue var(--sig) ease-in-out infinite; }
.hero-globe-node--red  { fill: #ff7a86; color: #ff7a86;
  animation: nodeFlareRed var(--sig) ease-in-out infinite; }
@keyframes globeSpin { to { transform: rotate(360deg); } }
/* dashoffset runs +24 → -88 so the lit dash enters at the blue end (offset 24)
   and exits at the red end. Signal only visible during the sweep window. */
@keyframes sigTravel {
  0%,  62% { stroke-dashoffset: 24;  opacity: 0; }
  66%      { stroke-dashoffset: 24;  opacity: .95; }
  96%      { stroke-dashoffset: -88; opacity: .95; }
  100%     { stroke-dashoffset: -88; opacity: 0; }
}
/* blue lights up as the signal LEAVES it (early in the sweep) */
@keyframes nodeFlareBlue {
  0%, 62%  { opacity: .14; }
  70%      { opacity: 1; }
  84%, 100% { opacity: .14; }
}
/* red lights up as the signal ARRIVES (end of the sweep) */
@keyframes nodeFlareRed {
  0%, 84%  { opacity: .14; }
  94%      { opacity: 1; }
  100%     { opacity: .14; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-globe-wire { animation: none; }
  .hero-globe-signal { animation: none; opacity: 0; }
  .hero-globe-node { animation: none; opacity: .14; }
}

/* the FULL twins — IN NORMAL FLOW: height drives the reserved box (guarantees
   spacing); the coin behind + z-index give the "popping out of the coin" 3D read. */
.hero-mascot-img {
  position: relative; z-index: 2;
  display: block; height: var(--mascot-h); width: auto; margin: 0 auto;
  pointer-events: none;
  /* cast the twins' shadow onto the coin + the world → 3D lift */
  filter: drop-shadow(0 8px 12px rgba(0,0,0,.5)) drop-shadow(0 2px 3px rgba(0,0,0,.4));
}
/* A whisper of motion — barely-perceptible ~2px drift, slow & gentle. */
@keyframes mascotFloat { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }

/* wordmark now sits with the form (below the spacer), so it needs its own stacking
   context above the neon backdrop, and a small gap to the form beneath it. */
.hero-url { position: relative; z-index: 2; margin: 0 0 12px; font-size: 17px; font-weight: 800;
  letter-spacing: 2.5px; text-transform: lowercase; color: var(--ink); text-align: center; }
.hero-url b { color: var(--gold); }

/* Splash responsive: on short viewports (wide-but-short desktop windows, small
   phones with browser chrome, or under zoom) shrink the badge so the name field +
   CTAs never get pushed below the fold. Only --badge changes; frame scales with it. */
/* Shrink ONLY on short + LANDSCAPE windows (wide-but-short desktop / landscape phone),
   where vertical space is genuinely tight. Portrait phones — even "short" ones — keep
   the full base size (min(46vh,380px) scales it to fit), so the mascot never gets
   wrongly shrunk on a phone in portrait. */
@media (max-height: 720px) and (orientation: landscape) {
  [data-for="connect"] { --app-menu-h: 30px; }
  .hero-mascot { --mascot-h: min(56vh, 200px); margin: 10px auto 8px; }
  .splash-spacer { min-height: 10px; }
}
@media (max-height: 560px) and (orientation: landscape) {
  [data-for="connect"] { --app-menu-h: 22px; }
  .hero-mascot { --mascot-h: min(52vh, 150px); margin: 8px auto 6px; }
  .hero-url { font-size: 15px; margin-bottom: 8px; }
  .splash-spacer { min-height: 6px; }
}
@media (max-height: 440px) and (orientation: landscape) {
  [data-for="connect"] { --app-menu-h: 16px; }
  .hero-mascot { --mascot-h: min(48vh, 118px); margin: 6px auto 4px; }
  .hero-url { font-size: 13px; margin-bottom: 6px; }
  .splash-spacer { min-height: 4px; }
}

.card { background: linear-gradient(165deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px; box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,.05); }
.stack { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: 12px; color: var(--ink-dim); letter-spacing: .5px; text-transform: uppercase; }
input[type=text] { background: var(--bg-2); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px; font-size: 17px; color: var(--ink); width: 100%;
  transition: border-color .15s ease, box-shadow .15s ease; }
input[type=text]:focus { outline: none; border-color: var(--blue);
  box-shadow: 0 0 0 1px var(--blue), 0 0 16px -4px rgba(90,160,255,.6); }
.code-input { text-align: center; letter-spacing: 6px; font-weight: 800; font-size: 22px; text-transform: uppercase; }

.btn { border-radius: 12px; padding: 15px 18px; font-size: 16px; font-weight: 700;
  transition: transform .08s ease, filter .15s ease, box-shadow .15s ease; }
.btn:active { transform: scale(.97); }
.btn--primary { background: linear-gradient(120deg, var(--red-neon), var(--red-dk)); color: #fff;
  border: 1px solid var(--line-hot);
  box-shadow: 0 8px 22px -4px rgba(255,70,85,.45), inset 0 1px 0 rgba(255,255,255,.22); }
.btn--ghost { background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line); }
.btn--text { background: transparent; color: var(--ink-dim); padding: 12px; font-weight: 600; }
.btn--danger { color: var(--red); }
.btn--sm { padding: 10px 14px; font-size: 14px; align-self: flex-start; }
.btn:disabled { opacity: .45; filter: grayscale(.4); }
.btn-row { display: flex; }
.btn-row .btn { flex: 1; }
.conn-status { text-align: center; color: var(--ink-dim); font-size: 13px; min-height: 18px; }
.cta-note { text-align: center; color: var(--ink-dim); font-size: 12.5px; margin: -6px 0 0; }

/* ── SPLASH form: cyberpunk polish (scoped so other screens keep the base look) ──
   A dark glossy panel with a neon red↔blue edge, a stronger URL glow, and a
   full-width primary CTA that reads like a lit-up arcade button. */
.splash .hero-url { text-shadow: 0 0 14px rgba(255,209,102,.45); }
.splash #connect-form { position: relative; z-index: 2;
  border: 1px solid rgba(120,150,255,.22);
  background:
    linear-gradient(180deg, rgba(28,22,48,.92), rgba(16,16,31,.94));
  box-shadow:
    0 12px 34px rgba(0,0,0,.55),
    0 0 0 1px rgba(0,0,0,.35),
    inset 0 1px 0 rgba(255,255,255,.06),
    0 0 26px -6px rgba(61,139,255,.35);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.splash #connect-form input[type=text]:focus { border-color: var(--blue);
  box-shadow: 0 0 0 1px var(--blue), 0 0 16px -2px rgba(61,139,255,.6); }
.splash #btn-clubhouse { position: relative; letter-spacing: .3px; text-transform: uppercase;
  font-weight: 800;
  background: linear-gradient(120deg, var(--blue) 0%, #7a53d6 48%, var(--red) 100%);
  box-shadow:
    0 10px 26px -6px rgba(255,70,85,.55),
    0 6px 22px -8px rgba(61,139,255,.6),
    inset 0 1px 0 rgba(255,255,255,.25);
  animation: ctaPulse 3.4s ease-in-out infinite; }
@keyframes ctaPulse {
  0%,100% { box-shadow: 0 10px 26px -6px rgba(255,70,85,.45), 0 6px 22px -8px rgba(61,139,255,.5), inset 0 1px 0 rgba(255,255,255,.25); }
  50%     { box-shadow: 0 12px 32px -6px rgba(255,70,85,.7),  0 8px 28px -8px rgba(61,139,255,.75), inset 0 1px 0 rgba(255,255,255,.3); }
}
.splash .cta-note { color: #b7c0e6; text-shadow: 0 0 10px rgba(61,139,255,.25); }
@media (prefers-reduced-motion: reduce) { .splash #btn-clubhouse { animation: none; } }

/* ═══════════ CLUB HOUSE (matchmaking room browser) ═══════════
   The splash's night-city world, now the lobby: a FIXED drift-grid backdrop
   framing the screen top AND bottom, blue-left / red-right blooms, scanlines.
   Sticky glass header, a LIVE red↔blue Quick Play slab, glossy tap-cards with a
   Barons spine. Same world as the splash, one step deeper in. */
[data-for="clubhouse"] { padding-top: calc(var(--safe-t) + 10px); gap: 10px;
  background: transparent; position: relative; }

/* ── FULL-VIEWPORT Club House backdrop ──
   Twin of .splash-fx: position:FIXED + inset:0 so it fills the viewport and
   escapes #app's overflow clip. Carries the base night gradient + dual blooms +
   grids (top ceiling + bottom floor) + scanlines. Behind all content, no taps. */
.ch-fx { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  background:
    radial-gradient(120% 60% at 50% -8%, #1c1836 0%, transparent 55%),
    radial-gradient(80% 55% at 8% 8%,  rgba(46,139,255,.20) 0%, transparent 60%),
    radial-gradient(80% 55% at 92% 8%, rgba(255,59,82,.20) 0%, transparent 60%),
    radial-gradient(80% 50% at 8% 96%, rgba(46,139,255,.14) 0%, transparent 60%),
    radial-gradient(80% 50% at 92% 96%,rgba(255,59,82,.14) 0%, transparent 60%),
    linear-gradient(180deg, #08090f 0%, #0d0e1c 50%, #08090f 100%); }
/* the Club House blooms sit lower than the splash's (up near the header rail) so
   the grid frame reads and the header still pops. */
.ch-fx .fx-glow { opacity: .42; }
.ch-fx .fx-grid { height: 46%; opacity: .7; }

/* Everything below the backdrop rides above it. */
.ch-head, .quick-play, .ch-divide, .ch-list { position: relative; z-index: 1; }

/* Sticky header so Back / Refresh are ALWAYS reachable — never scroll off on a
   short window or a long room list. Glass + a red↔blue↔gold neon underline. */
.ch-head { position: sticky; top: 0; z-index: 6; display: flex; align-items: center; gap: 8px;
  padding: 6px 4px 12px; margin: 0;
  background: linear-gradient(180deg, rgba(8,9,15,.92) 55%, transparent);
  -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px); }
.ch-head::after { content: ''; position: absolute; left: 4px; right: 4px; bottom: 4px; height: 2px;
  border-radius: 2px; opacity: .8;
  background: linear-gradient(90deg, transparent, var(--blue-neon) 22%, var(--gold) 50%, var(--red-neon) 78%, transparent); }

/* Centered title stack: main title "Game Room" · "Who's got next?" tagline. */
.ch-title-block { flex: 1; min-width: 0; text-align: center; display: flex; flex-direction: column;
  align-items: center; line-height: 1.05; }
.ch-title { margin: 0; font-size: 23px; font-weight: 900; letter-spacing: .2px;
  text-shadow: 0 0 18px rgba(120,150,255,.35); }
/* RAINBOW-FX — the same red→gold→blue Barons gradient the Telestrations reveal
   uses on its origin word (.tele-reveal-word.orig), clipped to the glyphs. Reused,
   not reinvented. The text-shadow above is dropped here (it can't tint clipped
   text) in favor of a soft drop-shadow glow so the rainbow still reads as neon. */
.ch-title--rainbow { background: linear-gradient(100deg, var(--red), var(--gold) 60%, var(--blue));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  text-shadow: none; filter: drop-shadow(0 0 12px rgba(120,150,255,.4)); }
.ch-tag { display: block; font-size: 11px; font-weight: 700; letter-spacing: .6px;
  color: var(--ink-dim); margin-top: 3px; }

.ch-head-actions { display: flex; gap: 6px; flex: 0 0 auto; }
.ch-head-btn { background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line); border-radius: 10px;
  padding: 0 12px; font-size: 14px; font-weight: 700; color: var(--ink);
  min-width: 42px; min-height: 42px; display: grid; place-items: center; line-height: 1;
  transition: transform .08s ease, filter .15s ease, box-shadow .15s ease; }
.ch-head-btn:active { transform: scale(.95); }
/* Back chevron: optically centered. The literal ‹ glyph carries extra left
   side-bearing and a high baseline, so it looks off-center in a plain button.
   Fix: zero the literal glyph and render the chevron via ::before on the button's
   grid center, nudged +1px right to sit optically true. */
.ch-back { font-size: 0; font-weight: 400; }
.ch-back::before { content: '‹'; display: block; font-size: 26px; line-height: 1;
  transform: translate(1px, -1px); }

/* QUICK PLAY hero — LIVE. A lit red↔blue arcade slab that gently breathes, an
   arrow that nudges on press. This is the fast lane into a table. */
.quick-play { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 15px 18px; border-radius: 16px; color: #fff; border: 1px solid rgba(255,255,255,.18);
  background: linear-gradient(120deg, var(--blue) 0%, #7a53d6 50%, var(--red) 100%);
  box-shadow: 0 12px 30px -8px rgba(255,70,85,.5), 0 8px 26px -10px rgba(61,139,255,.55),
              inset 0 1px 0 rgba(255,255,255,.25);
  animation: qpBreath 5.5s ease-in-out infinite;
  transition: transform .1s ease, box-shadow .15s ease; }
.quick-play:active { transform: scale(.985);
  box-shadow: 0 8px 22px -8px rgba(255,70,85,.55), 0 6px 20px -10px rgba(61,139,255,.6),
              inset 0 1px 0 rgba(255,255,255,.25); }
@keyframes qpBreath {
  0%,100% { box-shadow: 0 12px 30px -8px rgba(255,70,85,.5), 0 8px 26px -10px rgba(61,139,255,.55), inset 0 1px 0 rgba(255,255,255,.25); }
  50%     { box-shadow: 0 14px 36px -8px rgba(255,70,85,.72), 0 10px 32px -10px rgba(61,139,255,.78), inset 0 1px 0 rgba(255,255,255,.3); }
}
.quick-play .qp-ico { flex: 0 0 auto; width: 44px; height: 44px; border-radius: 12px; display: grid;
  place-items: center; font-size: 24px; background: rgba(0,0,0,.22); box-shadow: inset 0 1px 0 rgba(255,255,255,.15); }
.quick-play .qp-main { flex: 1; min-width: 0; }
.quick-play .qp-t { display: block; font-size: 18px; font-weight: 900; letter-spacing: .2px; }
.quick-play .qp-s { display: block; font-size: 12px; opacity: .92; margin-top: 2px; }
.quick-play .qp-go { flex: 0 0 auto; font-size: 26px; font-weight: 700; opacity: .85; line-height: 1;
  transition: transform .15s ease; }
.quick-play:active .qp-go { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) { .quick-play { animation: none; } }

.ch-divide { text-align: center; color: var(--ink-dim); font-size: 10.5px;
  font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; margin: 2px 4px; display: flex;
  align-items: center; gap: 10px; }
.ch-divide::before, .ch-divide::after { content: ''; flex: 1; height: 1px; background: var(--line); }

.ch-list { display: flex; flex-direction: column; gap: 10px; padding-bottom: calc(var(--safe-b) + 14px); }
.ch-card { display: flex; align-items: center; gap: 12px; text-align: left; width: 100%;
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, rgba(31,36,64,.92), rgba(23,27,48,.92));
  border: 1px solid var(--line); border-radius: 14px; padding: 14px 16px;
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,.05);
  transition: transform .08s ease, border-color .15s ease, box-shadow .15s ease; }
/* a thin red↔blue neon spine down the left edge — the Barons heart */
.ch-card::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(180deg, var(--blue-neon), var(--red-neon)); opacity: .85; }
.ch-card:active { transform: scale(.98); border-color: var(--line-cold);
  box-shadow: var(--shadow), 0 0 20px -6px var(--blue-neon); }
.ch-card--full { opacity: .55; }

/* LIVE (in-progress) tables — joinable/watchable. The spine glows and softly
   pulses, a "LIVE" tint washes the fill, and a status dot ticks. Distinct from
   an open lobby card but unmistakably the same family. */
.ch-card--live { border-color: var(--line-hot);
  background: linear-gradient(160deg, rgba(45,30,44,.92), rgba(28,24,44,.92)); }
.ch-card--live::before { width: 4px; opacity: 1;
  box-shadow: 0 0 12px -1px var(--red-neon); animation: chLiveSpine 2.4s ease-in-out infinite; }
@keyframes chLiveSpine { 0%,100% { opacity: .7; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .ch-card--live::before { animation: none; } }

/* Game glyph badge, tinted per game (Sorry! = red, Telestrations = blue). */
.ch-card-badge { flex: 0 0 auto; width: 46px; height: 46px; border-radius: 12px; display: grid;
  place-items: center; font-size: 24px; }
.ch-card--sorry .ch-card-badge { background: radial-gradient(circle at 35% 30%, #ff7a86, var(--red-dk)); }
.ch-card--telestrations .ch-card-badge { background: radial-gradient(circle at 35% 30%, #7db0ff, var(--blue-dk)); }
.ch-card-main { flex: 1; min-width: 0; }
.ch-card-host { display: block; font-weight: 800; font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-card-game { display: block; color: var(--ink-dim); font-size: 12.5px; margin-top: 2px; }
/* subtle hint under a live card when bots hold seats — an invitation to take over */
.ch-bots-hint { display: block; font-size: 11px; font-weight: 700; margin-top: 4px;
  color: var(--gold); opacity: .85; letter-spacing: .2px; }
/* live face-pips: mini pawns for who's seated (Presence at a glance). Filled pips
   = seated humans (playerCount), dashed = open chairs (up to maxSeats). We only
   have counts from rooms_list, so pips are count-accurate, not per-identity. */
.ch-pips { display: flex; gap: 3px; margin-top: 6px; }
.ch-pip { width: 13px; height: 15px; border-radius: 50% 50% 45% 45% / 60% 60% 40% 40%;
  background: var(--pc, var(--ink-dim)); box-shadow: 0 0 6px var(--pc, transparent); }
.ch-pip--empty { background: transparent; border: 1px dashed var(--line); box-shadow: none; }
.ch-card-right { flex: 0 0 auto; text-align: right; display: flex; flex-direction: column;
  align-items: flex-end; gap: 6px; }
.ch-card-players { display: block; font-weight: 900; font-size: 17px; letter-spacing: -.2px; white-space: nowrap; }
.ch-card-players b { color: var(--gold); }
/* join affordance on OPEN cards. `.ch-cta` is the contract name; `.ch-card-cta`
   is kept as an alias so the current renderRooms() markup styles correctly until
   CODE renames it (see the hand-off note). */
.ch-cta, .ch-card-cta { display: block; color: var(--ink-dim); font-size: 11px; white-space: nowrap; }
.ch-card:active .ch-cta, .ch-card:active .ch-card-cta { color: var(--blue-neon); }
/* LIVE badge chip on in-progress cards (CODE may drop it in .ch-card-right) */
.ch-live-tag { display: inline-flex; align-items: center; gap: 5px; font-size: 10px; font-weight: 900;
  letter-spacing: 1.2px; color: var(--red-glow); text-transform: uppercase; }
.ch-live-tag::before { content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--red-neon); box-shadow: 0 0 8px var(--red-neon); animation: chLiveDot 1.6s ease-in-out infinite; }
@keyframes chLiveDot { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .ch-live-tag::before { animation: none; } }

/* WATCH — a clear secondary action on watchable/live cards. Ghost-glass pill so
   it reads as "look, don't touch the game" next to the loud primary join/live. */
.ch-watch { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
  padding: 7px 12px; border-radius: 10px; font-size: 12.5px; font-weight: 800; color: var(--ink);
  background: rgba(12,14,26,.55); border: 1px solid var(--line-cold);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
  transition: transform .08s ease, box-shadow .15s ease, border-color .15s ease; }
.ch-watch:active { transform: scale(.94); border-color: var(--blue-neon);
  box-shadow: 0 0 16px -6px var(--blue-neon); }

.ch-empty { text-align: center; color: var(--ink-dim); font-size: 14px; padding: 40px 16px;
  background: linear-gradient(160deg, rgba(31,36,64,.9), rgba(23,27,48,.9));
  border: 1px dashed var(--line-cold); border-radius: 14px; }
.ch-empty b { color: var(--ink); }

/* ── SPECTATOR + TAKEOVER chrome (CSS only; CODE toggles #app.is-spectating) ──
   Read-only-but-joinable feel: a persistent "Spectating" banner and a bright
   "Take over" control that surfaces on bot seats during a spectated game.
   The banner is a top-center OVERLAY — a direct #app child OUTSIDE every .screen
   (like #brand-bar), so it floats over whichever screen is active while watching.
   Absolute vs #app (which is position:relative); high z-index above all screen
   chrome. Default hidden; #app.is-spectating reveals it. */
.spectate-bar { display: none; position: absolute; top: calc(var(--safe-t) + 8px);
  left: 50%; transform: translateX(-50%); z-index: 40; pointer-events: none;
  align-items: center; justify-content: center; gap: 8px;
  padding: 7px 16px; border-radius: 999px; white-space: nowrap;
  font-size: 12px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; color: var(--blue-glow);
  background: linear-gradient(180deg, rgba(46,139,255,.22), rgba(10,14,28,.9));
  border: 1px solid var(--line-cold);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  box-shadow: 0 8px 22px -8px rgba(0,0,0,.6), 0 0 20px -8px var(--blue-neon), inset 0 1px 0 rgba(255,255,255,.08); }
#app.is-spectating .spectate-bar { display: flex; }
.spectate-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--blue-neon);
  box-shadow: 0 0 10px var(--blue-neon); animation: chLiveDot 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .spectate-dot { animation: none; } }
/* "Take over" — offered on a bot seat while spectating. Gold arcade pill: the one
   loud, inviting action on an otherwise read-only screen. */
.ch-takeover { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 14px; border-radius: 999px; font-size: 12.5px; font-weight: 900; letter-spacing: .4px;
  color: var(--yellow-ink); border: 1px solid var(--gold-neon);
  background: linear-gradient(180deg, var(--gold-neon), var(--gold));
  box-shadow: 0 6px 18px -6px rgba(255,209,102,.6), inset 0 1px 0 rgba(255,255,255,.4);
  transition: transform .08s ease, box-shadow .15s ease; }
.ch-takeover:active { transform: scale(.94);
  box-shadow: 0 4px 12px -6px rgba(255,209,102,.7), inset 0 1px 0 rgba(255,255,255,.4); }

/* ═══════════ LOBBY ═══════════ */
[data-for="lobby"] { gap: 16px; padding-top: calc(var(--safe-t) + 44px); }

/* ── FULL-VIEWPORT framed backdrop ──
   Twin of .ch-fx / .splash-fx: position:FIXED + inset:0 so it fills the viewport
   and escapes #app's overflow clip. Carries the base night gradient + dual blooms
   + grids (top ceiling + bottom floor) + scanlines. Behind all content, no taps.
   Reuses the GLOBAL .fx-glow / .fx-grid / .fx-grid--top / .fx-scan primitives. */
.lobby-fx { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  background:
    radial-gradient(120% 60% at 50% -8%, #1c1836 0%, transparent 55%),
    radial-gradient(80% 55% at 8% 8%,  rgba(46,139,255,.20) 0%, transparent 60%),
    radial-gradient(80% 55% at 92% 8%, rgba(255,59,82,.20) 0%, transparent 60%),
    radial-gradient(80% 50% at 8% 96%, rgba(46,139,255,.14) 0%, transparent 60%),
    radial-gradient(80% 50% at 92% 96%,rgba(255,59,82,.14) 0%, transparent 60%),
    linear-gradient(180deg, #08090f 0%, #0d0e1c 50%, #08090f 100%); }
.lobby-fx .fx-glow { opacity: .42; }
.lobby-fx .fx-grid { height: 46%; opacity: .7; }

/* Everything in the lobby rides ABOVE the backdrop (same trick as .ch-fx). */
.lobby-head, .game-strip, .game-grid, .seats-wrap, .lobby-foot {
  position: relative; z-index: 1; }

.lobby-head { text-align: center; }
/* TABLE CODE — a compact chip OVERLAID on the top ceiling-grid band. Glassy +
   near-transparent so the neon grid reads THROUGH it (a heavier blur keeps the
   code legible over the moving grid); nudged up into the top frame. It's a quiet
   reference the host can read aloud or copy; the invite link sits right beside. */
.room-code { display: inline-flex; flex-direction: row; align-items: center; gap: 8px;
  margin-top: -8px; padding: 5px 8px 5px 12px; border-radius: 999px;
  background: linear-gradient(180deg, rgba(18,20,34,.34), rgba(10,12,22,.28));
  border: 1px solid rgba(255,255,255,.12);
  -webkit-backdrop-filter: blur(7px) saturate(1.1); backdrop-filter: blur(7px) saturate(1.1);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08), 0 4px 18px -10px rgba(0,0,0,.6); }
/* "TABLE" rides the rainbow-fx text treatment (via .ch-title--rainbow in the
   markup); size + tracking are tuned here so the clipped gradient still reads. */
.room-code-label { font-size: 10px; font-weight: 900; letter-spacing: 2px; }
/* the code glyphs carry the rainbow-fx (.ch-title--rainbow); we only size them
   here and leave background/color to that class so the clip-to-text reads. */
.room-code-value { font-size: 16px; font-weight: 900; letter-spacing: 2px; }
.room-code-value:active { transform: scale(.96); }
.room-code-copy { min-height: 34px; padding: 6px 12px; border-radius: 999px; }
.section-title { font-size: 13px; color: var(--ink-dim); letter-spacing: 1px; text-transform: uppercase; margin: 0 0 8px; }

/* ── compact GAME STRIP (the table's game at a glance) ── */
.game-strip { position: relative; z-index: 1; display: flex; align-items: center; gap: 10px;
  background: linear-gradient(160deg, var(--panel-2), var(--panel)); border: 1px solid var(--line);
  border-radius: 12px; padding: 11px 14px; box-shadow: inset 0 1px 0 rgba(255,255,255,.04); }
.game-strip .gs-ico { width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  font-size: 20px; background: radial-gradient(circle at 35% 30%, #ff7a86, var(--red-dk)); flex: 0 0 auto; }
.game-strip.is-tele .gs-ico { background: radial-gradient(circle at 35% 30%, #7db0ff, var(--blue-dk)); }
.game-strip .gs-main { flex: 1; min-width: 0; }
.game-strip .gs-t { display: block; font-weight: 900; font-size: 15px; }
.game-strip .gs-s { display: block; font-size: 11.5px; color: var(--ink-dim); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⚙️ cog — a subtle round button that toggles the Game Settings sheet (host). */
.game-strip .gs-cog { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; font-size: 18px; color: var(--ink-dim);
  background: var(--bg-2); border: 1px solid var(--line);
  transition: transform .12s ease, background .14s ease, box-shadow .14s ease; }
.game-strip .gs-cog:active { transform: scale(.92) rotate(20deg); }
.game-strip .gs-cog[aria-expanded="true"] { color: var(--ink); background: rgba(61,139,255,.14);
  border-color: var(--line-cold); box-shadow: 0 0 14px -6px var(--blue-neon); }

/* ── GAME SELECTION grid (host-only): always-visible 2×2 of games ── */
.game-grid { position: relative; z-index: 1; padding: 12px; }
.game-grid .game-pick { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.game-grid .game-tile { position: relative; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; min-height: 92px; padding: 14px 10px;
  border-radius: 14px; background: var(--bg-2); border: 1px solid var(--line); color: var(--ink);
  transition: transform .1s ease, border-color .14s ease, box-shadow .16s ease, background .14s ease; }
.game-grid .game-tile:active { transform: scale(.97); }
.game-grid .game-tile-emoji { font-size: 26px; line-height: 1; }
.game-grid .game-tile-name { font-size: 13px; font-weight: 800; text-align: center; }
/* selected game — a red↔blue Barons wash + lift */
.game-grid .game-tile.is-active { border-color: transparent; color: #fff;
  background: linear-gradient(135deg, var(--blue) 0%, #7a53d6 55%, var(--red) 100%);
  box-shadow: 0 8px 22px -8px rgba(255,70,85,.5), inset 0 1px 0 rgba(255,255,255,.2); }
/* "Soon" tiles (Blank Slate / Uno) — visible but disabled + a corner badge */
.game-grid .game-tile[data-tbd] { opacity: .5; filter: saturate(.7); cursor: default; }
.game-grid .game-tile[data-tbd]:active { transform: none; }
.game-grid .game-tile-soon { position: absolute; top: 6px; right: 6px; font-size: 8.5px;
  font-weight: 900; letter-spacing: .6px; text-transform: uppercase; padding: 2px 6px; border-radius: 999px;
  background: rgba(154,162,196,.22); color: var(--ink-dim); }
/* "At the table" header — title left, a live count / max on the right. */
.seats-title { display: flex; align-items: baseline; justify-content: space-between; }
.seats-title .seat-count { font-size: 13px; font-weight: 900; letter-spacing: .5px;
  color: var(--gold); text-transform: none; }
/* Sorry! tops out at 4 seats, Telestrations at 8 — a plain vertical stack
   grows cleanly either way. The whole lobby screen already scrolls
   (.screen { overflow-y: auto }), so an 8-seat table just makes that page
   taller rather than squeezing the Start/Ready buttons off-screen. */
.seat-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.seat { display: flex; align-items: center; gap: 11px; position: relative; overflow: hidden;
  background: linear-gradient(160deg, var(--panel-2), var(--panel)); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px 14px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04);
  animation: seatIn .28s cubic-bezier(.2,.9,.3,1); }
@keyframes seatIn { from { opacity: 0; transform: translateY(-6px) scale(.98); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .seat { animation: none; } }
/* the seat's left edge glows in that player's identity hue (--pc from JS) */
.seat::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--pc, var(--line-cold)); opacity: .8; box-shadow: 0 0 10px var(--pc, transparent); }
.seat--off { opacity: .5; }
.seat--you { border-color: var(--line-cold);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04), 0 0 18px -8px var(--blue-neon); }
/* EMPTY CHAIR — a quiet placeholder so the table reads as a table with open
   seats. Dim, dashed, no identity glow, no animation. Just a chair outline +
   "Open seat"; capped at 4 in renderSeats(). */
.seat--empty { background: rgba(18,20,34,.28); border-style: dashed;
  border-color: rgba(154,162,196,.22); box-shadow: none; animation: none; opacity: .62; }
.seat--empty::before { display: none; }   /* no identity-hue edge glow */
.seat-pawn--empty { background: transparent; box-shadow: none;
  border: 1.5px dashed rgba(154,162,196,.5); }
.seat-name--empty { color: var(--ink-dim); font-weight: 700; letter-spacing: .3px; }
.seat-pawn { width: 20px; height: 24px; background: var(--pc); border-radius: 50% 50% 45% 45% / 60% 60% 40% 40%;
  box-shadow: 0 0 10px var(--pc); flex: 0 0 auto; position: relative; }
/* ready = a green ✓ that POPS onto the pawn (nicer than a flat tag) */
.seat--ready .seat-pawn::after { content: '✓'; position: absolute; right: -6px; bottom: -4px;
  font-size: 10px; font-weight: 900; color: #06210f; background: var(--green); border-radius: 50%;
  width: 15px; height: 15px; display: grid; place-items: center; box-shadow: 0 0 8px var(--green);
  animation: readyPop .3s cubic-bezier(.2,1.5,.4,1); }
@keyframes readyPop { from { transform: scale(0); } to { transform: scale(1); } }
.seat-name { font-weight: 700; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seat-sub { display: block; font-size: 11px; font-weight: 600; color: var(--ink-dim); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seat-tags { display: flex; gap: 5px; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; }
.tag { font-size: 10px; font-weight: 800; padding: 3px 6px; border-radius: 6px; letter-spacing: .5px; }
.tag--host { background: rgba(255,209,102,.2); color: var(--gold); }
.tag--bot { background: rgba(154,162,196,.2); color: var(--ink-dim); }
.tag--ready { background: rgba(55,214,122,.2); color: #6ff0a0; }
.tag--you { background: rgba(61,139,255,.25); color: #9fc4ff; }
.seat-x { background: transparent; color: var(--ink-dim); font-size: 20px; line-height: 1; padding: 0 4px; flex: 0 0 auto; }
.seat-x:active { transform: scale(.9); }

.settings { padding: 14px 16px; }
/* Inside the #settings-modal the cards are bare (no card chrome) — the modal-card
   is the container, so drop the padding + let rows own their spacing. */
.settings-card .settings { padding: 0; }
.settings-modal-title { margin-bottom: 14px; padding-right: 28px; }
.settings-card .settings .setting-row--stack:first-child { margin-top: 0; }
.setting-row { display: flex; align-items: center; justify-content: space-between; }
.setting-label { font-weight: 600; }
.setting-note { font-size: 12px; color: var(--ink-dim); margin: 8px 0 0; }
.setting-row--stack { flex-direction: column; align-items: stretch; gap: 8px; margin-top: 12px; }
.setting-row--stack .setting-label { margin-bottom: 2px; }

/* segmented control (bot difficulty) — matches the stepper's material feel */
.seg { display: flex; gap: 4px; background: var(--bg-2); border: 1px solid var(--line);
  border-radius: 12px; padding: 4px; }
.seg-btn { flex: 1 1 0; min-height: 44px; border-radius: 9px; background: transparent;
  color: var(--ink-dim); font-size: 14px; font-weight: 800; letter-spacing: .2px;
  transition: background .12s ease, color .12s ease, box-shadow .12s ease; }
.seg-btn:active { transform: scale(.97); }
.seg-btn.is-active { color: #fff; background: linear-gradient(120deg, var(--red), var(--red-dk));
  box-shadow: 0 4px 12px -4px var(--red), inset 0 1px 0 rgba(255,255,255,.15); }

/* The lobby action bar. It follows DIRECTLY under the seat list with a small gap
   (the section's `gap:16px`) — no `margin-top:auto` void pushing it to the
   viewport bottom. It STILL sticks to the bottom of the scroll area so on a tall
   window the Start/Ready button stays in view; but when the content is short, it
   sits snug against the table rather than floating in dead space. A soft top
   rule + a faint backdrop keep it legible over any scrolled content beneath.
   Layout: the primary action (Start/Ready) sits BESIDE "How to play"; the ⏻
   Leave power button is centered on its own row below, last. */
.lobby-foot { position: sticky; bottom: 0; z-index: 5;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  margin-top: 2px; padding: 12px 0 calc(var(--safe-b) + 2px);
  background: linear-gradient(180deg, transparent, rgba(8,9,15,.72) 34%);
  border-top: 1px solid rgba(255,255,255,.08); }
/* the two-up primary row: Start/Ready (grows) beside How-to-play */
.lobby-foot-row { display: flex; width: 100%; gap: 10px; align-items: stretch; }
/* Full-width-within-row CTAs. `display:block` is explicit so a button can never
   collapse to a zero-size inline box on any engine. */
.lobby-foot-row .btn--primary, .lobby-foot-row .btn--ghost { display: block; flex: 1 1 0; min-width: 0; }
.lobby-foot-row .btn--primary { position: relative; z-index: 1; min-height: 52px; }
.lobby-foot-row .btn--ghost { min-height: 52px; }
/* ⏻ Leave — a subtle round power-down button, centered + bottom-most. */
.lobby-foot .btn--leave { width: 46px; height: 46px; min-height: 0; flex: 0 0 auto; padding: 0;
  border-radius: 50%; display: grid; place-items: center; font-size: 20px; line-height: 1;
  color: var(--ink-dim); background: var(--bg-2); border: 1px solid var(--line);
  transition: transform .1s ease, color .14s ease, border-color .14s ease, box-shadow .16s ease; }
.lobby-foot .btn--leave:active { transform: scale(.92); }
.lobby-foot .btn--leave:hover { color: var(--red-neon); border-color: rgba(255,70,85,.45);
  box-shadow: 0 0 14px -6px var(--red); }

/* ═══════════ GAME ═══════════ */
/* Layout goal: the BOARD is the hero. A slim standings rail sits up top, the
   board fills all remaining height, and the turn banner + action dock stack at
   the very bottom within thumb reach. Everything is flex-column, no page scroll. */
/* ── The game screen: a CENTERED stack (player bar · status bar · board · deck)
   so PORTRAIT fills the screen like tablet-landscape — any leftover height splits
   evenly above/below the group instead of pooling into one dead gap (brief C). The
   board is the flex hero and eats the middle; the small chrome hugs it. Near-zero
   side padding lets the square board fill the full device width. */
/* NOTE: .screen already sets position:absolute; inset:0 (the containing block for
   the .stage-fx backdrop) — we must NOT override position here or the section stops
   filling #app and the board collapses. Just tune the game-screen flow. */
/* ── A1 FIX: RE-IMAGINED VERTICAL STACK ───────────────────────────────────────
   Old: the whole stack was justify:center, so on tall screens the leftover height
   split evenly ABOVE and below the centered square board — and the top slice read
   as a dead seam between the status bar and the board (the "A1" gap).
   New order (top→bottom): ① TOP ZONE → ② player bar → ③ status bar → ④ board.
   The stack is justify:flex-start; the TOP ZONE (.game-top, flex:1) DELIBERATELY
   ABSORBS ALL the flexible slack and turns it into an intentional grid-backed
   band (reactions + chat). The player bar + status bar are fixed-height and hug
   the board; the board fills the remaining height. Result: the status bar sits
   FLUSH on the board's top edge with NO gap on mobile / desktop / tablet. */
/* TOP-GAP FIX (brief 1b): the stack hugs the TOP; the top zone (flex:1) absorbs
   ALL the flexible slack so there is NEVER a void below the bottom strip. The zone
   no longer reads as a "dead" band because it's now a fully-FILLED neon table-side
   band (drift-grid + strengthened horizon bloom + the chat feed) — intentional
   atmosphere, not empty dark. See .game-top. */
[data-for="game"] {
  padding: calc(var(--safe-t) + 6px) 0 calc(var(--safe-b) + 6px);
  gap: 0; overflow: hidden; justify-content: flex-start; }

/* ── (①) TOP ZONE — the reclaimed slack, now an intentional table-side band ──
   flex:1 so it EATS every spare pixel that used to pool as the A1 seam. It has a
   sensible min-height (so it always reads as a real zone, never a hairline) and
   a max so it can't starve the board on very tall screens. Grid-backed via its
   own ::grid child; reactions float up through it, chat rests at its base. */
/* TOP GAP FIX (brief 1b): the zone absorbs ALL slack (flex:1 — no bottom void) but
   no longer reads as a DEAD band. Its drift-grid + a strengthened red↔blue horizon
   bloom (see ::before) now FILL it as intentional "space above the table", with the
   chat resting at its base. So a tall phone shows a lit, atmospheric top band, not an
   empty black void. Capped only so it can't starve the board on very tall screens. */
/* SPACING FIX (James, definitive): the top zone ABSORBS ALL slack. The board is a
   TRUE width-square below it and the bars hug the board, so every leftover pixel of
   the tall screen flows UP here — "all space at the top". No max-height cap (that
   used to starve this zone and let slack pool as gaps below); just a sensible
   min-height so it always reads as a real band. It grows as tall as the screen
   allows once the width-square board + bars are placed. */
.game-top { position: relative; z-index: 2; flex: 1 1 auto;
  min-height: 54px;
  display: flex; flex-direction: column; justify-content: flex-end;
  margin: 0 8px 0; padding: 0; overflow: hidden; border-radius: 14px 14px 4px 4px; }
/* the drift-grid that BACKS the top zone: a receding neon floor read as the space
   "above the table". Masked so it dissolves at the top (no hard edge under the
   safe area) and fades toward the player bar so the join reads soft. GPU-cheap. */
.game-top-grid { position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    repeating-linear-gradient(to right, rgba(130,160,255,.30) 0 1.5px, transparent 1.5px 42px),
    repeating-linear-gradient(to top,   rgba(130,160,255,.30) 0 1.5px, transparent 1.5px 40px);
  transform: perspective(300px) rotateX(56deg); transform-origin: bottom center;
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.7) 30%, rgba(0,0,0,.4) 66%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.7) 30%, rgba(0,0,0,.4) 66%, transparent 100%);
  opacity: .9; }
/* a red↔blue horizon glow that FILLS the top zone (brief 1b): strengthened blooms +
   a soft vertical base wash so the whole band reads as lit, atmospheric "space above
   the table" rather than an empty dark void — even on a tall phone. Warmth, not a
   hard band; the drift-grid rides over it and the chat rests at its base. */
.game-top::before { content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    /* a distant CITY-HORIZON glow high in the zone so the upper band reads lit */
    radial-gradient(120% 46% at 26% 8%, rgba(46,139,255,.22), transparent 64%),
    radial-gradient(120% 46% at 74% 8%, rgba(255,59,82,.22), transparent 64%),
    /* mid blooms flanking the horizon */
    radial-gradient(90% 90% at 14% 34%, rgba(46,139,255,.22), transparent 60%),
    radial-gradient(90% 90% at 86% 34%, rgba(255,59,82,.22), transparent 60%),
    /* soft violet depth wash filling the whole band top-to-bottom */
    radial-gradient(120% 90% at 50% 0%, rgba(44,36,80,.6), transparent 72%),
    linear-gradient(180deg, rgba(20,22,42,.55) 0%, rgba(16,18,34,.2) 55%, transparent 82%); }

/* CHAT FEED — recent lines, anchored to the BOTTOM of the top zone (just over the
   player bar). Fades OLDER lines toward the top so the newest is brightest and the
   feed dissolves into the grid rather than butting a hard edge. Reactions float
   over it. Kept lightweight: a few lines, no scrollbar chrome fighting for focus. */
.game-chat { position: relative; z-index: 1; display: flex; flex-direction: column;
  justify-content: flex-end; gap: 3px; max-height: 100%; overflow: hidden;
  padding: 0 12px 5px; pointer-events: none; transition: opacity .5s ease;
  -webkit-mask-image: linear-gradient(to top, #000 62%, transparent 100%);
          mask-image: linear-gradient(to top, #000 62%, transparent 100%); }
/* Auto-fade after a short idle; a new line un-fades it (messages are never removed). */
.game-chat.is-faded { opacity: 0; }
.game-chat-line { font-size: 12.5px; line-height: 1.3; word-break: break-word;
  text-shadow: 0 1px 3px rgba(0,0,0,.6);
  animation: gChatIn .28s cubic-bezier(.2,.9,.3,1); }
.game-chat-line b { color: var(--blue-neon); font-weight: 800; }
.game-chat-line.is-me b { color: var(--red-neon); }
.game-chat-line .sys { color: var(--ink-dim); font-style: italic; }
@keyframes gChatIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .game-chat-line { animation: none; } .game-chat { transition: none; } }

/* floating emoji REACTIONS — rise up through the top zone (emotes already wired). */
.game-top-emotes { position: absolute; inset: 0; z-index: 2; pointer-events: none; overflow: hidden; }
.game-top-emotes .emote-float { position: absolute; bottom: 2%; font-size: 34px;
  filter: drop-shadow(0 2px 6px rgba(0,0,0,.5));
  animation: gEmoteRise 1.7s ease forwards; }
@keyframes gEmoteRise {
  0%   { opacity: 0; transform: translateY(14px) scale(.6); }
  14%  { opacity: 1; transform: translateY(2px) scale(1.12); }
  100% { opacity: 0; transform: translateY(-92%) scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .game-top-emotes .emote-float { animation: gEmoteFade 1.4s ease forwards; }
  @keyframes gEmoteFade { 0% { opacity: 0; } 15% { opacity: 1; } 100% { opacity: 0; } }
}

/* ── CYBERPUNK STAGE on the game screen (brief #2) ──
   The splash's tech-grid + red/blue neon blooms + faint scanlines, brought onto the
   board as real Tokyo-drift atmosphere. Absolutely fills the game section BEHIND the
   stack (z-index 0). The board panel (canvas felt) is semi-opaque so the grid reads
   clearly in the margins/backdrop while tiles + pawns stay instantly legible.
   James's call: CLEARLY present, not the whisper-faint mockup level. */
[data-for="game"] .stage-fx { position: absolute; inset: 0; z-index: 0; overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(120% 62% at 50% -6%, #221a3d 0%, transparent 58%),
    radial-gradient(78% 60% at 6% 8%, rgba(46,139,255,.28) 0%, transparent 60%),
    radial-gradient(78% 60% at 94% 8%, rgba(255,59,82,.28) 0%, transparent 60%),
    radial-gradient(80% 55% at 8% 96%, rgba(46,139,255,.20) 0%, transparent 62%),
    radial-gradient(80% 55% at 92% 96%, rgba(255,59,82,.20) 0%, transparent 62%),
    linear-gradient(180deg, #0c0d1c 0%, #10121f 50%, #0c0d1c 100%); }
/* the game stack sits ABOVE the stage */
[data-for="game"] > .player-rail,
[data-for="game"] > .status-bar,
[data-for="game"] > .board-stage,
[data-for="game"] > .game-bottom { position: relative; z-index: 2; }
/* NOTE (regression fix): the game-screen perspective GRIDS used to live here as two
   full-screen bands (.fx-grid bottom + .fx-grid--top). That produced a duplicate top
   grid behind the new .game-top zone (grid → gap → grid) and a bottom band the square
   board grew down over and collided with. Both grids now live in their proper zones:
   the TOP grid is .game-top-grid (inside the top zone), the BOTTOM grid is the
   .game-bottom STRIP below the board (see §⑤). .stage-fx is now pure ambient backdrop
   (base gradient + neon glows + scanlines) behind the whole stack. */
[data-for="game"] .fx-glow { position: absolute; width: 64vw; height: 64vw;
  max-width: 560px; max-height: 560px; border-radius: 50%; filter: blur(70px);
  opacity: .42; mix-blend-mode: screen; animation: fxPulse 7s ease-in-out infinite; }
[data-for="game"] .fx-glow--red  { bottom: -16%; right: -14%;
  background: radial-gradient(circle, var(--red-neon), transparent 70%); }
[data-for="game"] .fx-glow--blue { top: -16%; left: -14%;
  background: radial-gradient(circle, var(--blue-neon), transparent 70%); animation-delay: -3.5s; }
[data-for="game"] .fx-scan { position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,.022) 0 1px, transparent 1px 3px);
  opacity: .5; mix-blend-mode: overlay; }

/* ── (②) PLAYER BAR — compact seat chips, all 4 fit with NO horizontal scroll ──
   Each chip flexes to share the row (flex:1) and truncates rather than scrolling.
   Explicit top gap (from the top zone) + a small gap to the status bar; the stack
   itself has gap:0 so the status-bar → board join stays flush. */
.player-rail { display: flex; gap: 6px; padding: 0 8px; flex: 0 0 auto;
  margin: 6px 0; }
.pchip { flex: 1 1 0; min-width: 0; position: relative; display: flex; align-items: center; gap: 5px;
  background: var(--glass); border: 1px solid var(--line); border-radius: 12px;
  padding: 6px 7px; overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04); }
/* a thin colored spine on the left edge = whose chip this is, at a glance */
.pchip::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--pc); box-shadow: 0 0 10px var(--pc); }
.pchip--turn { border-color: var(--pc);
  box-shadow: 0 0 0 1px var(--pc), 0 0 20px -6px var(--pc), inset 0 1px 0 rgba(255,255,255,.06);
  animation: chipTurn 1.8s ease-in-out infinite; }
@keyframes chipTurn { 50% { box-shadow: 0 0 0 1px var(--pc), 0 0 26px -4px var(--pc); } }
.pchip--off { opacity: .45; filter: grayscale(.4); }
/* color swatch */
.pchip-sw { flex: 0 0 auto; width: 14px; height: 14px; border-radius: 5px; background: var(--pc);
  box-shadow: 0 0 10px -1px var(--pc), inset 0 0 0 1px rgba(255,255,255,.3); }
.pchip--turn .pchip-sw { animation: swPulse 1.1s ease-in-out infinite; }
@keyframes swPulse { 0%,100% { box-shadow: 0 0 8px -1px var(--pc); } 50% { box-shadow: 0 0 16px 1px var(--pc); } }
/* two-letter label (initials / bot number). The 2 glyphs are SACRED — they must
   ALWAYS show in full (JA / CO / B1 / B2), never truncate to "B…" (B). So the label
   never shrinks or ellipses: it holds a fixed min-width sized for two bold glyphs and
   never flexes below it. The optional bot glyph rides beside it and yields first when
   space is tight. */
.pchip-id { font-size: 12.5px; font-weight: 900; letter-spacing: .2px; white-space: nowrap;
  flex: 0 0 auto; min-width: 1.9em; /* ≈ two 900-weight glyphs — never squeezed */ }
.pchip-bot { font-size: 8.5px; opacity: .7; flex: 0 0 auto; }
/* home/pawn count, pushed to the right */
.pchip-home { margin-left: auto; flex: 0 0 auto; font-size: 12px; font-weight: 900; color: var(--gold);
  letter-spacing: .3px; }
.pchip-home small { font-size: 9px; font-weight: 800; opacity: .6; color: var(--ink-dim); }

/* ── (B) STATUS BAR — a real docked bar: turn ping + the "what can I do now" line.
   It carries whose turn it is AND the current action instructions / pending prompts
   (renderPending fills #turn-msg). Replaces the old floating turn-pill. ── */
/* NEUTRAL + GLOW (brief 1c): the yellow/gold "your turn" frame is GONE. The board
   below now FLOATS free (frameless), so the bar no longer docks onto it — it's a
   fully-rounded neon pill with a cool slate/white hairline + a soft glow VFX at all
   times. The your-turn state just BRIGHTENS that neutral edge + glow (never gold);
   the seat-colored status dot pulses to keep the "it's you" cue discernible. A small
   bottom margin gives clean separation between the bar and the floating board. */
/* SPACING FIX: the status bar HUGS the board — margin-bottom:0, no seam between the
   bar and the board's top edge (James's lock: "bars hug the board"). */
.status-bar { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  margin: 0 8px 0; padding: 9px 12px; border-radius: 14px; position: relative; overflow: hidden;
  background: var(--glass); border: 1px solid rgba(198,214,255,.30);
  box-shadow: 0 0 18px -6px rgba(150,180,255,.42), inset 0 1px 0 rgba(255,255,255,.06); }
.status-ping { flex: 0 0 auto; display: flex; align-items: center; gap: 7px; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--pc);
  box-shadow: 0 0 10px var(--pc); }
.status-bar.is-you { border-color: rgba(224,236,255,.62);
  box-shadow: 0 0 0 1px rgba(198,214,255,.40), 0 0 26px -6px rgba(160,190,255,.75),
              inset 0 1px 0 rgba(255,255,255,.08); }
.status-bar.is-you .status-dot { box-shadow: 0 0 14px var(--pc);
  animation: swPulse 1s ease-in-out infinite; }
.status-bar .turn-who { font-size: 14px; font-weight: 900; letter-spacing: .2px; white-space: nowrap; }
.status-bar.is-you .turn-who { color: #eaf1ff; text-shadow: 0 0 12px rgba(170,200,255,.6); }
.status-sep { width: 1px; align-self: stretch; background: var(--line); margin: 1px 0; }
.status-bar .turn-msg { font-size: 12.5px; color: var(--ink-dim); line-height: 1.25; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; }
.status-bar .turn-msg:empty { display: none; }
.status-bar .turn-msg b { color: var(--ink); font-weight: 800; }
/* A flashed gameplay moment (bump / slide / swap) briefly brightens the status line. */
.status-bar .turn-msg.is-moment { color: var(--ink); font-weight: 800;
  text-shadow: 0 0 10px rgba(170,200,255,.55); }

/* The board stage is the HERO. The renderer centers a SQUARE board in this box and
   reports geometry relative to it (Board.onGeo → positionDeck). The A1 fix: on a
   tall PORTRAIT screen the board is width-limited, so we size the stage to a SQUARE
   (aspect-ratio:1) — the board fills it edge-to-edge with NO internal empty band,
   and ALL leftover height flows UP into the top zone (which absorbs it as intentional
   space) instead of pooling as a seam under the status bar. The stage is capped at the
   available height (max-height) so it can never overflow; if height is the limiter
   (landscape / short), it shrinks and the board tracks it. The status bar sits
   directly on this box's top edge → flush, no gap. */
/* SPACING FIX (James, definitive): the stage is a TRUE SQUARE, EXACTLY width×width.
   flex:0 0 auto + no max-height cap means it can NEVER be shrunk or squashed
   non-square — so min(W,H)=W in board.js buildGeo and the board fills the stage with
   only the tiny pad (the old symmetric internal-centering gap disappears with zero
   changes to board.js). The board is the HERO: as big as the width allows. */
.board-stage { position: relative; flex: 0 0 auto; min-height: 0;
  width: 100%; aspect-ratio: 1 / 1; max-height: none;
  display: grid; place-items: stretch; margin: 0; }
/* Landscape / short screens: HEIGHT is the limiter. Let the board grow to fill the
   remaining height (flex:1) and drop the square cap so it uses every vertical pixel;
   the renderer keeps it square internally and centers it, so wide side-margins just
   become tasteful grid backdrop (as before). */
@media (orientation: landscape), (max-height: 620px) {
  .board-stage { flex: 1 1 auto; aspect-ratio: auto; max-height: none; }
  /* HEIGHT is precious here: shrink the top zone hard so it can't push the board
     off-screen (it kept flex:1 for the tall-portrait fill). Chat still shows a line
     or two; the board reclaims the vertical budget. */
  .game-top { flex: 0 0 auto; max-height: 16vh; min-height: 40px; }
}
/* FRAMELESS / FLOATING (brief 1a): the canvas is TRANSPARENT — board.js no longer
   paints a felt/rail plate, so the cyberpunk stage (.stage-fx grid + neon blooms)
   reads straight THROUGH behind the floating tiles/pawns/slides/globe. The board's
   own ambient glow-pool (drawn in-canvas) gives the pieces atmosphere, not a frame. */
.board-stage { background: transparent; }
/* STARFIELD BEHIND THE FLOATING BOARD (James, definitive): the space behind the
   board is a twinkling STARFIELD — the "floating in space" vibe — NOT a grid (the
   grid stays up top in .game-top-grid). Layered radial-gradient star dots of varied
   size + brightness sit behind the transparent canvas so the board's tiles/pawns
   float on illuminated stars. A soft red↔blue depth-bloom gives it warmth; the whole
   field is masked to a soft radial so it dissolves at the edges (no box/frame) and
   pointer-events:none so it never eats a tap. Two star layers cross-fade on a slow
   twinkle. */
.board-stage::before { content: ''; position: absolute; inset: -6%; z-index: 0;
  pointer-events: none;
  background-color: transparent;
  background-image:
    /* red↔blue depth bloom (kept, subtle) */
    radial-gradient(60% 60% at 30% 24%, rgba(46,139,255,.13), transparent 66%),
    radial-gradient(60% 60% at 74% 80%, rgba(255,59,82,.12), transparent 66%),
    /* bright hero stars (large, few) */
    radial-gradient(1.6px 1.6px at 18% 22%, rgba(255,255,255,.95), transparent 60%),
    radial-gradient(1.8px 1.8px at 82% 30%, rgba(210,228,255,.9), transparent 60%),
    radial-gradient(1.5px 1.5px at 66% 74%, rgba(255,232,232,.9), transparent 60%),
    radial-gradient(1.7px 1.7px at 34% 82%, rgba(255,255,255,.9), transparent 60%),
    /* mid stars scattered on a tile so they repeat across the field */
    radial-gradient(1.1px 1.1px at 12% 62%, rgba(200,220,255,.75), transparent 60%),
    radial-gradient(1.1px 1.1px at 88% 58%, rgba(255,255,255,.7), transparent 60%),
    radial-gradient(1px 1px at 48% 12%, rgba(255,220,225,.7), transparent 60%),
    radial-gradient(1px 1px at 58% 44%, rgba(210,228,255,.6), transparent 60%),
    /* fine dust (tiny, tiled) */
    radial-gradient(1px 1px at 8% 8%, rgba(255,255,255,.5), transparent 60%),
    radial-gradient(1px 1px at 72% 16%, rgba(255,255,255,.45), transparent 60%),
    radial-gradient(1px 1px at 26% 48%, rgba(255,255,255,.4), transparent 60%),
    radial-gradient(1px 1px at 92% 88%, rgba(255,255,255,.45), transparent 60%);
  background-repeat: no-repeat, no-repeat,
    no-repeat, no-repeat, no-repeat, no-repeat,
    repeat, repeat, repeat, repeat,
    repeat, repeat, repeat;
  background-size: auto, auto,
    auto, auto, auto, auto,
    150px 150px, 170px 170px, 130px 130px, 190px 190px,
    90px 90px, 110px 110px, 140px 140px;
  -webkit-mask-image: radial-gradient(66% 66% at 50% 50%, #000 54%, transparent 100%);
          mask-image: radial-gradient(66% 66% at 50% 50%, #000 54%, transparent 100%);
  opacity: 1;
  animation: starTwinkle 5.5s ease-in-out infinite; }
/* a second, offset star layer that cross-fades against the first → gentle twinkle
   without repainting positions (GPU-cheap opacity pulse). */
.board-stage::after { content: ''; position: absolute; inset: -6%; z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(1.4px 1.4px at 40% 30%, rgba(255,255,255,.9), transparent 60%),
    radial-gradient(1.2px 1.2px at 76% 66%, rgba(210,228,255,.85), transparent 60%),
    radial-gradient(1.2px 1.2px at 22% 70%, rgba(255,232,232,.85), transparent 60%),
    radial-gradient(1px 1px at 60% 20%, rgba(255,255,255,.6), transparent 60%),
    radial-gradient(1px 1px at 14% 40%, rgba(255,255,255,.5), transparent 60%),
    radial-gradient(1px 1px at 86% 40%, rgba(255,255,255,.5), transparent 60%);
  background-repeat: no-repeat, no-repeat, no-repeat, repeat, repeat, repeat;
  background-size: auto, auto, auto, 120px 120px, 100px 100px, 160px 160px;
  -webkit-mask-image: radial-gradient(60% 60% at 50% 50%, #000 46%, transparent 100%);
          mask-image: radial-gradient(60% 60% at 50% 50%, #000 46%, transparent 100%);
  opacity: .35;
  animation: starTwinkle2 5.5s ease-in-out infinite; }
@keyframes starTwinkle  { 0%,100% { opacity: .95; } 50% { opacity: .72; } }
@keyframes starTwinkle2 { 0%,100% { opacity: .30; } 50% { opacity: .70; } }
@media (prefers-reduced-motion: reduce) {
  .board-stage::before, .board-stage::after { animation: none; }
}
/* The canvas fills the TRUE square stage exactly. It's positioned ABSOLUTELY to the
   stage's box (inset:0) rather than participating in the grid's intrinsic sizing —
   a <canvas> carries an intrinsic aspect ratio from its width/height attributes, and
   as a stretched grid item that let it grow taller than the square stage (spilling
   over the button bar). inset:0 against the position:relative square stage pins it to
   exactly width×width, so board.js measures a square box and fills it edge-to-edge. */
#board-canvas { position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; display: block; touch-action: manipulation;
  background: transparent; }
.board-overlay, .emote-layer { position: absolute; inset: 0; pointer-events: none; }

/* ── Centered DRAW DECK (classic Sorry! placement) ─────────────────────────────
   Lives absolutely inside the board-stage, positioned over the true board center
   by JS (Board.centerXY). It's the primary "draw" affordance during your turn. */
.deck-center { position: absolute; z-index: 12; transform: translate(-50%, -50%);
  left: 50%; top: 50%; pointer-events: auto;
  display: grid; place-items: center; touch-action: manipulation;
  -webkit-tap-highlight-color: transparent; }
/* deck + discard sit side by side, centered on the board (deck LEFT, discard
   RIGHT). Stacks vertically on very narrow portrait so neither spills the felt. */
/* gap scales with the card size (set by positionDeck as --deck-gap) so the deck +
   discard stay in harmony and never collide on any device (E). */
.deck-row { display: flex; align-items: center; justify-content: center; gap: var(--deck-gap, 12px);
  position: relative; z-index: 2; }   /* cards ABOVE the ambient globe */

/* (F) BOARD GLOBE — the splash's world-globe, tucked into the center well BEHIND the
   deck + discard. Centered on the well; sized by positionDeck(). Low ambient opacity
   so it's alive but never fights card legibility. z-index 0 = under the cards (z 2),
   still above the board canvas (the whole deck-center layer is z 12 over the board). */
.board-globe { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 0; pointer-events: none; opacity: .34; border-radius: 50%; overflow: visible; }
/* Board globe (2b): SLOW, majestic drift — a stately turning world at the table's
   center, not the splash coin's brisk spin. Scoped to .board-globe so the splash
   coin keeps its own 40s. reduced-motion still fully halts it (base rule below). */
.board-globe .hero-globe-wire { animation-duration: 180s; }
.deck-center .deck { transition: transform .12s cubic-bezier(.2,1.3,.4,1), box-shadow .12s ease; }

/* ── DISCARD PILE — the played card, face-up, beside the deck (C+E) ────────────
   Replaces the old floating drawn-card tooltip. Shows the engine's card face
   (number + name + effect, all QA-signed rules copy). Sized to match the deck by
   positionDeck(); collapses to nothing until a card is played this turn. */
.discard { position: relative; flex: 0 0 auto; border-radius: 12px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 6px 8px; overflow: hidden;
  background: linear-gradient(140deg, #262c48, #12162a);
  border: 1px solid var(--line);
  box-shadow: 3px 3px 0 rgba(0,0,0,.45), inset 0 0 0 2px rgba(255,255,255,.05),
              inset 0 -12px 16px -12px rgba(0,0,0,.6);
  animation: cardFlip .42s cubic-bezier(.3,1.1,.5,1); }
/* When there's no played card, the discard slot is HIDDEN but KEEPS its footprint
   (visibility, not display) so the deck+discard flex row never re-centers — the
   draw deck's on-screen x is IDENTICAL whether or not a discard is present (fix #2).
   We also suppress its flip-in animation + card chrome while empty. */
.discard.is-empty { visibility: hidden; animation: none; box-shadow: none;
  background: none; border-color: transparent; }
.discard.is-empty::after { display: none; }
/* a faint "played" stacked-edge, mirroring the deck's stacked look */
.discard::after { content: ''; position: absolute; inset: 3px; border-radius: 9px;
  border: 1px solid rgba(255,255,255,.06); pointer-events: none; }
/* Type scales to the actual (JS-sized) card frame so it never clips on phones.
   The discard well is ~60px wide / ~84px tall on ~390px phones and grows on
   desktop/tablet; clamp()+vmin tracks that so the number+name stay fully visible
   at phone widths while capping at the roomy desktop sizes. min-width:0 lets the
   flex column shrink to its box instead of forcing overflow. */
/* Big, legible NUMBER — the whole card is number + name now (effect moved to the
   status bar), so the number can grow to own the card face without any clip risk. */
.discard-num { font-size: clamp(30px, 8.4vmin, 46px); font-weight: 900; line-height: .95;
  min-width: 0; max-width: 100%; color: #fff; text-shadow: 0 1px 8px rgba(0,0,0,.5),
  0 0 18px rgba(140,165,255,.35); }
/* A word card (notSorry!) is a deliberate two-line, two-tone split: a white top
   line ("not") over a gold bottom line ("Sorry!"). Both are sized/spaced to fit the
   ~60px phone card box (padding trimmed so the wider bottom word never clips) and
   grow with the card on tablet/desktop. */
.discard.is-word { padding: 6px 4px; }
.discard-word-top { font-size: clamp(11px, 3.2vmin, 15px); font-weight: 900; line-height: 1;
  letter-spacing: .5px; min-width: 0; max-width: 100%; color: #fff;
  text-shadow: 0 1px 8px rgba(0,0,0,.5), 0 0 18px rgba(140,165,255,.35); }
.discard-word-bot { font-size: clamp(15px, 4.4vmin, 22px); font-weight: 900; line-height: 1;
  letter-spacing: .3px; min-width: 0; max-width: 100%; margin-top: 2px; color: var(--gold);
  text-shadow: 0 1px 8px rgba(0,0,0,.5); }
.discard-name { font-size: clamp(9px, 2.6vmin, 12px); font-weight: 900; letter-spacing: 1.5px;
  min-width: 0; max-width: 100%; text-transform: uppercase; color: var(--gold); margin-top: 3px; }
/* a quick gold pop when the played card is a "2" (extra turn) */
.discard.is-flourish { animation: cardFlip .42s cubic-bezier(.3,1.1,.5,1),
  discardFlourish .6s ease .1s; }
@keyframes discardFlourish { 0%,100% { box-shadow: 3px 3px 0 rgba(0,0,0,.45),
    inset 0 0 0 2px rgba(255,255,255,.05); }
  40% { box-shadow: 3px 3px 0 rgba(0,0,0,.45), 0 0 0 2px var(--gold), 0 0 18px -2px var(--gold); } }
/* RESHUFFLE riffle (E): a quick two-beat card-riffle + settle when the deck renews.
   A brief lift/tilt shimmy with a bright edge flash reads as "cards riffled & squared
   up". GPU-cheap (transform + box-shadow); the count-up runs in JS alongside it. */
.deck.is-riffle { animation: deckRiffle .7s cubic-bezier(.32,1.2,.5,1) both; }
@keyframes deckRiffle {
  0%   { transform: translateZ(0) rotate(0) scale(1); box-shadow: 3px 3px 0 rgba(0,0,0,.45), inset 0 0 0 2px rgba(255,255,255,.05); }
  18%  { transform: translateY(-6px) rotate(-4deg) scale(1.05); box-shadow: 5px 6px 0 rgba(0,0,0,.4), inset 0 0 0 2px rgba(255,255,255,.05), 0 0 20px -4px var(--gold); }
  42%  { transform: translateY(-3px) rotate(3.5deg) scale(1.04); box-shadow: 4px 5px 0 rgba(0,0,0,.42), inset 0 0 0 2px rgba(255,255,255,.09), 0 0 22px -6px var(--gold); }
  66%  { transform: translateY(-4px) rotate(-2deg) scale(1.03); box-shadow: 4px 5px 0 rgba(0,0,0,.42), inset 0 0 0 2px rgba(255,255,255,.06); }
  100% { transform: translateZ(0) rotate(0) scale(1); box-shadow: 3px 3px 0 rgba(0,0,0,.45), inset 0 0 0 2px rgba(255,255,255,.05); }
}
@media (prefers-reduced-motion: reduce) {
  .deck.is-riffle { animation: none; }   /* keep the count-up + SFX; drop the shimmy */
}

.deck-center.is-empty { pointer-events: none; }
/* readiness states painted on the centered deck */
.deck-center.can-draw .deck { box-shadow: 3px 3px 0 rgba(0,0,0,.5),
    inset 0 0 0 2px rgba(255,255,255,.06), 0 0 0 2px var(--gold), 0 0 26px -4px var(--gold); }
.deck-center.can-draw { animation: deckBob 1.5s ease-in-out infinite; }
@keyframes deckBob { 50% { transform: translate(-50%, calc(-50% - 4px)); } }
.deck-center.is-waiting { opacity: .82; filter: saturate(.8); }
/* a soft "draw here" hint ring behind the deck when it's your turn to draw */
.deck-center.can-draw::before { content: ''; position: absolute; inset: -14px; border-radius: 22px;
  background: radial-gradient(circle, rgba(255,209,102,.22), transparent 68%); z-index: -1;
  animation: deckHalo 1.5s ease-in-out infinite; }
@keyframes deckHalo { 0%,100% { opacity: .5; transform: scale(.94); } 50% { opacity: 1; transform: scale(1.06); } }

/* (mascot takeover styles removed — classic-only card set, no mascot cards) */

.emote-layer { z-index: 25; }
.emote-float { position: absolute; bottom: 20%; font-size: 40px; animation: emoteRise 1.6s ease forwards; }
@keyframes emoteRise { 0% { opacity: 0; transform: translateY(10px) scale(.6);} 15% { opacity:1; transform: scale(1.1);}
  100% { opacity: 0; transform: translateY(-120px) scale(1);} }

/* (The old floating .turn-pill is RETIRED — its turn ping + instruction line now
   live in the docked (B) STATUS BAR above the board, styled as .status-bar.) */

/* ── The centered DRAW DECK card. (The old floating action-center / drawn-card
   tooltip and move-choice chips are RETIRED — the discard shows the played card
   beside the deck, and all move interaction is pure pawn/ghost/target touch.) */
.deck { position: relative; width: 62px; height: 88px; border-radius: 12px; flex: 0 0 auto;
  background: linear-gradient(140deg, #313a63, #161a2e);
  border: 1px solid var(--line);
  box-shadow: 3px 3px 0 rgba(0,0,0,.45), inset 0 0 0 2px rgba(255,255,255,.05),
              inset 0 -12px 16px -12px rgba(0,0,0,.6);
  transition: transform .1s ease; }
.deck:active { transform: translate(2px, 2px) scale(.98); box-shadow: 1px 1px 0 rgba(0,0,0,.45), inset 0 0 0 2px rgba(255,255,255,.05); }
/* the "stacked cards" edge */
.deck::after { content: ''; position: absolute; inset: 4px; border-radius: 9px;
  border: 1px solid rgba(255,255,255,.06);
  background: repeating-linear-gradient(115deg, transparent 0 6px, rgba(255,255,255,.02) 6px 7px); }
.deck.pulse { animation: deckPulse 1.1s ease-in-out infinite; }
@keyframes deckPulse { 50% { box-shadow: 3px 3px 0 rgba(0,0,0,.45), 0 0 0 3px var(--gold), 0 0 18px -2px var(--gold); } }
.deck-face { position: absolute; inset: 0; display: grid; place-items: center; z-index: 1; }
.deck-count { font-size: 34px; font-weight: 900; color: var(--gold); text-shadow: 0 1px 6px rgba(255,209,102,.4); }
/* (.deck-tag "DRAW" label retired — the gold count + can-draw halo carry the affordance) */
/* the discard's flip-in (shared by the played-card reveal). */
@keyframes cardFlip { from { transform: perspective(500px) rotateY(78deg) scale(.9); opacity: 0; }
  to { transform: none; opacity: 1; } }
/* (drawn-card tooltip + move-choice chip styles removed — see .discard above and
   the zero-button pure-pawn interaction; ghosts/targets are drawn on canvas.) */

/* ── (⑤) BOTTOM GRID STRIP — the "A2" perspective floor at the screen's bottom edge,
   now a REAL band the board docks flush on top of. The board's bottom sits on this
   strip's top edge; the sound + emote toggles LIVE inside it (out of the old absolute
   board-corner positioning) so they never overlap the board. Fixed-height auto band,
   flex:0 so it can't steal the board's height — the top zone (flex:1) absorbs slack. */
/* NEUTRAL + GLOW, NOT CLIPPED (brief 1d): the board above now FLOATS (frameless),
   so the strip no longer has a frame colliding with the board edge. It's a clean,
   fully-rounded neon band with a cool slate/white hairline + a soft top glow that
   reads as tasteful separation from the floating board — never a hard-cut frame.
   A small TOP margin opens clean air between the board and the strip. overflow is
   visible so the emote tray can still pop UP out of it. */
/* BUTTON BAR (James, definitive): NO frame/border/box — the buttons FLOAT. The bar
   HUGS the board bottom (margin-top:0). Layout: LEFT group [chat + emoji], RIGHT the
   sound toggle → justify-content:space-between. The receding drift-grid stays behind
   as atmosphere but the strip itself is frameless. */
.game-bottom { flex: 0 0 auto; position: relative; overflow: visible;
  height: 56px; margin: 0 8px 0; border-radius: 14px;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 10px; }
/* LEFT group: chat bubble + emoji, packed together at the left. */
.game-bottom-left { position: relative; z-index: 2; display: flex; align-items: center; gap: 10px; }
.game-bottom-right { position: relative; z-index: 2; display: flex; align-items: center; gap: 10px; }
/* the receding neon floor (the A2 blend): reuse the bottom-grid perspective — bottom-
   anchored, tilted away, fading UP so it dissolves rather than butting the board edge.
   Overflows past the strip's rounded box (clipped by the section's overflow:hidden). */
.game-bottom-grid { position: absolute; left: -30%; right: -30%; bottom: 0; top: -14px; z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right, rgba(130,160,255,.30) 0 1.5px, transparent 1.5px 42px),
    repeating-linear-gradient(to top,   rgba(130,160,255,.30) 0 1.5px, transparent 1.5px 40px);
  transform: perspective(300px) rotateX(70deg); transform-origin: bottom center;
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.5) 55%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.5) 55%, transparent 100%);
  opacity: .95; }
/* a soft red↔blue horizon glow across the strip — warmth on the floor, echoing the
   top zone's horizon so the top and bottom bands read as one continuous atmosphere. */
.game-bottom::before { content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(60% 130% at 10% 100%, rgba(46,139,255,.20), transparent 62%),
    radial-gradient(60% 130% at 90% 100%, rgba(255,59,82,.20), transparent 62%); }

/* FRAMELESS FLOATING BUTTONS (James, definitive): no bordered circles — the glyphs
   float. A soft round tap-glow appears only on :active for touch feedback; otherwise
   just the emoji + a drop-shadow so it reads on the grid without a boxed frame. */
.game-bottom-btn { position: relative; z-index: 2; flex: 0 0 auto;
  width: 46px; height: 46px; border-radius: 50%;
  background: transparent; border: none; box-shadow: none;
  font-size: 24px; line-height: 1; color: inherit;
  display: grid; place-items: center; cursor: pointer;
  filter: drop-shadow(0 2px 5px rgba(0,0,0,.55));
  transition: transform .1s ease, opacity .12s ease; }
.game-bottom-btn::before { content: ''; position: absolute; inset: 4px; border-radius: 50%;
  background: radial-gradient(circle, rgba(150,180,255,.22), transparent 70%);
  opacity: 0; transition: opacity .12s ease; z-index: -1; }
.game-bottom-btn:active { transform: scale(.9); }
.game-bottom-btn:active::before { opacity: 1; }

/* sound toggle — now the RIGHT-aligned floating glyph (frameless). */
.mute-toggle { opacity: .95; }
.mute-toggle.is-muted { opacity: .5; filter: grayscale(1) drop-shadow(0 2px 5px rgba(0,0,0,.55)); }
/* In-game quit (⏻) — shares .game-bottom-btn; a faint red hint on press/hover. */
.quit-toggle { opacity: .9; }
.quit-toggle:hover { color: var(--red-neon); }

/* emote toggle — part of the LEFT group; its tray pops UP from it (anchored to .emote-wrap) */
.emote-wrap { position: relative; z-index: 2; flex: 0 0 auto; display: flex; }
.emote-toggle { /* frameless glyph (shares .game-bottom-btn) */ }
.emote-tray { position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 26;
  display: flex; gap: 6px; background: var(--panel); border: 1px solid var(--line); border-radius: 30px;
  padding: 8px; box-shadow: var(--shadow); }
.emote-btn { width: 42px; height: 42px; border-radius: 50%; background: var(--bg-2); font-size: 22px; }
.emote-btn:active { transform: scale(1.15); }

/* ── CHAT COMPOSE BAR (in-game) ──────────────────────────────────────────────
   A lightweight neon text+send row that pops UP from the LEFT of the button bar
   when the chat button is tapped. Emits the existing `chat` event; the feed lives
   in the top zone (#game-chat). Positioned above the bar so it never covers the
   board or the buttons. */
.game-compose { position: absolute; left: 8px; right: 8px; bottom: calc(100% + 8px);
  z-index: 27; display: flex; align-items: center; gap: 8px;
  padding: 7px 8px 7px 12px; border-radius: 24px;
  background: linear-gradient(140deg, rgba(38,44,72,.96), rgba(18,22,42,.96));
  border: 1px solid rgba(150,180,255,.42);
  box-shadow: 0 10px 30px -10px rgba(0,0,0,.6), 0 0 22px -8px rgba(120,150,255,.6),
              inset 0 1px 0 rgba(255,255,255,.06);
  animation: composeIn .2s cubic-bezier(.2,.9,.3,1); }
@keyframes composeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .game-compose { animation: none; } }
.game-compose-input { flex: 1 1 auto; min-width: 0; background: transparent; border: none;
  color: var(--ink); font-size: 15px; padding: 6px 2px; outline: none; }
.game-compose-input::placeholder { color: var(--ink-dim); }
.game-compose-send { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
  border: none; cursor: pointer; font-size: 17px; color: #0b1020;
  background: linear-gradient(140deg, var(--blue-neon), #6fa8ff);
  box-shadow: 0 0 16px -4px var(--blue-neon); transition: transform .1s ease, filter .12s ease; }
.game-compose-send:active { transform: scale(.9); }
.game-compose-send:disabled { opacity: .45; filter: grayscale(.4); box-shadow: none; }

/* ═══════════ SCORE — single-winner celebration ═══════════ */
/* Classic ruleset: the game ends when one player gets all pawns Home. One big,
   warm winner moment in that player's team color (--pc set from JS). */
[data-for="score"] { justify-content: center; align-items: center; gap: 22px; text-align: center;
  background:
    radial-gradient(80% 46% at 50% 0%, rgba(61,139,255,.10), transparent 60%),
    radial-gradient(80% 46% at 50% 100%, rgba(255,70,85,.10), transparent 60%); }
/* faint drift-road floor under the winner, echoing the splash's night-city grid */
[data-for="score"]::after { content: ''; position: absolute; left: -30%; right: -30%; bottom: 0;
  height: 42%; pointer-events: none; z-index: 0; opacity: .5;
  background-image:
    repeating-linear-gradient(to right, rgba(120,150,255,.14) 0 1px, transparent 1px 46px),
    repeating-linear-gradient(to top,   rgba(120,150,255,.14) 0 1px, transparent 1px 44px);
  transform: perspective(360px) rotateX(68deg); transform-origin: bottom center;
  -webkit-mask-image: linear-gradient(to top, #000 0%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, transparent 100%); }

.winner-card { --pc: var(--gold); position: relative; z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 30px 30px 26px; border-radius: 24px; width: 100%; max-width: 380px;
  /* fallback for browsers without color-mix() */
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 2px solid var(--pc);
  box-shadow: 0 18px 50px -14px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.06);
  animation: winnerIn .6s cubic-bezier(.2,1.35,.4,1) both; }
@supports (background: color-mix(in srgb, red, blue)) {
  .winner-card {
    background:
      radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--pc) 22%, transparent), transparent 70%),
      linear-gradient(180deg, var(--panel-2), var(--panel));
    box-shadow: 0 18px 50px -14px color-mix(in srgb, var(--pc) 60%, transparent),
                inset 0 1px 0 rgba(255,255,255,.06);
  }
}
@keyframes winnerIn { from { opacity: 0; transform: translateY(16px) scale(.94); } to { opacity: 1; transform: none; } }

.winner-crown { font-size: 46px; line-height: 1; filter: drop-shadow(0 4px 10px rgba(0,0,0,.4));
  animation: crownBob 2.4s ease-in-out infinite; }
@keyframes crownBob { 50% { transform: translateY(-5px) rotate(-4deg); } }

.winner-pawn { width: 40px; height: 50px; margin: 2px 0 4px;
  background:
    radial-gradient(circle at 35% 28%, color-mix(in srgb, #fff 55%, var(--pc)), var(--pc) 55%, var(--pc-deep, var(--pc)));
  border-radius: 50% 50% 42% 42% / 62% 62% 38% 38%;
  box-shadow: 0 0 22px -2px var(--pc), inset 0 -6px 10px -6px rgba(0,0,0,.5);
  animation: winnerPawn 2s ease-in-out infinite; }
@keyframes winnerPawn { 50% { transform: translateY(-4px) scale(1.04); } }

.winner-name { font-size: clamp(30px, 9vw, 44px); font-weight: 900; letter-spacing: -.5px;
  color: var(--pc-glow, var(--pc)); text-shadow: 0 0 26px var(--pc);
  max-width: 100%; overflow-wrap: anywhere; line-height: 1.02; }
.winner-sub { font-size: 22px; font-weight: 800; color: var(--ink); opacity: .92; letter-spacing: .3px; }
.winner-you { margin-top: 4px; font-size: 13px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  color: var(--pc-glow, var(--pc));
  background: rgba(255,255,255,.08); border: 1px solid var(--pc);
  padding: 5px 12px; border-radius: 20px; animation: youPop .5s ease .3s both; }
@supports (background: color-mix(in srgb, red, blue)) {
  .winner-you { background: color-mix(in srgb, var(--pc) 18%, transparent); }
}
@keyframes youPop { from { opacity: 0; transform: scale(.7); } to { opacity: 1; transform: none; } }

.score-burst { --pc: var(--gold); position: absolute; inset: 0; pointer-events: none; opacity: 0; z-index: 1; }
.score-burst.is-on { opacity: 1;
  background: radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--pc) 22%, transparent), transparent 58%);
  animation: burstPulse 1.6s ease; }
@keyframes burstPulse { 0% { transform: scale(.9); opacity: 0; } 40% { opacity: 1; } 100% { opacity: .35; } }
.score-foot { margin-top: 4px; z-index: 2; }
.score-wait { color: var(--ink-dim); font-size: 14px; }

/* ═══════════ MODAL ═══════════ */
.modal { position: absolute; inset: 0; z-index: 50; display: grid; place-items: center;
  background: rgba(0,0,0,.6); padding: 20px; }
.modal-card { background: linear-gradient(165deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line-cold); border-radius: var(--radius);
  box-shadow: var(--neon-edge), var(--shadow);
  padding: 22px; max-width: 440px; width: 100%; max-height: 80%; overflow-y: auto; position: relative; }
.modal-close { position: absolute; top: 10px; right: 12px; font-size: 26px; color: var(--ink-dim); background: none; }
.how-sec { margin-bottom: 14px; }
.how-sec h3 { margin: 0 0 4px; font-size: 15px; }
.how-sec p { margin: 0; color: var(--ink-dim); font-size: 14px; line-height: 1.45; }
.how-pending { font-size: 11px; color: var(--gold); opacity: .7; border-top: 1px solid var(--line); padding-top: 10px; }

/* Quit-confirmation modal (in-game ⏻) — reuses .modal / .modal-card. */
.quit-card { max-width: 360px; text-align: center; }
.quit-title { justify-content: center; margin: 0 0 8px; }
.quit-note { margin: 0 0 18px; color: var(--ink-dim); font-size: 14px; line-height: 1.45; }
.quit-actions { display: flex; gap: 12px; justify-content: center; }
.quit-actions .btn { flex: 1 1 0; }

/* ═══════════ TOAST ═══════════ */
.toast { position: absolute; left: 50%; bottom: calc(var(--safe-b) + 90px); transform: translateX(-50%);
  z-index: 60; background: var(--panel-2); border: 1px solid var(--line); border-radius: 30px;
  padding: 10px 18px; font-size: 14px; font-weight: 700; box-shadow: var(--shadow); max-width: 88%;
  text-align: center; animation: toastIn .25s ease; }
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translateX(-50%);} }
.toast--error { border-color: var(--red); color: #ff9aa4; }
.toast--warn { border-color: var(--gold); color: var(--gold); }
/* (mascot-card toast variants .toast--chaos / .toast--blessing removed — classic-only card set, no mascot cards) */

/* landscape / short screens: keep the centered layer tight so the board dominates */
@media (max-height: 560px) {
  .deck-count { font-size: 28px; }   /* still legible on short screens (fix #3) */
  .status-bar .turn-who { font-size: 13px; }
  .discard-num { font-size: clamp(24px, 6.4vmin, 34px); }  /* cap on short screens, still scales down */
}

/* very narrow portrait: stack the discard BELOW the deck so neither spills the
   felt (C+E). The deck-row switches to a column and re-centers on the board. */
/* Narrow phones: keep all 4 chips in the row with NO scroll while the 2-char label
   still shows in FULL (B). The "B" prefix already signals a bot, so the 🤖 glyph is
   the first thing dropped, then the count/padding tighten — the label never yields. */
@media (max-width: 400px) {
  .player-rail { gap: 4px; padding: 0 6px; }
  .pchip { gap: 4px; padding: 6px 5px; }
  .pchip-bot { display: none; }              /* the "B" prefix carries the bot read */
  .pchip-home small { display: none; }       /* drop "/4" — the home number is enough */
}
@media (max-width: 340px) {
  .pchip { padding: 6px 4px; }
  .pchip-sw { width: 11px; height: 11px; }   /* trim the swatch, never the label */
  .pchip-id { font-size: 11.5px; min-width: 1.7em; }
}

@media (max-width: 360px) {
  .deck-row { flex-direction: column; gap: 8px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   TELESTRATIONS  —  lobby picker + draw / guess / wait / reveal / gallery
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── lobby: game picker ── (shell — each game carries its neon identity) */
.game-pick { display: flex; gap: 10px; }
.game-tile { flex: 1 1 0; display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: linear-gradient(165deg, var(--panel-2), var(--bg-2));
  border: 1px solid var(--line); border-radius: 14px; padding: 14px 10px;
  color: var(--ink); transition: transform .1s ease, border-color .12s ease, box-shadow .12s ease; }
.game-tile:active { transform: scale(.97); }
.game-tile-emoji { font-size: 30px; line-height: 1; }
.game-tile-name { font-size: 15px; font-weight: 900; }
.game-tile-desc { font-size: 11px; color: var(--ink-dim); text-align: center; line-height: 1.3; }
.game-tile.is-active { border-color: var(--gold);
  box-shadow: 0 0 0 1px var(--gold), 0 6px 18px -8px var(--gold);
  background: linear-gradient(180deg, rgba(255,209,102,.10), var(--bg-2)); }

/* ── lobby: telestrations sliders ── */
.setting-value { font-weight: 900; color: var(--gold); font-variant-numeric: tabular-nums; }
.slider { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; border-radius: 4px;
  background: var(--bg-2); border: 1px solid var(--line); outline: none; margin: 4px 0; }
.slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 26px; height: 26px;
  border-radius: 50%; background: radial-gradient(circle at 35% 30%, #fff8, var(--red)); border: none;
  box-shadow: 0 2px 8px -2px var(--red); cursor: pointer; }
.slider::-moz-range-thumb { width: 26px; height: 26px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff8, var(--red)); border: none; box-shadow: 0 2px 8px -2px var(--red); }
#tele-settings .setting-label { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
#tele-settings .setting-label span:first-child { flex: 1; }

/* ── shared telestrations screen shell ── */
.tele-screen { gap: 10px; }
.brand-inline { display: flex; align-items: center; justify-content: center; gap: 8px;
  font-weight: 800; letter-spacing: 3px; font-size: 12px; color: var(--ink-dim); opacity: .95; }
.brand-inline .brand-crest { width: 8px; height: 8px; }

/* ── FULL-VIEWPORT Barons neon stage for every Teledoodle screen ──
   Twin of [data-for="game"] .stage-fx: the night-city gradient + blue-left /
   red-right blooms + scanlines, so the doodle screens live in the SAME world as
   the board and the lobby (not a bare dark panel). Behind all content, no taps.
   Reuses the global .fx-glow / .fx-scan primitives. */
.tele-fx { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  background:
    radial-gradient(120% 62% at 50% -6%, #221a3d 0%, transparent 58%),
    radial-gradient(78% 60% at 6% 8%,  rgba(46,139,255,.26) 0%, transparent 60%),
    radial-gradient(78% 60% at 94% 8%, rgba(255,59,82,.26) 0%, transparent 60%),
    radial-gradient(80% 55% at 8% 96%, rgba(46,139,255,.18) 0%, transparent 62%),
    radial-gradient(80% 55% at 92% 96%,rgba(255,59,82,.18) 0%, transparent 62%),
    linear-gradient(180deg, #0c0d1c 0%, #10121f 50%, #0c0d1c 100%); }
.tele-fx .fx-glow { position: absolute; width: 64vw; height: 64vw; max-width: 520px; max-height: 520px;
  border-radius: 50%; filter: blur(70px); opacity: .40; mix-blend-mode: screen;
  animation: fxPulse 7s ease-in-out infinite; }
.tele-fx .fx-glow--red  { bottom: -16%; right: -14%; background: radial-gradient(circle, var(--red-neon), transparent 70%); }
.tele-fx .fx-glow--blue { top: -16%; left: -14%; background: radial-gradient(circle, var(--blue-neon), transparent 70%); animation-delay: -3.5s; }
.tele-fx .fx-scan { position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,.022) 0 1px, transparent 1px 3px);
  opacity: .5; mix-blend-mode: overlay; }
/* the reveal stage runs its blooms a touch hotter — this is the marquee moment */
.tele-fx--reveal .fx-glow { opacity: .52; }
/* everything in a tele screen rides ABOVE the stage */
.tele-screen > *:not(.tele-fx) { position: relative; z-index: 1; }

/* ── shared head: brand crest bar + round chip on one line ── */
.tele-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex: 0 0 auto;
  min-height: 26px; }
.tele-head--reveal, .tele-head--gallery { flex-direction: column; gap: 4px; align-items: center; text-align: center; }
.tele-round-chip { align-self: center; font-size: 11px; font-weight: 800; letter-spacing: 1px;
  color: var(--blue-glow); background: rgba(46,139,255,.12); border: 1px solid var(--line-cold);
  border-radius: 20px; padding: 4px 12px; flex: 0 0 auto; white-space: nowrap;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }

/* ── DRAW / GUESS: glossy glass prompt bar + framed paper canvas ── */
.tele-prompt-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex: 0 0 auto;
  padding: 12px 14px; border-radius: 14px;
  background: var(--glass); border: 1px solid var(--line);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  box-shadow: var(--neon-edge); position: relative; overflow: hidden; }
/* a Barons spine down the left edge — red on draw (you author), blue on guess (you read) */
.tele-prompt-bar::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; }
.tele-prompt-bar--draw::before  { background: linear-gradient(180deg, var(--red-neon), var(--red-dk)); box-shadow: 0 0 12px -2px var(--red-neon); }
.tele-prompt-bar--guess::before { background: linear-gradient(180deg, var(--blue-neon), var(--blue-dk)); box-shadow: 0 0 12px -2px var(--blue-neon); }
.tele-prompt-lead { min-width: 0; }
.tele-prompt-label { font-size: 11px; color: var(--ink-dim); letter-spacing: 1px; text-transform: uppercase; font-weight: 800; }
.tele-prompt-word { font-size: 30px; font-weight: 900; color: var(--gold); letter-spacing: -.3px;
  text-shadow: 0 0 18px rgba(255,209,102,.45); overflow-wrap: anywhere; line-height: 1.05; margin-top: 2px; }
.tele-timer { font-variant-numeric: tabular-nums; font-size: 22px; font-weight: 900; min-width: 54px; text-align: center;
  padding: 8px 10px; border-radius: 12px; background: var(--bg-2); border: 1px solid var(--line); flex: 0 0 auto;
  color: var(--ink); box-shadow: inset 0 1px 0 rgba(255,255,255,.05); transition: background .2s, color .2s, box-shadow .2s; }
.tele-timer.warn { background: linear-gradient(180deg, var(--red-neon), var(--red-dk)); color: #fff; border-color: var(--line-hot);
  box-shadow: 0 0 18px -4px var(--red-neon); animation: teleTick .5s ease infinite; }
@keyframes teleTick { 50% { transform: scale(1.08); } }

/* the drawing surface — a real framed "sheet of paper" (warm off-white) with a
   faint sketch grid, a neon Barons edge and a lifted shadow so it reads as a
   physical pad on the night-city table, not a bare white box. */
.tele-canvas-wrap { flex: 1 1 auto; min-height: 0; position: relative; border-radius: var(--radius); overflow: hidden;
  background: #fbfbff; border: 1px solid rgba(255,255,255,.5);
  box-shadow: 0 14px 34px -10px rgba(0,0,0,.6),
              0 0 0 1px rgba(90,160,255,.28),
              0 0 26px -8px rgba(90,160,255,.4),
              0 0 26px -8px rgba(255,94,120,.3),
              inset 0 2px 6px rgba(0,0,0,.06); }
.tele-canvas-wrap canvas { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; display: block;
  z-index: 1; }
/* faint sketch-pad grid ON the paper (behind the ink) so an empty sheet still
   reads as a doodle pad. Pure CSS, GPU-cheap. */
.tele-paper-grid { position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    repeating-linear-gradient(to right, rgba(46,90,180,.07) 0 1px, transparent 1px 26px),
    repeating-linear-gradient(to bottom, rgba(46,90,180,.07) 0 1px, transparent 1px 26px);
  -webkit-mask-image: radial-gradient(120% 120% at 50% 40%, #000 60%, transparent 100%);
          mask-image: radial-gradient(120% 120% at 50% 40%, #000 60%, transparent 100%); }
/* guess paper leans blue (you're reading someone else's ink) */
.tele-canvas-wrap--guess { box-shadow: 0 14px 34px -10px rgba(0,0,0,.6),
              0 0 0 1px rgba(90,160,255,.34), 0 0 26px -8px rgba(90,160,255,.5),
              inset 0 2px 6px rgba(0,0,0,.06); }

.tele-draw-tools { display: flex; gap: 8px; flex: 0 0 auto; }
.tele-tool { flex: 0 0 auto; width: auto; padding: 14px 16px; min-height: 52px; font-weight: 800; }
.tele-done { flex: 1; }

.tele-guess-field { display: flex; flex-direction: column; gap: 10px; flex: 0 0 auto; }
#tele-guess-input { text-align: center; font-size: 19px; }
#tele-guess-input:focus { border-color: var(--blue); box-shadow: 0 0 0 1px var(--blue), 0 0 18px -4px var(--blue-neon); }

/* ── WAIT ── a glowing orb over the neon stage ── */
[data-for="tele-wait"] { justify-content: center; align-items: center; text-align: center; }
.tele-wait-stage { display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 340px; }
.tele-wait-orb { width: 108px; height: 108px; border-radius: 50%; display: grid; place-items: center;
  background: radial-gradient(circle at 38% 30%, rgba(90,160,255,.35), rgba(20,24,48,.9) 72%);
  border: 1px solid var(--line-cold);
  box-shadow: 0 0 40px -8px var(--blue-neon), 0 0 40px -8px var(--red-neon), inset 0 2px 10px rgba(255,255,255,.1);
  animation: teleOrb 3.2s ease-in-out infinite; }
@keyframes teleOrb { 0%,100% { box-shadow: 0 0 40px -10px var(--blue-neon), 0 0 40px -10px var(--red-neon), inset 0 2px 10px rgba(255,255,255,.1); }
  50% { box-shadow: 0 0 54px -8px var(--blue-neon), 0 0 54px -8px var(--red-neon), inset 0 2px 10px rgba(255,255,255,.14); } }
.tele-wait-pencils { font-size: 52px; display: block; animation: teleWobble 1.6s ease-in-out infinite; }
@keyframes teleWobble { 0%,100% { transform: rotate(-8deg); } 50% { transform: rotate(8deg); } }
.tele-wait-title { font-size: 25px; font-weight: 900; letter-spacing: -.2px; }
.tele-wait-sub { font-size: 14px; color: var(--ink-dim); }
.tele-wait-count { font-size: 13px; font-weight: 800; color: var(--gold); letter-spacing: .5px; margin-top: 4px;
  text-shadow: 0 0 12px rgba(255,209,102,.35); }
.tele-wait-track { width: 240px; height: 10px; border-radius: 6px; background: var(--bg-2);
  border: 1px solid var(--line); overflow: hidden; box-shadow: inset 0 1px 3px rgba(0,0,0,.4); }
.tele-wait-track i { display: block; height: 100%; border-radius: 6px;
  background: linear-gradient(90deg, var(--blue), #7a53d6 50%, var(--red));
  box-shadow: 0 0 12px -2px var(--red-neon); transition: width .5s cubic-bezier(.3,1.2,.5,1); }
.tele-wait-timer { font-variant-numeric: tabular-nums; font-size: 13px; color: var(--ink-dim); }
.tele-wait-timer.urgent { color: var(--red-glow); font-weight: 800; }

/* ── REVEAL (the payoff — the highest-value surface) ── */
[data-for="tele-reveal"] { justify-content: flex-start; }
.tele-reveal-book { text-align: center; font-size: 12.5px; font-weight: 800; color: var(--gold);
  letter-spacing: .5px; min-height: 16px; text-shadow: 0 0 12px rgba(255,209,102,.3); }
.tele-reveal-stage { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 16px; position: relative; overflow: hidden; }
.tele-reveal-kicker { font-size: 12px; letter-spacing: 3px; text-transform: uppercase; color: var(--ink-dim);
  opacity: 0; transition: opacity .4s; }
.tele-reveal-kicker.show { opacity: .9; }
.tele-reveal-word { font-size: clamp(30px, 9vw, 46px); font-weight: 900; letter-spacing: -1px; text-align: center;
  opacity: 0; transform: translateY(18px) scale(.8); overflow-wrap: anywhere; line-height: 1.05; max-width: 100%;
  transition: transform .5s cubic-bezier(.2,1.4,.4,1), opacity .5s; }
.tele-reveal-word.show { opacity: 1; transform: none; }
.tele-reveal-word.orig { background: linear-gradient(100deg, var(--red), var(--gold) 60%, var(--blue));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 0 18px rgba(255,209,102,.4)); }
.tele-reveal-word.guess { color: #fff; text-shadow: 0 0 18px rgba(143,192,255,.5); }
.tele-reveal-word.placeholder { color: var(--ink-dim); font-style: italic; font-weight: 700; font-size: 26px; }
.tele-reveal-arrow { color: var(--gold); font-size: 24px; opacity: 0; transition: opacity .4s;
  filter: drop-shadow(0 0 8px rgba(255,209,102,.5)); }
.tele-reveal-arrow.show { opacity: .8; animation: teleArrowBob 1.2s ease-in-out infinite; }
@keyframes teleArrowBob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(4px); } }
.tele-reveal-canvas-wrap { width: 100%; max-width: 360px; aspect-ratio: 1 / 1; border-radius: var(--radius);
  background: #fbfbff; position: relative; overflow: hidden;
  border: 1px solid rgba(255,255,255,.5);
  box-shadow: 0 16px 40px -12px rgba(0,0,0,.7),
              0 0 0 1px rgba(90,160,255,.3), 0 0 30px -8px rgba(90,160,255,.5), 0 0 30px -8px rgba(255,94,120,.4);
  opacity: 0; transform: scale(.92); transition: opacity .4s, transform .4s; }
.tele-reveal-canvas-wrap.show { opacity: 1; transform: none; }
.tele-reveal-canvas-wrap canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; }
.tele-reveal-author { position: absolute; left: 8px; bottom: 8px; z-index: 2;
  font-size: 10.5px; font-weight: 800; letter-spacing: .4px; color: #33405e;
  background: rgba(255,255,255,.72); border-radius: 8px; padding: 3px 8px; pointer-events: none; }
.tele-reveal-dots { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; flex: 0 0 auto; }
.tele-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--panel-2);
  transition: background .3s, transform .3s, box-shadow .3s; }
.tele-dot.done { background: var(--gold); box-shadow: 0 0 6px -1px var(--gold); }
.tele-dot.active { background: #fff; transform: scale(1.6); box-shadow: 0 0 12px 1px #fff; }
.tele-reveal-progress-num { text-align: center; font-size: 11px; color: var(--ink-dim);
  font-variant-numeric: tabular-nums; letter-spacing: 1px; flex: 0 0 auto; }

/* REACTION RAIL — the shared-moment layer. A row of tap-to-react pills over the
   reveal; taps ride the server emote wire so the room laughs together. */
.tele-react-rail { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; flex: 0 0 auto;
  padding-top: 4px; }
.tele-react-btn { width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center;
  font-size: 22px; line-height: 1;
  background: var(--glass); border: 1px solid var(--line);
  -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 4px 14px -6px rgba(0,0,0,.5);
  transition: transform .1s ease, box-shadow .15s ease, border-color .15s ease; }
.tele-react-btn:active { transform: scale(.86); border-color: var(--line-cold);
  box-shadow: 0 0 16px -4px var(--blue-neon); }
.tele-emote-layer { position: absolute; inset: 0; z-index: 3; pointer-events: none; overflow: hidden; }
.tele-emote-layer .emote-float { position: absolute; bottom: 12%; font-size: 40px;
  filter: drop-shadow(0 2px 8px rgba(0,0,0,.55)); animation: gEmoteRise 1.9s ease forwards; }

/* ── GALLERY ── themed book cards with Barons spines ── */
[data-for="tele-gallery"] { padding-top: calc(var(--safe-t) + 14px); }
.tele-gallery-books { flex: 1 1 auto; overflow-y: auto; display: flex; flex-direction: column; gap: 14px;
  -webkit-overflow-scrolling: touch; padding-bottom: calc(var(--safe-b) + 8px); }
.tele-gallery-books::-webkit-scrollbar { display: none; }
.tele-book { position: relative; overflow: hidden;
  background: linear-gradient(160deg, rgba(31,36,64,.92), rgba(23,27,48,.92));
  border: 1px solid var(--line); border-radius: 14px; padding: 12px 12px 12px 15px;
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,.05);
  animation: teleBookIn .4s cubic-bezier(.2,.9,.3,1) both; }
.tele-book::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(180deg, var(--blue-neon), var(--red-neon)); opacity: .85; }
@keyframes teleBookIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.tele-book-head { font-size: 13px; font-weight: 900; color: var(--gold); margin-bottom: 8px; letter-spacing: .3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-shadow: 0 0 12px rgba(255,209,102,.3); }
.tele-book-strip { display: flex; align-items: center; gap: 6px; overflow-x: auto; padding-bottom: 4px;
  scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.tele-book-strip::-webkit-scrollbar { display: none; }
.tele-step { flex: 0 0 auto; min-width: 100px; max-width: 130px; min-height: 100px; border-radius: 10px;
  background: var(--bg-2); border: 1px solid var(--line); display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 8px; font-size: 13px; font-weight: 700; word-break: break-word;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04); }
.tele-step--word { color: var(--gold); border-color: var(--line-hot); }
.tele-step--guess { color: var(--ink); }
.tele-step--placeholder { color: var(--ink-dim); font-style: italic; font-weight: 600; }
.tele-step--draw { padding: 0; overflow: hidden; background: #fbfbff; }
.tele-step-canvas { width: 100%; height: 100%; display: block; }
.tele-step-arrow { flex: 0 0 auto; color: var(--blue-glow); font-size: 18px; opacity: .7; }
.tele-gallery-foot { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 8px; padding-top: 6px; }
.tele-gallery-foot .btn { width: 100%; max-width: 380px; }

/* short screens: keep the reveal canvas from crowding the dots out */
@media (max-height: 560px) {
  .tele-reveal-canvas-wrap { max-width: 240px; }
  .tele-prompt-word { font-size: 24px; }
  .tele-react-btn { width: 42px; height: 42px; font-size: 20px; }
}
@media (prefers-reduced-motion: reduce) {
  .tele-fx .fx-glow, .tele-wait-orb, .tele-reveal-arrow.show, .tele-book { animation: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SEED ROLL — the card + D6 "your starting word" moment (SHOW ONLY, wireable).
   Self-contained: Dice.roll({ face, words }) drives the cube to a target face
   (1–6) and lights the matching row on the 6-word card. The overlay + component
   carry the full Barons theme so it drops into the turn→minigame seam clean.
   NOTE: rule/number/word copy is PLACEHOLDER pending QA's rules-arbiter gate;
   real words come from server config when wired.
═══════════════════════════════════════════════════════════════════════════ */
.tele-seed-modal { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center;
  padding: calc(var(--safe-t) + 16px) 18px calc(var(--safe-b) + 16px);
  background: rgba(6,7,14,.55); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  animation: screenFade .3s ease; }
.tele-seed-fx { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  background:
    radial-gradient(120% 60% at 50% -6%, #221a3d 0%, transparent 58%),
    radial-gradient(78% 60% at 6% 8%,  rgba(46,139,255,.24) 0%, transparent 60%),
    radial-gradient(78% 60% at 94% 8%, rgba(255,59,82,.24) 0%, transparent 60%); }
.tele-seed-fx .fx-glow { position: absolute; width: 60vw; height: 60vw; max-width: 480px; max-height: 480px;
  border-radius: 50%; filter: blur(70px); opacity: .4; mix-blend-mode: screen; animation: fxPulse 7s ease-in-out infinite; }
.tele-seed-fx .fx-glow--red  { bottom: -14%; right: -12%; background: radial-gradient(circle, var(--red-neon), transparent 70%); }
.tele-seed-fx .fx-glow--blue { top: -14%; left: -12%; background: radial-gradient(circle, var(--blue-neon), transparent 70%); animation-delay: -3.5s; }
.tele-seed-fx .fx-scan { position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,.02) 0 1px, transparent 1px 3px); opacity: .5; mix-blend-mode: overlay; }
.tele-seed-stage { position: relative; z-index: 1; width: 100%; max-width: 360px;
  display: flex; flex-direction: column; align-items: center; gap: 20px; }
.tele-seed-kicker { font-size: 12px; }

/* the D6 — a real 3D cube spun via CSS transforms; Dice.roll drives --dice-rx/ry.
   Six faces positioned on the cube; pips are the <i> children laid out per face. */
.dice-stage { position: relative; width: 96px; height: 96px; perspective: 620px; }
.dice { position: absolute; inset: 0; transform-style: preserve-3d;
  transform: rotateX(var(--dice-rx, -20deg)) rotateY(var(--dice-ry, 24deg));
  transition: transform var(--dice-dur, 0ms) cubic-bezier(.2,.75,.25,1); }
.dice-face { position: absolute; width: 96px; height: 96px; border-radius: 16px;
  background: linear-gradient(150deg, #fff 0%, #eef1fb 60%, #dfe4f5 100%);
  border: 1px solid rgba(120,140,200,.5);
  box-shadow: inset 0 2px 6px rgba(255,255,255,.9), inset 0 -6px 14px rgba(60,80,140,.18);
  display: grid; padding: 14px; box-sizing: border-box; }
.dice-face i { width: 15px; height: 15px; border-radius: 50%; align-self: center; justify-self: center;
  background: radial-gradient(circle at 35% 30%, #ff8fa0, var(--red-dk));
  box-shadow: inset 0 -2px 3px rgba(0,0,0,.35), 0 1px 1px rgba(0,0,0,.2); }
/* pip layouts per face */
.dice-face--1 { grid-template: 1fr / 1fr; place-items: center; }
.dice-face--2 { grid-template: repeat(2,1fr) / repeat(2,1fr); }
.dice-face--2 i:nth-child(1) { grid-area: 1/1; } .dice-face--2 i:nth-child(2) { grid-area: 2/2; }
.dice-face--3 { grid-template: repeat(3,1fr) / repeat(3,1fr); }
.dice-face--3 i:nth-child(1) { grid-area: 1/1; } .dice-face--3 i:nth-child(2) { grid-area: 2/2; } .dice-face--3 i:nth-child(3) { grid-area: 3/3; }
.dice-face--4 { grid-template: repeat(2,1fr) / repeat(2,1fr); }
.dice-face--5 { grid-template: repeat(3,1fr) / repeat(3,1fr); }
.dice-face--5 i:nth-child(1) { grid-area: 1/1; } .dice-face--5 i:nth-child(2) { grid-area: 1/3; }
.dice-face--5 i:nth-child(3) { grid-area: 2/2; } .dice-face--5 i:nth-child(4) { grid-area: 3/1; } .dice-face--5 i:nth-child(5) { grid-area: 3/3; }
.dice-face--6 { grid-template: repeat(3,1fr) / repeat(2,1fr); }
/* face placement in 3D — standard opposite-face-sums-to-7 die.
   1 front, 6 back, 2 right, 5 left, 3 top, 4 bottom. */
.dice-face--1 { transform: translateZ(48px); }
.dice-face--6 { transform: rotateY(180deg) translateZ(48px); background: linear-gradient(150deg,#eef1fb,#dfe4f5); }
.dice-face--2 { transform: rotateY(90deg) translateZ(48px); }
.dice-face--5 { transform: rotateY(-90deg) translateZ(48px); }
.dice-face--3 { transform: rotateX(90deg) translateZ(48px); }
.dice-face--4 { transform: rotateX(-90deg) translateZ(48px); }
/* even-face pips lean blue so the die itself carries red↔blue */
.dice-face--2 i, .dice-face--4 i, .dice-face--6 i { background: radial-gradient(circle at 35% 30%, #8fc0ff, var(--blue-dk));
  box-shadow: inset 0 -2px 3px rgba(0,0,0,.3), 0 1px 1px rgba(0,0,0,.2); }
.dice-shadow { position: absolute; left: 50%; bottom: -22px; transform: translateX(-50%);
  width: 76px; height: 16px; border-radius: 50%; background: radial-gradient(ellipse, rgba(0,0,0,.5), transparent 70%);
  filter: blur(3px); transition: transform var(--dice-dur,0ms) ease; }
.dice.is-rolling { animation: diceHop var(--dice-dur,900ms) cubic-bezier(.3,.7,.3,1); }
@keyframes diceHop { 0% { translate: 0 0; } 22% { translate: 0 -26px; } 48% { translate: 0 4px; }
  68% { translate: 0 -10px; } 100% { translate: 0 0; } }

/* ── THE TAP PAD — generous, finger-friendly hit area around the die ──
   Neutral by default (a spectator sees a resting die). The roller gets the
   .is-roller class → the die reads as pressable: it lifts, breathes, and a
   red↔blue ring pulses under it. */
.dice-pad { -webkit-appearance: none; appearance: none; background: none; border: 0; margin: 0;
  padding: 22px 40px; display: grid; place-items: center; cursor: default;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation; outline: none;
  min-height: 140px; min-width: 176px; /* comfortable thumb target */ }
.dice-ring { position: absolute; inset: 50% auto auto 50%; width: 128px; height: 128px;
  margin: -64px 0 0 -64px; border-radius: 50%; pointer-events: none; opacity: 0;
  background: radial-gradient(circle, transparent 52%, rgba(90,160,255,.30) 62%, rgba(255,94,120,.30) 78%, transparent 86%);
  filter: blur(2px); transition: opacity .3s ease; }

/* roller idle — the die is alive and asking to be tapped */
.tele-seed-modal.is-roller .dice-pad { cursor: pointer; }
.tele-seed-modal.is-roller.is-idle .dice { animation: diceBreathe 2.4s ease-in-out infinite; }
.tele-seed-modal.is-roller.is-idle .dice-ring { opacity: 1; animation: ringPulse 2.4s ease-in-out infinite; }
.tele-seed-modal.is-roller.is-idle .dice-shadow { animation: shadowBreathe 2.4s ease-in-out infinite; }
/* press feedback (pointer held / :active) */
.tele-seed-modal.is-roller.is-idle .dice-pad:active .dice { animation: none; transform: rotateX(-16px) rotateY(28deg) scale(.9); transition: transform .09s ease; }
@keyframes diceBreathe {
  0%,100% { transform: rotateX(-20deg) rotateY(24deg) translateY(0) scale(1); }
  50%     { transform: rotateX(-24deg) rotateY(30deg) translateY(-6px) scale(1.04); } }
@keyframes shadowBreathe { 0%,100% { transform: translateX(-50%) scale(1); opacity: 1; }
  50% { transform: translateX(-50%) scale(.8); opacity: .7; } }
@keyframes ringPulse { 0%,100% { opacity: .55; transform: scale(1); } 50% { opacity: .9; transform: scale(1.14); } }

/* ── THE ROLL CUE — one line under the die; JS toggles which span shows ── */
.dice-cue { min-height: 34px; display: grid; place-items: center; text-align: center; }
.dice-cue > span { grid-area: 1/1; display: none; }
.dice-cue-tap { font-size: 13px; font-weight: 900; letter-spacing: 2.5px; text-transform: uppercase;
  color: var(--gold-neon); padding: 9px 18px; border-radius: 999px;
  background: linear-gradient(180deg, rgba(255,224,138,.16), rgba(255,224,138,.04));
  border: 1px solid rgba(255,224,138,.5); box-shadow: 0 0 20px -6px rgba(255,224,138,.5);
  animation: tapNudge 1.5s ease-in-out infinite; }
.dice-cue-wait { font-size: 14px; font-weight: 700; color: var(--ink-dim); }
.dice-cue-wait b { color: var(--ink); font-weight: 900; }
.dice-cue-rolling { font-size: 13px; font-weight: 900; letter-spacing: 3px; text-transform: uppercase;
  color: var(--ink-dim); animation: rollingBlink 1s ease-in-out infinite; }
.tele-seed-modal.is-roller.is-idle .dice-cue-tap { display: inline-block; }
.tele-seed-modal:not(.is-roller).is-idle .dice-cue-wait { display: inline-block; }
.tele-seed-modal.is-rolling .dice-cue-rolling { display: inline-block; }
@keyframes tapNudge { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
@keyframes rollingBlink { 0%,100% { opacity: .55; } 50% { opacity: 1; } }

/* the WORD CARD — 6 rows; the landed row lights up gold when the die settles. */
.word-card { width: 100%; border-radius: 16px; overflow: hidden;
  background: linear-gradient(160deg, rgba(31,36,64,.94), rgba(20,24,44,.94));
  border: 1px solid var(--line); box-shadow: var(--neon-edge); }
.word-card-head { font-size: 11px; font-weight: 900; letter-spacing: 2px; text-transform: uppercase;
  color: var(--ink-dim); text-align: center; padding: 10px; border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,.04), transparent); }
.word-card-list { list-style: none; margin: 0; padding: 6px; display: flex; flex-direction: column; gap: 6px;
  counter-reset: wc; }
.word-card-row { position: relative; display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: 10px; background: var(--bg-2); border: 1px solid var(--line);
  transition: transform .25s ease, background .25s ease, border-color .25s ease, box-shadow .25s ease; }
.word-card-row .wc-num { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center;
  font-size: 13px; font-weight: 900; color: var(--ink-dim); background: var(--panel-2); border: 1px solid var(--line); }
.word-card-row .wc-word { flex: 1; min-width: 0; font-size: 16px; font-weight: 800; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* dimmed non-winners once the roll resolves */
.word-card.resolved .word-card-row:not(.landed) { opacity: .4; }
/* the LANDED row — gold arcade highlight */
.word-card-row.landed { background: linear-gradient(180deg, rgba(255,209,102,.18), rgba(255,209,102,.06));
  border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold), 0 8px 24px -8px rgba(255,209,102,.5);
  transform: scale(1.02); }
.word-card-row.landed .wc-num { color: var(--yellow-ink); background: linear-gradient(180deg, var(--gold-neon), var(--gold)); border-color: var(--gold-neon); }
.word-card-row.landed .wc-word { color: var(--gold-neon); text-shadow: 0 0 12px rgba(255,209,102,.4); }
@media (prefers-reduced-motion: reduce) {
  .dice { transition: none; } .dice.is-rolling { animation: none; }
  .tele-seed-fx .fx-glow { animation: none; }
  /* kill idle breathing loops but KEEP the affordance readable: a steady ring
     + a static (non-nudging) TAP chip still say "this is pressable". */
  .tele-seed-modal.is-roller.is-idle .dice,
  .tele-seed-modal.is-roller.is-idle .dice-ring,
  .tele-seed-modal.is-roller.is-idle .dice-shadow { animation: none; }
  .tele-seed-modal.is-roller.is-idle .dice-ring { opacity: .8; }
  .dice-cue-tap, .dice-cue-rolling { animation: none; }
}

/* Accessibility: honor reduced-motion. Kill ambient/looping CSS animation but
   keep functional transitions. (Canvas game-feel motion is essential to reading
   a move, so it stays; these are the decorative loops.) */
@media (prefers-reduced-motion: reduce) {
  .pchip--turn, .pchip--turn .pchip-sw, .status-bar.is-you .status-dot,
  [data-for="game"] .fx-glow,
  .deck.pulse, .score-burst.is-on,
  .emote-float, .winner-crown, .winner-pawn,
  .deck-center.can-draw, .deck-center.can-draw::before,
  .tele-wait-pencils, .tele-timer.warn,
  .hero-mascot { animation: none !important; }
  /* keep the deck readable + centered even with its bob animation killed */
  .deck-center.can-draw { transform: translate(-50%, -50%); }
  .winner-card, .winner-you { animation-duration: .01ms !important; }
  .screen { animation: none !important; }
  .discard, .discard.is-flourish { animation: none !important; }
}
