/* ==========================================================================
   Charades — design system
   See DESIGN.md for the reasoning; this file is the implementation.

   ONE dark surface across every screen — phone and TV are the same product
   seen at two distances, not a light app with a dark companion. The room is
   dim, the TV is on, and a near-black board is what belongs in it.

   Three rules hold the whole thing together:

     1. Weight and scale carry hierarchy, not boxes. Borders are a last resort;
        contrast and space do the separating. Outlining every element is what
        makes an interface read as a toy.
     2. A neutral grotesque at heavy weights and tight tracking. Rounded
        terminals read playful-cheap at size; a serif reads editorial, not
        living-room. Neither is this product.
     3. Exactly one bright surface exists — the actor's word card. On an
        otherwise dark app it is the only thing lit, which is the entire point
        of the moment.
   ========================================================================== */

:root {
  /* --- palette -----------------------------------------------------------
     Each token ships a hex first and the same colour in OKLCH second, so a
     browser without OKLCH support takes the hex and everything else gets the
     wider gamut. Do not reorder these pairs.

     Warm near-black, not pure black and not neutral grey: the accent and team
     colours are warm, and a neutral ground makes them look muddy. */
  --c-bg: #121010;              --c-bg: oklch(0.180 0.005 55);
  --c-bg-deep: #0A0908;         --c-bg-deep: oklch(0.130 0.004 55);
  --c-raised: #1D1A18;          --c-raised: oklch(0.243 0.007 60);
  --c-raised-hi: #262220;       --c-raised-hi: oklch(0.290 0.008 60);

  --c-ink: #F6F1EA;             --c-ink: oklch(0.958 0.011 82);
  --c-ink-soft: #ABA298;        --c-ink-soft: oklch(0.712 0.014 75);
  --c-ink-faint: #6B6259;       --c-ink-faint: oklch(0.492 0.013 72);

  --c-accent: #F26A1B;          --c-accent: oklch(0.688 0.180 47);
  --c-accent-hi: #FF9A55;       --c-accent-hi: oklch(0.780 0.140 52);
  --c-accent-dim: #7A3208;      --c-accent-dim: oklch(0.404 0.106 45);

  --c-team-a: #F0714A;          --c-team-a: oklch(0.685 0.150 38);
  --c-team-b: #4FC4B2;          --c-team-b: oklch(0.756 0.098 178);
  --c-team-a-dim: #4A1E12;      --c-team-a-dim: oklch(0.283 0.062 38);
  --c-team-b-dim: #123A34;      --c-team-b-dim: oklch(0.343 0.043 178);

  --c-good: #5FCB63;            --c-good: oklch(0.762 0.157 145);
  --c-good-dim: #17401A;        --c-good-dim: oklch(0.340 0.073 145);
  --c-warn: #FF6B5A;            --c-warn: oklch(0.696 0.176 27);

  /* Semantic aliases. Components read these, never the raw --c-* values. */
  --bg: var(--c-bg);
  --bg-raised: var(--c-raised);
  --bg-hi: var(--c-raised-hi);
  --fg: var(--c-ink);
  --fg-soft: var(--c-ink-soft);
  --fg-faint: var(--c-ink-faint);
  --rule: rgba(246, 241, 234, 0.10);
  --rule-strong: rgba(246, 241, 234, 0.20);
  --team-a-fg: var(--c-team-a);
  --team-b-fg: var(--c-team-b);
  --accent-fg: var(--c-accent-hi);
  --good-fg: var(--c-good);
  --warn-fg: var(--c-warn);

  /* --- type --------------------------------------------------------------
     One neutral system grotesque. The voice comes from weight and scale, not
     from a second family — and not from a webfont, because this game runs on
     a LAN that may have no route to the internet at all. */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text",
          "Segoe UI Variable Text", "Segoe UI", Roboto, system-ui, sans-serif;
  --font-display: "SF Pro Display", -apple-system, BlinkMacSystemFont,
          "Segoe UI Variable Display", "Segoe UI", Roboto, system-ui, sans-serif;

  --t-xs: 0.8125rem;
  --t-sm: 0.9375rem;
  --t-base: 1.125rem;
  --t-md: 1.3125rem;
  --t-lg: 1.5rem;
  --t-xl: 2rem;
  --t-2xl: 2.75rem;
  --t-3xl: 3.5rem;

  /* --- space, shape ------------------------------------------------------ */
  --s1: 0.25rem; --s2: 0.5rem;  --s3: 0.75rem; --s4: 1rem;
  --s5: 1.5rem;  --s6: 2rem;    --s7: 2.75rem; --s8: 4rem;

  --r-sm: 0.5rem; --r-md: 0.875rem; --r-lg: 1.25rem; --r-xl: 1.75rem;
  --r-pill: 999px;

  /* On a dark ground a shadow is nearly invisible; depth comes from a lighter
     surface plus a hairline of light along the top edge. */
  --edge: inset 0 1px 0 rgba(255, 240, 225, 0.07);
  --shadow-md: 0 12px 30px -12px rgba(0, 0, 0, 0.8);
  --shadow-lg: 0 30px 70px -20px rgba(0, 0, 0, 0.9);

  /* --- z, semantic ------------------------------------------------------- */
  --z-base: 0; --z-raised: 10; --z-sticky: 20; --z-scrim: 30;
  --z-sheet: 40; --z-announce: 50; --z-confetti: 60;

  /* --- motion ------------------------------------------------------------ */
  --ease: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-settle: cubic-bezier(0.22, 1.04, 0.36, 1);
  --dur-fast: 140ms; --dur-base: 200ms; --dur-slow: 320ms; --dur-moment: 560ms;
}

/* ============================================================ base ======== */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

/*
   `color-scheme: dark` is the load-bearing line, not the custom scrollbar rules
   below it: it tells the browser the whole UI is dark, so the native scrollbar,
   form controls, spell-check underlines and overscroll glow all follow without
   being restyled one at a time. Without it you get a bright grey scrollbar
   welded to the side of a near-black page.
*/
:root { color-scheme: dark; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: var(--c-bg-deep);
  /* Firefox. Thin, and the thumb is the page's own warm neutral rather than a
     grey that belongs to no palette. */
  scrollbar-width: thin;
  scrollbar-color: rgba(246, 241, 234, 0.22) transparent;
}

/* WebKit and Blink. Overlay-style: no track chrome, a thumb that only asserts
   itself on hover, and padding via a transparent border so it never crowds the
   content it sits beside. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: rgba(246, 241, 234, 0.18);
  border-radius: var(--r-pill);
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background-color: rgba(246, 241, 234, 0.34); }
::-webkit-scrollbar-corner { background: transparent; }

/* The sheet scrolls independently and is a smaller surface, so its bar is
   slimmer to match. */
dialog.sheet::-webkit-scrollbar { width: 6px; }

body {
  position: relative;
  min-height: 100svh;
  background-color: var(--bg);
  background-image:
    radial-gradient(110% 70% at 50% -12%, rgba(242, 106, 27, 0.10), transparent 60%),
    linear-gradient(180deg, var(--c-bg) 0%, var(--c-bg-deep) 100%);
  background-attachment: fixed;
  color: var(--fg);
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overflow-x: hidden;
}

::selection { background: rgba(242, 106, 27, 0.35); }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.03;
  text-wrap: balance;
}
h1 { font-size: var(--t-xl); letter-spacing: -0.035em; }
h2 { font-size: var(--t-md); letter-spacing: -0.02em; }
h3 { font-size: var(--t-base); letter-spacing: -0.015em; }
p  { text-wrap: pretty; max-width: 68ch; }
b, strong { font-weight: 700; }

/*
   Almost nothing in this product is long-form prose — it is short UI copy two
   or three lines deep. `pretty` only rescues a single-word orphan, so it still
   leaves "at once." or "the code." stranded on a line of their own. `balance`
   evens the lines instead, which is what short copy actually wants. It caps out
   around six lines in browsers that support it and quietly does nothing beyond
   that, so it is safe to apply broadly here.
*/
p.muted,
.note,
.lead,
.switch-row .switch-note,
.announce-rule,
.tv-pack,
.hero-lead,
.hero-note { text-wrap: balance; }

.muted { color: var(--fg-soft); }
.tnum  { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
.center { text-align: center; }
.display {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.035em;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  border: 0;
}

/* ========================================================== layout ======== */

.shell {
  position: relative;
  z-index: var(--z-raised);
  width: 100%;
  max-width: 30rem;
  margin-inline: auto;
  padding: var(--s5) var(--s4) calc(var(--s7) + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}
.shell > * { min-width: 0; }
.shell.fill { min-height: 100svh; justify-content: space-between; }

.stack     { display: flex; flex-direction: column; gap: var(--s3); }
.stack-lg  { display: flex; flex-direction: column; gap: var(--s5); }
.row       { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.row-tight { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.spread    { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.grow      { flex: 1; min-width: 0; }

.rule { height: 1px; background: var(--rule); border: 0; margin: 0; }

/* A labelled divider — "or" with a rule running out to each side. Lives here
   rather than in landing.css because the front door renders the same join block
   in two places, and only one of them loads the landing stylesheet. */
.split {
  display: flex;
  align-items: center;
  gap: var(--s3);
  color: var(--fg-faint);
  font-size: var(--t-sm);
}
.split::before,
.split::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rule);
}

/* ========================================================== header ======== */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: 2.5rem;
}

.wordmark {
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--fg);
  text-decoration: none;
}
.wordmark .dot { color: var(--c-accent); }

.eyebrow {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-soft);
}

/* ========================================================= controls ======= */

.btn {
  --btn-bg: rgba(246, 241, 234, 0.09);
  --btn-fg: var(--fg);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  width: 100%;
  min-height: 3.5rem;
  padding: var(--s3) var(--s5);
  border: 0;
  border-radius: var(--r-md);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-display);
  font-size: var(--t-base);
  font-weight: 700;
  letter-spacing: -0.015em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  transition: transform var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              opacity var(--dur-fast) var(--ease);
}
.btn:hover:not(:disabled) { --btn-bg: rgba(246, 241, 234, 0.14); }
.btn:active:not(:disabled) { transform: scale(0.985); }
.btn:disabled { opacity: 0.32; cursor: not-allowed; }

/* Vivid fill, near-black label. White on this orange lands at 3.2:1 and fails;
   near-black on it is 5.6:1 and passes at every size. */
.btn.primary {
  --btn-bg: var(--c-accent);
  --btn-fg: #16110C;
  min-height: 3.75rem;
  font-size: var(--t-md);
  font-weight: 800;
  letter-spacing: -0.025em;
}
.btn.primary:hover:not(:disabled) { --btn-bg: var(--c-accent-hi); }

.btn.good {
  --btn-bg: var(--c-good);
  --btn-fg: #0B1F0C;
  font-weight: 800;
  letter-spacing: -0.025em;
}
.btn.good:hover:not(:disabled) { --btn-bg: #74DA78; }

.btn.danger {
  --btn-bg: transparent;
  --btn-fg: var(--warn-fg);
  box-shadow: inset 0 0 0 1px rgba(255, 107, 90, 0.32);
}
.btn.danger:hover:not(:disabled) { --btn-bg: rgba(255, 107, 90, 0.10); }
/* A tap whose request is still on the wire: visibly busy, and dead to further
   taps until the answer lands. Lag must never feel like a broken button. */
.btn.waiting { opacity: 0.55; pointer-events: none; }

.btn.quiet {
  --btn-bg: transparent;
  --btn-fg: var(--fg-soft);
  min-height: 2.5rem;
  padding: var(--s2) var(--s3);
  font-size: var(--t-sm);
  font-weight: 700;
  width: auto;
}
.btn.quiet:hover:not(:disabled) { --btn-bg: transparent; --btn-fg: var(--fg); }

/* The two big in-turn actions. Pressed in a hurry, often without looking. */
.btn.hero {
  min-height: 4.25rem;
  font-size: var(--t-lg);
  border-radius: var(--r-lg);
  letter-spacing: -0.03em;
}

.btn.loading { pointer-events: none; opacity: 0.6; }
.btn.loading::after {
  content: "";
  width: 1rem; height: 1rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin 620ms linear infinite;
}

:where(button, a, input, select, [tabindex]):focus-visible {
  outline: 2.5px solid var(--c-accent-hi);
  outline-offset: 3px;
}

/* --- text input ----------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: var(--s2); }

label, .label {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-soft);
}

input[type="text"], input:not([type]) {
  width: 100%;
  min-height: 3.5rem;
  padding: var(--s3) var(--s4);
  border: 0;
  border-radius: var(--r-md);
  background: var(--bg-raised);
  box-shadow: inset 0 0 0 1px var(--rule);
  color: var(--fg);
  font: inherit;
  font-size: var(--t-md);
  font-weight: 600;
  transition: box-shadow var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
input::placeholder { color: var(--fg-faint); opacity: 1; }
input:focus {
  outline: none;
  background: var(--bg-hi);
  box-shadow: inset 0 0 0 2px var(--c-accent);
}

/* --- room code -----------------------------------------------------------
   Four boxes, not one letter-spaced field. It reads as a game code instead of
   a form input, and it makes each character a separate target on a phone. */

.code-entry { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s2); }
.code-entry input {
  min-height: 4.75rem;
  padding: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  font-weight: 800;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}
.code-entry input:not(:placeholder-shown) { box-shadow: inset 0 0 0 2px var(--c-accent); }

/* --- segmented choice ----------------------------------------------------
   The workhorse of setup. Every game option is one of these, so the whole
   creation flow is taps with a single text field in it. */

.seg {
  display: flex;
  gap: var(--s1);
  padding: var(--s1);
  background: rgba(246, 241, 234, 0.05);
  border-radius: calc(var(--r-md) + var(--s1));
}
.seg-opt {
  flex: 1;
  min-height: 3rem;
  padding: var(--s2);
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--fg-soft);
  font-family: var(--font-display);
  font-size: var(--t-base);
  font-weight: 700;
  letter-spacing: -0.015em;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.seg-opt[aria-pressed="true"] {
  background: var(--c-ink);
  color: #16110C;
}
.seg-opt .sub {
  display: block;
  margin-top: 1px;
  font-family: var(--font);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0;
  opacity: 0.62;
}

/* --- switch row ----------------------------------------------------------- */

.switch-row {
  display: flex;
  align-items: center;
  gap: var(--s4);
  width: 100%;
  padding: var(--s4);
  border: 0;
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--edge);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease);
}
.switch-row:hover:not(:disabled) { background: var(--bg-hi); }
.switch-row .switch-text { flex: 1; min-width: 0; }
.switch-row .switch-title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-base);
  font-weight: 700;
  letter-spacing: -0.015em;
}
.switch-row .switch-note {
  display: block; margin-top: 0.2rem;
  font-size: var(--t-sm); line-height: 1.4; color: var(--fg-soft);
}

.switch {
  flex: none;
  width: 3.25rem; height: 1.875rem;
  border-radius: var(--r-pill);
  background: rgba(246, 241, 234, 0.14);
  position: relative;
  transition: background-color var(--dur-base) var(--ease);
}
.switch::after {
  content: "";
  position: absolute;
  inset: 3px auto 3px 3px;
  width: 1.375rem;
  border-radius: 50%;
  background: var(--c-ink);
  transition: transform var(--dur-base) var(--ease-settle);
}
[aria-pressed="true"] > .switch { background: var(--c-good); }
[aria-pressed="true"] > .switch::after { transform: translateX(1.375rem); background: #0B1F0C; }

/* ============================================================ chips ======= */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s4);
  border: 0;
  border-radius: var(--r-pill);
  background: var(--bg-hi);
  color: var(--fg);
  font-size: var(--t-base);
  font-weight: 600;
  line-height: 1.35;
}

/* Team identity is colour + shape + position, never colour alone. The mark is
   a disc for team one and a ring for team two: same footprint, different in
   greyscale, readable across a room.

   inline-block, not bare inline: this mark sits inside plain <span> labels as
   well as flex rows, and an inline span ignores width/height entirely — which
   silently collapses the one cue that carries team identity without colour. */
.mark {
  display: inline-block;
  flex: none;
  width: 0.75em; height: 0.75em;
  border-radius: 50%;
  background: currentColor;
  vertical-align: -0.02em;
}
.mark.ring { background: transparent; border: 0.2em solid currentColor; }

.chip.t0 { background: var(--c-team-a-dim); color: var(--team-a-fg); }
.chip.t1 { background: var(--c-team-b-dim); color: var(--team-b-fg); }
.chip.t0 .name, .chip.t1 .name { color: var(--fg); }
.chip.you { box-shadow: inset 0 0 0 2px var(--c-accent); }

/* ------------------------------------------------------------- teams ----- */
/*
   Two teams, always two equal columns, team one always left. An empty team
   keeps its full shape and says what it is waiting for — a team that collapses
   to a line of muted text reads as a rendering fault, not as an empty state.
*/
.team-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s3);
  align-items: stretch;
}
.team-col {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s3);
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--edge);
}
.team-col.t0 { background: linear-gradient(180deg, rgba(240, 113, 74, 0.13), transparent 62%), var(--bg-raised); }
.team-col.t1 { background: linear-gradient(180deg, rgba(79, 196, 178, 0.13), transparent 62%), var(--bg-raised); }

.team-col-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-family: var(--font-display);
  font-size: var(--t-sm);
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.team-col.t0 .team-col-head { color: var(--team-a-fg); }
.team-col.t1 .team-col-head { color: var(--team-b-fg); }
.team-col-head .count { margin-left: auto; font-variant-numeric: tabular-nums; opacity: 0.6; }

/*
   `anywhere` would break a normal word mid-letter to make it fit — it turned
   the "you" tag into "yo / u". `break-word` only splits a word that genuinely
   cannot fit on its own line, which is the actual protection wanted here (one
   very long name), and the tag is kept whole and allowed to drop to its own
   line instead of being squeezed alongside the name.
*/
.team-col .chip {
  width: 100%;
  justify-content: flex-start;
  flex-wrap: wrap;
  overflow-wrap: break-word;
  background: rgba(246, 241, 234, 0.07);
  color: var(--fg);
}
.chip > .muted { white-space: nowrap; font-size: var(--t-sm); }

.team-empty {
  display: grid;
  place-items: center;
  min-height: 3.25rem;
  padding: var(--s3) var(--s2);
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--rule);
  color: var(--fg-faint);
  font-size: var(--t-sm);
  text-align: center;
  text-wrap: balance;
}

/* One family-added word: the word itself leads, who added it is a footnote,
   and remove sits at the end of the same line rather than wrapping under it. */
.word-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--bg-raised);
}
.word-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.word-row-text b { font-weight: 700; overflow-wrap: anywhere; }
.word-row-text .muted { font-size: var(--t-sm); }
.word-row .btn.quiet { flex: none; }

/* ============================================================ packs ======= */

.pack-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: var(--s2);
}
.pack {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  min-height: 4.25rem;
  padding: var(--s3) var(--s4);
  border: 0;
  border-radius: var(--r-md);
  background: var(--bg-raised);
  box-shadow: var(--edge);
  color: var(--fg-soft);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.pack:active { transform: scale(0.985); }
.pack .pack-name {
  font-family: var(--font-display);
  font-size: var(--t-base);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  padding-right: 1.1rem;   /* room for the tick, so it never crowds the name */
}
.pack .pack-meta { font-size: var(--t-sm); opacity: 0.7; }
/* Every pack is on by default, so "selected" is the resting state and must stay
   calm — ten saturated tiles would be a wall of colour that means nothing.
   Switching one OFF is the event, so that is what changes loudly. */
.pack[aria-pressed="true"] {
  background: var(--bg-hi);
  box-shadow: var(--edge), inset 0 0 0 1px rgba(242, 106, 27, 0.30);
  color: var(--fg);
}
.pack[aria-pressed="false"] {
  opacity: 0.42;
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--rule);
}
.pack[aria-pressed="false"] .pack-name { text-decoration: line-through; text-decoration-thickness: 1px; }
/* The tick is a third signal so selection never rests on colour alone. Pinned
   to the corner rather than trailing the label, which wandered when a pack
   name wrapped to two lines. */
.pack[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  top: var(--s3);
  right: var(--s4);
  width: 0.38em; height: 0.68em;
  border: solid var(--c-accent-hi);
  border-width: 0 0.17em 0.17em 0;
  transform: rotate(45deg);
}

/* ========================================================= scoreboard ==== */
/*
   One object split down the middle, not two cards side by side. The acting
   side is washed in its own colour, so which team is up reads instantly from
   across the room without anyone comparing two numbers.
*/
.scores {
  display: flex;
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--edge);
  overflow: hidden;
}
.score {
  flex: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  padding: var(--s4);
  transition: background-color var(--dur-base) var(--ease);
}
.score + .score { box-shadow: inset 1px 0 0 var(--rule); }
.score .score-team {
  display: flex; align-items: center; gap: var(--s2);
  font-family: var(--font-display);
  font-size: var(--t-sm);
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.score .score-pts {
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  font-weight: 800;
  line-height: 0.95;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.05em;
  color: var(--fg);
}
.score.t0 .score-team { color: var(--team-a-fg); }
.score.t1 .score-team { color: var(--team-b-fg); }
.score.t0.acting { background: linear-gradient(180deg, rgba(240, 113, 74, 0.20), transparent 75%); }
.score.t1.acting { background: linear-gradient(180deg, rgba(79, 196, 178, 0.20), transparent 75%); }
/* A weight bar on the acting side — position and thickness, not just hue. */
.score.acting::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; }
.score.t0.acting::before { background: var(--c-team-a); }
.score.t1.acting::before { background: var(--c-team-b); }

.score-bar {
  height: 0.25rem;
  margin-top: var(--s2);
  border-radius: var(--r-pill);
  background: rgba(246, 241, 234, 0.12);
  overflow: hidden;
}
.score-bar span { display: block; height: 100%; border-radius: inherit; transition: width var(--dur-slow) var(--ease); }
.score.t0 .score-bar span { background: var(--c-team-a); }
.score.t1 .score-bar span { background: var(--c-team-b); }

/* ========================================================= standings ===== */
/*
   Everyone-for-themselves has no teams, so the scoreboard becomes a ranking.
   Same job as .scores, different shape: position carries the information, and
   whoever is acting is marked so the room can see whose points are moving.
*/
.standings {
  list-style: none;
  margin: 0;
  padding: 0;
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--edge);
  overflow: hidden;
}
.standings li {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  font-size: var(--t-base);
}
.standings li + li { box-shadow: inset 0 1px 0 var(--rule); }
.standings .rank {
  flex: none;
  width: 1.5rem;
  font-family: var(--font-display);
  font-weight: 800;
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
}
.standings .who { flex: 1; min-width: 0; font-weight: 700; overflow-wrap: break-word; }
.standings .who .muted { font-weight: 600; font-size: var(--t-sm); white-space: nowrap; }
.standings .pts {
  flex: none;
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-weight: 800;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}
.standings li.acting {
  background: linear-gradient(90deg, rgba(242, 106, 27, 0.18), transparent 70%);
  box-shadow: inset 3px 0 0 var(--c-accent);
}
.standings li.acting .rank { color: var(--c-accent-hi); }
.standings li.you .who { color: var(--c-accent-hi); }
/* The leader, once anyone is actually ahead. */
.standings li.lead .pts { color: var(--good-fg); }

body.tv .standings li { padding: clamp(var(--s2), 1.4vh, var(--s4)) var(--s5); font-size: clamp(1rem, 1.7vw, 1.6rem); }
body.tv .standings .pts { font-size: clamp(1.5rem, 3.4vw, 2.75rem); }
body.tv .standings .rank { width: 2.25rem; }
body.tv .standings { max-width: 46rem; margin-inline: auto; width: 100%; }

/* ============================================================ timer ====== */

.timer {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  max-width: 11rem;
  margin-inline: auto;
  aspect-ratio: 1;
}
.timer svg { width: 100%; height: 100%; transform: rotate(-90deg); overflow: visible; }
.timer .track { fill: none; stroke: rgba(246, 241, 234, 0.10); stroke-width: 3.5; }
.timer .fill {
  fill: none;
  stroke: var(--c-good);
  stroke-width: 3.5;
  stroke-linecap: round;
  transition: stroke-dashoffset 240ms linear, stroke var(--dur-base) var(--ease);
}
.timer .timer-num {
  position: absolute;
  font-family: var(--font-display);
  font-size: var(--t-3xl);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.055em;
  line-height: 1;
}
.timer.warn .fill { stroke: var(--c-accent); }
.timer.low  .fill { stroke: var(--c-warn); filter: drop-shadow(0 0 12px rgba(255, 107, 90, 0.6)); }
.timer.low  .timer-num { color: var(--warn-fg); }
.timer.low  { animation: pulse 1s var(--ease) infinite; }

.timer.inline { max-width: 6rem; }
.timer.inline .timer-num { font-size: var(--t-xl); }

/* The actor's own clock. Deliberately small: they are mid-performance and the
   word has to dominate their screen — the room and the TV are already tracking
   the time for them. It grows and flares when it actually matters. */
.timer.mini { width: 3.25rem; max-width: 3.25rem; margin: 0; }
.timer.mini .timer-num { font-size: var(--t-base); letter-spacing: -0.04em; }
.timer.mini .track, .timer.mini .fill { stroke-width: 8; }
.timer.mini.low { width: 4rem; max-width: 4rem; }

/* ======================================================== word card ====== */
/*
   The only light surface in the product. On an all-dark app this is the thing
   that is lit, which is the whole moment: the actor turns the phone over and
   the room sees them read it.
*/
/* The actor's screen groups the clock and the card as one centred stage, with
   the two big actions pinned to the bottom. Letting the shell space everything
   evenly dumped all the slack into a single gap under the card. */
.turn-stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s5);
  min-height: 0;
}

.word-card {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s3);
  flex: 1;
  max-height: 30rem;
  padding: var(--s7) var(--s5);
  border-radius: var(--r-xl);
  background: linear-gradient(178deg, #FFFDF9 0%, #F3EADC 100%);
  color: #16110C;
  text-align: center;
  box-shadow: var(--shadow-lg), 0 0 90px -30px rgba(242, 106, 27, 0.55);
  animation: deal var(--dur-slow) var(--ease-settle) both;
}
/* The rest of the deck, just visible under the top card. */
.word-card::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: auto 0.85rem -0.55rem;
  height: 2rem;
  border-radius: 0 0 var(--r-xl) var(--r-xl);
  background: rgba(243, 234, 220, 0.34);
}
.word-card .word-pack {
  font-size: var(--t-xs);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #A94407;
}
.word-card .word {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 13vw, 3.75rem);
  font-weight: 800;
  line-height: 0.98;
  letter-spacing: -0.045em;
  overflow-wrap: break-word;
  text-wrap: balance;
}
/* The situation half of a combined card. Visually subordinate to the subject
   but unmistakably part of it. */
.word-card .word-twist {
  font-size: var(--t-md);
  font-weight: 700;
  letter-spacing: -0.015em;
  color: #A94407;
  text-wrap: balance;
}
.word-card .word-alt {
  margin-top: var(--s1);
  padding-top: var(--s3);
  border-top: 1px solid rgba(22, 17, 12, 0.12);
  font-size: var(--t-sm);
  color: #5C4B3B;
}
.word-card .word-alt b { color: #16110C; font-weight: 700; }

/* ======================================================== modifier ======= */

.mod-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 0.3rem var(--s3);
  border-radius: var(--r-pill);
  background: var(--c-accent-dim);
  color: var(--c-accent-hi);
  font-family: var(--font-display);
  font-size: var(--t-xs);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Full-surface takeover announcing a surprise round. Above the sheet layer
   because it must never be occluded. */
.announce {
  position: fixed;
  inset: 0;
  z-index: var(--z-announce);
  display: grid;
  place-content: center;
  gap: var(--s4);
  padding: var(--s6);
  text-align: center;
  background:
    radial-gradient(80% 55% at 50% 34%, rgba(242, 106, 27, 0.34), transparent 70%),
    linear-gradient(180deg, var(--c-bg) 0%, var(--c-bg-deep) 100%);
  animation: announce-in var(--dur-moment) var(--ease-expo) both;
}
.announce .announce-kicker {
  font-size: var(--t-sm);
  font-weight: 800;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--c-accent-hi);
}
.announce .announce-name {
  font-family: var(--font-display);
  font-size: clamp(2.75rem, 14vw, 5rem);
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 0.94;
  text-transform: uppercase;
  text-wrap: balance;
}
.announce .announce-rule {
  font-size: var(--t-md);
  color: var(--fg-soft);
  max-width: 26ch;
  margin-inline: auto;
}

/* ========================================================= results ======= */

.results { list-style: none; padding: 0; display: flex; flex-direction: column; gap: var(--s1); }
.results li {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--bg-raised);
  font-size: var(--t-base);
  animation: rise var(--dur-base) var(--ease) both;
  animation-delay: calc(var(--i, 0) * 45ms);
}
.results li .res-mark {
  flex: none; width: 1.3rem;
  font-weight: 800; font-size: var(--t-md); line-height: 1; text-align: center;
}
.results li .res-text { flex: 1; font-weight: 700; }
.results li .res-alt  { font-size: var(--t-sm); color: var(--fg-soft); font-weight: 600; }
.results li.hit { background: var(--c-good-dim); }
.results li.hit .res-mark { color: var(--good-fg); }
.results li.miss { opacity: 0.72; }
.results li.miss .res-text { font-weight: 600; }
.results li.miss .res-mark { color: var(--fg-faint); }

/* Live tally of this turn, for people watching the room and not the phone. */
.pips { display: flex; gap: var(--s1); flex-wrap: wrap; align-items: center; }
.pips i {
  width: 0.6rem; height: 0.6rem;
  border-radius: 50%;
  background: rgba(246, 241, 234, 0.18);
}
.pips i.hit { background: var(--good-fg); }

/* ========================================================== panel ======== */

.panel {
  padding: var(--s4);
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--edge);
}
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3); margin-bottom: var(--s3);
}

/* ====================================================== lobby hero ======= */
/*
   The lobby has one job — get people into the room — so the code, the QR and
   the arrivals get the whole top of the screen and everything configurable is
   folded away underneath.
*/
.join-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s3);
  padding: var(--s5) var(--s4) var(--s6);
  border-radius: var(--r-xl);
  background:
    radial-gradient(90% 70% at 50% 0%, rgba(242, 106, 27, 0.14), transparent 65%),
    var(--bg-raised);
  box-shadow: var(--edge);
  text-align: center;
}
.room-code {
  font-family: var(--font-display);
  font-size: clamp(3rem, 17vw, 4.5rem);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-indent: 0.06em;
  line-height: 1;
  color: var(--fg);
  text-shadow: 0 0 50px rgba(242, 106, 27, 0.35);
}
.lobby-qr {
  padding: var(--s2);
  background: #fff;
  border-radius: var(--r-md);
  line-height: 0;
}
.lobby-qr canvas { display: block; width: 8.5rem; height: auto; image-rendering: pixelated; }

/* Whether the room is still filling up. The dots are the redundant channel —
   the sentence says it, the movement makes it noticeable from across a table. */
.waiting {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s4);
  border-radius: var(--r-pill);
  background: rgba(246, 241, 234, 0.07);
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--fg-soft);
}
.waiting i {
  width: 0.35rem; height: 0.35rem;
  border-radius: 50%;
  background: var(--c-accent);
  animation: blink 1.3s var(--ease) infinite;
}
.waiting i:nth-child(3) { animation-delay: 0.18s; }
.waiting i:nth-child(4) { animation-delay: 0.36s; }
.waiting.ready { background: var(--c-good-dim); color: var(--good-fg); }

@keyframes blink { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }

/* The way out of a turn nobody is answering. Hidden until the wait is long
   enough to be real, so it never invites skipping someone who is simply still
   picking up their phone. */
.stuck {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  padding: var(--s4);
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--edge);
  text-align: center;
  animation: rise var(--dur-base) var(--ease) both;
}
.stuck[hidden] { display: none; }
.stuck .muted { font-size: var(--t-sm); }
.stuck .btn { width: auto; padding-inline: var(--s6); }

/* Pull to refresh. There is no native gesture once the app is installed, so
   this is the whole affordance — it has to be visible enough to confirm the
   pull registered. */
.ptr {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: var(--z-sticky);
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  margin-left: -1.125rem;
  border-radius: 50%;
  background: var(--bg-hi);
  box-shadow: var(--shadow-md);
  transform: translateY(0);
  opacity: 0;
  pointer-events: none;
  transition: transform var(--dur-base) var(--ease), opacity var(--dur-base) var(--ease);
}
.ptr .ptr-spin {
  width: 1rem;
  height: 1rem;
  border: 2px solid var(--fg-faint);
  border-top-color: var(--c-accent);
  border-radius: 50%;
  transition: transform var(--dur-base) var(--ease);
}
.ptr[style*="translateY"] { opacity: 1; }
.ptr.armed .ptr-spin { border-top-color: var(--c-accent); transform: rotate(180deg); }
.ptr.spinning .ptr-spin { animation: spin 700ms linear infinite; }

@media (prefers-reduced-motion: reduce) {
  .ptr, .ptr .ptr-spin { transition: none; }
  .ptr.spinning .ptr-spin { animation-duration: 1.6s; }
}

/* Turning this device into the board — the usual way a phone gets cast to a TV. */
.cast-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  margin-top: var(--s2);
  text-align: center;
}
.cast-row .btn { width: auto; padding-inline: var(--s6); }
.cast-row .muted { font-size: var(--t-xs); line-height: 1.4; max-width: 30ch; }

/* The way back off a cast board. Deliberately faint: on an actual television it
   should be almost invisible, and it only renders for the device that is a
   player in this room. */
.tv-back {
  position: fixed;
  top: max(var(--s3), env(safe-area-inset-top));
  left: var(--s4);
  z-index: var(--z-sticky);
  opacity: 0.35;
  transition: opacity var(--dur-base) var(--ease);
}
.tv-back:hover, .tv-back:focus-visible { opacity: 1; }

/* --------------------------------------------------------- identity ----- */
/*
   Who you are, asked once per device and used by both joining and creating.
   Splitting it across two screens made it look like two different questions.
   Language is deliberately NOT here — it lives in the page header, the same
   control on every screen, so it is never asked twice either.
*/
/* The second, quieter action inside the join card — creating, which needs no
   code. The rule above it makes the break from the form explicit. */
.alt-action {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin-top: var(--s2);
}
.alt-action .muted { font-size: var(--t-sm); line-height: 1.45; text-wrap: balance; }

/* The board: a device's action, not a player's, so it sits outside the card.
   Centred column rather than a space-between row: once it wrapped on a phone the
   caption and the button sat against opposite edges, and the button's own
   horizontal padding inset its label past the caption's left edge, so neither
   agreed with the other or with the card above. */
.tv-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s1);
  margin-top: var(--s5);
  text-align: center;
  font-size: var(--t-sm);
}

.identity-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: rgba(246, 241, 234, 0.05);
  font-weight: 700;
}

/* ---------------------------------------------------------- options ----- */

details.options {
  border-radius: var(--r-lg);
  background: var(--bg-raised);
  box-shadow: var(--edge);
  padding: var(--s4);
}
details.options > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  font-family: var(--font-display);
  font-size: var(--t-base);
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg-soft);
}
details.options > summary::-webkit-details-marker { display: none; }
details.options > summary::after {
  content: "";
  width: 0.45em; height: 0.45em;
  border: solid currentColor;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translateY(-0.12em);
  transition: transform var(--dur-base) var(--ease);
}
details.options[open] > summary { color: var(--fg); }
details.options[open] > summary::after { transform: rotate(-135deg) translateY(-0.12em); }

/* ========================================================== sheet ======== */

dialog.sheet {
  width: min(100%, 30rem);
  max-height: 88svh;
  margin: auto auto 0;
  padding: var(--s5) var(--s4) calc(var(--s5) + env(safe-area-inset-bottom, 0px));
  border: 0;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--c-raised);
  color: var(--fg);
  box-shadow: var(--shadow-lg);
  animation: sheet-in var(--dur-slow) var(--ease-expo);
}
dialog.sheet::backdrop { background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px); }
@media (min-width: 34rem) {
  dialog.sheet { margin: auto; border-radius: var(--r-xl); }
}

/* =========================================================== note ======== */

.note {
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: rgba(246, 241, 234, 0.05);
  font-size: var(--t-base);
  color: var(--fg-soft);
}
.note.error {
  background: rgba(255, 107, 90, 0.12);
  color: var(--warn-fg);
  font-weight: 600;
}
.note:empty { display: none; }

/* The deck size at setup. The one number that tells a family the game will not
   repeat itself, so it gets to be a moment rather than a caption. */
.deck-count {
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: rgba(242, 106, 27, 0.12);
  color: var(--c-accent-hi);
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 800;
  letter-spacing: -0.02em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.skeleton {
  border-radius: var(--r-md);
  background: linear-gradient(90deg, var(--c-raised) 25%, var(--c-raised-hi) 50%, var(--c-raised) 75%);
  background-size: 300% 100%;
  animation: shimmer 1.4s linear infinite;
}

/* ============================================================ TV ========= */
/*
   Read from three metres. This is the one place fluid type is right: a 40"
   screen at 2m and a 65" at 4m are genuinely different problems and a fixed
   rem scale cannot serve both.

   The TV must never scroll. Nobody is holding it, so anything below the fold is
   simply invisible — the board is fixed to the viewport and everything inside
   is sized in vh so a 720p panel and a 4K one both fit.
*/

body.tv { overflow: hidden; font-size: clamp(1.1rem, 1.6vw, 1.6rem); }

/* A vignette, so the board sits in the room rather than glowing flatly at it. */
body.tv::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-base);
  background: radial-gradient(120% 85% at 50% 42%, transparent 48%, rgba(0, 0, 0, 0.55) 100%);
}

body.tv .shell {
  max-width: 82rem;
  padding: 3vh var(--s6);
  gap: 2vh;
  height: 100svh;
  justify-content: center;
}
body.tv h1 { font-size: clamp(2.25rem, 4.6vw, 4rem); letter-spacing: -0.045em; }
body.tv h2 { font-size: clamp(1.5rem, 2.4vw, 2.25rem); }

.tv-code {
  font-family: var(--font-display);
  font-size: clamp(5rem, 12vw, 9rem);
  font-weight: 800;
  letter-spacing: 0.02em;
  line-height: 0.92;
  color: var(--fg);
  text-shadow: 0 0 80px rgba(242, 106, 27, 0.45);
}

.tv-join { display: flex; align-items: center; justify-content: center; gap: clamp(var(--s5), 5vw, var(--s8)); flex-wrap: wrap; }
.tv-qr {
  flex: none;
  padding: var(--s3);
  background: #fff;
  border-radius: var(--r-md);
  box-shadow: 0 20px 44px -14px rgba(0, 0, 0, 0.85);
  line-height: 0;
}
.tv-qr canvas, .tv-qr svg {
  display: block;
  width: clamp(8rem, 15vw, 13rem);
  height: auto;
  image-rendering: pixelated;
}
.tv-url { font-size: clamp(0.95rem, 1.4vw, 1.35rem); color: var(--fg-soft); word-break: break-all; }

body.tv .timer { max-width: clamp(9rem, 24vh, 20rem); }
body.tv .timer .timer-num { font-size: clamp(3rem, 10vh, 7rem); }
body.tv .timer .track, body.tv .timer .fill { stroke-width: 3; }

body.tv .scores { max-width: 62rem; margin-inline: auto; width: 100%; }
body.tv .score { padding: clamp(var(--s4), 2.2vh, var(--s6)); }
body.tv .score .score-team { font-size: clamp(0.95rem, 1.5vw, 1.5rem); }
body.tv .score .score-pts  { font-size: clamp(3rem, 7vw, 6rem); }

body.tv .team-cols { gap: var(--s5); max-width: 62rem; margin-inline: auto; width: 100%; }
body.tv .team-col { padding: var(--s4); border-radius: var(--r-xl); gap: var(--s3); }
body.tv .team-col-head { font-size: clamp(0.95rem, 1.5vw, 1.5rem); }
body.tv .team-empty { min-height: 5rem; font-size: clamp(0.95rem, 1.4vw, 1.3rem); }

body.tv .chip { font-size: clamp(1rem, 1.6vw, 1.5rem); padding: var(--s2) var(--s4); }
body.tv .results { max-width: 48rem; margin-inline: auto; width: 100%; }
body.tv .results li { font-size: clamp(1rem, 1.6vw, 1.5rem); padding: var(--s3) var(--s5); }
body.tv .pips i { width: 0.9rem; height: 0.9rem; }
body.tv .mod-badge { font-size: clamp(0.85rem, 1.15vw, 1.15rem); padding: var(--s2) var(--s4); }

.tv-actor {
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 5vw, 4.25rem);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1;
  text-wrap: balance;
}
.tv-pack  { font-size: clamp(1.1rem, 2vw, 1.9rem); color: var(--fg-soft); }
.tv-pack b { color: var(--fg); font-weight: 700; }
.tv-meta  {
  font-size: clamp(0.8rem, 1.1vw, 1.15rem);
  color: var(--fg-soft);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* The winner floods the screen in the winning team's colour. */
/* A bilingual headline set as one string wraps badly — the separator ends up
   opening the second line. Big shared headlines stack instead: Spanish leads,
   English sits under it, smaller but not decorative. */
.bi { display: flex; flex-direction: column; align-items: center; gap: 0.15em; }
.bi-alt {
  font-size: 0.5em;
  line-height: 1.05;
  color: var(--fg-soft);
  letter-spacing: -0.02em;
}

.tv-winner { display: grid; place-items: center; gap: var(--s5); text-align: center; }
.tv-winner .win-name {
  font-family: var(--font-display);
  font-size: clamp(3rem, 9vw, 6.5rem);
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 0.94;
  text-transform: uppercase;
  text-wrap: balance;
}
body.tv.won-a {
  background-image:
    radial-gradient(80% 60% at 50% 40%, rgba(240, 113, 74, 0.42), transparent 72%),
    linear-gradient(180deg, var(--c-bg), var(--c-bg-deep));
}
body.tv.won-b {
  background-image:
    radial-gradient(80% 60% at 50% 40%, rgba(79, 196, 178, 0.40), transparent 72%),
    linear-gradient(180deg, var(--c-bg), var(--c-bg-deep));
}

#confetti { position: fixed; inset: 0; z-index: var(--z-confetti); pointer-events: none; }

/* ======================================================== animation ====== */

@keyframes spin   { to { transform: rotate(360deg); } }
@keyframes shimmer{ to { background-position: -300% 0; } }
@keyframes rise   { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
@keyframes pulse  { 50% { transform: scale(1.035); } }
@keyframes deal {
  from { opacity: 0; transform: translateY(20px) scale(0.95) rotate(-1.5deg); filter: blur(8px); }
  to   { opacity: 1; transform: none; filter: none; }
}
@keyframes sheet-in { from { transform: translateY(16px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes announce-in { from { opacity: 0; transform: scale(1.06); } to { opacity: 1; transform: none; } }
@keyframes pop { 0% { transform: scale(0.86); opacity: 0; } 100% { transform: none; opacity: 1; } }

.pop { animation: pop var(--dur-base) var(--ease-settle) both; }

/* Reduced motion is a full alternate path, not a switch-off: entrances become
   crossfades, the timer stops pulsing but still changes colour and thickens,
   and the confetti routine (in app.js) never allocates a canvas at all. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .word-card, .announce, .results li, dialog.sheet {
    animation: fade var(--dur-fast) linear both !important;
  }
  .timer.low { animation: none; }
  .timer.low .fill { stroke-width: 8; }
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

/* ===================================================== wide phones ======= */

@media (min-width: 26rem) {
  :root { --t-base: 1.1875rem; }
}
