/* ============================================================================
   SAH components. Logical properties only — no left/right anywhere.
   Source of truth: team/reports/ui-design-system.md §7, copied as landed.
   Class prefix convention: .tv-* is display-only, .ph-* is phone-only,
   unprefixed is shared.

   Components specced here for phases the current protocol does not have yet
   (avatars, podium, controller strip, sheets, QR panel) are kept verbatim so
   the two-device rework has them waiting. Nothing in this file is dead weight
   at runtime — it is one stylesheet, cached once.
   ============================================================================ */

/* ── BUTTON: the SAH glass standard ──────────────────────────────────────
   One button language for the whole platform and every game, TV and phone
   (Chris, 2026-09-27): Apple-style glass. A pill (or a circle for icons),
   a translucent body over a blurred, saturated background, a bright
   specular rim on top, a soft ink shadow under, and a quick press-in.
   Brand tokens only: the tint is a brand accent, text follows the brand
   rule (bright fills take ink, clear glass takes cream).

     .btn               primary: yellow-tinted glass, ink text
     .btn--aqua         green-tinted glass (confirm / safe)
     .btn--danger       rummen-tinted glass
     .btn--ghost        clear glass, cream text (secondary)
     .sah-iconbtn       clear glass circle for a single glyph (fullscreen,
                        back, close); 44px floor
   Other tappables (vote options, pack and character pickers) reuse the
   --glass-* recipe below so they read as the same family.               */
:root {
  --glass-blur: blur(18px) saturate(1.8);
  --glass-clear:
    linear-gradient(180deg,
      color-mix(in srgb, var(--sah-cream) 20%, transparent) 0%,
      color-mix(in srgb, var(--sah-cream) 7%, transparent) 55%,
      color-mix(in srgb, var(--sah-cream) 10%, transparent) 100%),
    color-mix(in srgb, var(--sah-ink) 40%, transparent);
  --glass-rim: color-mix(in srgb, var(--sah-cream) 30%, transparent);
  --glass-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--sah-cream) 60%, transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--sah-ink) 30%, transparent),
    0 0.35em 1em color-mix(in srgb, var(--sah-ink) 45%, transparent);
}
.btn {
  --tint: var(--sah-yellow);
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s-2);
  font-family: var(--font-ui); font-weight: 700; font-size: var(--p-md);
  line-height: 1;
  min-block-size: 48px;              /* > the 44px floor, with room for Arabic */
  min-inline-size: 48px;
  padding-block: var(--s-3);
  padding-inline: var(--s-5);
  border: 1px solid color-mix(in srgb, var(--sah-cream) 45%, var(--tint));
  border-radius: var(--r-pill);
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--sah-cream) 38%, transparent) 0%,
      transparent 48%,
      color-mix(in srgb, var(--sah-ink) 12%, transparent) 100%),
    color-mix(in srgb, var(--tint) 88%, transparent);
  -webkit-backdrop-filter: var(--glass-blur);
          backdrop-filter: var(--glass-blur);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--sah-cream) 75%, transparent),
    inset 0 -2px 0 color-mix(in srgb, var(--sah-ink) 14%, transparent),
    0 0.35em 1em color-mix(in srgb, var(--tint) 30%, transparent),
    0 0.2em 0.5em color-mix(in srgb, var(--sah-ink) 40%, transparent);
  color: var(--c-ink);               /* 12.17:1                                */
  cursor: pointer;
  position: relative;
  transition: transform var(--dur-micro) var(--ease-out),
              filter    var(--dur-micro) var(--ease-smooth),
              background var(--dur-micro) var(--ease-smooth);
  touch-action: manipulation;        /* kills the 300ms double-tap delay        */
  -webkit-tap-highlight-color: transparent;
}
.btn:hover  { filter: brightness(1.06) saturate(1.05); }
.btn:active { transform: scale(0.96); filter: brightness(0.96); }
                                          /* fires with the haptic; the network
                                             call goes on pointerdown, never on
                                             animationend                       */
.btn:focus-visible { outline: 3px solid var(--c-aqua); outline-offset: 3px; }

.btn--block   { inline-size: 100%; }
.btn--aqua    { --tint: var(--c-aqua);   color: var(--c-ink); }
.btn--danger  { --tint: var(--c-rummen); color: var(--c-ink); }
.btn--ghost,
.sah-iconbtn {
  background: var(--glass-clear);
  border: 1px solid var(--glass-rim);
  box-shadow: var(--glass-shadow);
  color: var(--sah-cream);
  -webkit-backdrop-filter: var(--glass-blur);
          backdrop-filter: var(--glass-blur);
}
.btn--ghost:hover { background: var(--glass-clear); filter: brightness(1.15); color: var(--sah-cream); }
.sah-iconbtn {
  display: inline-grid; place-items: center;
  inline-size: 44px; block-size: 44px; padding: 0;
  border-radius: 50%;
  cursor: pointer; line-height: 1;
  font: inherit; font-size: 20px;
  transition: transform var(--dur-micro) var(--ease-out), filter var(--dur-micro) var(--ease-smooth);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  text-decoration: none;
}
.sah-iconbtn:hover  { filter: brightness(1.18); }
.sah-iconbtn:active { transform: scale(0.92); }
.sah-iconbtn:focus-visible { outline: 3px solid var(--c-aqua); outline-offset: 3px; }
.sah-iconbtn svg { inline-size: 46%; block-size: 46%; display: block; }

/* OVERRIDE of PAGES.md "disabled: 40% opacity".
   design-host-split.md §1.1: "never a silently dead button". A disabled
   control must stay READABLE and must carry its reason.                     */
.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--glass-clear);
  box-shadow: var(--glass-shadow);
  color: var(--c-text-disabled);     /* 4.58:1 on surface-2 — still legible     */
  border-color: var(--c-line);
  cursor: not-allowed;
  transform: none;
  filter: none;
}
.btn__reason {                        /* ALWAYS rendered when disabled          */
  display: block; margin-block-start: var(--s-2);
  font-size: var(--p-xs); color: var(--c-text-dim); text-align: center;
}

/* The countdown ring drawn ON the button (design-host-split.md §1.1, Results).
   Not decoration: it is how the controller learns the button is an
   ACCELERATOR, not a dependency — that the room cannot stall without them.  */
.btn__countdown {
  position: absolute; inset: -2px;
  border-radius: inherit; pointer-events: none;
  background: conic-gradient(var(--c-ink) calc(var(--progress, 0) * 1turn),
                             transparent 0);
  opacity: 0.28;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  padding: 3px;
}

/* ── CARD ─────────────────────────────────────────────────────────────── */

.card {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  padding: var(--s-5);
  box-shadow: var(--shadow-card);
  animation: pop-in var(--dur-fast) var(--ease-pop) both;
}
/* On the TV, fill difference is 1.23:1 — invisible through a projector.
   The border does the work, not the fill.                                   */
.tv .card {
  border: 0.3vmin solid var(--c-line-loud);   /* 4.06:1 vs TV bg               */
  border-radius: var(--rv-lg);
  padding: var(--sv-3);
  box-shadow: var(--shadow-tv);
  animation-duration: var(--dur-enter);
}

/* ── ROOM CODE ────────────────────────────────────────────────────────── */

.roomcode { text-align: center; }
.roomcode__label {
  font-size: var(--h-sm); font-weight: 600; color: var(--c-text-2);
  text-transform: uppercase; letter-spacing: 0.1em;   /* Latin label only      */
}
.roomcode__value {
  font-family: var(--font-mono); font-weight: 800;
  font-size: var(--h-code);
  color: var(--c-amber);                                /* 11.41:1             */
  direction: ltr; unicode-bidi: isolate;                /* never mirrors       */
  letter-spacing: 0.15em;                               /* THE one exception   */
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  animation: pop-in var(--dur-enter) var(--ease-pop) both;
}
.roomcode__value--pulse { animation: pulse-attention 4s var(--ease-smooth) infinite; }
.phone .roomcode__value { font-size: var(--p-2xl); letter-spacing: 0.22em; }

/* ── QR ───────────────────────────────────────────────────────────────── */
/* Unthemed on purpose. Dark on light, >=4-module quiet zone, no logo,
   no rounded modules, NEVER inverted. It must work on the worst phone
   in the room. (design-host-split.md §2.1)                                  */

.qr-panel {
  background: var(--c-qr-paper);
  padding: var(--sv-2);                     /* the quiet zone                  */
  border-radius: var(--rv-md);
  inline-size: 24vmin; block-size: 24vmin;
  display: grid; place-items: center;
  box-shadow: var(--shadow-tv);
  animation: pop-in var(--dur-enter) var(--ease-pop) both;
}
.qr-panel svg, .qr-panel img { inline-size: 100%; block-size: 100%; display: block; }
.qr-panel__caption {
  margin-block-start: var(--sv-1);
  font-size: var(--h-sm); color: var(--c-text-2); text-align: center;
}
/* The persistent corner QR for latecomers — every non-lobby phase (§2.5).    */
.qr-mini {
  position: absolute;
  inset-block-end: var(--sv-4); inset-inline-end: var(--sv-4);
  display: flex; align-items: center; gap: var(--sv-2);
  opacity: 0.9;
}
.qr-mini .qr-panel { inline-size: 9vmin; block-size: 9vmin; padding: 0.8vmin; }
.qr-mini__code {
  font-family: var(--font-mono); font-weight: 800; font-size: var(--h-md);
  color: var(--c-amber); direction: ltr; letter-spacing: 0.12em;
}

/* ── AVATAR ───────────────────────────────────────────────────────────── */
/* 8 shapes x 8 colours, deterministic from playerId. Shape is the redundant
   channel that makes colour non-load-bearing. Shapes come from the
   carreaux-de-ciment vocabulary.                                            */

.avatar {
  --av-size: 10vmin;
  inline-size: var(--av-size); block-size: var(--av-size);
  display: grid; place-items: center;
  background: var(--av-color, var(--c-amber));
  color: var(--c-ink);                       /* >=7.44:1 on all 8 colours      */
  font-family: var(--font-display); font-weight: 800;
  font-size: calc(var(--av-size) * 0.42);
  position: relative; flex: none;
  animation: pop-in var(--dur-enter) var(--ease-pop) both;
}
.phone .avatar { --av-size: 56px; font-size: 24px; }

.avatar--circle    { border-radius: 50%; }
.avatar--squircle  { border-radius: 32%; }
.avatar--hex       { clip-path: polygon(25% 3%, 75% 3%, 100% 50%, 75% 97%, 25% 97%, 0 50%); }
.avatar--octagon   { clip-path: polygon(30% 0,70% 0,100% 30%,100% 70%,70% 100%,30% 100%,0 70%,0 30%); }
.avatar--star8     { clip-path: polygon(50% 0,64% 21%,85% 15%,79% 36%,100% 50%,79% 64%,
                                        85% 85%,64% 79%,50% 100%,36% 79%,15% 85%,21% 64%,
                                        0 50%,21% 36%,15% 15%,36% 21%); }
.avatar--diamond   { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }
.avatar--arch      { border-radius: 50% 50% 14% 14% / 62% 62% 14% 14%; }
.avatar--quatrefoil{ border-radius: 50% 14% 50% 14% / 50% 14% 50% 14%; }

.avatar--float {
  /* phase randomised per element in JS so twelve avatars never sync into a
     wave: element.style.setProperty('--float-delay', -(Math.random()*3)+'s') */
  animation: pop-in var(--dur-enter) var(--ease-pop) both,
             idle-float 3s var(--ease-smooth) infinite alternate;
  animation-delay: 0s, var(--float-delay, 0s);
}
.avatar--offline { opacity: 0.4; filter: grayscale(1); }
.avatar--pending { opacity: 0.6; }

/* Controller crown. amber ring + glyph + the display's controller line = 3
   channels. Colour alone never says who is driving.                          */
.avatar--controller { box-shadow: 0 0 0 0.6vmin var(--c-amber); }
.avatar__crown {
  position: absolute; inset-block-start: -28%; inset-inline-end: -14%;
  font-size: calc(var(--av-size) * 0.42);
  filter: drop-shadow(0 2px 6px rgb(0 0 0 / 0.6));
}

.player-tile { display: grid; justify-items: center; gap: var(--sv-1); }
.player-tile__name {
  font-size: var(--h-md); font-weight: 600; color: var(--c-text);
  max-inline-size: 16vmin; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  unicode-bidi: plaintext;
}

.tag {
  display: inline-block;
  font-size: var(--p-xs); font-weight: 700;
  padding: 2px var(--s-2); border-radius: var(--r-pill);
  background: var(--c-surface-2); color: var(--c-text-dim);
}
.tv .tag { font-size: var(--h-xs); color: var(--c-text-2); }
.tag--pending  { background: var(--c-amber-tint);  color: var(--c-amber); }
.tag--offline  { background: var(--c-surface-2);   color: var(--c-text-2); }

/* ── TIMER RING (display) ─────────────────────────────────────────────── */
/* FOUR channels, because this is the most consequential widget on screen:
   colour + stroke width + the numeral + motion. Never colour alone.
   dir stays LTR: a clock does not mirror.                                    */

.timer-ring {
  /* Apple-style countdown (Chris, 2026-09-27): a clear glass disc, a thin
     rounded progress ring and big clean numerals. The four channels stay:
     colour + stroke width + numeral + motion (critical only). */
  --ring-size: 18vmin;
  inline-size: var(--ring-size); block-size: var(--ring-size);
  position: relative; direction: ltr;
  box-sizing: border-box;
  padding: calc(var(--ring-size) * 0.06);
  border-radius: 50%;
  background: var(--glass-clear);
  border: 1px solid var(--glass-rim);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: var(--glass-blur);
          backdrop-filter: var(--glass-blur);
}
.timer-ring svg { display: block; inline-size: 100%; block-size: 100%; transform: rotate(-90deg); overflow: visible; }
.timer-ring__track { fill: none; stroke: color-mix(in srgb, var(--sah-cream) 16%, transparent); stroke-width: 7; }
.timer-ring__fill {
  fill: none;
  stroke: var(--c-aqua);                     /* 11.11:1 · 5.31:1 vs the track  */
  stroke-width: 7; stroke-linecap: round;
  stroke-dasharray: var(--circumference);
  stroke-dashoffset: 0;
  transition: stroke var(--dur-swap) var(--ease-smooth),
              stroke-width var(--dur-swap) var(--ease-smooth);
  /* JS drives dashoffset from deadlineAt/serverNow. LINEAR — it is time.      */
  animation: none;
}
.timer-ring__value {                          /* ALWAYS visible. The numeral is
                                                 the colour-independent channel */
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--font-ui); font-weight: 600;
  font-size: calc(var(--ring-size) * 0.36);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--sah-cream);
}
.timer-ring.is-warning .timer-ring__fill  { stroke: var(--c-amber);  stroke-width: 8.5; }
.timer-ring.is-warning .timer-ring__value { color: var(--c-amber); }
.timer-ring.is-critical .timer-ring__fill { stroke: var(--c-rummen); stroke-width: 10; }
.timer-ring.is-critical .timer-ring__value{ color: var(--c-rummen); }
.timer-ring.is-critical {
  animation: pulse-attention 600ms var(--ease-smooth) infinite;  /* tier 3     */
}

/* ── TIMER BAR (phone) ────────────────────────────────────────────────── */
/* A thin strip at the very top edge. It must never occupy attention that
   belongs to the textarea.                                                   */

.timer-bar {
  position: fixed; inset-block-start: 0; inset-inline: 0;
  block-size: 4px; background: var(--c-surface-2);
  z-index: var(--z-banner); direction: ltr;
}
.timer-bar__fill {
  block-size: 100%; inline-size: 100%;
  transform-origin: left center;               /* LTR-locked: time, not text   */
  background: var(--c-aqua);
  animation: drain-bar var(--anim-dur, 75s) var(--ease-linear) forwards;
}
.timer-bar.is-warning  .timer-bar__fill { background: var(--c-amber);  }
.timer-bar.is-critical .timer-bar__fill { background: var(--c-rummen); }
.timer-bar.is-critical { block-size: 7px; }    /* second channel: thickness    */

/* ── PROGRESS DOTS ────────────────────────────────────────────────────── */
/* OVERRIDE: PAGES.md says dots REPLACE "3 of 5 answered". Keep both — dots
   are the fast channel the room reads at 3 m, the numeral is the accurate
   one and the only one a screen reader can use.                              */

.progress-dots {
  display: flex; gap: var(--sv-1); justify-content: center;
  direction: ltr;                              /* progress, not text           */
}
.progress-dots__dot {
  inline-size: 1.6vmin; block-size: 1.6vmin; border-radius: 50%;
  background: var(--c-surface-2);
  border: 0.25vmin solid var(--c-line-loud);   /* empty dots must be visible   */
  transition: background var(--dur-swap) var(--ease-smooth);
}
.progress-dots__dot.is-filled {
  background: var(--c-aqua); border-color: var(--c-aqua);
}
.progress-dots__dot.dot--filling {
  animation: dot-fill var(--dur-micro) var(--ease-pop) both;   /* tier 1       */
}
.progress-dots__count {
  margin-block-start: var(--sv-1);
  font-size: var(--h-sm); color: var(--c-text-2);
  font-variant-numeric: tabular-nums; text-align: center;
}
.phone .progress-dots__dot { inline-size: 10px; block-size: 10px; border-width: 2px; }
.phone .progress-dots__count { font-size: var(--p-xs); color: var(--c-text-dim); }

/* ── PROMPT ───────────────────────────────────────────────────────────── */

.prompt {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--h-xl);
  line-height: 1.22;
  color: var(--c-text);                        /* 16.98:1 — the prompt is not
                                                  decorative, it is the game   */
  text-align: center; text-wrap: balance;
  max-inline-size: 78vw;
  margin-inline: auto;
  letter-spacing: 0;
}
.prompt__word {                                 /* word-by-word, 60ms apart,
                                                   synced to vo.answering.start */
  display: inline-block; opacity: 0;
  animation: pop-in var(--dur-enter) var(--ease-pop) forwards;
  animation-delay: calc(var(--i) * 60ms);
}
.phone .prompt {
  font-size: var(--p-lg); font-weight: 600; text-align: start;
  color: var(--c-text-2); max-inline-size: none;
}
.prompt__blank {                                /* Kezzeb's "____"             */
  color: var(--c-amber); border-block-end: 0.5vmin solid var(--c-amber);
  padding-inline: 0.4em;
}

/* ── ANSWER CARD (display) ────────────────────────────────────────────── */

.answer-card {
  background: var(--c-surface);
  border: 0.3vmin solid var(--c-line-loud);
  border-radius: var(--rv-lg);
  padding: var(--sv-3);
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--h-lg);
  color: var(--c-text);
  box-shadow: var(--shadow-tv);
  animation: pop-in var(--dur-enter) var(--ease-pop) both;
  animation-delay: calc(var(--i) * var(--stagger-tv));
  transform-style: preserve-3d;
}
.answer-card--flip { animation: flip-reveal var(--dur-flip) var(--ease-out) both; }
.answer-card__author {
  display: block; margin-block-start: var(--sv-1);
  font-family: var(--font-ui); font-size: var(--h-md); font-weight: 600;
  color: var(--c-aqua);
}
.answer-card__votes {
  font-family: var(--font-display); font-size: var(--h-xl); font-weight: 800;
  color: var(--c-amber); font-variant-numeric: tabular-nums;
}
.answer-card--zero {
  filter: grayscale(0.85); opacity: 0.66;
  transform: translateY(20px);
  transition: all var(--dur-slow) var(--ease-out);
}
.answer-card--zero .answer-card__votes { color: var(--c-text-2); }
.answer-card--truth { border-color: var(--c-aqua); box-shadow: 0 0 6vmin rgb(69 224 196 / 0.3); }
.answer-card--truth .answer-card__author::before { content: '✔ '; }
.answer-card__fooled {                          /* Kezzeb: the actual payoff    */
  display: block; margin-block-start: var(--sv-1);
  font-size: var(--h-md); color: var(--c-rummen); unicode-bidi: plaintext;
}

/* ── BALLOT OPTION (phone) ────────────────────────────────────────────── */
/* OVERRIDE: PAGES.md gives the chosen state a border only. Three channels
   here — border, a ✓ badge, scale — plus the unchosen options going inert.   */

.ballot-option {
  display: flex; align-items: center; gap: var(--s-3);
  inline-size: 100%;
  min-block-size: 64px;
  padding: var(--s-4);
  background: var(--c-surface);
  border: 2px solid var(--c-line-loud);
  border-radius: var(--r-md);
  font: inherit; font-size: var(--p-md); color: var(--c-text);
  text-align: start; cursor: pointer;
  animation: pop-in var(--dur-fast) var(--ease-pop) both;
  animation-delay: calc(var(--i) * var(--stagger-phone));
  transition: transform var(--dur-fast) var(--ease-pop),
              border-color var(--dur-fast) var(--ease-smooth),
              background var(--dur-fast) var(--ease-smooth),
              opacity var(--dur-fast) var(--ease-smooth);
  touch-action: manipulation;
}
.ballot-option:active { transform: scale(0.98); }
.ballot-option__badge {
  inline-size: 28px; block-size: 28px; border-radius: 50%; flex: none;
  border: 2px solid var(--c-line-loud);
  display: grid; place-items: center;
  font-size: var(--p-sm); color: transparent;
}
.ballot-option.is-chosen {
  border-color: var(--c-aqua);
  background: var(--c-aqua-tint);
  transform: scale(1.03);
}
.ballot-option.is-chosen .ballot-option__badge {
  background: var(--c-aqua); border-color: var(--c-aqua);
  color: var(--c-ink);                          /* the ✓ glyph                 */
}
.ballot.is-locked .ballot-option { pointer-events: none; }
.ballot.is-locked .ballot-option:not(.is-chosen) { opacity: 0.4; }

/* ── SCOREBOARD ───────────────────────────────────────────────────────── */

.score-row {
  display: flex; align-items: center; gap: var(--sv-2);
  padding: var(--sv-2) var(--sv-3);
  background: var(--c-surface);
  border-radius: var(--rv-md);
  border: 0.2vmin solid var(--c-line);
  font-size: var(--h-lg);
  animation: pop-in var(--dur-enter) var(--ease-pop) both;
  animation-delay: calc(var(--i) * var(--stagger-tv));
  /* FLIP: JS measures, sets --dy, plays. Watching someone physically
     overtake someone else is worth more than the number.                     */
  transition: transform var(--dur-slow) var(--ease-out);
}
.score-row__rank {
  font-family: var(--font-mono); font-weight: 800;
  color: var(--c-text-2); min-inline-size: 2.5ch; direction: ltr;
}
.score-row__name { flex: 1; unicode-bidi: plaintext; overflow: hidden;
                   text-overflow: ellipsis; white-space: nowrap; }
.score-row__pts {
  font-family: var(--font-display); font-weight: 800;
  color: var(--c-amber); font-variant-numeric: tabular-nums;
}
.score-row__delta {
  color: var(--c-aqua); font-size: var(--h-md); font-variant-numeric: tabular-nums;
}
.score-row--leader { border-color: var(--c-amber); background: var(--c-amber-tint); }
.score-row--leader .score-row__rank::before { content: '👑 '; }   /* not colour alone */
.score-row--you { border-color: var(--c-aqua); }
.score-row--offline { opacity: 0.45; }

/* Points flying from an answer card to a scoreboard row.                     */
.pts-fly {
  position: fixed; z-index: var(--z-raised);
  font-family: var(--font-display); font-weight: 800; font-size: var(--h-lg);
  color: var(--c-amber); pointer-events: none;
  offset-path: var(--arc);                      /* set by JS per flight        */
  animation: pts-fly var(--dur-slow) var(--ease-out) forwards;
}
@keyframes pts-fly {
  from { offset-distance: 0%;   opacity: 1; transform: scale(1); }
  to   { offset-distance: 100%; opacity: 0; transform: scale(0.6); }
}

/* ── PODIUM ───────────────────────────────────────────────────────────── */

.podium { display: flex; align-items: flex-end; justify-content: center; gap: var(--sv-3); }
.podium__step {
  display: grid; justify-items: center; gap: var(--sv-1);
  background: var(--c-surface);
  border: 0.3vmin solid var(--c-line-loud);
  border-start-start-radius: var(--rv-md);      /* logical — flips under RTL    */
  border-start-end-radius: var(--rv-md);
  padding: var(--sv-2);
  transform-origin: bottom center;
  animation: pop-in var(--dur-enter) var(--ease-pop) both;
}
.podium__step--1 { block-size: 34vmin; border-color: var(--c-amber);
                   background: var(--c-amber-tint); animation-delay: 1200ms; }
.podium__step--2 { block-size: 26vmin; animation-delay: 600ms; }
.podium__step--3 { block-size: 20vmin; animation-delay: 0ms; }
.podium__step--1 .avatar { animation: stamp var(--dur-enter) var(--ease-pop) 1200ms both; }
.podium__place {
  font-family: var(--font-mono); font-weight: 800; font-size: var(--h-2xl);
  color: var(--c-text-2); direction: ltr;
}
.podium__step--1 .podium__place { color: var(--c-amber-bright); }

/* ── CONTROLLER STRIP ─────────────────────────────────────────────────── */
/* The owner's phone is a normal player screen PLUS this bar. Nothing else.
   It must feel like furniture — hence the label CROSS-FADES on phase change
   rather than popping. (PAGES.md P9 — upheld.)                               */

.ctrl-strip {
  position: fixed; inset-block-end: 0; inset-inline: 0;
  z-index: var(--z-strip);
  background: var(--c-surface-2);
  border-block-start: 1px solid var(--c-line-loud);
  padding: var(--s-3) var(--s-4);
  padding-block-end: calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
  display: flex; align-items: center; gap: var(--s-3);
  animation: sheet-up var(--dur-swap) var(--ease-out) both;
}
.ctrl-strip__primary { flex: 1; }
.ctrl-strip__label {
  transition: opacity var(--dur-swap) var(--ease-smooth);   /* cross-fade only  */
}
.ctrl-strip__label.is-swapping { opacity: 0; }
.ctrl-strip__more {
  inline-size: 48px; block-size: 48px; flex: none;
  border-radius: var(--r-pill);
  background: transparent; border: 2px solid var(--c-line-loud);
  color: var(--c-text-2); font-size: var(--p-lg); cursor: pointer;
}
.ctrl-strip__waiting {
  flex: 1; text-align: center;
  font-size: var(--p-md); color: var(--c-text-dim);
}

/* ── SHEET (⋯ menu, handover request) ─────────────────────────────────── */

.sheet-scrim {
  position: fixed; inset: 0; z-index: var(--z-sheet);
  background: rgb(0 0 0 / 0.6);
  animation: fade-in var(--dur-swap) var(--ease-smooth) both;
}
.sheet {
  position: fixed; inset-block-end: 0; inset-inline: 0;
  z-index: calc(var(--z-sheet) + 1);
  background: var(--c-surface);
  border-start-start-radius: var(--r-lg);
  border-start-end-radius: var(--r-lg);
  padding: var(--s-5) var(--s-4);
  padding-block-end: calc(var(--s-5) + env(safe-area-inset-bottom, 0px));
  display: grid; gap: var(--s-3);
  animation: sheet-up var(--dur-swap) var(--ease-out) both;
  box-shadow: var(--shadow-pop);
}
.sheet::before {                                 /* grab handle                 */
  content: ''; inline-size: 44px; block-size: 4px; border-radius: var(--r-pill);
  background: var(--c-line-loud); justify-self: center; margin-block-end: var(--s-2);
}
.sheet__item {
  display: flex; align-items: center; gap: var(--s-3);
  min-block-size: 52px; padding: var(--s-3) var(--s-4);
  background: var(--c-surface-2); border: 0; border-radius: var(--r-md);
  color: var(--c-text); font: inherit; font-size: var(--p-md); text-align: start;
  cursor: pointer;
}
.sheet__item--danger { color: var(--c-rummen); }
.sheet__item--danger::before { content: '⚠ '; }  /* never colour alone          */

/* Handover request. 20 s auto-decline, drawn as a ring on the Accept button
   so "it will time out" is visible rather than a surprise.                    */
.handover__who { font-size: var(--p-xl); font-weight: 700; unicode-bidi: plaintext; }
.handover__actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }

/* ── BANNER ───────────────────────────────────────────────────────────── */
/* Non-blocking, pinned to the top. The game stays visible and interactive
   underneath. (PAGES.md H8/P8 — upheld.)                                     */

.banner {
  position: sticky; inset-block-start: 0; z-index: var(--z-banner);
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  font-size: var(--p-sm); font-weight: 600;
  background: var(--c-amber-tint); color: var(--c-amber);
  border: 1px solid var(--c-amber);
  animation: banner-drop var(--dur-swap) var(--ease-out) both;
}
.banner::before { content: attr(data-glyph); }   /* ⟳ ⚠ 📺 — never colour alone */
.banner--error { background: var(--c-rummen-tint); color: var(--c-rummen);
                 border-color: var(--c-rummen); }
.banner--ok    { background: var(--c-aqua-tint);   color: var(--c-aqua);
                 border-color: var(--c-aqua); }
.tv .banner { font-size: var(--h-sm); padding: var(--sv-2) var(--sv-3);
              border-width: 0.25vmin; }

/* ── TOAST ────────────────────────────────────────────────────────────── */

.toast {
  position: fixed; inset-block-start: var(--s-4);
  inset-inline-start: 50%; transform: translateX(-50%);
  z-index: var(--z-toast);
  background: var(--c-surface-2); color: var(--c-text);
  border: 1px solid var(--c-line-loud);
  border-radius: var(--r-pill);
  padding: var(--s-3) var(--s-5);
  font-size: var(--p-sm); box-shadow: var(--shadow-pop);
  animation: pop-in var(--dur-fast) var(--ease-pop) both;
}
/* role="status" in the markup, aria-live="polite", auto-dismiss at 4s.       */

/* ── FIELD ────────────────────────────────────────────────────────────── */

.field { display: grid; gap: var(--s-2); margin-block-end: var(--s-4); }
.field__label {
  font-size: var(--p-xs); font-weight: 700; color: var(--c-text-dim);
  text-transform: uppercase; letter-spacing: 0.06em;
}
[dir="rtl"] .field__label { text-transform: none; letter-spacing: 0; }  /* §3.6 */

.field input, .field textarea {
  font: inherit;
  font-size: var(--p-sm);          /* 16px FLOOR — below it iOS Safari zooms   */
  inline-size: 100%;
  padding: var(--s-3) var(--s-4);
  background: var(--c-bg);
  color: var(--c-text);
  border: 2px solid var(--c-line-loud);
  border-radius: var(--r-md);
  transition: border-color var(--dur-fast) var(--ease-smooth);
}
.field textarea {
  min-block-size: 112px; resize: none;
  unicode-bidi: plaintext;         /* Arabic + English + Arabizi + emoji, one
                                      answer. Never lock dir.                  */
  text-align: start;
  overflow-wrap: anywhere;
}
.field input:focus, .field textarea:focus {
  outline: none; border-color: var(--c-aqua);
}
.field__error {
  font-size: var(--p-xs); color: var(--c-rummen);
  animation: shake 300ms var(--ease-smooth) both;
}
.field__error::before { content: '⚠ '; }         /* never colour alone          */
.field__counter {
  text-align: end; font-size: var(--p-xs); color: var(--c-text-dim);
  font-variant-numeric: tabular-nums;
}
.field__counter.is-warn { color: var(--c-amber);  }   /* 180                    */
.field__counter.is-near { color: var(--c-rummen); }   /* 195                    */

/* ── SEGMENTED CODE INPUT (join fallback, gift redeem) ────────────────── */

.code-input { display: flex; gap: var(--s-2); justify-content: center; direction: ltr; }
.code-input input {
  inline-size: 60px; block-size: 72px;
  text-align: center; text-transform: uppercase;
  font-family: var(--font-mono); font-weight: 800; font-size: var(--p-2xl);
  background: var(--c-surface); color: var(--c-amber);
  border: 2px solid var(--c-line-loud); border-radius: var(--r-md);
  animation: pop-in var(--dur-fast) var(--ease-pop) both;
  animation-delay: calc(var(--i) * var(--stagger-phone));
}
/* Markup: inputmode="text" autocapitalize="characters" autocomplete="off",
   auto-advance, paste-aware, auto-submit on the 4th character.               */
.code-input.is-error input { border-color: var(--c-rummen); }
.code-input.is-error { animation: shake 300ms var(--ease-smooth); }

/* ── BIG STAT (your points this round) ────────────────────────────────── */

.stat { text-align: center; display: grid; gap: var(--s-1); }
.stat__value {
  font-family: var(--font-display); font-weight: 800; font-size: var(--p-3xl);
  color: var(--c-amber); font-variant-numeric: tabular-nums;
  animation: pop-in var(--dur-fast) var(--ease-pop) both;
}
.stat__label { font-size: var(--p-sm); color: var(--c-text-dim); }
.stat--zero .stat__value { color: var(--c-text-2); }

/* ── LOBBY GRID + STATUS ──────────────────────────────────────────────── */

.player-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16vmin, 1fr));
  gap: var(--sv-3); justify-items: center; align-content: center;
}
.ctrl-line {                                     /* host.lobby.ctrlHeld/Vacant  */
  font-size: var(--h-md); color: var(--c-text-2); text-align: center;
}
.ctrl-line--vacant { color: var(--c-amber); }
.ctrl-line--vacant::before {                    /* the VIP tarbouch */
  content: ''; display: inline-block; inline-size: 1.1em; block-size: 1.1em; vertical-align: -0.15em;
  margin-inline-end: 0.3em; background: url('/assets/brand/tarbouch.svg') center / contain no-repeat;
}
.tv-status { font-size: var(--h-md); color: var(--c-text-2); text-align: center; }
.tv-status--cta { color: var(--c-amber); animation: pulse-attention 2.4s infinite; }

/* Fallback panel — the ONLY non-output thing on the display. Deliberately
   quiet: if it is prominent, people reach for it and the whole split dies.   */
.fallback-trigger {
  position: absolute; inset-block-start: var(--sv-4); inset-inline-start: var(--sv-4);
  inline-size: 6vmin; block-size: 6vmin; border-radius: 50%;
  background: transparent; border: 0.25vmin solid var(--c-line);
  color: var(--c-text-dim); font-size: var(--h-sm);
  opacity: 0.35; cursor: pointer;
  transition: opacity var(--dur-swap) var(--ease-smooth);
}
.fallback-trigger:hover, .fallback-trigger:focus-visible { opacity: 1; }

.mute-toggle {
  position: absolute; inset-block-start: var(--sv-4); inset-inline-end: var(--sv-4);
  inline-size: 6vmin; block-size: 6vmin; border-radius: 50%;
  background: transparent; border: 0.25vmin solid var(--c-line);
  color: var(--c-text-2); font-size: var(--h-sm); cursor: pointer;
}

/* ── PHASE WIPE ───────────────────────────────────────────────────────── */

.wipe {
  position: fixed; inset: 0; z-index: var(--z-modal);
  background: linear-gradient(100deg, transparent, var(--c-amber) 40%,
                              var(--c-amber) 60%, transparent);
  animation: wipe var(--dur-enter) var(--ease-smooth) forwards;
  pointer-events: none;
}

/* ── Manuscript scroll ─────────────────────────────────────────────────────
   Speech from the host, player name tags and written text sit on a small
   parchment scroll: a paper band between two wooden rollers with end caps.
   Tokens only. --scroll-roll tints the rollers (a player's colour on name
   tags); the paper itself never changes, so ink text always reads.        */
.sah-scroll {
  --scroll-wood: color-mix(in srgb, var(--sah-yellow) 42%, var(--sah-ink));
  --scroll-roll: var(--scroll-wood);
  --scroll-w: 0.5em;                         /* roller thickness */
  position: relative;
  padding-inline: calc(var(--scroll-w) + 0.55em);
  color: var(--sah-ink);
  background:
    radial-gradient(ellipse at 18% 30%, color-mix(in srgb, var(--sah-yellow) 22%, transparent), transparent 55%),
    radial-gradient(ellipse at 80% 75%, color-mix(in srgb, var(--sah-ink) 10%, transparent), transparent 60%),
    linear-gradient(90deg,
      color-mix(in srgb, var(--sah-ink) 26%, transparent) 0,
      transparent calc(var(--scroll-w) + 0.6em),
      transparent calc(100% - var(--scroll-w) - 0.6em),
      color-mix(in srgb, var(--sah-ink) 26%, transparent) 100%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--sah-cream) 80%, var(--sah-yellow)) 0%,
      color-mix(in srgb, var(--sah-cream) 66%, var(--sah-yellow)) 100%);
  border: 0;
  border-block: 0.07em solid color-mix(in srgb, var(--sah-yellow) 45%, var(--sah-ink));
  border-radius: 0.12em;
  box-shadow: 0 0.18em 0.35em color-mix(in srgb, var(--sah-ink) 55%, transparent);
}
.sah-scroll::before,
.sah-scroll::after {
  content: ''; position: absolute; z-index: 1;
  inset-block: -0.3em;
  inline-size: var(--scroll-w);
  border-radius: 0.3em / 0.18em;
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--scroll-roll) 70%, var(--sah-ink)) 0%,
      color-mix(in srgb, var(--scroll-roll) 70%, var(--sah-cream)) 42%,
      var(--scroll-roll) 62%,
      color-mix(in srgb, var(--scroll-roll) 55%, var(--sah-ink)) 100%);
  box-shadow:
    inset 0 0.2em 0 color-mix(in srgb, var(--scroll-roll) 45%, var(--sah-ink)),
    inset 0 -0.2em 0 color-mix(in srgb, var(--scroll-roll) 45%, var(--sah-ink)),
    0 0.1em 0.2em color-mix(in srgb, var(--sah-ink) 50%, transparent);
  pointer-events: none;
}
.sah-scroll::before { inset-inline-start: -0.14em; }
.sah-scroll::after  { inset-inline-end: -0.14em; }

/* ── VIP mark: the tarbouch ────────────────────────────────────────────────
   The VIP (the player who starts the game) wears a tarbouch, not a crown
   (Chris, 2026-09-27). One image for every screen and game; sized in em so
   it follows the text it sits in. Leader/winner crowns are a separate mark. */
.sah-tarbouch {
  display: inline-block; vertical-align: -0.18em;
  inline-size: 1.15em; block-size: 1.15em;
  background: url('/assets/brand/tarbouch.svg') center / contain no-repeat;
}
