/*
 * main.css — SusGames mobile-first design system
 *
 * Structure:
 *   1. CSS custom properties (design tokens)
 *   2. Reset / base
 *   3. Layout shell
 *   4. Shared components (buttons, top-nav)
 *   5. Homepage
 *   6. Game-card list
 *   7. Mode-select page
 *   8. How-to-play accordion
 *   9. Coming-soon page
 *   9b. Room page (online create, join form, lobby player list)
 *  10. Desktop adjustments (min-width: 600px)
 *
 * All sizing uses rem (base 16 px) so users can scale via browser font size.
 * Touch targets are at least 48 px tall per WCAG 2.5.5.
 * No hover-only interactions — all interactive states work via :focus-visible
 * and :active for touch devices.
 */

/* ─────────────────────────────────────────────────────────────────
   1. Design tokens
──────────────────────────────────────────────────────────────────── */
:root {
  /* Palette */
  --color-bg:          #1a1a2e;   /* deep navy — main background */
  --color-surface:     #16213e;   /* slightly lighter navy — cards */
  --color-surface-alt: #0f3460;   /* mid blue — secondary cards / highlights */
  --color-accent:      #e94560;   /* vivid pink-red — primary CTA / danger / imposter / tie */
  --color-accent-dim:  #b53347;   /* darker accent — hover/active state */
  --color-info:        #4aa8ff;   /* electric blue — neutral/secondary/info accents */
  --color-gold:        #f5c451;   /* restrained warm gold — special moments only */
  --color-text:        #f0f0f0;   /* near-white — primary text */
  --color-text-muted:  #9090aa;   /* muted — secondary text */
  --color-soon:        #3a3a5c;   /* disabled card background */
  --color-soon-text:   #6060aa;   /* disabled card text */

  /* Typography.
     The two LARGEST steps are fluid (clamp) so big headings — the Results outcome
     ("Innocents Win"), the room title, the secret word, the starting player —
     don't become oversized and force ugly wrapping / excess vertical space on a
     narrow phone, WHILE staying exactly the same on desktop (the clamp upper bound
     equals the previous fixed value). Small/body steps are unchanged. */
  --font-family:  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-size-xs: 0.75rem;   /* 12 px */
  --font-size-sm: 0.875rem;  /* 14 px */
  --font-size-md: 1rem;      /* 16 px */
  --font-size-lg: 1.25rem;   /* 20 px */
  --font-size-xl: clamp(1.375rem, 4.2vw, 1.75rem);  /* 22 → 28 px */
  --font-size-2xl: clamp(1.875rem, 6.5vw, 2.5rem);  /* 30 → 40 px */

  /* Spacing */
  --space-xs:  0.25rem;  /* 4 px  */
  --space-sm:  0.5rem;   /* 8 px  */
  --space-md:  1rem;     /* 16 px */
  --space-lg:  1.5rem;   /* 24 px */
  --space-xl:  2rem;     /* 32 px */
  --space-2xl: 3rem;     /* 48 px */

  /* Borders & radii */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 22px;

  /* Minimum tap target height (WCAG 2.5.5) */
  --tap-min: 48px;

  /* Max content width — keeps layout comfortable on large screens */
  --max-width: 480px;

  /* ── Visual system: ONE cohesive language for depth, motion & glow ──
     Every new effect references these tokens so the whole app shares the
     same easing, timing, elevation, and accent-glow — never ad-hoc values. */

  /* Elevation (soft, dark-friendly shadows — layered for realism) */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.45);

  /* Accent glow — the signature "premium mystery" halo. */
  --glow-accent: 0 0 0 1px rgba(233, 69, 96, 0.35),
                 0 8px 30px rgba(233, 69, 96, 0.28);
  --glow-accent-soft: 0 6px 24px rgba(233, 69, 96, 0.18);
  /* Gold glow — reserved for special/celebratory moments (winner, #1 rank). */
  --glow-gold: 0 0 0 1px rgba(245, 196, 81, 0.4), 0 8px 30px rgba(245, 196, 81, 0.25);
  /* Warm amber — the "Play Together" mode family. Distinct from the red accent and
     the blue surface, yet part of the same cohesive palette. The gradient is kept
     a touch DEEPER (Issue 5) so white text/icons stay clearly legible on it — the
     brightest earlier stop washed the description out. */
  --color-warm:      #d9821f;   /* warm amber (slightly deeper for white-text contrast) */
  --grad-warm:  linear-gradient(135deg, #e6952b 0%, var(--color-warm) 55%, #b5670c 100%);
  --glow-warm-soft: 0 6px 24px rgba(217, 130, 31, 0.20);
  --glow-warm: 0 0 0 1px rgba(217, 130, 31, 0.38), 0 8px 30px rgba(217, 130, 31, 0.28);
  /* Info glow — neutral electric-blue halo for secondary/informational accents. */
  --glow-info: 0 6px 24px rgba(74, 168, 255, 0.18);

  /* Gradients (tasteful, restrained) */
  --grad-surface: linear-gradient(160deg,
                    rgba(255, 255, 255, 0.05) 0%,
                    rgba(255, 255, 255, 0) 55%),
                  var(--color-surface);
  --grad-accent:  linear-gradient(135deg, #ff5470 0%, var(--color-accent) 55%, #c9354f 100%);

  /* Motion — shared easing + durations so timing feels consistent. */
  --ease-out:    cubic-bezier(0.22, 1, 0.36, 1);   /* decelerate, premium */
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* gentle overshoot */
  --dur-fast:   0.14s;
  --dur-med:    0.28s;
  --dur-slow:   0.5s;
}

/* ─────────────────────────────────────────────────────────────────
   2. Reset / base
──────────────────────────────────────────────────────────────────── */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  /* Prevent text inflation on mobile Safari */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  color: var(--color-text);
  font-family: var(--font-family);
  font-size: var(--font-size-md);
  line-height: 1.5;
  min-height: 100dvh;            /* dvh: accounts for mobile browser chrome */
  overflow-x: hidden;
  /* Layered, premium backdrop: two faint accent auras over the deep navy base.
   * Fixed attachment so it reads as depth behind the content, not a tiled fill.
   * Pure paint (no animation) — zero runtime cost. */
  background-color: var(--color-bg);
  background-image:
    radial-gradient(60% 45% at 15% 0%, rgba(233, 69, 96, 0.12) 0%, rgba(233, 69, 96, 0) 60%),
    radial-gradient(55% 40% at 100% 100%, rgba(15, 52, 96, 0.55) 0%, rgba(15, 52, 96, 0) 55%),
    linear-gradient(180deg, #1c1c34 0%, var(--color-bg) 45%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  cursor: pointer;
  font: inherit;
  border: none;
  background: none;
}

ol, ul {
  list-style: none;
}

/*
 * Make the HTML `hidden` attribute reliably hide elements.
 * The browser default is [hidden]{display:none}, but that is user-agent
 * origin and any author `display:` rule (e.g. display:flex on a component)
 * would override it. Marking this !important guarantees `hidden` always
 * wins — so toggling `el.hidden = true` in JS truly hides the element.
 * Applies everywhere, so future components that use `hidden` are safe too.
 */
[hidden] {
  display: none !important;
}

/* ─────────────────────────────────────────────────────────────────
   3. Layout shell
──────────────────────────────────────────────────────────────────── */
.app-shell {
  /*
   * Constrains content to --max-width and centres it on wide screens.
   * On mobile the full viewport width is used.
   */
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* ─────────────────────────────────────────────────────────────────
   4. Shared components
──────────────────────────────────────────────────────────────────── */

/* -- Generic button ------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: var(--space-sm) var(--space-xl);
  border-radius: var(--radius-md);
  font-size: var(--font-size-lg);
  font-weight: 600;
  /* Micro-interaction: a quick, responsive press. transform + opacity only
   * (compositor-friendly — no layout/paint thrash). Reduced-motion opts out of
   * the transform globally (see the reduced-motion block at the end of the file). */
  transition: background-color 0.15s ease, opacity 0.15s ease,
              transform 0.1s ease, box-shadow 0.15s ease, filter 0.15s ease;
  text-align: center;
  width: 100%;
  /* Mobile tap responsiveness: remove the ~300ms click delay so the :active
   * press shows instantly on tap, and suppress the default grey tap-highlight box
   * (we provide our own clearer press feedback below). Desktop/mouse unaffected. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
/* Clear "press" on tap/click for every enabled button. Never on :disabled.
 * Slightly deeper scale + a brightness dip so a valid tap is OBVIOUSLY registered
 * on mobile (the prior 0.97 alone read as unresponsive on some phones). This is
 * "tap received" feedback only — it never implies the server succeeded. */
.btn:not(:disabled):active {
  transform: scale(0.96);
  filter: brightness(0.92);
}
/* A gentle hover-lift on pointer devices (compositor-only). Reduced-motion stills
 * it (the global rule cancels .btn:hover transforms). Pure decoration — the
 * :active press still wins on tap, so touch behaviour is unchanged. */
@media (hover: hover) {
  .btn:not(:disabled):hover {
    transform: translateY(-2px);
  }
}

.btn--primary {
  background-image: var(--grad-accent);
  background-color: var(--color-accent);   /* fallback under the gradient */
  color: #fff;
  box-shadow: var(--glow-accent-soft), var(--shadow-sm);
}
.btn--primary:active,
.btn--primary:focus-visible {
  background-color: var(--color-accent-dim);
}
/* A little lift on hover-capable devices; harmless (and unused) on touch. */
@media (hover: hover) {
  .btn--primary:not(:disabled):hover {
    filter: brightness(1.06);
    box-shadow: var(--glow-accent), var(--shadow-md);
    transform: translateY(-1px);
  }
  .btn--secondary:not(:disabled):hover,
  .btn--danger:not(:disabled):hover {
    filter: brightness(1.12);
    transform: translateY(-1px);
  }
}

.btn--secondary {
  background-color: var(--color-surface-alt);
  color: var(--color-text);
}
.btn--secondary:active,
.btn--secondary:focus-visible {
  background-color: var(--color-surface);
}

/* Disabled buttons — visibly inert: dimmed, no pointer, and pointer-events off
 * so they can't be pressed and don't show a press/hover state. */
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/*
 * "Magical" reveal button (Part 7). Reserved for the HIGH-INTENT reveal moments
 * only — Reveal My Information, Reveal Result, Lock In Guess — so pressing them
 * feels like an event. A slow accent-glow breathe plus a diagonal shimmer sweep
 * (both transform/opacity/filter — compositor-cheap), layered on top of the
 * normal .btn--primary. NOT used for ordinary buttons, so nothing else competes.
 * Disabled + reduced-motion both fall back to a calm static button.
 */
.btn--reveal {
  position: relative;
  overflow: hidden;
  /* A static accent halo (no animated box-shadow — animations stay compositor-
   * only: transform/opacity/filter). The "breathe" is a filter pulse instead. */
  box-shadow: var(--glow-accent), var(--shadow-md);
  animation: reveal-btn-breathe 2.6s ease-in-out infinite;
}
.btn--reveal::after {
  content: "";
  position: absolute;
  top: -60%;
  left: -70%;
  width: 45%;
  height: 220%;
  background: linear-gradient(100deg,
      rgba(255, 255, 255, 0) 0%,
      rgba(255, 255, 255, 0.35) 50%,
      rgba(255, 255, 255, 0) 100%);
  transform: translateX(0) rotate(10deg);
  animation: reveal-btn-shimmer 3s var(--ease-out) infinite;
  pointer-events: none;
}
/* filter-only pulse (compositor-friendly) — a gentle brighten/settle breathe. */
@keyframes reveal-btn-breathe {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.1); }
}
@keyframes reveal-btn-shimmer {
  0%   { transform: translateX(0) rotate(10deg); opacity: 0; }
  12%  { opacity: 1; }
  55%  { opacity: 1; }
  100% { transform: translateX(360%) rotate(10deg); opacity: 0; }
}
/* A disabled reveal button (already acknowledged / locked) is calm — no glow. */
.btn--reveal:disabled { animation: none; }
.btn--reveal:disabled::after { animation: none; opacity: 0; }

/* Keyboard focus ring — visible for keyboard/tab users on every interactive
 * control, without showing a ring on mouse/touch press (:focus-visible only).
 * Uses outline (doesn't affect layout) so it can't cause overflow. */
.btn:focus-visible,
.btn-link:focus-visible,
.setup__chip:focus-visible,
.player-list__item--interactive:focus-visible,
.top-nav__back:focus-visible,
.how-to-play__toggle:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 2px;
}

/* -- Top nav (back link) ------------------------------------------- */
.top-nav {
  padding: var(--space-md) var(--space-md) 0;
}

.top-nav__back {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  font-size: var(--font-size-lg);
  color: var(--color-text-muted);
  padding: var(--space-xs) 0;
}
.top-nav__back:active,
.top-nav__back:focus-visible {
  color: var(--color-text);
}

/* ─────────────────────────────────────────────────────────────────
   5. Homepage
──────────────────────────────────────────────────────────────────── */
.home {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--space-xl) var(--space-md) var(--space-md);
  gap: var(--space-lg);
}

.home__header {
  text-align: center;
  padding-top: var(--space-md);
}

.home__logo {
  font-size: var(--font-size-2xl);
  font-weight: 900;
  letter-spacing: -0.02em;
  /* Accent gradient text for a premium logotype, with a soft halo. */
  background-image: linear-gradient(135deg, #ff6b83 0%, var(--color-accent) 60%, #c9354f 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  line-height: 1.1;
  filter: drop-shadow(0 4px 18px rgba(233, 69, 96, 0.35));
}

.home__tagline {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
}

/* A floating cast of "suspects" above the logo — a small, playful, game-like
 * flourish. Each emoji bobs on its own gentle offset. Decorative; reduced-motion
 * stills them. */
.home__suspects {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
  font-size: 1.9rem;
  line-height: 1;
}
.home__suspect {
  display: inline-block;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.35));
  animation: suspect-bob 3.4s var(--ease-out) infinite;
}
.home__suspect:nth-child(2) { animation-delay: 0.4s; }
.home__suspect:nth-child(3) { animation-delay: 0.8s; }
.home__suspect:nth-child(4) { animation-delay: 1.2s; }
.home__suspect:nth-child(5) { animation-delay: 1.6s; }
@keyframes suspect-bob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-7px) rotate(-3deg); }
}

/* ─────────────────────────────────────────────────────────────────
   Dynamic floating emojis — a reusable, DECORATIVE strip of emojis that
   gently bob/drift, used on every page for personality (built by
   SusGamesCopy.floatEmojis into a [data-emoji] container). Purely
   presentational and aria-hidden; the global reduced-motion rule stills
   the motion (and the emojis simply sit still — no layout shift).
──────────────────────────────────────────────────────────────────── */
.emoji-floats {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
  font-size: 1.6rem;
  line-height: 1;
  /* Never intercept taps — this is decoration layered near real controls. */
  pointer-events: none;
  user-select: none;
}
.emoji-float {
  display: inline-block;
  filter: drop-shadow(0 5px 9px rgba(0, 0, 0, 0.32));
  /* Each emoji bobs + drifts on its own stagger (animation-delay set inline). A
   * slightly varied duration per index keeps them from marching in lockstep. */
  animation: emoji-float-bob calc(3s + (var(--i, 0) * 0.4s)) var(--ease-out) infinite;
  will-change: transform;
}
@keyframes emoji-float-bob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  30%      { transform: translateY(-8px) rotate(-6deg); }
  60%      { transform: translateY(-3px) rotate(5deg); }
}

/* Rotating sus one-liner under the tagline. Coral-tinted, subtle. */
.home__rotator {
  margin-top: var(--space-md);
  font-size: var(--font-size-md);
  font-weight: 600;
  color: var(--color-accent);
  min-height: 1.5em;                 /* reserve space so the swap doesn't jump */
  transition: opacity 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
/* Applied briefly by the rotator just before swapping text (fade/slide out). */
.copy-rotate--out { opacity: 0; transform: translateY(-4px); }

/* Non-interactive teaser for upcoming games — plain text, never a control. */
.home__soon {
  margin: var(--space-lg) var(--space-md) 0;
  text-align: center;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  letter-spacing: 0.02em;
}

.home__footer {
  margin-top: auto;
  text-align: center;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  padding-bottom: var(--space-md);
}

/* ─────────────────────────────────────────────────────────────────
   6. Game-card list
──────────────────────────────────────────────────────────────────── */
.game-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/*
 * Base card — shared styles for both available and coming-soon variants.
 * Each card is a full-width tap target with adequate height.
 */
.game-card {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-md);
  min-height: var(--tap-min);
  transition: background-color 0.15s ease;
}

.game-card--available {
  background-image: var(--grad-surface);
  background-color: var(--color-surface);
  border: 1px solid var(--color-surface-alt);
  box-shadow: var(--shadow-md);
  transition: background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.game-card--available:active,
.game-card--available:focus-visible {
  background-color: var(--color-surface-alt);
}
@media (hover: hover) {
  .game-card--available:hover {
    transform: translateY(-3px);
    box-shadow: var(--glow-accent-soft), var(--shadow-lg);
    border-color: var(--color-accent);
  }
  /* Nudge the arrow to invite the tap. */
  .game-card--available:hover .game-card__arrow { transform: translateX(3px); }
}
.game-card__arrow { transition: transform var(--dur-fast) var(--ease-out); }

.game-card--soon {
  background-color: var(--color-soon);
  opacity: 0.7;
  cursor: default;
}

.game-card__emoji {
  font-size: var(--font-size-xl);
  width: 2rem;
  text-align: center;
  flex-shrink: 0;
}

.game-card__name {
  flex: 1;
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--color-text);
}

.game-card--soon .game-card__name {
  color: var(--color-soon-text);
}

.game-card__arrow {
  font-size: var(--font-size-xl);
  color: var(--color-accent);
  font-weight: 700;
}

.game-card__badge {
  font-size: var(--font-size-xs);
  color: var(--color-soon-text);
  background-color: var(--color-bg);
  padding: 2px var(--space-sm);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

/* Hero game card (the one playable game): taller, with a subtitle and a live
 * "Play now" badge, so the primary action reads as the obvious place to click. */
.game-card--hero {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-lg);
}
.game-card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.game-card__sub {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.4;
}
/* A small pulsing "Play now" pill with a live dot, gold-tinted for a premium
 * "this is ready" cue. */
.game-card__live {
  flex-shrink: 0;
  align-self: center;
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: #2a2410;
  background-color: var(--color-gold);
  padding: 3px var(--space-sm);
  border-radius: var(--radius-sm);
  white-space: nowrap;
  box-shadow: var(--glow-gold);
  animation: live-pulse 2.2s var(--ease-out) infinite;
}
@keyframes live-pulse {
  0%, 100% { opacity: 0.9; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.05); }
}

/* ─────────────────────────────────────────────────────────────────
   7. Mode-select page (Imposter)
──────────────────────────────────────────────────────────────────── */
.mode-select {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding-bottom: var(--space-xl);
}

.mode-select__header {
  text-align: center;
  padding: var(--space-xl) var(--space-md) var(--space-lg);
}

.mode-select__emoji {
  /* Fluid so the hero emoji doesn't dominate a 320px screen; desktop unchanged
     (upper bound = the original 3.5rem). */
  font-size: clamp(2.75rem, 11vw, 3.5rem);
  display: block;
  margin-bottom: var(--space-sm);
}

.mode-select__title {
  font-size: var(--font-size-xl);
  font-weight: 900;
  color: var(--color-accent);
}

.mode-select__desc {
  margin-top: var(--space-sm);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.6;
}

/* -- Mode cards ---------------------------------------------------- */
.mode-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: 0 var(--space-md);
}

.mode-card {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg);
  border-radius: var(--radius-lg);
  min-height: 80px;
  transition: background-color 0.15s ease;
}

.mode-card {
  transition: background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.mode-card--primary {
  background-image: var(--grad-accent);
  background-color: var(--color-accent);
  box-shadow: var(--glow-accent-soft), var(--shadow-md);
}
.mode-card--primary:active,
.mode-card--primary:focus-visible {
  background-color: var(--color-accent-dim);
}

/* Play Together — warm amber/gold family. Same card shape/motion as the accent
   card, just a different (cohesive) hue so the three modes read as distinct. */
.mode-card--warm {
  background-image: var(--grad-warm);
  background-color: var(--color-warm);
  box-shadow: var(--glow-warm-soft), var(--shadow-md);
}
.mode-card--warm:active,
.mode-card--warm:focus-visible {
  background-color: #b5670c;
}

/* Issue 5 — text/icon readability on the two BRIGHT (colored) cards. A subtle
   text shadow lifts white text off the vivid red/amber backgrounds without
   changing the palette; the secondary (blue surface) card is dark already and
   doesn't need it. */
.mode-card--primary .mode-card__name,
.mode-card--warm .mode-card__name {
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.mode-card--primary .mode-card__hint,
.mode-card--warm .mode-card__hint,
.mode-card--primary .mode-card__arrow,
.mode-card--warm .mode-card__arrow {
  color: #fff;                       /* full-strength white (was 0.7–0.75 alpha) */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.mode-card--primary .mode-card__hint,
.mode-card--warm .mode-card__hint {
  opacity: 0.92;                     /* keep it secondary without being washed out */
}

.mode-card--secondary {
  background-image: var(--grad-surface);
  background-color: var(--color-surface-alt);
  border: 1px solid var(--color-surface);
  box-shadow: var(--shadow-md);
}
.mode-card--secondary:active,
.mode-card--secondary:focus-visible {
  background-color: var(--color-surface);
}
@media (hover: hover) {
  .mode-card:hover { transform: translateY(-3px); }
  .mode-card--primary:hover { box-shadow: var(--glow-accent), var(--shadow-lg); }
  .mode-card--warm:hover { box-shadow: var(--glow-warm), var(--shadow-lg); }
  .mode-card--secondary:hover {
    box-shadow: var(--shadow-lg);
    border-color: var(--color-accent);
  }
  .mode-card:hover .mode-card__arrow { transform: translateX(3px); }
}
.mode-card__arrow { transition: transform var(--dur-fast) var(--ease-out); }

.mode-card__icon {
  font-size: var(--font-size-xl);
  flex-shrink: 0;
}

.mode-card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mode-card__name {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: #fff;
}

.mode-card__hint {
  font-size: var(--font-size-xs);
  color: rgba(255, 255, 255, 0.75);
  line-height: 1.4;
}

.mode-card__arrow {
  font-size: var(--font-size-xl);
  color: rgba(255, 255, 255, 0.7);
  font-weight: 700;
}

/* A mode-card rendered as a <button> (e.g. "Play In Person" which reveals a
   sub-panel rather than navigating). Reset the native button chrome so it
   matches the <a> cards exactly, and stretch it to full width. */
button.mode-card {
  width: 100%;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
button.mode-card--secondary {
  border: 1px solid var(--color-surface);
}

/* Second-level (in-person sub-mode) panel: a heading plus a Back control. */
.mode-list--sub {
  margin-top: var(--space-sm);
}
.mode-list__heading {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
  text-align: center;
  margin-bottom: var(--space-xs);
}
.mode-list__back {
  align-self: center;
  min-height: var(--tap-min);
  padding: var(--space-sm) var(--space-lg);
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* Small badge on the Create Room screen echoing the chosen interaction mode. */
.room__mode-badge {
  display: inline-block;
  margin-top: var(--space-sm);
  padding: 4px 12px;
  border-radius: var(--radius-md);
  background-color: var(--color-surface-alt);
  color: var(--color-text);
  font-size: var(--font-size-sm);
  font-weight: 600;
}

/* ── Public room browser (Play Online) ──────────────────────────────
   A lightweight, live list of open online rooms. Each card is a clean two-row
   layout so nothing ever wraps into ugly vertical text (Issue 2):
     row 1 — the ROOM NAME (primary, full width, truncates with an ellipsis)
     row 2 — the player count (left) and the Join button (right), space-between
   This keeps the name prominent, the count on one line, and Join clearly
   separated, at every width from 320px up. */
.open-rooms {
  margin: var(--space-md) 0 var(--space-lg);
  padding: 0 var(--space-md);
  /* The parent .room__view centers its children (align-items:center), which would
     otherwise size this section to its widest content (a long room name + pill)
     and overflow a 320px screen. Pin it to the view width and allow its rows to
     shrink. */
  width: 100%;
  min-width: 0;
}
.open-rooms__legend {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: var(--space-sm);
}
.open-rooms__list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.open-rooms__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  background-image: var(--grad-surface);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-sm);
  /* Let children (name row, meta row) shrink below their content width so a long
     name + "In game" pill can never push the card past a 320px viewport. */
  min-width: 0;
}
/* Row 1: the room name (primary) + an optional "In game" pill on the right. */
.open-rooms__namerow {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
}
.open-rooms__name {
  font-size: var(--font-size-md);
  font-weight: 700;
  color: var(--color-text);
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Small "In game" pill — subordinate, never wraps, doesn't crowd the name. */
.open-rooms__pill {
  flex: 0 0 auto;
  font-size: var(--font-size-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 2px 8px;
  border-radius: 999px;
  color: var(--color-gold);
  background-color: rgba(245, 196, 81, 0.14);
  white-space: nowrap;
}
/* Row 2: count on the left, Join on the right. */
.open-rooms__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}
.open-rooms__count {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  white-space: nowrap;         /* never wrap "5 / 12 players" into vertical text */
}
.open-rooms__join {
  flex: 0 0 auto;
  width: auto;                 /* override the base .btn width:100% */
  min-height: var(--tap-min);
  padding: var(--space-sm) var(--space-xl);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
}
.open-rooms__empty {
  text-align: center;
  padding: var(--space-lg) var(--space-md);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-sm);
}
.open-rooms__empty-title {
  font-size: var(--font-size-md);
  font-weight: 700;
  color: var(--color-text);
}
.open-rooms__empty-body {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  /* Slightly brighter than --color-text-muted so this small (14px) empty-state
     line clears WCAG AA 4.5:1 on the translucent empty-state surface (axe measured
     the muted token at 4.35:1 here — just under). #a6a6c0 ≈ 6.8:1 on the base. */
  color: #a6a6c0;
}

/* ─────────────────────────────────────────────────────────────────
   8. How-to-play accordion
──────────────────────────────────────────────────────────────────── */
.how-to-play {
  margin: var(--space-xl) var(--space-md) 0;
}

.how-to-play__toggle {
  width: 100%;
  min-height: var(--tap-min);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  transition: color 0.15s ease;
}
.how-to-play__toggle:active,
.how-to-play__toggle:focus-visible {
  color: var(--color-text);
}

.how-to-play__body {
  padding: var(--space-md);
  background-color: var(--color-surface);
  border-radius: var(--radius-md);
  margin-top: var(--space-xs);
}

.how-to-play__steps {
  list-style: decimal;
  padding-left: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.how-to-play__steps li {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.6;
}
.how-to-play__steps li em { color: var(--color-text); font-style: italic; }
.how-to-play__steps li strong { color: var(--color-text); font-weight: 700; }

/* Section headings inside the expanded How to Play. */
.how-to-play__section {
  font-size: var(--font-size-md);
  font-weight: 800;
  color: var(--color-text);
  margin: var(--space-md) 0 var(--space-xs);
}
.how-to-play__section:first-child { margin-top: 0; }
.how-to-play__lead {
  font-size: var(--font-size-sm);
  color: var(--color-text);
  line-height: 1.6;
  margin-bottom: var(--space-xs);
}
.how-to-play__meta {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin-top: var(--space-xs);
}
/* A plain (non-numbered) list variant for the modes / guess bullets. */
.how-to-play__steps--plain {
  list-style: disc;
}
.how-to-play__steps--plain li { margin-bottom: 2px; }

/* ─────────────────────────────────────────────────────────────────
   9. Coming-soon page
──────────────────────────────────────────────────────────────────── */
.coming-soon {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.coming-soon__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: var(--space-xl) var(--space-md);
  text-align: center;
}

.coming-soon__emoji {
  font-size: 4rem;
}

.coming-soon__title {
  font-size: var(--font-size-xl);
  font-weight: 900;
  color: var(--color-text);
}

.coming-soon__text {
  font-size: var(--font-size-md);
  color: var(--color-text-muted);
}

.coming-soon__body .btn {
  margin-top: var(--space-md);
  max-width: 240px;
}

/* ─────────────────────────────────────────────────────────────────
   9b. Room page (online create)
──────────────────────────────────────────────────────────────────── */
.room {
  display: flex;
  flex-direction: column;
  flex: 1;
}

/*
 * A room view fills the space below the top-nav and centres its content.
 * Both #view-create and #view-room use this; only one is visible at a time.
 */
.room__view {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
  padding: var(--space-xl) var(--space-md);
  text-align: center;
  /* Never let a flex child force the view wider than the viewport. */
  width: 100%;
  min-width: 0;
  /* Every game screen fades + rises in on navigation, so each phase transition
   * (setup→reveal→clue→voting→elimination→results→lobby) reads as a deliberate
   * step rather than a hard cut. transform/opacity only; disabled by
   * reduced-motion. This is the single, consistent "state transition" flourish. */
  animation: view-enter var(--dur-med) var(--ease-out) both;
}
@keyframes view-enter {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Headings/labels can carry arbitrary content (mode labels, long titles); let
 * long unbroken tokens wrap rather than trigger horizontal scroll anywhere. */
.room__title,
.room__mode-badge,
.reveal__legend,
.play__lead {
  max-width: 100%;
  overflow-wrap: anywhere;
}

.room__header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.room__emoji {
  /* Fluid on narrow screens; desktop unchanged (upper bound = original 3.5rem). */
  font-size: clamp(2.75rem, 11vw, 3.5rem);
}

.room__title {
  font-size: var(--font-size-xl);
  font-weight: 900;
  color: var(--color-accent);
}

.room__subtitle {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.6;
  max-width: 30ch;
}

/*
 * The big room code. letter-spacing spreads the characters so they are
 * easy to read aloud and copy by eye. word-break keeps it inside the
 * viewport even on a 320px screen.
 */
.room__code {
  /* Fluid so the 4-char code + wide letter-spacing never overflows a 320px
     screen, while staying the full 4rem on desktop. */
  font-size: clamp(2.75rem, 13vw, 4rem);
  font-weight: 900;
  letter-spacing: 0.25em;
  /* Nudge right so the trailing letter-spacing doesn't visually off-centre it */
  padding-left: 0.25em;
  color: var(--color-text);
  background-image: var(--grad-surface);
  background-color: var(--color-surface);
  border: 2px dashed var(--color-surface-alt);
  border-radius: var(--radius-lg);
  box-shadow: var(--glow-accent-soft), var(--shadow-md);
  padding-top: var(--space-md);
  padding-bottom: var(--space-md);
  padding-right: var(--space-md);
  width: 100%;
  max-width: 320px;
  word-break: break-all;
  font-variant-numeric: tabular-nums;
}

/* Action buttons stack full-width on mobile. */
.room__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 100%;
  max-width: 320px;
}

.room__hint {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  min-height: 1.5em;   /* reserve space so layout doesn't jump when text appears */
}

.room__error {
  font-size: var(--font-size-sm);
  color: var(--color-accent);
}

/* Top-aligned variant — used by the lobby, which can grow with the player
 * list and shouldn't be vertically centred. */
.room__view--top {
  justify-content: flex-start;
  padding-top: var(--space-lg);
}

/* Smaller room code — used on the join page where the code is a confirmation,
 * not the hero element. */
.room__code--sm {
  font-size: 2.5rem;
  padding-top: var(--space-sm);
  padding-bottom: var(--space-sm);
}

/* -- Join form ----------------------------------------------------- */
.room__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 100%;
  max-width: 320px;
  align-items: stretch;
  text-align: left;
}

.room__label {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.room__input {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-lg);
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 2px solid var(--color-surface-alt);
  border-radius: var(--radius-md);
}
.room__input:focus-visible {
  outline: none;
  border-color: var(--color-accent);
}
.room__input::placeholder {
  color: var(--color-text-muted);
}
/* Invalid state (Part F): a clue failing the live safety check gets a clear red
   edge so the "not allowed" state is immediately obvious while typing. */
.room__input--invalid,
.room__input--invalid:focus-visible {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.28);
}

/* Host "Remove" control (Part Q) — a small, low-emphasis button in a player row's
   badge area. Clear but not visually dominant; host-only (rendered only for the
   host, enforced on the server). */
.player-list__kick {
  appearance: none;
  border: 1px solid rgba(233, 69, 96, 0.5);
  background: transparent;
  color: var(--color-accent);
  font-size: var(--font-size-xs);
  font-weight: 700;
  padding: 3px 9px;
  border-radius: 999px;
  cursor: pointer;
  line-height: 1.2;
}
.player-list__kick:not(:disabled):hover {
  background-color: rgba(233, 69, 96, 0.14);
}
.player-list__kick:disabled { opacity: 0.5; cursor: default; }

/* -- Player list (lobby) ------------------------------------------- */
.player-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  width: 100%;
  max-width: 320px;
}

/*
 * Player row — a SYMMETRIC 3-column grid so the name is GENUINELY centred
 * relative to the ENTIRE row, not just the space left over beside the badges:
 *
 *     [ balancer (1fr) ] [ name (auto) ] [ badges (1fr, pinned right) ]
 *
 * The left balancer is an empty ::before that mirrors the right badge track; the
 * two 1fr side tracks stay equal, so the content-sized name in the middle track
 * reads centred whatever the badge combination (Host / You / Ready / Out / Seen,
 * alone or combined) — never off-centre "in the space left after the badges".
 * min-width:0 on the name lets a long name grow into both side tracks and wrap
 * instead of overlapping the badges or overflowing (safe degradation). This is
 * purely structural — NO fixed padding, NO absolute positioning — so it can't
 * collide at any width or badge count. (.results__row and .leaderboard__row keep
 * their own explicit grids below and reset/repurpose the ::before accordingly.)
 */
.player-list__item {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: var(--space-sm);
  min-height: var(--tap-min);
  padding: var(--space-sm) var(--space-md);
  background-image: var(--grad-surface);
  background-color: var(--color-surface);
  border: 1px solid var(--color-surface-alt);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
/* The left balancing area (empty). It takes the first 1fr track so the name's
 * middle track is flanked by two equal flexible tracks → true centre. */
.player-list__item::before {
  content: "";
}

/* The viewing player's own row is accented so they can find themselves. */
.player-list__item--me {
  border-color: var(--color-accent);
  box-shadow: var(--glow-accent-soft);
}

/* A player whose Socket.IO connection has dropped (still a room member). */
.player-list__item--offline {
  opacity: 0.5;
}

.player-list__name {
  /* Middle grid track (auto), flanked by two equal 1fr tracks → the name sits in
   * the true centre of the row regardless of badges. min-width:0 lets a long name
   * grow into the side tracks and wrap (overflow-wrap:anywhere) rather than
   * pushing the badges off-screen or overflowing. */
  min-width: 0;
  text-align: center;
  font-size: var(--font-size-md);
  font-weight: 600;
  color: var(--color-text);
  /* Long names wrap instead of pushing anything off a narrow screen; a single
   * very long unbroken token breaks rather than causing horizontal overflow. */
  word-break: break-word;
  overflow-wrap: anywhere;
}

/*
 * Badge box: the second grid track, sized to its content and pinned to the row's
 * right edge (justify-self:end). In normal flow — NOT absolutely positioned — so
 * it always reserves real space the name track respects. Every player row wraps
 * its badge(s) in this box (server templates + JS-built rows), so one rule covers
 * any count. A loose .player-list__tag that is a direct child of the row (older
 * markup) is treated the same way.
 */
.player-list__badges,
.player-list__item > .player-list__tag,
.player-list__item > .one-phone__remove {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.player-list__tag {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--color-text-muted);
  background-color: var(--color-bg);
  padding: 2px var(--space-sm);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.player-list__tag--me {
  color: #fff;
  background-color: var(--color-accent);
}

/* -- "or" divider + code input (online entry: create vs. join) ----- */
.room__divider {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
  max-width: 320px;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}
.room__divider::before,
.room__divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background-color: var(--color-surface-alt);
}

/* Room-code entry field — spaced, uppercase, centred to match the code look. */
.room__input--code {
  text-align: center;
  letter-spacing: 0.35em;
  padding-left: calc(var(--space-md) + 0.35em); /* offset trailing letter-spacing */
  text-transform: uppercase;
  font-weight: 700;
}

/* ─────────────────────────────────────────────────────────────────
   9b-name. Edit-your-name control (Milestone 6)
──────────────────────────────────────────────────────────────────── */
.name-edit {
  width: 100%;
  max-width: 320px;
  margin: var(--space-sm) auto 0;
  text-align: left;
}

.name-edit__label {
  display: block;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--space-xs);
}

.name-edit__row {
  display: flex;
  align-items: stretch;
  gap: var(--space-sm);
}

.name-edit__input {
  /* Take all the leftover row width. min-width:0 lets the flex item shrink
     below its content width on narrow phones instead of forcing overflow. */
  flex: 1 1 auto;
  min-width: 0;
  min-height: 48px;
  padding: 0.75rem;
  font-size: var(--font-size-md);
  border: 1px solid var(--color-surface-alt);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
}

.name-edit__save {
  /* Only as wide as its label needs. The base .btn rule sets width:100% (for
     full-width primary actions); here the Save button sits next to the input,
     so we override that and let flex-basis size it to its content. */
  flex: 0 0 auto;
  width: auto;
  min-height: 48px;
  padding-left: var(--space-lg);
  padding-right: var(--space-lg);
  white-space: nowrap;
}

/* ─────────────────────────────────────────────────────────────────
   9c. Setup / configuration page (Milestone 5)
──────────────────────────────────────────────────────────────────── */
.setup__notice {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-accent);
}

.setup__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  width: 100%;
  max-width: 320px;
  text-align: left;
}

/* Remove the default fieldset chrome; it is only a semantic/disable wrapper. */
.setup__fieldset {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

/* A disabled fieldset (non-host view) dims and blocks interaction with all
 * controls inside it — the server also rejects non-host changes. */
.setup__fieldset:disabled {
  opacity: 0.75;
}

.setup__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.setup__legend {
  font-size: var(--font-size-md);
  font-weight: 700;
  color: var(--color-text);
}

.setup__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* A row variant for small option sets (Imposters, difficulty). */
.setup__options--row .setup__chip {
  flex: 1;
}

/* Selectable option "chip". Uses aria-pressed for state; styled via class. */
.setup__chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: var(--tap-min);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  font-size: var(--font-size-md);
  font-weight: 600;
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 2px solid var(--color-surface-alt);
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.setup__chip:active,
.setup__chip:focus-visible {
  border-color: var(--color-accent);
}
.setup__chip--selected {
  /* Use the DARKER accent so white label text clears WCAG AA (4.5:1): plain
   * --color-accent (#e94560) with #fff is only 3.83:1; --color-accent-dim
   * (#b53347) is 5.95:1. Same visual language, accessible contrast. */
  background-color: var(--color-accent-dim);
  border-color: var(--color-accent-dim);
  color: #fff;
}
.setup__chip:disabled {
  opacity: 0.4;
  cursor: default;
}

/* "Recommended" pill inside an Imposter-count chip (Part 6). The NUMBER is the
   primary element; this badge is subordinate — noticeably smaller text, tighter
   padding, lower-emphasis fill, and it never wraps or overflows the chip on a
   narrow phone. Reads as "2 · Recommended" rather than dominating the control. */
.setup__rec {
  font-size: 0.625rem;              /* 10px — clearly secondary to the number */
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.2;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  background-color: rgba(255, 255, 255, 0.16);
  opacity: 0.9;
  white-space: nowrap;
  flex: 0 0 auto;
}
/* Make the NUMBER in an Imposter-count chip the primary element (larger + bold),
   so the chip hierarchy reads number-first with a small badge beside it. */
.setup__options--row .setup__chip {
  font-weight: 800;
}

.setup__hint {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
}

/* ─────────────────────────────────────────────────────────────────
   Role-reveal screen (Milestone 8 — private per-player reveal)
──────────────────────────────────────────────────────────────────── */
.reveal__card {
  background-image: var(--grad-surface);
  background-color: var(--color-surface);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: var(--radius-lg);
  padding: var(--space-lg) var(--space-md);
  margin: var(--space-md) 0;
  text-align: center;
  box-shadow: var(--shadow-md);
}

.reveal__lead {
  font-size: var(--font-size-lg);
  font-weight: 600;
  margin: 0 0 var(--space-sm);
}

.reveal__note {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin: 0;
}

/* The two card states stack their buttons full-width with comfortable gaps. */
.reveal__state {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-sm);
}

.reveal__btn {
  margin-top: var(--space-sm);
}

/* The role headline — the mysterious centrepiece of the reveal. */
.reveal__role {
  font-size: var(--font-size-xl);
  font-weight: 700;
  letter-spacing: 0.01em;
  margin: 0 0 var(--space-sm);
}
.reveal__role--imposter { color: var(--color-accent); }
.reveal__role--innocent { color: var(--color-text); }

/* Category / secret word / hint / fellow-Imposter as a clean label→value list. */
.reveal__facts {
  margin: 0 0 var(--space-sm);
  padding: 0;
  text-align: left;
}
.reveal__term {
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  margin-top: var(--space-sm);
}
.reveal__value {
  font-size: var(--font-size-lg);
  font-weight: 600;
  margin: 0;
  word-break: break-word;   /* long words never cause horizontal scroll */
}

.reveal__players {
  margin-top: var(--space-lg);
}

.reveal__legend {
  font-size: var(--font-size-md);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-sm);
}

/* ─────────────────────────────────────────────────────────────────
   Host-disconnect banner (host lifecycle). Shown to OTHER players while a
   disconnected host is within the grace window; cleared on reconnect or once a
   new host is named. A single fixed strip at the top of the shell.
──────────────────────────────────────────────────────────────────── */
.host-banner {
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-sm) var(--space-md);
  background-color: rgba(255, 193, 7, 0.14);
  border-bottom: 1px solid var(--color-gold, #ffc107);
  color: var(--color-text);
  font-size: var(--font-size-sm);
  font-weight: 600;
  text-align: center;
}
.host-banner[hidden] { display: none; }

/* Secondary "you're out but still host" note on the gameplay screen. Sits above
   the current phase panel; never replaces host controls (issue #4). */
.play__elim-host-note {
  margin: 0 0 var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-md);
  background-color: rgba(255, 193, 7, 0.10);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  text-align: center;
}
.play__elim-host-note[hidden] { display: none; }

/* ─────────────────────────────────────────────────────────────────
   Game-start / clue-order screen (Milestone 9)
──────────────────────────────────────────────────────────────────── */
.start__card {
  background-image: var(--grad-surface);
  background-color: var(--color-surface);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: var(--radius-lg);
  padding: var(--space-lg) var(--space-md);
  margin: var(--space-md) 0;
  text-align: center;
  width: 100%;
  max-width: 320px;
  box-shadow: var(--shadow-md);
}

.start__label {
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-xs);
}

/* The starting player's name — the centrepiece of this screen. */
.start__starter {
  font-size: var(--font-size-xl);
  font-weight: 900;
  color: var(--color-accent);
  margin: 0 0 var(--space-md);
  word-break: break-word;   /* long names never cause horizontal scroll */
}

.start__direction {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--color-text);
  margin: 0 0 var(--space-sm);
}

.start__instruction {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin: 0;
}

/* The clue-order list reuses .player-list. Because each row is display:grid the
 * native <ol> marker is suppressed, so we number the seats with a CSS counter
 * placed in the row's LEFT balancer track (::before) — the name stays in the
 * true centre (the right badge track balances it). #start-order kept for any
 * legacy markup; #spoken-order is the live clue-phase turn order. */
#spoken-order,
#start-order {
  counter-reset: seat;
  list-style: none;
  margin: var(--space-sm) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-xs);
}
#spoken-order .player-list__item::before,
#start-order .player-list__item::before {
  counter-increment: seat;
  content: counter(seat) ".";
  justify-self: start;
  color: var(--color-text-muted);
  font-weight: 700;
  font-size: var(--font-size-sm);
}

/* ─────────────────────────────────────────────────────────────────
   Gameplay screen (Milestone 10 — clue / discussion / voting / elim)
──────────────────────────────────────────────────────────────────── */
.play__panel {
  width: 100%;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  align-items: stretch;
  text-align: center;
}

.play__lead {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--color-text);
}

.play__note {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
}

/* Animated "waiting" indicator — three dots that pulse in sequence. Tasteful,
 * tiny, and it signals the app is alive while waiting on others. Append
 * <span class="dots"><i></i><i></i><i></i></span> after any waiting message. */
.dots {
  display: inline-flex;
  gap: 3px;
  margin-left: 4px;
  vertical-align: middle;
}
.dots i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background-color: currentColor;
  opacity: 0.4;
  animation: dot-pulse 1.2s var(--ease-out) infinite;
}
.dots i:nth-child(2) { animation-delay: 0.2s; }
.dots i:nth-child(3) { animation-delay: 0.4s; }
@keyframes dot-pulse {
  0%, 100% { opacity: 0.3; transform: translateY(0); }
  40%      { opacity: 1;   transform: translateY(-3px); }
}

.play__progress {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  min-height: 1.4em;
}

/* The clue countdown — a display aid; the server owns the real deadline. */
.play__timer {
  font-size: var(--font-size-xl);
  font-weight: 900;
  color: var(--color-accent);
  min-height: 1.2em;
}

.play__tie {
  font-size: var(--font-size-sm);
  font-weight: 700;
  color: var(--color-accent);
}

/*
 * Digital-mode tie announcement. Shown ABOVE the elimination result card when a
 * vote tied, so the random elimination reads as a deliberate, explained outcome
 * rather than an arbitrary pick. Visually distinct (accent border + heading) but
 * within the existing SusGames identity. Dynamic text only wraps — no fixed
 * widths, no absolute positioning — so it can't overflow at any width.
 */
.tie-announce {
  width: 100%;
  max-width: 320px;
  box-sizing: border-box;
  padding: var(--space-md);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  /* A faint coral wash so the tie moment reads as a HIGH, slightly dramatic beat
   * (Part 6) without a heavy effect. */
  background:
    radial-gradient(130% 130% at 50% 0%,
      rgba(233, 69, 96, 0.16) 0%, rgba(233, 69, 96, 0) 60%),
    var(--color-surface);
  box-shadow: var(--glow-accent-soft);
  text-align: center;
}
.tie-announce__title {
  font-size: var(--font-size-lg);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-accent);
  margin: 0 0 var(--space-xs);
  /* Subtle heartbeat so the tie announcement feels tense but not gimmicky. */
  animation: tie-throb 2s ease-in-out infinite;
}
@keyframes tie-throb {
  0%, 100% { transform: scale(1);    opacity: 0.92; }
  50%      { transform: scale(1.04); opacity: 1; }
}
.tie-announce__body {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin: 0;
  overflow-wrap: anywhere;
}

/*
 * Reveal gate — a shared, mysterious "concealed card + reveal button" used for
 * suspenseful reveals (digital tie result, private role/secret cards). Premium,
 * subtle, on-brand: a dark card with an accent glow and a "sealed" glyph. The
 * concealed state carries NO real content; the content panel is filled/shown
 * only on reveal (see reveal-gate.js). Animation is opt-out via reduced-motion.
 */
.reveal-gate {
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
}
.reveal-gate__conceal {
  position: relative;
  overflow: hidden;                 /* clip the shimmer sweep */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xl) var(--space-md);
  border: 1px solid rgba(233, 69, 96, 0.35);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 120% at 50% 0%,
      rgba(233, 69, 96, 0.20) 0%, rgba(233, 69, 96, 0) 62%),
    var(--grad-surface);
  box-shadow: var(--glow-accent-soft), var(--shadow-md);
}
/* A slow diagonal shimmer that sweeps across the sealed card — the signature
 * "there's something behind here" flourish. Pure transform, GPU-cheap. */
.reveal-gate__conceal::before {
  content: "";
  position: absolute;
  top: -50%;
  left: -60%;
  width: 40%;
  height: 200%;
  background: linear-gradient(100deg,
      rgba(255, 255, 255, 0) 0%,
      rgba(255, 255, 255, 0.10) 50%,
      rgba(255, 255, 255, 0) 100%);
  transform: translateX(0) rotate(8deg);
  animation: reveal-gate-shimmer 3.2s var(--ease-out) infinite;
  pointer-events: none;
}
@keyframes reveal-gate-shimmer {
  0%   { transform: translateX(0) rotate(8deg); opacity: 0; }
  15%  { opacity: 1; }
  60%  { opacity: 1; }
  100% { transform: translateX(420%) rotate(8deg); opacity: 0; }
}
.reveal-gate__glyph {
  position: relative;
  font-size: 2.5rem;
  line-height: 1;
  color: var(--color-accent);
  letter-spacing: 0.2em;
  /* A slow, subtle "breathing" pulse + accent halo so the sealed card feels
   * alive without being distracting. transform/opacity/filter only. */
  animation: reveal-gate-pulse 2.4s ease-in-out infinite;
}
/* A faint rotating "seal ring" orbiting the glyph — the extra flourish that makes
 * the reveal moment feel special (Part 14). Pure rotate transform, GPU-cheap,
 * and it sits BEHIND the glyph so it never obscures content. */
.reveal-gate__glyph::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 3.6em;
  height: 3.6em;
  margin: -1.8em 0 0 -1.8em;
  border-radius: 50%;
  border: 1px dashed rgba(233, 69, 96, 0.45);
  animation: seal-rotate 9s linear infinite;
  pointer-events: none;
}
@keyframes seal-rotate {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
@keyframes reveal-gate-pulse {
  0%, 100% { opacity: 0.85; transform: scale(1);    filter: drop-shadow(0 0 4px rgba(233, 69, 96, 0.4)); }
  50%      { opacity: 1;    transform: scale(1.06); filter: drop-shadow(0 0 12px rgba(233, 69, 96, 0.7)); }
}
.reveal-gate__label {
  position: relative;
  font-size: var(--font-size-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  margin: 0;
}
/* The content panel flips + fades in on reveal — noticeably more than a plain
 * show/hide, but short and satisfying. */
.reveal-gate__content--in {
  animation: reveal-gate-flip var(--dur-slow) var(--ease-out) both;
  transform-origin: center top;
}
@keyframes reveal-gate-flip {
  0%   { opacity: 0; transform: perspective(700px) rotateX(-14deg) translateY(10px) scale(0.97); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: perspective(700px) rotateX(0) translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .reveal-gate__content--in { animation: none; }
  .reveal-gate__conceal::before { animation: none; opacity: 0; }
}

/* Imposter guess card — reuses the reveal-gate "sealed" look for the final-guess
 * moment. The inner form/controls stack full-width within it. */
.guess-card {
  gap: var(--space-md);
}
.guess-card .room__form,
.guess-card #guess-host { width: 100%; }
/* Host record-outcome buttons (multi-device / one phone): two compact buttons in
 * the badges track, sized to content so a row stays tidy on narrow screens. */
.guess-host__actions { gap: var(--space-xs); }
.guess-host__btn {
  width: auto;
  min-height: 40px;
  padding: var(--space-xs) var(--space-md);
  font-size: var(--font-size-sm);
  font-weight: 700;
}
/* A final-guess row whose decision is RECORDED: visibly locked (dimmed, accent
 * edge) so it's immediately obvious the host already decided this Imposter and
 * no further tap is possible. The outcome itself (correct/wrong) is deliberately
 * NOT shown here — it stays private until the Results screen. */
.guess-host__row--locked {
  border-color: var(--color-accent);
  opacity: 0.75;
}
.guess-host__recorded {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: var(--space-xs) var(--space-md);
  font-size: var(--font-size-sm);
  font-weight: 700;
  color: var(--color-accent);
  white-space: nowrap;
}

/* Voting list — each active option is a big tappable target. */
.play__vote-list {
  gap: var(--space-sm);
}
.play__vote-option {
  cursor: default;
}
.play__vote-option--active {
  cursor: pointer;
  border-color: var(--color-surface-alt);
}
@media (hover: hover) {
  .play__vote-option--active:hover {
    transform: translateY(-2px);
    border-color: var(--color-accent);
    box-shadow: var(--glow-accent-soft);
  }
}
.play__vote-option--active:active {
  transform: scale(0.98);
}
.play__vote-option--active:focus-visible {
  border-color: var(--color-accent);
  background-color: var(--color-surface-alt);
}

/* A vote option toggled ON (One Phone tie pick, or a just-cast digital vote):
 * clearly, unambiguously selected — accent border + glow + a tiny check, with a
 * one-shot pop so the choice is obviously registered. */
.play__vote-option--selected {
  border-color: var(--color-accent);
  background-image: linear-gradient(135deg,
      rgba(233, 69, 96, 0.22), rgba(233, 69, 96, 0.08));
  box-shadow: var(--glow-accent);
  animation: vote-pop var(--dur-med) var(--ease-spring) both;
}
/* A check chip on the right of the selected row (added via the badges track). */
.play__vote-option--selected .player-list__name::after {
  content: " ✓";
  color: var(--color-accent);
  font-weight: 900;
}
@keyframes vote-pop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}

/*
 * Clue card (Discussion screen). A revealed clue can be a whole sentence, so the
 * name and the clue text each get their OWN block area, STACKED vertically — the
 * clue wraps freely on its own line(s) beneath the name and can never overlap the
 * name or overflow the card. This deliberately does NOT reuse .player-list__tag
 * (an absolutely-positioned, nowrap chip sized for short badges), which is exactly
 * why long clues previously spilled over the player's name.
 */
.clue-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  /* Constrain to the row so nothing extends past the card edges. */
  min-width: 0;
  padding: var(--space-sm) var(--space-md);
  background-color: var(--color-surface);
  border: 1px solid var(--color-surface-alt);
  border-radius: var(--radius-md);
  text-align: left;
}
.clue-card__name {
  font-size: var(--font-size-sm);
  font-weight: 700;
  color: var(--color-text-muted);
  /* Long names wrap rather than forcing horizontal overflow. */
  min-width: 0;
  word-break: break-word;
}
.clue-card__text {
  margin: 0;
  font-size: var(--font-size-md);
  font-weight: 600;
  color: var(--color-text);
  /* The core of the fix: normal wrapping for sentences PLUS overflow-wrap so a
   * single very long token/URL breaks instead of overflowing the card. */
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* ─────────────────────────────────────────────────────────────────
   Discussion chat (ONLINE only) — a modern, self-contained chat panel:
   a defined card with a header, a tall scrollable message region (newest at the
   bottom), and a clear composer. Sized with a fixed internal scroll region so the
   whole PAGE never grows unbounded and the Continue control stays reachable.
──────────────────────────────────────────────────────────────────── */
.chat {
  margin-top: var(--space-md);
  display: flex;
  flex-direction: column;
  /* CHAT is a distinct SOCIAL surface (Issue 2), not another gameplay control:
     gameplay CTAs are the red accent; the chat leans into the electric-blue info
     hue so it reads instantly as "this is where players talk". A subtle blue-tinted
     panel, a blue top edge, and a soft glow separate it from the clue cards and the
     red Continue/vote controls without introducing a new design language. */
  border: 1px solid rgba(74, 168, 255, 0.35);
  border-top: 2px solid var(--color-info);
  border-radius: var(--radius-lg);
  background-image:
    linear-gradient(160deg, rgba(74, 168, 255, 0.10) 0%, rgba(74, 168, 255, 0) 42%),
    var(--grad-surface);
  background-color: var(--color-surface);
  box-shadow: 0 0 0 1px rgba(74, 168, 255, 0.08), var(--shadow-md);
  overflow: hidden;
}
.chat__legend {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--font-size-sm);
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--color-text);
  background-color: rgba(74, 168, 255, 0.10);
  border-bottom: 1px solid rgba(74, 168, 255, 0.22);
}
.chat__legend-icon { font-size: 1.05em; line-height: 1; }
.chat__legend-text { margin-right: auto; }
/* A small "live" pill — the social, of-the-moment cue. Blue, subtle pulse. */
.chat__legend-live {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-info);
  padding: 2px 8px;
  border-radius: 999px;
  background-color: rgba(74, 168, 255, 0.16);
  border: 1px solid rgba(74, 168, 255, 0.35);
}
.chat__log {
  list-style: none;
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  /* Substantially larger, usable message region — but a FIXED internal scroll
     region so the page never grows unbounded and Continue stays reachable. Scales
     with viewport but clamped on both ends. */
  height: clamp(200px, 38vh, 420px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;   /* momentum scroll on iOS */
}
.chat__msg {
  font-size: var(--font-size-sm);
  line-height: 1.45;
  /* Chat bubble: sits left by default, own-messages align right (see --me). */
  max-width: 85%;
  align-self: flex-start;
  padding: 6px 10px;
  border-radius: 14px 14px 14px 4px;
  background-color: var(--color-surface-alt);
  /* Long messages / single long tokens wrap instead of overflowing. */
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.chat__msg--me {
  align-self: flex-end;
  border-radius: 14px 14px 4px 14px;
  /* Own messages use the CHAT's blue identity — deliberately NOT the red accent
     (red is reserved for imposter/danger/CTA), so a player's own line reads as
     "me talking" rather than a warning. */
  background-image: linear-gradient(135deg, #4aa8ff 0%, #2f7fd6 100%);
  background-color: #2f7fd6;
}
.chat__who {
  display: block;
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--color-info);
  margin-bottom: 1px;
}
.chat__msg--me .chat__who { color: rgba(255, 255, 255, 0.9); }
.chat__text { color: var(--color-text); }
.chat__msg--me .chat__text { color: #fff; }
.chat__composer {
  display: flex;
  gap: var(--space-sm);
  align-items: stretch;
  padding: var(--space-sm) var(--space-md);
  border-top: 1px solid rgba(74, 168, 255, 0.22);
  background-color: rgba(74, 168, 255, 0.05);
}
/* Flex items in the composer row: the base .room__input / .btn both set
   width:100%, which — side by side — overflows the row. Reset both to auto and
   let flex size them (input grows, button hugs its label). min-width:0 lets the
   input shrink below its content on a narrow phone. */
.chat__input {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
  font-size: var(--font-size-md);      /* comfortable, and ≥16px avoids iOS zoom */
}
/* The Send button carries the chat's blue identity (distinct from the red primary
   CTAs on the same screen). Self-contained so it needs no .btn--* modifier. */
.chat__send {
  flex: 0 0 auto;
  width: auto;
  color: #fff;
  background-image: linear-gradient(135deg, #4aa8ff 0%, #2f7fd6 100%);
  background-color: #2f7fd6;
  border: none;
}
.chat__send:not(:disabled):hover { filter: brightness(1.08); }
.chat__error { margin: 0 var(--space-md) var(--space-sm); }

/* ─────────────────────────────────────────────────────────────────
   Results screen (Milestone 10 — winners, roles, leaderboard)
──────────────────────────────────────────────────────────────────── */
.results__title--players   { color: var(--color-text); }
.results__title--imposters { color: var(--color-accent); }

/* A small gold "crown" flourish above the winner headline — the celebratory beat
 * that makes the ending feel like a payoff (Part 13/14). It pops in with a spring
 * just before the title. Emoji only (no image asset); reduced-motion disables the
 * pop but keeps it visible. */
.winner-crown {
  font-size: 2.25rem;
  line-height: 1;
  filter: drop-shadow(0 4px 16px rgba(245, 196, 81, 0.5));
  animation: crown-pop var(--dur-slow) var(--ease-spring) both;
}
@keyframes crown-pop {
  0%   { opacity: 0; transform: translateY(8px) scale(0.6) rotate(-12deg); }
  70%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1) rotate(0deg); }
}

/* A small playful one-liner under the winner headline. Contextual, subtle,
 * never covering the actual result. Rises in just after the title. */
.results__flavor {
  font-size: var(--font-size-md);
  color: var(--color-text-muted);
  margin-top: var(--space-xs);
  animation: rise-in 0.4s ease 0.12s both;
}

/*
 * FINAL-GUESS OUTCOME banner (Part 6). When all Imposters were caught and got a
 * last word guess, the OUTCOME of that guess is the headline of the match — so it
 * is a big, unmistakable card here. A WIN-tinted gold banner for "Nailed It" (a
 * caught Imposter still won), a calm neutral one for "Missed It". This is what
 * stops the caught-but-won case from reading like a loss: the guess verdict
 * dominates, and the earlier "eliminated" status is just history in the roster.
 * Sits directly under the winner headline; rises in with the results cascade.
 */
.guess-outcome {
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
  box-sizing: border-box;
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  text-align: center;
  animation: rise-in var(--dur-med) var(--ease-out) 0.22s both;
}
.guess-outcome--win {
  border: 1px solid rgba(245, 196, 81, 0.55);
  background:
    radial-gradient(130% 130% at 50% 0%,
      rgba(245, 196, 81, 0.20) 0%, rgba(245, 196, 81, 0) 62%),
    var(--color-surface);
  box-shadow: var(--glow-gold);
}
.guess-outcome--miss {
  border: 1px solid var(--color-surface-alt);
  background: var(--grad-surface);
  box-shadow: var(--shadow-md);
}
.guess-outcome__eyebrow {
  font-size: var(--font-size-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-xs);
}
.guess-outcome__verdict {
  font-size: var(--font-size-2xl);
  font-weight: 900;
  line-height: 1.1;
  margin: 0 0 var(--space-xs);
}
.guess-outcome--win .guess-outcome__verdict {
  color: var(--color-gold);
  text-shadow: 0 0 22px rgba(245, 196, 81, 0.45);
}
.guess-outcome--miss .guess-outcome__verdict {
  color: var(--color-text);
}
.guess-outcome__detail {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin: 0;
  overflow-wrap: anywhere;
}

.results__row--imposter { border-color: var(--color-accent); }

/*
 * Final Roles rows carry TWO wide badges (role + elimination status), so the
 * global "absolutely-positioned badge box" model used for compact Host/You chips
 * would overlap the name. These rows instead use an explicit 3-column grid —
 * [ name ] [ role ] [ status ] — so the name and each badge get their own
 * dedicated, non-overlapping space. This is scoped to .results__row only, so the
 * centered-name behaviour everywhere else is unchanged.
 *
 * The name column flexes (1fr, min-width:0 so long names wrap/ellipsis instead
 * of pushing the badges); the two badge columns size to their content (auto) and
 * are right-aligned. Rows stay equal width/height via the shared .player-list
 * sizing plus the row overrides below.
 */
.results__row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  column-gap: var(--space-sm);
  /* This row has its OWN [name][role][status] grid — drop the base centering
   * balancer so the empty ::before doesn't steal the name's first column. */
  /* Reset the symmetric 4.5rem side padding used for the absolute-badge model;
   * the grid reserves badge space itself, so normal padding is correct here. */
  padding: var(--space-sm) var(--space-md);
  text-align: left;
}

/* Name column: left-aligned in the grid, takes the flexible first column, and
 * wraps long names instead of overflowing into the badge columns. */
.results__row::before { content: none; }   /* no centering balancer here */
.results__row .player-list__name {
  text-align: left;
  min-width: 0;
  word-break: break-word;
}

/*
 * The Final Roles badges are laid out by the grid, NOT the absolute box. Undo the
 * absolute positioning for this scope so the role/status tags occupy their own
 * grid tracks. `display: contents` lets the two child tags become direct grid
 * items (columns 2 and 3), each getting dedicated space.
 */
.results__row .player-list__badges {
  position: static;
  transform: none;
  display: contents;
}

/* Fixed, consistent badge widths so the columns line up down the whole list and
 * every row has the same structure regardless of role/status text length. */
.results__row .results__tag {
  justify-self: end;
  text-align: center;
}

.results__tag--imposter {
  color: #fff;
  background-color: var(--color-accent);
}
.results__tag--innocent {
  color: var(--color-text);
  background-color: var(--color-surface-alt);
}

/*
 * Leaderboard rows — [ rank ] [ name (flexible) ] [ wins ]. The player rows are
 * a 2-column grid by default; leaderboard rows add a rank column on the left.
 * The rank is a CSS counter rendered via ::before as a real grid item (NOT an
 * <ol> ::marker, which on a grid list-item would steal the first content column
 * and misalign name/wins). This keeps name and wins in dedicated, non-colliding
 * tracks with long names wrapping in the flexible middle track.
 * The list is populated client-side from this browser's local history.
 */
.leaderboard {
  list-style: none;
  counter-reset: leaderboard-rank;
}
/*
 * Leaderboard row — a SYMMETRIC 3-track grid, [ rank (1fr) ] [ name (auto) ]
 * [ wins (1fr) ], so the player name is CENTRED relative to the ENTIRE row and
 * its centre does NOT depend on the width of the rank or the wins pill (both side
 * tracks are equal 1fr). Rank pins to the far left of its track; wins pins to the
 * far right of its track — each keeps its own independent area. Long names grow
 * the middle track and wrap safely (min-width:0) without breaking the centring.
 */
.leaderboard__row {
  grid-template-columns: 1fr auto 1fr;
  column-gap: var(--space-md);   /* even breathing room between rank / name / wins */
  counter-increment: leaderboard-rank;
}
.leaderboard__row::before {
  content: counter(leaderboard-rank) ".";
  justify-self: start;           /* rank sits at the far left of its track */
  /* Fixed min-width rank column so the rank digits align down the whole list
   * (1. and 10. line up), keeping the left edge tidy. */
  min-width: 1.75em;
  text-align: right;
  color: var(--color-text-muted);
  font-weight: 700;
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
}
.leaderboard__row .player-list__name {
  text-align: center;   /* centred relative to the whole row (equal 1fr sides) */
  min-width: 0;
}
.leaderboard__wins {
  color: #fff;
  background-color: var(--color-accent);
  font-weight: 700;
  white-space: nowrap;   /* "10 wins" never wraps into two lines */
}
/* The top-ranked player gets a subtle gold treatment — a small, premium flourish
 * that makes #1 feel earned (restrained: just the rank + a soft gold edge). */
.leaderboard__row:first-child::before {
  color: var(--color-gold);
}
.leaderboard__row:first-child {
  border-color: rgba(245, 196, 81, 0.5);
  box-shadow: var(--glow-gold);
}
.leaderboard__row:first-child .leaderboard__wins {
  background-color: var(--color-gold);
  color: #2a2410;
}

/*
 * Reset Leaderboard — a deliberately SECONDARY, low-emphasis control so it never
 * competes with the primary Play Again / gameplay actions. It only opens the
 * confirmation; nothing is cleared without the explicit confirm.
 */
/*
 * The leaderboard section is a STABLE container: a fixed width that never depends
 * on its children's content. The reset button and its (wider) confirmation panel
 * live INSIDE this container, so opening/closing the confirmation can never change
 * the section's width or centring — the leaderboard rows above stay put (no
 * sideways shift). This is the structural fix (not an arbitrary margin).
 */
.leaderboard-section {
  width: 100%;
  max-width: 320px;
  margin-left: auto;
  margin-right: auto;
}
.leaderboard__reset {
  margin-top: var(--space-sm);
  text-align: center;
}
.btn-link {
  background: none;
  border: none;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--tap-min);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  text-decoration: underline;
  cursor: pointer;
}
.btn-link:active,
.btn-link:focus-visible {
  color: var(--color-text);
}

/* Confirmation panel — visually distinct (accent border) but not alarming.
 * Buttons stack in a normal actions row; dynamic text wraps, never overflows. */
.leaderboard__confirm {
  margin-top: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  text-align: center;
  /* Stay within the stable section so the confirmation can't widen it. */
  width: 100%;
  box-sizing: border-box;
}
.leaderboard__confirm-title {
  font-size: var(--font-size-md);
  font-weight: 700;
  color: var(--color-text);
  margin: 0 0 var(--space-xs);
}
.leaderboard__confirm-body {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin: 0 0 var(--space-md);
  overflow-wrap: anywhere;
}
.leaderboard__confirm-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Destructive confirm — clearly different from the primary accent CTA, so
 * "Reset Leaderboard" (confirm) reads as a deliberate, cautionary action. */
.btn--danger {
  background-color: transparent;
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
}
.btn--danger:active,
.btn--danger:focus-visible {
  background-color: var(--color-accent);
  color: #fff;
}

/* ─────────────────────────────────────────────────────────────────
   One Phone — setup + play (Milestone 10, single shared device)
──────────────────────────────────────────────────────────────────── */
.one-phone .room__view {
  gap: var(--space-md);
}

/* Remove-player control in the One Phone setup list. */
.one-phone__remove {
  flex: 0 0 auto;
  min-height: 40px;
  padding: var(--space-xs) var(--space-md);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  background-color: var(--color-bg);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
.one-phone__remove:active,
.one-phone__remove:focus-visible {
  color: var(--color-text);
  background-color: var(--color-surface-alt);
}

/* The One Phone config form is a normal setup form but centred within the view. */
.one-phone-setup .setup__form,
.one-phone-play .play__panel {
  width: 100%;
  max-width: 320px;
}

/* Reveal options in One Phone are tappable rows. */
.op-reveal-option {
  cursor: pointer;
}
.op-reveal-option:active,
.op-reveal-option:focus-visible {
  border-color: var(--color-accent);
}
/* A reveal row whose secret has already been seen is LOCKED (Issue 2): visibly
 * inert (dimmed, default cursor) so it's clear it can't be reopened. The server
 * also rejects a repeat reveal — this is only the visual half of the lock. */
.op-reveal-option--seen {
  cursor: default;
  opacity: 0.55;
}
.op-reveal-option--seen:active,
.op-reveal-option--seen:focus-visible {
  border-color: transparent;
}

/* ─────────────────────────────────────────────────────────────────
   10. Desktop adjustments (secondary — mobile is the default)
──────────────────────────────────────────────────────────────────── */
@media (min-width: 600px) {
  .app-shell {
    /* Visual border on wide screens helps signal the mobile-sized container */
    border-left:  1px solid var(--color-surface);
    border-right: 1px solid var(--color-surface);
  }

  .home {
    padding-top: var(--space-2xl);
  }

  .home__logo {
    font-size: 3.5rem;
  }

  .game-card {
    padding: var(--space-lg) var(--space-xl);
  }
}

/* ─────────────────────────────────────────────────────────────────
   11. Shared micro-animation language
   A small, restrained set of entrance animations for HIGH-PRIORITY
   reveals/results only (never every card). transform/opacity only, so
   they run on the compositor — no layout/paint cost. All are disabled
   wholesale by the reduced-motion block below.
──────────────────────────────────────────────────────────────────── */

/* Results — a STAGGERED entrance so the ending reads as a sequence: the
 * outcome title lands first (with a little spring), then the flavor line, the
 * secret-word card, the Final Roles, and the leaderboard cascade in. Each uses
 * transform/opacity only and is disabled under reduced motion. */
.results__title {
  animation: result-title-in var(--dur-slow) var(--ease-spring) both;
}
.results__flavor        { animation: rise-in var(--dur-med) var(--ease-out) 0.18s both; }
.results .start__card   { animation: rise-in var(--dur-med) var(--ease-out) 0.28s both; }
.results [aria-label="Final roles"] { animation: rise-in var(--dur-med) var(--ease-out) 0.40s both; }
.results .leaderboard-section       { animation: rise-in var(--dur-med) var(--ease-out) 0.52s both; }
.results .room__actions             { animation: rise-in var(--dur-med) var(--ease-out) 0.62s both; }

/* Winning-side flavour: Innocents get a calm cyan-tinged halo; Imposters get a
 * dramatic accent glow. Applied to the outcome title. */
.results__title--players {
  text-shadow: 0 0 22px rgba(120, 200, 255, 0.35);
}
.results__title--imposters {
  text-shadow: 0 0 22px rgba(233, 69, 96, 0.5);
}

@keyframes rise-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes result-title-in {
  0%   { opacity: 0; transform: translateY(14px) scale(0.9); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
/* Staggered rows inside a freshly rendered list (leaderboard / roles). Set via
 * an inline --i custom property by JS, or falls back to 0 for server markup. */
.list-stagger-in {
  animation: rise-in var(--dur-med) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 45ms);
}

/* ─────────────────────────────────────────────────────────────────
   12. Reduced motion — one authoritative opt-out
   Respect prefers-reduced-motion: reduce by removing DECORATIVE motion
   everywhere (button press, glyph pulse, entrances, reveal animation)
   while preserving all functional state changes (show/hide, disabled,
   selected). No content is hidden and no artificial waits are added.
──────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  /* Explicitly cancel every DECORATIVE effect (state changes are preserved:
   * show/hide, disabled, selected all still work — nothing is hidden). */
  .btn:not(:disabled):active,
  .btn:not(:disabled):hover,
  .play__vote-option--active:hover { transform: none; }
  .reveal-gate__glyph,
  .reveal-gate__glyph::before,
  .reveal-gate__conceal::before,
  .btn--reveal,
  .btn--reveal::after,
  .room__view,
  .dots i,
  .play__vote-option--selected,
  .list-stagger-in,
  .results__title,
  .results__flavor,
  .results [aria-label="Final roles"],
  .results .leaderboard-section,
  .results .room__actions,
  .results .start__card,
  .winner-crown,
  .home__suspect,
  .tie-announce__title,
  .guess-outcome,
  .game-card__live { animation: none; }
  /* The rotating microcopy still swaps text under reduced motion, but without
   * the fade/slide transform. */
  .home__rotator { transition: none; }
  .copy-rotate--out { opacity: 1; transform: none; }
  /* Keep the selected check visible even without the pop animation. */
  .play__vote-option--selected { box-shadow: var(--glow-accent); }
}
