/* ==========================================================================
   In-call screen: header, video stage, sidebar, overlays, PiP
   ========================================================================== */

.call-screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface-base);
}

/* --- Header --------------------------------------------------------------- */

.call-header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border-subtle);
  min-height: 54px;
}

/* Leave room for the macOS traffic lights in the frameless window. */
body.platform-darwin .call-header {
  padding-left: 88px;
}

/* --- The three header groups (Paper "Round 8 — H7", 2026-09-13) ---------
   LEFT = who I am + my tools, left-aligned; CENTRE = where I am / what is
   happening to me, centred in the REMAINING space (flex:1 + min-width:0,
   so it can never overlap a side group); RIGHT = the call's status. The
   groups are no-drag because the fixed .drag-strip (base.css) handles the
   frameless window; every pill inside must stay clickable. */
.hdr-left,
.hdr-centre,
.call-meta {
  display: flex;
  align-items: center;
  -webkit-app-region: no-drag;
}

.hdr-left {
  gap: 10px;
  flex: none;
}

.hdr-centre {
  flex: 1;
  min-width: 0;
  justify-content: center;
  gap: 8px;
}

.hdr-divider {
  width: 1px;
  height: 22px;
  background: var(--border-strong);
  flex: none;
}

/* --- The pill family (.hp) -------------------------------------------------
   One style for every header pill: 26px, uppercase bold 12.5px, one dot or
   icon, the family colour on --hp-color. Existing pills (block timer, ask,
   leaderboard, connection, REC) join by class; the centre pills (room,
   mode, whisper, sound) are born in it. Casing is CSS so no textContent a
   test reads changes. */
.hp {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 26px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  color: var(--hp-color, var(--text-muted));
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  flex: none;
  position: relative;
}

.hp[hidden] {
  display: none;
}

.hp-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.hp-icon {
  display: inline-flex;
  flex: none;
}

.hp-icon svg {
  width: 12px;
  height: 12px;
}

/* The chevron is a real BUTTON — the keyboard-reachable toggle of a pill's
   popover — styled as the bare glyph; the pill body stays the pointer
   target. */
.hp-chevron {
  display: inline-flex;
  flex: none;
  opacity: 0.8;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.hp-chevron:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  opacity: 1;
}

.hp-chevron[hidden] {
  display: none;
}

.hp-chevron svg {
  width: 10px;
  height: 10px;
}

/* The inline action inside a pill (the whisper's End): a solid mini pill in
   the family colour with dark text, so it reads as a button, not a word. */
.hp-end {
  height: 18px;
  padding: 0 9px;
  border-radius: var(--radius-pill);
  border: 0;
  background: var(--hp-color, var(--color-accent));
  color: var(--surface-base);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: normal;
  text-transform: none;
  cursor: pointer;
  flex: none;
}

.hp-end[hidden] {
  display: none;
}

/* Screen-reader-only text: the full sentence a pill stands for. */
.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;
}

/* A pill's popover (a bystander's whisper list, the sharer's sound
   controls): anchored under the pill, above the stage. */
.hp-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-raised);
  z-index: 60;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 500;
  font-size: 13px;
  color: var(--text-primary);
  white-space: nowrap;
}

.hp-popover[hidden] {
  display: none;
}

.hp-popover-line {
  padding: 4px 6px;
}

.hp[data-popover="1"] {
  cursor: pointer;
}

/* --- The width budget's steps (lib/header-budget.ts) ----------------------
   call-screen measures the three groups and writes data-shrink = the first
   step at which everything fits; each step ALSO applies at every later
   step, so the selector lists run downward. Order: version → ASK word →
   connection word → sound word → mode short word → name clip. */

/* Step 1: the student's version number. */
.call-header[data-shrink="1"] .call-version,
.call-header[data-shrink="2"] .call-version,
.call-header[data-shrink="3"] .call-version,
.call-header[data-shrink="4"] .call-version,
.call-header[data-shrink="5"] .call-version,
.call-header[data-shrink="6"] .call-version {
  display: none;
}

/* Step 2: the ASK word — the student's pill keeps its icon, the guide's
   running-question pill keeps its clock. */
.call-header[data-shrink="2"] .ask-pill-word,
.call-header[data-shrink="3"] .ask-pill-word,
.call-header[data-shrink="4"] .ask-pill-word,
.call-header[data-shrink="5"] .ask-pill-word,
.call-header[data-shrink="6"] .ask-pill-word,
.call-header[data-shrink="2"] .rpl-count,
.call-header[data-shrink="3"] .rpl-count,
.call-header[data-shrink="4"] .rpl-count,
.call-header[data-shrink="5"] .rpl-count,
.call-header[data-shrink="6"] .rpl-count {
  display: none;
}

/* Step 3: the connection word — the dot alone carries the verdict. */
.call-header[data-shrink="3"] .conn-text,
.call-header[data-shrink="4"] .conn-text,
.call-header[data-shrink="5"] .conn-text,
.call-header[data-shrink="6"] .conn-text {
  display: none;
}

/* Step 4: the sound word — the speaker icon alone. */
.call-header[data-shrink="4"] .share-sound-banner-label,
.call-header[data-shrink="5"] .share-sound-banner-label,
.call-header[data-shrink="6"] .share-sound-banner-label {
  display: none;
}

/* Step 5: the mode pill's short word replaces its label. */
.hp-mode-short {
  display: none;
}

.call-header[data-shrink="5"] .hp-mode-label,
.call-header[data-shrink="6"] .hp-mode-label {
  display: none;
}

.call-header[data-shrink="5"] .hp-mode-short,
.call-header[data-shrink="6"] .hp-mode-short {
  display: inline;
}

/* Step 6: name pills clip at ~14 characters; the full name stays in the
   pill's tooltip (call-screen sets `title`). The one place the app clips
   text — a header pill cannot wrap, and this is the last resort after
   every word that could go has gone. */
.call-header[data-shrink="6"] .room-banner-text,
.call-header[data-shrink="6"] .whisper-banner-label {
  max-width: 14ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

.call-brand {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.06em;
  color: var(--text-primary);
}

/* --- Block timer (guide-only header pill + popover) ----------------------- */

.block-timer {
  position: relative;
}

.block-timer-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-size: 12.5px;
  transition: all var(--duration-normal) var(--ease-default);
}

.block-timer-pill:hover {
  color: var(--text-primary);
  border-color: var(--color-accent);
}

/* The running-question pill (ask-header-pill.ts): the block timer's family
   (same padding, radius, 12.5px type) on a NEUTRAL raised surface, so the two
   never look alike even when the timer turns amber in its last minute. Icon
   and time carry the accent; the count stays muted. */
.ask-header-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 26px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--color-accent);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition:
    color var(--duration-normal) var(--ease-default),
    border-color var(--duration-normal) var(--ease-default);
}
.ask-header-pill[hidden] {
  display: none;
}
.ask-header-pill:hover {
  border-color: var(--color-accent);
}
.ask-header-pill:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.ask-header-pill .rpl-icon svg {
  width: 13px;
  height: 13px;
  display: block;
}
.ask-header-pill .rpl-time {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.ask-header-pill .rpl-count {
  color: var(--text-muted);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.ask-header-pill .rpl-count[hidden] {
  display: none;
}
.ask-header-pill.ending {
  color: var(--color-error-text);
  border-color: var(--color-error);
}
.ask-header-pill.ending:hover {
  border-color: var(--color-error-text);
}

/* The overrun warning pill (Paper W2): block-timer amber while the final
   minute runs, the expired red under 15s. The time is mono in a reserved
   box so ticking digits never resize the pill (the header-budget rule).
   The root is a flex item of .hdr-centre: while the pill is hidden the
   root leaves the flow too, so it holds no 8px gap slot beside the room
   pill and the centred group does not drift. */
#overrun-pill-root:has(> .overrun-pill[hidden]) {
  display: none;
}
.overrun-pill {
  --hp-color: var(--color-accent);
  border-color: var(--color-accent);
  background: rgba(245, 158, 11, 0.22);
  gap: 6px;
}
.overrun-pill .op-time {
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: normal;
  display: inline-block;
  min-width: calc(4ch + 1px);
  text-align: center;
}
.overrun-pill.ending {
  --hp-color: var(--color-error-text);
  border-color: var(--color-error);
  background: rgba(220, 38, 38, 0.18);
}

.block-timer-pill .bt-icon svg {
  width: 13px;
  height: 13px;
  display: block;
}

/* Sans bold caps, the conn/REC pill typography — the header pills read
   as one family (the label is transformed, so scripts still read the
   textContent "Timer"). */
.block-timer-pill .bt-label {
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

/* THE COUNTDOWN TICKS IN PLACE (2026-09-14). `font-variant-numeric` above
   is inert here: the label inherits DM Sans, which ships no tabular
   figures, so the declaration that claimed to stop the wobble did nothing
   and every second re-measured the digits — "15:37" → "15:35" moved the
   pill ~1.5px, pushed .hdr-left, and (since that group is one of the
   header ResizeObserver's targets) re-ran the whole width budget once a
   second. Mono is the house pattern for ticking digits — .session-timer,
   .rpl-time and the sidebar's .row-time all use it — and the reserved box
   holds the pill still across the whole countdown: 6ch is "180:00", the
   longest the 180-minute clamp can produce, and min-width is a floor, so
   a longer value would still grow rather than clip (nothing is ever
   ellipsized). Scoped to the counting states so the idle "TIMER" keeps
   its sans caps; a guide starting a timer changes the pill once, which is
   the state change they just asked for, not a tick. */
.block-timer-pill.running .bt-label,
.block-timer-pill.ending .bt-label {
  display: inline-block;
  /* The reserve is 6 glyph advances, so the digits must carry NO tracking:
     the caps label's 0.04em would add 6 × 0.02em on top of 6ch and put
     "180:00" half a pixel outside its own box (the layout suite measures
     the longest form and caught exactly that). Mono digits need no
     tracking anyway. The +1px absorbs per-glyph advance rounding. */
  min-width: calc(6ch + 1px);
  text-align: center;
  font-family: var(--font-mono);
  letter-spacing: normal;
}

.block-timer-pill.running,
.block-timer-pill.ending {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: rgba(245, 158, 11, 0.1);
}

.block-timer-pill.ending {
  background: rgba(245, 158, 11, 0.22);
}

.block-timer-pill.expired {
  color: #f4a5a5;
  border-color: var(--color-error);
  background: rgba(220, 38, 38, 0.18);
  /* The pulse rides the ::before veil (opacity-only, compositor);
     animating background-color repainted the pill every frame. isolate
     keeps the negative-z veil above the pill's own background but under
     its label, matching the old under-the-text tint. */
  position: relative;
  isolation: isolate;
}

.block-timer-pill.expired::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  /* 0.25 over the 0.18 base composites to the old 0.38 peak. */
  background: rgba(220, 38, 38, 0.25);
  opacity: 0;
  animation: bt-expired-pulse 1.2s var(--ease-in-out) infinite;
}

/* The academic block's structural marker: an amber tint on the pill plus
   the small BLOCK chip (a persistent child toggled by `hidden` — never a
   label rewrite; the countdown label logic is untouched). Keyed on the
   CURRENT room's effective block, so a timer-started block shows it and
   expiry drops it on the pill's own tick. */
.block-timer-pill.academic {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: rgba(245, 158, 11, 0.16);
}

.block-timer-pill .bt-block-chip {
  padding: 1px 5px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--text-inverse);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.06em;
}

.block-timer-pill .bt-block-chip[hidden] {
  display: none;
}

@keyframes bt-expired-pulse {
  50% {
    opacity: 1;
  }
}

.block-timer-pop {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 90; /* above tiles/toolbar (≤60), below dialogs (100/120) */
  width: 236px;
  padding: 12px;
  border-radius: var(--radius-md);
  background: var(--surface-overlay);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-raised);
}

.block-timer-pop .bt-title {
  font-size: 12px;
  color: var(--text-muted);
  margin-bottom: 10px;
}

/* Mode-first (2026-09-08): the FOCUS | BREAK segment is the popover's
   first control; the chips, the accent and the Start label follow it.
   The popover carries `.mode-break` while Break is armed, and every
   accent below reads that class — one switch, one hue (success green vs
   the academic amber), so the sheet's color says which one is armed. */
.block-timer-pop .bt-mode {
  display: flex;
  gap: 3px;
  padding: 3px;
  border-radius: var(--radius-md);
  background: var(--surface-base);
  margin-bottom: 8px;
}

/* The PLAIN (workshop) sheet has no mode: the segment and its caption are
   `hidden` at mount and must stay gone — the flex rule above would
   otherwise outrank the UA's [hidden]. The sheet carries `.plain
   .mode-break`, so every accent below reads as the plain countdown's. */
.block-timer-pop .bt-mode[hidden],
.block-timer-pop .bt-mode-caption[hidden] {
  display: none;
}

.block-timer-pop .bt-mode-btn {
  flex: 1;
  padding: 6px 0;
  border-radius: 9px; /* radius-md minus the 3px inset */
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  transition: all var(--duration-fast) var(--ease-default);
}

.block-timer-pop .bt-mode-btn:hover {
  color: var(--text-primary);
}

.block-timer-pop .bt-mode-btn[aria-checked="true"] {
  background: var(--color-accent);
  color: var(--text-inverse);
}

.block-timer-pop.mode-break .bt-mode-btn[aria-checked="true"] {
  background: var(--color-success);
}

/* The one-line caption under the segment spells out what the ARMED mode
   does to students — the checkbox's old sub-caption, now following the
   mode instead of a box. */
.block-timer-pop .bt-mode-caption {
  font-size: 11px;
  color: var(--text-subtle);
  line-height: 1.35;
  margin-bottom: 10px;
}

.block-timer-pop .bt-presets {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
}

.block-timer-pop .bt-preset {
  flex: 1;
  padding: 5px 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  transition: all var(--duration-fast) var(--ease-default);
}

.block-timer-pop .bt-preset:hover {
  color: var(--text-primary);
  border-color: var(--color-accent);
}

.block-timer-pop .bt-preset.selected {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: rgba(245, 158, 11, 0.12);
}

.block-timer-pop.mode-break .bt-preset:hover {
  border-color: var(--color-success);
}

.block-timer-pop.mode-break .bt-preset.selected {
  color: var(--color-success);
  border-color: var(--color-success);
  background: rgba(87, 201, 155, 0.12);
}

.block-timer-pop.mode-break {
  border-color: rgba(87, 201, 155, 0.35);
}

.block-timer-pop .bt-custom {
  display: flex;
  align-items: center;
  gap: 8px;
}

.block-timer-pop .bt-minutes {
  width: 64px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 13px;
}

.block-timer-pop .bt-min-unit {
  font-size: 11px;
  color: var(--text-subtle);
}

.block-timer-pop .bt-start {
  flex: 1;
  padding: 6px 0;
  white-space: nowrap;
  border-radius: var(--radius-sm);
  background: var(--color-accent);
  color: var(--text-inverse);
  font-size: 12.5px;
  font-weight: 700;
  transition: background var(--duration-fast) var(--ease-default);
}

.block-timer-pop .bt-start:hover {
  background: var(--color-accent-hover);
}

.block-timer-pop.mode-break .bt-start {
  background: var(--color-success);
}

.block-timer-pop.mode-break .bt-start:hover {
  background: #45b88a;
}

.block-timer-pop .bt-stop {
  width: 100%;
  margin-top: 8px;
  padding: 6px 0;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(220, 38, 38, 0.5);
  color: #f4a5a5;
  font-size: 12.5px;
  transition: all var(--duration-fast) var(--ease-default);
}

.block-timer-pop .bt-stop:hover {
  background: rgba(220, 38, 38, 0.15);
  border-color: var(--color-error);
}

/* --- Ask popover (components/ask-popover.ts) -------------------- */
.ask-pop {
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 50; /* the device-menu layer: above tiles, below dialogs */
  /* Guide direction A (2026-09-03, Rodrigo's pick): calm and minimal. A
     hairline progress line along the top edge while a question runs, the
     question as the biggest thing on the sheet, an underlined question field
     instead of a box, text chips, counts as words. 340px keeps the longest
     realistic count line ("300 answered · 300 posted") on one line. */
  width: 340px;
  padding: 0 0 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  border-radius: var(--radius-lg);
  background: var(--surface-overlay);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-raised);
  text-align: left;
  overflow: hidden;
}
.ask-pop .ap-progress {
  height: 3px;
  background: var(--surface-tile);
  display: flex;
}
.ask-pop .ap-progress[hidden] {
  display: none;
}
.ask-pop .ap-progress-bar {
  width: 100%;
  background: var(--color-accent);
  transition: width 250ms linear;
}
.ask-pop .ap-progress.ending .ap-progress-bar {
  background: var(--color-error);
}
.ask-pop .ap-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 6px 16px 0;
}
/* The sheet's chrome opens with 6px of body padding when the progress
   line is hidden too; the extra top space comes from the body padding. */
.ask-pop .ap-progress[hidden] + .ap-body {
  padding-top: 12px;
}
.ask-pop .ap-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.ask-pop .ap-title {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.ask-pop .ap-countdown {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  color: var(--color-accent);
  white-space: nowrap;
}
.ask-pop .ap-countdown[hidden] {
  display: none;
}
.ask-pop .ap-countdown.ending {
  color: var(--color-error-text);
}
.ask-pop .ap-active {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ask-pop .ap-active[hidden] {
  display: none;
}
.ask-pop .ap-active-question {
  font-size: 1.0625rem;
  line-height: 1.3;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ask-pop .ap-count {
  font-size: 0.875rem;
  color: var(--text-muted);
}
.ask-pop .ap-count strong {
  color: var(--text-primary);
  font-weight: 600;
}
.ask-pop .ap-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.ask-pop .ap-end,
.ask-pop .ap-ask-another,
.ask-pop .ap-start {
  padding: 8px 14px;
  font-size: 0.875rem;
  border-radius: var(--radius-pill);
}
.ask-pop .ap-end {
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-primary);
}
.ask-pop .ap-end:hover {
  border-color: var(--text-muted);
}
.ask-pop .ap-ask-another,
.ask-pop .ap-start {
  font-weight: 600;
  border: none;
  background: var(--color-accent);
  color: var(--text-inverse);
}
.ask-pop .ap-ask-another:hover,
.ask-pop .ap-start:hover {
  background: var(--color-accent-hover);
}
.ask-pop .ap-start:disabled {
  opacity: 0.6;
}
.ask-pop .ap-compose {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ask-pop .ap-compose[hidden] {
  display: none;
}
.ask-pop .ap-question {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  padding: 6px 0 10px;
  border: none;
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0;
  background: transparent;
  color: var(--text-primary);
  box-sizing: border-box;
}
.ask-pop .ap-question::placeholder {
  color: var(--text-muted);
}
.ask-pop .ap-question:focus {
  outline: none;
  border-bottom: 2px solid var(--color-accent);
  padding-bottom: 9px;
}
.ask-pop .ap-durations {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}
.ask-pop .ap-duration {
  padding: 8px 10px;
  font-size: 0.875rem;
  border-radius: var(--radius-sm);
  border: none;
  background: transparent;
  color: var(--text-muted);
}
.ask-pop .ap-duration:hover {
  color: var(--text-primary);
}
.ask-pop .ap-duration.selected {
  color: var(--color-accent);
  background: var(--color-accent-glow);
  font-weight: 600;
}
.ask-pop .ap-custom {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ask-pop .ap-custom[hidden] {
  display: none;
}
.ask-pop .ap-custom-seconds {
  width: 72px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 0.875rem;
}
.ask-pop .ap-custom-unit {
  font-size: 0.875rem;
  color: var(--text-muted);
}
.ask-pop .ap-start {
  align-self: flex-end;
  padding: 9px 18px;
  font-size: 0.9375rem;
}

/* --- Ask popover: POLLS (Flow A, 2026-09-09) ------------------------
   The Question | Poll switch shares the header line with the title; the
   chosen half is filled amber, the other reads muted on the base surface.
   It only shows with the composer — a running or ended sheet puts the
   countdown / turnout in that slot instead. */
.ask-pop .ap-kind {
  display: inline-flex;
  padding: 3px;
  border-radius: var(--radius-pill);
  background: var(--surface-base);
  border: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.ask-pop .ap-kind[hidden] {
  display: none;
}
.ask-pop .ap-kind-btn {
  padding: 5px 12px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
}
.ask-pop .ap-kind-btn:hover {
  color: var(--text-primary);
}
.ask-pop .ap-kind-btn.selected {
  background: var(--color-accent);
  color: var(--text-inverse);
}
/* The choice rows: a lettered chip, the field, an × — fixed-width slots so
   the letters and the ×s form lanes down the sheet. */
.ask-pop .ap-choices {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ask-pop .ap-choices[hidden] {
  display: none;
}
.ask-pop .ap-choice-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ask-pop .ap-choice-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 38px;
  padding: 0 6px 0 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-base);
  border: 1px solid var(--border-subtle);
}
.ask-pop .ap-choice-row:focus-within {
  border-color: var(--color-accent);
}
.ask-pop .ap-choice-letter,
.ask-pop .ap-tally-letter {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 6px;
  background: var(--color-accent-glow);
  color: var(--color-accent);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
}
.ask-pop .ap-choice {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 0.9375rem;
  padding: 8px 0;
  border: none;
  background: transparent;
  color: var(--text-primary);
}
.ask-pop .ap-choice::placeholder {
  color: var(--text-subtle);
}
.ask-pop .ap-choice:focus {
  outline: none;
}
.ask-pop .ap-choice-remove {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-subtle);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.ask-pop .ap-choice-remove:hover {
  color: var(--text-primary);
  background: var(--surface-raised);
}
.ask-pop .ap-add-choice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 36px;
  padding: 0 10px;
  border-radius: var(--radius-sm);
  border: 1px dashed var(--border-strong);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}
.ask-pop .ap-add-choice:hover {
  color: var(--text-primary);
  border-color: var(--text-muted);
}
.ask-pop .ap-add-choice[hidden] {
  display: none;
}
.ask-pop .ap-add-choice-label::before {
  content: "+ ";
}
.ask-pop .ap-choices-left {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-subtle);
}
/* "Students see results": a switch, off by default (the reveal is the
   guide's opt-in). Same track/knob as the block timer's focus switch. */
.ask-pop .ap-show-results {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
  align-self: flex-start;
}
.ask-pop .ap-show-results:hover {
  color: var(--text-primary);
}
.ask-pop .ap-show-results.on {
  color: var(--text-primary);
}
.ask-pop .ap-switch {
  width: 30px;
  height: 18px;
  padding: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-navy-muted);
  display: flex;
  align-items: center;
  transition: background var(--duration-fast) var(--ease-default);
}
.ask-pop .ap-switch::after {
  content: "";
  width: 14px;
  height: 14px;
  border-radius: var(--radius-pill);
  background: var(--text-muted);
  transition: transform var(--duration-fast) var(--ease-default);
}
.ask-pop .ap-show-results.on .ap-switch {
  background: var(--color-accent);
}
.ask-pop .ap-show-results.on .ap-switch::after {
  background: var(--text-inverse);
  transform: translateX(12px);
}
/* The tally (running and ended): letter, label, `count · pct%`, then a bar
   scaled to the leading choice, which alone reads amber. */
.ask-pop .ap-tally,
.ask-pop .ap-ended-tally {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ask-pop .ap-tally[hidden] {
  display: none;
}
.ask-pop .ap-tally-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.ask-pop .ap-tally-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ask-pop .ap-tally-row .ap-tally-letter {
  background: transparent;
  width: auto;
  height: auto;
  color: var(--text-muted);
  font-size: 0.8125rem;
}
.ask-pop .ap-tally-row.lead .ap-tally-letter {
  color: var(--color-accent);
}
.ask-pop .ap-tally-label {
  flex: 1;
  min-width: 0;
  font-size: 0.9375rem;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ask-pop .ap-tally-row.lead .ap-tally-label {
  font-weight: 600;
}
.ask-pop .ap-tally-num {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.ask-pop .ap-tally-row.lead .ap-tally-num {
  color: var(--text-primary);
}
.ask-pop .ap-tally-track {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-base);
  display: flex;
}
.ask-pop .ap-tally-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--text-muted);
  transition: width 250ms linear;
}
.ask-pop .ap-tally-row.lead .ap-tally-bar {
  background: var(--color-accent);
}
/* The ended sheet: the same tally, a quiet note about where the record went. */
.ask-pop .ap-ended {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ask-pop .ap-ended[hidden] {
  display: none;
}
.ask-pop .ap-ended-question {
  font-size: 1.0625rem;
  line-height: 1.3;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ask-pop .ap-ended-note {
  font-size: 0.75rem;
  color: var(--text-subtle);
}
.ask-pop .ap-run-again {
  padding: 8px 14px;
  font-size: 0.875rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-primary);
}
.ask-pop .ap-run-again:hover {
  border-color: var(--text-muted);
}
.ask-pop .ap-ended-ask-another {
  padding: 8px 14px;
  font-size: 0.875rem;
  font-weight: 600;
  border-radius: var(--radius-pill);
  border: none;
  background: var(--color-accent);
  color: var(--text-inverse);
}
.ask-pop .ap-ended-ask-another:hover {
  background: var(--color-accent-hover);
}

/* --- Ask popover: SAVED questions (concept 02, 2026-09-15) -----------
   The composer opens under a New | Saved tab row. New is today's composer
   plus a bookmark beside Start; Saved is the guide's list, one Send per
   row, with Edit list flipping the same panel into edit mode. The same
   restraint as the rest of the sheet: one amber moment per view. */
.ask-pop .ap-tabs {
  display: flex;
  gap: 18px;
  border-bottom: 1px solid var(--border-subtle);
}
.ask-pop .ap-tabs[hidden] {
  display: none;
}
.ask-pop .ap-tab {
  padding: 2px 0 9px;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}
.ask-pop .ap-tab:hover {
  color: var(--text-primary);
}
.ask-pop .ap-tab.selected {
  color: var(--text-primary);
  border-bottom-color: var(--color-accent);
}
.ask-pop .ap-tab-count {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-muted);
}
.ask-pop .ap-tab.selected .ap-tab-count {
  color: var(--color-accent);
}
.ask-pop .ap-head[hidden] {
  display: none;
}

/* The bookmark, twice: a labelled pill beside Start, and a bare glyph in
   the running sheet's header. Saved = filled amber, inert. */
.ask-pop .ap-compose-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.ask-pop .ap-save {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--surface-base);
  color: var(--text-muted);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
}
.ask-pop .ap-save:hover:not(:disabled) {
  color: var(--text-primary);
}
.ask-pop .ap-save:disabled {
  cursor: default;
  opacity: 0.55;
}
.ask-pop .ap-save.saved,
.ask-pop .ap-save.saved:disabled {
  opacity: 1;
  background: rgba(245, 158, 11, 0.12);
  color: var(--color-accent);
}
.ask-pop .ap-save.saved .ap-bookmark,
.ask-pop .ap-save-live.saved .ap-bookmark {
  fill: currentColor;
}
.ask-pop .ap-save-live {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  flex-shrink: 0;
}
.ask-pop .ap-save-live[hidden] {
  display: none;
}
.ask-pop .ap-save-live:hover:not(:disabled) {
  color: var(--text-primary);
  background: var(--surface-base);
}
.ask-pop .ap-save-live.saved,
.ask-pop .ap-save-live.saved:disabled {
  color: var(--color-accent);
  cursor: default;
}
.ask-pop .ap-saved-note {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: var(--radius-md);
  background: rgba(87, 201, 155, 0.1);
  color: var(--color-success);
  font-size: 0.8125rem;
}
.ask-pop .ap-saved-note::before {
  content: "";
  width: 6px;
  height: 10px;
  border-right: 1.8px solid currentColor;
  border-bottom: 1.8px solid currentColor;
  transform: rotate(45deg) translate(-1px, -2px);
  flex-shrink: 0;
}
.ask-pop .ap-saved-note[hidden] {
  display: none;
}

/* The Saved list: kind glyph, text + meta, Send — fixed slots so the
   glyphs and the buttons form lanes down the sheet. */
.ask-pop .ap-saved {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ask-pop .ap-saved[hidden] {
  display: none;
}
.ask-pop .ap-saved-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 336px;
  overflow-y: auto;
  margin: 0 -8px;
}
.ask-pop .ap-saved-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 8px 9px 10px;
  border-radius: 10px;
}
.ask-pop .ap-saved-row:hover {
  background: var(--surface-base);
}
.ask-pop .ap-saved-kind,
.ask-pop .ap-saved-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  flex-shrink: 0;
  color: var(--color-accent);
}
.ask-pop .ap-saved-row.sent .ap-saved-kind {
  color: var(--color-success);
}
.ask-pop .ap-saved-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ask-pop .ap-saved-text {
  font-size: 0.875rem;
  line-height: 1.35;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ask-pop .ap-saved-row.sent .ap-saved-text {
  color: var(--text-muted);
}
.ask-pop .ap-saved-meta {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.ask-pop .ap-saved-row.sent .ap-saved-meta {
  color: var(--color-success);
}
.ask-pop .ap-saved-send {
  flex-shrink: 0;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
}
.ask-pop .ap-saved-row:hover .ap-saved-send:not(.again) {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--text-inverse);
}
.ask-pop .ap-saved-send.again {
  color: var(--text-muted);
  border-color: var(--border-subtle);
}
.ask-pop .ap-saved-send.again:hover {
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.ask-pop .ap-saved-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 4px;
}
.ask-pop .ap-saved-add,
.ask-pop .ap-saved-edit,
.ask-pop .ap-saved-retry,
.ask-pop .ap-saved-go-new {
  border: none;
  background: transparent;
  font: inherit;
  cursor: pointer;
  padding: 0;
}
.ask-pop .ap-saved-add {
  color: var(--color-accent);
  font-size: 0.8125rem;
}
.ask-pop .ap-saved-add:hover {
  color: var(--color-accent-hover);
}
.ask-pop .ap-saved-edit,
.ask-pop .ap-saved-retry {
  color: var(--text-muted);
  font-size: 0.75rem;
}
.ask-pop .ap-saved-edit:hover,
.ask-pop .ap-saved-retry:hover {
  color: var(--text-primary);
}
.ask-pop .ap-saved-edit[hidden],
.ask-pop .ap-edit-hint[hidden] {
  display: none;
}
.ask-pop .ap-edit-hint {
  font-size: 0.75rem;
  color: var(--text-muted);
}
.ask-pop .ap-saved-loading,
.ask-pop .ap-saved-error {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 2px;
  font-size: 0.8125rem;
  color: var(--text-muted);
}
.ask-pop .ap-saved-loading[hidden],
.ask-pop .ap-saved-error[hidden] {
  display: none;
}
.ask-pop .ap-saved-error {
  color: var(--color-error);
}

/* Empty Saved: the two ways in, and nothing else. */
.ask-pop .ap-saved-empty {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 6px 2px 2px;
}
.ask-pop .ap-saved-empty[hidden] {
  display: none;
}
.ask-pop .ap-saved-empty-title {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--text-primary);
}
.ask-pop .ap-saved-empty-body {
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--text-muted);
  margin-top: -6px;
}
.ask-pop .ap-saved-way {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--text-primary);
}
.ask-pop .ap-saved-way-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--surface-base);
  color: var(--color-accent);
}
.ask-pop .ap-saved-way-line::before {
  content: "";
  width: 14px;
  height: 3px;
  border-radius: 2px;
  background: var(--color-accent);
}
.ask-pop .ap-saved-empty-actions {
  display: flex;
  justify-content: flex-end;
  padding-top: 2px;
}
.ask-pop .ap-saved-go-new {
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  font-size: 0.875rem;
  font-weight: 600;
}
.ask-pop .ap-saved-go-new:hover {
  border-color: var(--text-muted);
}

/* Edit list: the tabs give way to "Editing Saved · Done"; rows grow a drag
   handle, an editable line, a timer menu and an × that asks once. */
.ask-pop .ap-edit-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-subtle);
}
.ask-pop .ap-edit-head[hidden] {
  display: none;
}
.ask-pop .ap-edit-title {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-primary);
}
.ask-pop .ap-edit-done {
  padding: 5px 12px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--text-inverse);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 700;
  cursor: pointer;
}
.ask-pop .ap-edit-done:hover {
  background: var(--color-accent-hover);
}
.ask-pop .ap-saved-row.editing {
  padding: 8px 6px;
  align-items: center;
}
.ask-pop .ap-saved-row.editing:hover {
  background: transparent;
}
.ask-pop .ap-saved-handle {
  width: 12px;
  color: var(--text-muted);
  cursor: grab;
}
.ask-pop .ap-saved-row.dragging {
  opacity: 0.5;
}
.ask-pop .ap-saved-row.drop-target {
  box-shadow: inset 0 2px 0 var(--color-accent);
}
.ask-pop .ap-saved-edit-text {
  width: 100%;
  padding: 2px 0 4px;
  border: none;
  border-bottom: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-size: 0.875rem;
  outline: none;
}
.ask-pop .ap-saved-edit-text:focus {
  border-bottom-color: var(--color-accent);
}
.ask-pop .ap-saved-edit-timer {
  flex-shrink: 0;
  padding: 4px 6px;
  border: none;
  border-radius: 6px;
  background: var(--surface-base);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  cursor: pointer;
}
.ask-pop .ap-saved-remove {
  flex-shrink: 0;
  min-width: 22px;
  padding: 2px 4px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}
.ask-pop .ap-saved-remove:hover {
  color: var(--text-primary);
}
.ask-pop .ap-saved-remove.armed {
  color: var(--color-error);
  font-size: 0.75rem;
  font-weight: 600;
}

/* LINK kind (2026-10-01): the url line sits above the note, mono like the
   running sheet's host line; the hint under it names the destination or
   the rule. */
.ask-pop .ap-url {
  width: 100%;
  font: inherit;
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  padding: 6px 0 10px;
  border: none;
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0;
  background: transparent;
  color: var(--text-primary);
  box-sizing: border-box;
  overflow-wrap: anywhere;
}
.ask-pop .ap-url::placeholder {
  color: var(--text-muted);
}
.ask-pop .ap-url:focus {
  outline: none;
  border-bottom: 2px solid var(--color-accent);
  padding-bottom: 9px;
}
.ask-pop .ap-url[hidden] {
  display: none;
}
.ask-pop .ap-link-dest {
  font-size: 0.8125rem;
  color: var(--text-muted);
}
.ask-pop .ap-link-dest.invalid {
  color: var(--color-error-text);
}
.ask-pop .ap-link-dest[hidden] {
  display: none;
}
.ask-pop .ap-active-url {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.ask-pop .ap-active-url[hidden] {
  display: none;
}
.ask-pop .ap-saved-text.mono {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
}
.ask-pop .ap-saved-row[data-kind="link"].editing {
  align-items: flex-start;
}
.ask-pop .ap-saved-edit-url {
  width: 100%;
  padding: 2px 0 4px;
  border: none;
  border-bottom: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  outline: none;
  overflow-wrap: anywhere;
}
.ask-pop .ap-saved-edit-url::placeholder,
.ask-pop .ap-saved-edit-text::placeholder {
  color: var(--text-muted);
}
.ask-pop .ap-saved-edit-url:focus {
  border-bottom-color: var(--color-accent);
}
.ask-pop .ap-saved-edit-url.invalid {
  border-bottom-color: var(--color-error);
}

.call-brand .accent {
  color: var(--color-accent);
}

/* Running-app version beside the brand, for debugging screenshots. */
.call-version {
  margin-left: 10px;
  font-size: 11px;
  letter-spacing: normal;
  color: var(--text-muted);
}

.call-meta {
  display: flex;
  align-items: center;
  gap: 14px;
  -webkit-app-region: no-drag;
}

/* The call clock. Mono, so its digits never wobble as it ticks — but the
   FORMAT grows at the hour ("59:59" → "1:00:00") and that shoved the
   connection pill sideways mid-call. The reservation is therefore in
   CHARACTERS, not pixels: 7ch is exactly the hour form in a monospace
   face, where 1ch IS the advance width, so the longer value lands inside
   the box it already occupied. (It was 58px, which is ~0.8px short of the
   hour form at this size — measured 58.81px on a live client — so the
   rollover still moved the group. Greptile P2 on #7719 caught that a
   second rule of mine further up was silently losing to this one; there
   is deliberately just this one now, and a test pins that.) Right-aligned
   so the seconds column stays put. Past ten hours it grows once more,
   which no class runs long enough to see. */
.session-timer {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 14px;
  color: var(--text-muted);
  min-width: calc(7ch + 1px); /* +1px: per-glyph advance rounding (see above) */
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The ask-a-question trigger — the student's standing invitation to use
   the support chat (call-screen renders it for exactly the clients the
   chat serves: Timeback students; guides and guests never get it). Sky
   blue, not the leaderboard's amber, so the two adjacent invitations read
   as two different doors; the hex is used directly rather than
   --color-info because that token (#0284c7) is a fill colour and reads
   muddy as TEXT on the navy header — the .conn-pill.offline precedent. */
.ask-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 14px;
  border: 1px solid rgba(56, 189, 248, 0.45);
  border-radius: var(--radius-pill);
  background: rgba(56, 189, 248, 0.08);
  color: #38bdf8;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: all var(--duration-normal) var(--ease-default);
}

.ask-pill:hover {
  background: rgba(56, 189, 248, 0.18);
}

.ask-pill.is-active {
  background: rgba(56, 189, 248, 0.24);
  color: #7dd3fc;
}

.ask-pill .ask-pill-icon {
  display: inline-flex;
}

.ask-pill .ask-pill-icon svg {
  width: 13px;
  height: 13px;
}

/* The leaderboard trigger. Shaped like its pill neighbors so the row keeps
   reading as one family, but amber: it is an INVITATION, not a status.
   Lives inside .call-meta, which is already -webkit-app-region: no-drag
   (the header strip itself drags the window). */
.lb-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 14px;
  border: 1px solid rgba(245, 158, 11, 0.45);
  border-radius: var(--radius-pill);
  background: rgba(245, 158, 11, 0.08);
  color: var(--color-accent);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: all var(--duration-normal) var(--ease-default);
}

.lb-pill:hover {
  background: rgba(245, 158, 11, 0.18);
}

.lb-pill.is-active {
  background: rgba(245, 158, 11, 0.24);
  color: #ffc75e;
}

.lb-pill .lb-pill-icon {
  display: inline-flex;
}

.lb-pill .lb-pill-icon svg {
  width: 13px;
  height: 13px;
}

/* The guide's student-layout preview (user decision 2026-09-14, Paper 04):
   an icon-only pill beside the trophy — quiet until pressed, then the
   family amber, because a pressed eye is a STATE the guide must not lose
   track of (the banner above the stage says the same thing in words). */
.view-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: all var(--duration-normal) var(--ease-default);
}

.view-pill:hover {
  color: var(--text-primary);
  border-color: rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.06);
}

.view-pill.is-active {
  border-color: rgba(245, 158, 11, 0.55);
  background: rgba(245, 158, 11, 0.16);
  color: #ffc75e;
}

.view-pill .view-pill-icon svg {
  width: 14px;
  height: 14px;
}

/* The preview banner (Paper 06): a slim amber row at the top of the video
   column, above the stage, for the WHOLE preview — never a toast. It says
   what the grid below is and carries the way back; the column's flex
   layout hands the stage row whatever height the banner does not take. */
.stage-preview-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  min-height: 36px;
  margin: 12px 18px 0 18px;
  padding: 4px 6px 4px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(245, 158, 11, 0.45);
  background: rgba(245, 158, 11, 0.1);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 18px;
}

.stage-preview-banner[hidden] {
  display: none;
}

.stage-preview-banner-icon {
  display: inline-flex;
  flex: none;
  color: var(--color-accent);
}

.stage-preview-banner-icon svg {
  width: 14px;
  height: 14px;
}

.stage-preview-banner-text {
  flex: 1;
  min-width: 0;
}

.stage-preview-banner-back {
  flex: none;
  height: 26px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  border: none;
  background: var(--color-accent);
  color: var(--text-inverse);
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-default);
}

.stage-preview-banner-back:hover {
  background: var(--color-accent-hover);
}

.conn-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 14px;
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: all var(--duration-normal) var(--ease-default);
}

.conn-pill .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

.conn-pill.connected {
  color: var(--color-success);
  background: rgba(87, 201, 155, 0.1);
}

.conn-pill.reconnecting,
.conn-pill.rejoining {
  color: var(--color-warning);
  background: rgba(245, 158, 11, 0.12);
}

.conn-pill.reconnecting .dot,
.conn-pill.rejoining .dot {
  animation: pulse 1s ease-in-out infinite;
}

.conn-pill.offline,
.conn-pill.ended {
  color: #f87171;
  background: rgba(220, 38, 38, 0.12);
}

/* Connected, but the SFU is measuring trouble. Compound selectors so they
   outrank the plain `.connected` green whatever the source order; no
   `pulse` on either, because the pulse means "something is in progress"
   and belongs to the reconnect states above. `quality-good` needs no rule
   — `.connected` is already the green. */

.conn-pill.connected.quality-fair {
  color: var(--color-warning);
  background: rgba(245, 158, 11, 0.12);
}

.conn-pill.connected.quality-poor {
  /* #f87171, not --color-error: the token is a fill colour (it is what the
     solid .net-dot.poor uses) and reads as muddy dark red for TEXT on the
     navy header — the same reason .offline above lightens it. */
  color: #f87171;
  background: rgba(220, 38, 38, 0.12);
}

@keyframes pulse {
  50% {
    opacity: 0.35;
  }
}

/* REC indicator (call-screen.ts): visible to everyone while the call is
   being recorded (store.recording.active). Sized off .conn-pill so the two
   header pills read as one pair, and last in the row so recording is the
   final word on the bar. */

.rec-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 14px;
  border-radius: var(--radius-pill);
  background: rgba(220, 38, 38, 0.16);
  border: 1px solid rgba(248, 113, 113, 0.4);
  color: #f87171;
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
}

.rec-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ef4444;
  animation: rec-pulse 1.6s ease-in-out infinite;
}

@keyframes rec-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

/* --- The centre pills: room · mode · whisper · sound ------------------------
   Same elements and class names as the strips they replaced (2026-09-13),
   each in its colour family: blue = where I am, amber = a whisper, green =
   a live share, violet/teal/slate = the mode holding a student's mic. */

.room-banner {
  --hp-color: #38bdf8;
  border-color: rgba(56, 189, 248, 0.5);
  background: rgba(56, 189, 248, 0.1);
}

.whisper-banner {
  --hp-color: var(--color-accent-text);
  border-color: rgba(245, 158, 11, 0.6);
  background: rgba(245, 158, 11, 0.14);
  /* The End mini-button sits flush with the pill's right edge. */
  padding-right: 6px;
}

.whisper-banner.is-bystander,
.whisper-banner:has(.whisper-banner-end[hidden]) {
  padding-right: 12px;
}

/* A guide MEMBER's "+N": the other pairs the dropdown lists. */
.whisper-banner-more {
  flex: none;
  font-size: 11px;
  opacity: 0.85;
}

.whisper-banner-more[hidden] {
  display: none;
}

.whisper-banner-end {
  --hp-color: var(--color-accent);
  transition: filter var(--duration-fast) var(--ease-default);
}

.whisper-banner-end:hover {
  filter: brightness(1.12);
}

/* The STUDENT's whisper STRIP (user decision 2026-09-13): the pre-pill
   full-width amber line under the header — a student reads who is
   whispering with whom at a glance, no hover. It shares the pill's classes
   and render (never `.hp`), so this undoes the pill's own chrome and hides
   the pill-only parts; the height and type match the mode hint line. */
.whisper-banner.whisper-strip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-height: 26px;
  padding: 3px 20px;
  border: 0;
  border-bottom: 1px solid rgba(245, 158, 11, 0.4);
  border-radius: 0;
  background: rgba(245, 158, 11, 0.14);
  color: var(--color-warning);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.3;
}

.whisper-banner.whisper-strip[hidden],
.whisper-strip .whisper-banner-label,
.whisper-strip .whisper-banner-more,
.whisper-strip .hp-chevron,
.whisper-strip .hp-popover {
  display: none;
}

.share-sound-banner {
  --hp-color: var(--color-success);
  border-color: rgba(87, 201, 155, 0.6);
  background: rgba(87, 201, 155, 0.12);
}

.share-sound-banner-btn {
  height: 26px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-success);
  background: transparent;
  color: var(--color-success);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  text-align: left;
  transition: all var(--duration-fast) var(--ease-default);
}

.share-sound-banner-btn:hover {
  background: rgba(87, 201, 155, 0.2);
}

.share-sound-banner-btn.is-muted {
  background: rgba(87, 201, 155, 0.2);
}

.share-sound-banner-btn[hidden] {
  display: none;
}

.hp-mode[data-kind="test"] {
  --hp-color: var(--color-test-text);
  border-color: rgba(139, 92, 246, 0.55);
  background: rgba(139, 92, 246, 0.16);
}

.hp-mode[data-kind="focus"],
.hp-mode[data-kind="academic"] {
  --hp-color: var(--color-focus-text);
  border-color: rgba(20, 184, 166, 0.55);
  background: rgba(20, 184, 166, 0.14);
}

.hp-mode[data-kind="mute-lock"],
.hp-mode[data-kind="mic-lock"] {
  --hp-color: var(--text-primary);
  border-color: rgba(255, 255, 255, 0.2);
  background: rgba(159, 176, 200, 0.12);
}

/* The held student's pill (Plan 2 Task 8): the same neutral-slate theme as
   .hold-banner and the mute-lock pill above — a hold is a standing notice,
   not a verdict (D6), so it borrows no alarm colour. */
.hp-mode[data-kind="hold"] {
  --hp-color: var(--text-primary);
  border-color: rgba(255, 255, 255, 0.2);
  background: rgba(159, 176, 200, 0.12);
}

/* --- The hint row (students) ----------------------------------------------
   One 26px line under the header while a mode holds the mic: the four
   elements keep their classes, spans and copy (lib/mode-banner,
   lib/mute-lock-banner) — only the shape changed from a strip to a line.
   A grid with every child in the same cell so a one-frame overlap (a test
   flipping on while focus clears) paints the stronger, LATER element on
   top rather than stacking two rows. */
.mode-hint-row {
  display: grid;
  grid-template-columns: 1fr;
}

.mode-hint-row > * {
  grid-area: 1 / 1;
}

.test-banner,
.hold-banner,
.focus-banner,
.academic-banner,
.mute-banner,
.send-wedge-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0 10px;
  min-height: 26px;
  padding: 3px 20px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.3;
}

.test-banner[hidden],
.hold-banner[hidden],
.focus-banner[hidden],
.academic-banner[hidden],
.mute-banner[hidden],
.send-wedge-banner[hidden] {
  display: none;
}

.test-banner-hint,
.hold-banner-hint,
.focus-banner-hint,
.academic-banner-hint,
.mute-banner-hint,
.send-wedge-banner-hint {
  font-weight: 500;
  opacity: 0.9;
}

.mute-banner-hint[hidden] {
  display: none;
}

/* The student's standing mute-lock line: neutral slate, full text colour
   (it has to be noticed by a student — and read over their shoulder by a
   parent — rather than merely be available to someone who set it). */
.mute-banner {
  background: rgba(159, 176, 200, 0.1);
  border-bottom: 1px solid var(--border-strong);
  color: var(--text-primary);
}

/* The held student's line (Plan 2 Task 8): same neutral slate treatment as
   the mute-lock line above — a held student is not being scolded, so this
   deliberately does NOT borrow the test banner's violet or any red/amber
   alarm colour; it reads as a standing classroom-adjacent notice, not a
   verdict (spec D6). */
.hold-banner {
  background: rgba(159, 176, 200, 0.1);
  border-bottom: 1px solid var(--border-strong);
  color: var(--text-primary);
}

/* The send-wedge line (lib/send-wedge-banner.ts), both roles, its own row
   under the hint row: this client's actions are not reaching the call.
   The red of .conn-pill.offline — the honest connection verdict the pill
   itself cannot give while the SDK still reads "connected". Text colour is
   --color-error-text, not --color-error (a fill colour that reads muddy
   as text on navy, the .conn-pill.connected.quality-poor note). */
.send-wedge-banner {
  background: rgba(220, 38, 38, 0.12);
  border-bottom: 1px solid rgba(220, 38, 38, 0.4);
  color: var(--color-error-text);
}

/* --- Body ----------------------------------------------------------------- */

.call-body {
  flex: 1;
  display: flex;
  min-height: 0;
}

/* The video column: stage above, toolbar below, both inside the body so the
   right panels (asides, default align-items: stretch) run to the window
   bottom. min-width: 0 lets the stage shrink beside a 340px panel instead
   of forcing a horizontal overflow. */
.video-column {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Column: the share stage stacks above the grid strip while sharing
   (`.sharing`); the pager no longer lives here (2026-09-15). */
#video-stage {
  flex: 1;
  min-width: 0;
  /* Inside the column the stage must be allowed to shrink so the toolbar
     below it always gets its full height. */
  min-height: 0;
  padding: 18px;
  display: flex;
  flex-direction: column;
}

video-player-container {
  flex: 1;
  /* !important: the SDK sets inline display:block on this element */
  display: grid !important;
  gap: 14px;
  align-content: center;
  grid-template-columns: repeat(var(--grid-cols, 1), 1fr);
  margin: auto;
  width: 100%;
}

/* --- Floating reactions (components/reaction-layer.ts) --------------------
   Google Meet's shape (user decision 2026-09-18): every reaction rises from
   the stage's bottom-left as the emoji with the sender's name, sways, and
   fades; a pile-on of one emoji (lib/reaction-burst.ts) adds ONE big glyph
   rising from the bottom-centre. One compositor-owned animation per
   element, no interval drawing; the component removes each node on
   animationend. The layer is click-through so the tile menus and the
   stage notices under it keep working.
   Viewport-fixed, NOT absolute-in-stage — the same rule as .ask-card-root
   (renderer-wiring-policy pins it): #video-stage must not become a
   positioned ancestor, or the three centered stage notices would move.
   The component sizes this root to #video-stage's client rect instead;
   inset: 0 is only the fallback before the first measure. */
.reaction-layer {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  /* Above tiles and the stage notices (40), below the toolbar menus (50)
     and the Ask card (55). */
  z-index: 45;
  /* How far a floater climbs: most of the stage on a laptop, never past
     the top of a tall window. */
  --rise: clamp(240px, 58vh, 520px);
}

.reaction-float {
  position: absolute;
  left: calc(26px + var(--lane, 0) * 58px);
  bottom: 22px;
  will-change: transform, opacity;
  /* ONE compositor layer per floater for its whole life, and NO easing
     kinks (2026-09-18, third pass, measured — see TESTING.md "Floating
     reactions" for the numbers): rise and sway ride ONE linear keyframe
     track on this element, with Y exactly proportional to time (constant
     climb speed, the Meet feel) and X a piecewise-linear sine for the
     sway; the fade is linear opacity stops. The first cut eased each of
     four segments and stop-started at every keyframe; the second cut
     fixed that with a sway layer + a pop layer per floater and cost the
     GPU process 25 points of a core under a 20/s flood — this cut keeps
     the smoothness at a third of the layer count (the 480ms pop-in on the
     glyph is the only other layer, and only while it runs). */
  animation:
    reactionFloat var(--float-ms, 3800ms) linear forwards,
    reactionFade var(--float-ms, 3800ms) linear forwards;
}

.reaction-float .rf-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}

.reaction-float .rf-emoji {
  font-size: 38px;
  line-height: 1;
  text-shadow: 0 4px 10px rgba(0, 0, 0, 0.45);
  /* No fill mode on purpose: a filled (finished) transform animation keeps
     the glyph promoted to its own compositor layer for the floater's whole
     life — measured as 66 live animations for 16 floaters. The pop ends at
     scale(1), the natural state, so nothing needs holding. */
  animation: reactionPop 480ms cubic-bezier(0.2, 0.8, 0.25, 1);
}

.reaction-float .rf-name {
  max-width: 150px;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  background: rgba(4, 10, 22, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  /* A long name wraps rather than truncates — the name IS the point. */
  overflow-wrap: anywhere;
}

@keyframes reactionFloat {
  0% {
    transform: translate(0, 0);
  }
  12.5% {
    transform: translate(
      calc(var(--sway, 1) * 9px),
      calc(var(--rise) * -0.125)
    );
  }
  25% {
    transform: translate(
      calc(var(--sway, 1) * 12px),
      calc(var(--rise) * -0.25)
    );
  }
  37.5% {
    transform: translate(
      calc(var(--sway, 1) * 9px),
      calc(var(--rise) * -0.375)
    );
  }
  50% {
    transform: translate(0, calc(var(--rise) * -0.5));
  }
  62.5% {
    transform: translate(
      calc(var(--sway, 1) * -9px),
      calc(var(--rise) * -0.625)
    );
  }
  75% {
    transform: translate(
      calc(var(--sway, 1) * -12px),
      calc(var(--rise) * -0.75)
    );
  }
  87.5% {
    transform: translate(
      calc(var(--sway, 1) * -9px),
      calc(var(--rise) * -0.875)
    );
  }
  100% {
    transform: translate(0, calc(var(--rise) * -1));
  }
}

@keyframes reactionFade {
  0% {
    opacity: 0;
  }
  7% {
    opacity: 1;
  }
  70% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

@keyframes reactionPop {
  0% {
    transform: scale(0.55);
  }
  60% {
    transform: scale(1.12);
  }
  100% {
    transform: scale(1);
  }
}

.reaction-burst {
  position: absolute;
  left: 50%;
  bottom: 24%;
  line-height: 1;
  will-change: transform, opacity;
  /* Same split as the floater: the rise on one curve, the fade linear,
     the pop on the glyph. translateX(-50%) is baked into both rise
     keyframes so the centring never snaps. */
  animation:
    reactionBurstRise var(--burst-ms, 2400ms) cubic-bezier(0.3, 0.05, 0.45, 1)
      forwards,
    reactionFade var(--burst-ms, 2400ms) linear forwards;
}

.reaction-burst .rb-emoji {
  display: block;
  font-size: clamp(88px, 14vh, 150px);
  line-height: 1;
  text-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  animation: reactionBurstPop 560ms cubic-bezier(0.2, 0.8, 0.25, 1);
}

@keyframes reactionBurstRise {
  from {
    transform: translate(-50%, 0);
  }
  to {
    transform: translate(-50%, -200px);
  }
}

@keyframes reactionBurstPop {
  0% {
    transform: scale(0.2);
  }
  55% {
    transform: scale(1.16);
  }
  100% {
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Fade in place: no travel, no sway, no pop. Same lifetimes, so the
     component's animationend removal is unchanged (the fade is the one
     animation left on the outer element). */
  .reaction-float,
  .reaction-burst {
    animation-name: reactionFade;
  }
  .reaction-burst {
    transform: translateX(-50%);
  }
  .reaction-float .rf-emoji,
  .reaction-burst .rb-emoji {
    animation: none;
  }
}

/* --- Screen-share spotlight -------------------------------------------
   While a share is active (either mode), #video-stage stacks the share
   stage above a collapsed grid strip instead of centering the grid alone. */

#video-stage.sharing {
  flex-direction: column;
  gap: 14px;
}

/* --- Grid strip mode (video-grid.ts's isStripMode) ----------------------
   Fixed-size horizontal thumbnail row; fitToStage early-returns so this
   sizing — not the computed --grid-cols/max-width — wins. !important
   matches the same-file precedent above: JS/SDK inline styles need it to
   be overridden reliably. */

video-player-container.grid-strip {
  flex: none;
  display: flex !important;
  flex-direction: row;
  align-content: normal;
  align-items: center;
  height: 110px;
  max-width: none !important;
  width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  margin: 0;
}

video-player-container.grid-strip .video-tile {
  flex: none;
  width: calc(110px * 16 / 9);
  height: 110px;
}

/* --- Main-floor stage mode (video-grid.ts's isStageDisplay) -------------
   One guide stage tile above a 6-tile student strip — the deliberate
   version of the share-collapse layout that ended the 2026-08-25 9am
   reconnect storm. The container is a 6-column grid with two rows: the
   `.stage-slot` tile (or the guideless placeholder) is a 16:9 box FIT TO
   ROW 1's HEIGHT, and row 1 absorbs every pixel the strip and pager
   don't need (user decision 2026-08-25: no dead space above the stage or
   under the strip — the stage grows to fill, the strip stays
   width-driven). fitToStage early-returns, so these rules own all sizing
   (the same !important precedent as the strip above). */

#video-stage.staged {
  flex-direction: column;
  gap: 14px;
}

video-player-container.grid-stage {
  /* Fill the stage area between the header and the pager: the 1fr row
     hands all slack to the stage tile. min-height: 0 so the fill can
     also SHRINK inside the flex column instead of overflowing it. */
  flex: 1 1 auto;
  min-height: 0;
  display: grid !important;
  /* 6 columns per STAGE CELL: sync sets --stage-cols to 6 × (tiles on the
     stage row) and --strip-span to that count, so each stage tile spans 6
     columns (an equal share of the row) while each strip tile spans one
     cell's worth — row 2 keeps exactly 6 slots under any stage row (user
     decision 2026-09-16: any number of spotlighted students). */
  grid-template-columns: repeat(var(--stage-cols, 6), minmax(0, 1fr));
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 10px;
  margin: 0;
  width: 100%;
  max-width: none !important;
}

/* Strip tiles are width-driven 16:9 here. fitToStage early-returns on the
   stage display, so a `--tile-h` the grid wrote EARLIER (a student who
   stood in a breakout room's grid before returning to the main floor)
   would otherwise still pin every strip tile to that stale height; height
   must read auto for the aspect-ratio to take over again. The stage tile
   below overrides this with its own row-height rule. */
video-player-container.grid-stage .video-tile {
  height: auto;
}

/* Strip tiles: one stage cell's worth of columns each — 6 per row. */
video-player-container.grid-stage
  .video-tile:not(.stage-slot):not(.stage-spotlight) {
  grid-column: span var(--strip-span, 1);
}

video-player-container.grid-stage .video-tile.stage-slot {
  /* One stage cell (the whole row while nobody is spotlighted). */
  grid-column: span 6;
  grid-row: 1;
  justify-self: center;
  align-self: center;
  /* A 16:9 box sized by the row's height (width follows from the base
     tile aspect-ratio; the explicit height is what keeps an empty div
     from collapsing to min-content in a centered grid cell). On a
     window narrower than the derived width, max-width binds and the
     video cover-crops like any aspect-mismatched tile. */
  height: 100%;
  width: auto;
  max-width: 100%;
}

/* Stage spotlight ({t:'spotlight'}): the spotlighted students join the
   guide on the stage row SIDE BY SIDE — any number of them (user
   decision 2026-09-16, generalizing 2026-09-14's Paper-03 duo): the
   guide's `.stage-slot` leftmost, then each holder's `.stage-spotlight`
   in the order they were added, every tile one stage cell (6 columns)
   wide — a 16:9 box fit to the row height and capped at its cell (on a
   tall window max-width binds and the tiles fill their cells,
   cover-cropping like any aspect-mismatched tile). Row 2 (the strip) is
   untouched: still 6 slots. With exactly two tiles the pair hugs the
   centre line — left cell end-aligned, right cell start-aligned — so the
   grid gap is the only seam (the duo geometry the e2e pins). */
video-player-container.grid-stage .video-tile.stage-spotlight {
  grid-column: span 6;
  grid-row: 1;
  justify-self: center;
  align-self: center;
  height: 100%;
  width: auto;
  max-width: 100%;
}

video-player-container.grid-stage.stage-duo .video-tile.stage-slot {
  justify-self: end;
}

video-player-container.grid-stage.stage-duo .video-tile.stage-spotlight {
  justify-self: start;
}

/* Guideless main floor: an honest placeholder card in the stage slot —
   never a student's face. A pseudo-element is a real grid item, so the
   strip keeps its shape without the DOM reconciler ever meeting a
   non-tile child (sync's cursor walk assumes every child is a tile). */
video-player-container.grid-stage.stage-empty::before {
  content: "Your guide will be here shortly";
  grid-column: 1 / -1;
  grid-row: 1;
  justify-self: center;
  align-self: center;
  height: 100%;
  width: auto;
  max-width: 100%;
  aspect-ratio: 16 / 9;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  background: var(--surface-tile);
  border: 1px dashed var(--border-subtle);
  color: var(--text-muted);
  font-size: 15px;
}

/* --- Grid pager (video-grid.ts; lib/grid-pager.ts) ----------------------
   ‹ [page] of N › in the bottom row (Paper board 06, 2026-09-16): inside
   the host strip's left slot for guides, alone in a `.pager-only` pill for
   students. The current page is a FIELD at all times — the box is the
   affordance, the amber text keeps it reading as "the page I am on" — so
   a guide types the page they want instead of scanning a row of numbers.
   Hidden with its slot at ≤ 1 page. */

.grid-pager-slot[hidden] {
  display: none;
}

.grid-pager {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 30px;
}

.grid-pager .pager-btn {
  width: 20px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
  background: transparent;
  transition: color var(--duration-fast) var(--ease-default);
}

.grid-pager .pager-btn:hover:not(:disabled) {
  color: var(--color-accent-text);
}

.grid-pager .pager-btn:disabled {
  color: var(--text-subtle);
  cursor: default;
}

.grid-pager .pager-btn svg {
  width: 14px;
  height: 14px;
}

/* The field. 32px seats two digits; --pager-digits (renderPager) widens
   it 7px per digit past that so a three-digit floor — a rig, not a class
   — still fits. Padding 0 and an explicit font so the app's input reset
   cannot restyle it. */
.grid-pager .pager-field {
  width: max(32px, calc(18px + var(--pager-digits, 1) * 7px));
  height: 24px;
  padding: 0;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.06);
  color: var(--color-accent-text);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 22px;
  text-align: center;
  outline: none;
  transition:
    border-color var(--duration-fast) var(--ease-default),
    background-color var(--duration-fast) var(--ease-default),
    box-shadow var(--duration-fast) var(--ease-default);
}

.grid-pager .pager-field:hover {
  border-color: rgba(255, 255, 255, 0.32);
}

/* Focus: amber ring, the number selected (video-grid selects on focus) so
   the first digit typed replaces it. */
.grid-pager .pager-field:focus {
  background: var(--surface-base);
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.22);
}

.grid-pager .pager-field::selection {
  background: rgba(245, 158, 11, 0.4);
  color: var(--color-accent-text);
}

/* Not a page of this grid (0, past the end): red until fixed or left. */
.grid-pager .pager-field.is-invalid,
.grid-pager .pager-field.is-invalid:focus {
  color: var(--color-error-text);
  border-color: var(--color-error-text);
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.2);
}

.grid-pager .pager-total {
  padding: 0 2px;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  white-space: nowrap;
}

/* --- Tiles ------------------------------------------------------------ */

.video-tile {
  position: relative;
  border-radius: var(--radius-md);
  background: var(--surface-tile);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  /* fitToStage writes --tile-h on the container (lib/grid-pages fitGrid):
     the tile takes its column's width and this explicit height, anywhere
     between 16:9 and 4:3, so a page fills the stage instead of leaving
     margins. Before the first fit — and in strip mode, whose rule below
     sets its own height — the 16:9 aspect-ratio is the fallback; CSS only
     honours aspect-ratio while height is auto. */
  aspect-ratio: 16 / 9;
  height: var(--tile-h, auto);
  /* A grid item with a definite height and an aspect-ratio would derive
     its WIDTH from the ratio (`normal` alignment is `start`, not `stretch`,
     for boxes with a preferred aspect ratio) and spill out of its column.
     The width is always the column's; the strip rule above overrides it. */
  width: 100%;
  transition: box-shadow var(--duration-normal) var(--ease-default);
  /* Query container for the tile's own chrome (the name pill). The tile's
     inline size comes from the grid track or the strip's fixed width —
     never from its contents — so this cannot feed back into layout. It
     covers strip mode too, which `fitToStage` deliberately does not size. */
  container-type: inline-size;
}

.video-tile.speaking {
  box-shadow:
    0 0 0 2.5px var(--color-accent),
    var(--glow-accent);
}

/* Canvas render mode (SharedArrayBuffer / cross-origin isolated): the SDK
   paints every tile's video onto ONE canvas inside the container's shadow
   root, which stacks BENEATH the slotted tiles — an opaque tile background
   would hide the rendered video. (Video-element mode painted a <video>
   INSIDE the tile, above the background, so this is invisible there.) */
.video-tile.has-video {
  background: transparent;
}

.video-tile .player-slot {
  position: absolute;
  inset: 0;
}

/* The grid attaches a bare <video> into the slot (video-grid.ts attach()).
   Without explicit sizing it lays out at the capture's intrinsic size
   (e.g. 1280×720) and the overflow:hidden tile shows only a corner crop —
   pin it to the tile and cover, cropping symmetrically on aspect mismatch. */
.video-tile .player-slot video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.video-tile.self .player-slot video {
  transform: scaleX(-1);
}

/* "Mirror my video" turned off — see themselves as others do. */
.video-tile.self.mirror-off .player-slot video {
  transform: none;
}

.tile-avatar {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.video-tile.has-video .tile-avatar {
  display: none;
}

.tile-avatar .avatar-circle {
  width: clamp(48px, 22%, 96px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--color-navy-muted);
  color: var(--color-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: clamp(18px, 3.2vw, 34px);
  letter-spacing: 0.02em;
}

/* The name pill scales with its tile. A fixed-size pill on a tile that
   shrinks with the roster reads as a name that GREW: measured at 20 tiles
   across 5 columns, a 142px pill covered 81% of a 175px tile (15% at
   1-up). Over half of it was fixed chrome, so the padding, gap and icons
   scale alongside the text rather than the font alone.

   Each ramp is `clamp(floor, calc(base + Ncqw), ceiling)`: cqw alone is
   too steep across this range (a coefficient that behaves at 175px pins
   to the floor by 460px), so the intercept carries the size and cqw only
   tilts it. Full 13px survives on any tile ≳460px wide, i.e. every call
   the complaint was not about. */
.tile-footer {
  position: absolute;
  left: 5px;
  bottom: 3px;
  display: flex;
  align-items: center;
  gap: clamp(4px, calc(2px + 1.2cqw), 8px);
  padding: clamp(3px, calc(2px + 0.6cqw), 5px)
    clamp(6px, calc(3px + 1.9cqw), 12px);
  border-radius: var(--radius-pill);
  background: rgba(4, 10, 22, 0.65);
  backdrop-filter: blur(6px);
  font-size: clamp(10px, calc(8px + 1.6cqw), 13px);
  font-weight: 600;
  max-width: calc(100% - 10px);
  z-index: 2;
}

.tile-footer .tile-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Below 150px the tag stops carrying anything the tile already says.
   150 sits between the tile widths the layout produces at a full stage —
   fitColumns() gives 4 columns up to 16 people (~170px) and 5 from 17
   (~134px) — so this flips exactly at that boundary and leaves the
   12-person call, which reads fine today, completely untouched. The
   width cap adds one more case below the line: at the 960px minimum
   window with the sidebar open, fitColumns() holds a 17+ stage at 4
   columns of ~133px, which drops the tag exactly as the 5-column page
   does.

   What goes, and why each is redundant rather than merely small:
   - the camera icon: a camera-off tile already shows the initials avatar,
     and a camera-on one shows the person;
   - a plainly LIVE mic icon: silence is the default state in a class, so
     only the muted case is news (this is Zoom's rule too). The whisper
     hues stay — an amber whisper mic is a state nothing else on the tile
     reports, so it survives at every size;
   The student's grade is not here to shed — it is a participant-list tag
   now, not part of the name. Measured on a 134px tile: 84% before, 39%. */
@container (max-width: 150px) {
  .tile-footer .camera-icon {
    display: none;
  }

  .tile-footer .mic-icon:not(.mic-off):not(.mic-whisper) {
    display: none;
  }
}

.tile-footer svg {
  width: clamp(10px, calc(8px + 1.9cqw), 14px);
  height: clamp(10px, calc(8px + 1.9cqw), 14px);
  flex: none;
}

.tile-footer .mic-off {
  color: #f87171;
}

.tile-footer .camera-off {
  color: #f87171;
}

/* Whisper pair members: amber mic (matches the whisper banner family). A
   whisper-muted pair member keeps the amber hue — the red mic-off color is
   reserved for the real session mute. */
.tile-footer .mic-whisper,
.tile-footer .mic-whisper.mic-off {
  color: var(--color-warning);
}

/* --- Share stage (share-stage.ts) --------------------------------------- */

.share-stage-body {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  background: var(--surface-tile);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
}

.share-header {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 2;
  padding: 6px 16px;
  border-radius: var(--radius-pill);
  background: rgba(4, 10, 22, 0.65);
  backdrop-filter: blur(6px);
  font-size: 13.5px;
  font-weight: 700;
  max-width: calc(100% - 28px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.share-viewport {
  position: relative;
  flex: 1;
  min-height: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.share-view {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.share-local-preview {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* The sharer's card for a DISPLAY share (share-stage.ts sharer mode,
   lib/share-preview.ts). The pick-time snapshot fills the stage under a
   near-black scrim, with the message and the sharer pill centred on top.
   Deliberately no <video>: the mirror this replaces was the live capture
   played back into the stage it was capturing. A source-less share
   (system picker) renders the same stage with no snapshot. */
.share-card {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #03070f;
}

.share-card-snapshot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.share-card-scrim {
  position: absolute;
  inset: 0;
  /* Translucent on purpose: the picture must stay readable through it.
     Legibility of the copy comes from the blurred panel below, not from
     darkening the whole stage. */
  background: rgba(4, 10, 22, 0.5);
}

.share-card-copy {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  text-align: center;
  padding: 28px 40px 30px;
  max-width: 640px;
  border-radius: 20px;
  background: rgba(4, 10, 22, 0.55);
  backdrop-filter: blur(14px);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.share-card-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-success);
}

.share-card-live {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-success);
  box-shadow: 0 0 0 4px rgba(87, 201, 155, 0.18);
  animation: share-card-live 1.6s ease-in-out infinite;
}

@keyframes share-card-live {
  50% {
    box-shadow: 0 0 0 7px rgba(87, 201, 155, 0.08);
  }
}

@media (prefers-reduced-motion: reduce) {
  .share-card-live {
    animation: none;
  }
}

.share-card-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--text-primary);
  text-wrap: balance;
}

.share-card-sub {
  margin: 0;
  font-size: 15px;
  color: var(--text-muted);
  max-width: 46ch;
}

/* In card mode the pill lives inside the card copy (share-stage.ts
   placePill): it sheds its floating chrome and becomes the button row.
   Its text span is redundant beside the title, so it is hidden here. */
.share-stage-body.is-card .share-sharer-pill {
  position: static;
  transform: none;
  padding: 6px 0 0;
  background: transparent;
  backdrop-filter: none;
  gap: 12px;
}

.share-stage-body.is-card .share-sharer-pill > span {
  display: none;
}

.share-stage-body.is-card .share-sharer-pill .btn {
  padding: 12px 22px;
  font-size: 14px;
}

.share-stage-body.is-card .share-sharer-pill [data-action="stop-share"] {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--text-inverse);
}

.share-stage-body.is-card
  .share-sharer-pill
  [data-action="stop-share"]:hover:not(:disabled) {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
  color: var(--text-inverse);
}

.share-sharer-pill {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 12px 10px 18px;
  border-radius: var(--radius-pill);
  background: rgba(4, 10, 22, 0.75);
  backdrop-filter: blur(6px);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}

/* --- Overlays --------------------------------------------------------- */

.call-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 11, 22, 0.82);
  backdrop-filter: blur(8px);
}

.overlay-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 18px;
  padding: 44px 56px;
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-raised);
  max-width: 460px;
}

.overlay-card .spinner.large {
  color: var(--color-accent);
}

.overlay-title {
  font-family: var(--font-display);
  font-size: 20px;
  text-transform: uppercase;
}

.overlay-message {
  color: var(--text-muted);
  font-size: 15px;
}

/* Waiting on a host (guide-wait-overlay.ts): the stage and toolbar hide
   under the overlay — nothing behind it may show peers or offer media
   controls while the session has no guide. The header (timer + connection
   pill) stays. The grid stays MOUNTED (display:none): call-service bumps
   videoEpoch on exit so tiles re-attach. */
.call-screen.is-guide-wait .call-body,
.call-screen.is-guide-wait #toolbar-root {
  display: none;
}

/* --- Share picker (share-picker.ts) -------------------------------------- */

.share-picker-card {
  align-items: stretch;
  text-align: left;
  width: 640px;
  max-width: min(640px, calc(100vw - 64px));
  max-height: calc(100vh - 96px);
  padding: 28px 32px;
  overflow: hidden;
}

.share-picker-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}

.share-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.share-picker-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}

.share-picker-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 12px;
}

.share-picker-sound {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-text-secondary, inherit);
  cursor: pointer;
  user-select: none;
}

/* "Share sound only" sits at the footer's far end even when the checkbox
   is absent — it is a pick of its own, never a third checkbox. */
.share-picker-sound-only {
  margin-left: auto;
}

.share-picker-sound input {
  accent-color: var(--color-accent);
}

.share-picker-body {
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-top: 20px;
  overflow-y: auto;
}

.share-picker-body .spinner {
  align-self: center;
  margin: 24px 0;
  color: var(--color-accent);
}

.share-picker-section-title {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-subtle);
  margin-bottom: 10px;
}

.share-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}

.share-source-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Grid items default to min-width:auto, so a long nowrap source name
     would widen the card past its track (overlapping neighbors) before
     the name's ellipsis could engage. */
  min-width: 0;
  padding: 8px;
  border-radius: var(--radius-md);
  border: 1.5px solid var(--border-subtle);
  background: rgba(255, 255, 255, 0.03);
  text-align: left;
  transition:
    border-color var(--duration-fast) var(--ease-default),
    background-color var(--duration-fast) var(--ease-default);
}

.share-source-card:hover {
  border-color: var(--color-accent);
  background: rgba(245, 158, 11, 0.08);
}

.share-source-thumb {
  display: block;
  border-radius: var(--radius-sm);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--surface-tile);
}

.share-source-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.share-source-label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  /* The card is a <button>: Chromium sizes a button's flex children to
     their content instead of stretching them to the cross axis, so
     without an explicit width this label grows to the full nowrap name
     and the ellipsis never engages (measured 2026-09-25 — a Slack
     channel title ran 461px across a 180px card). */
  width: 100%;
}

.share-source-icon {
  width: 16px;
  height: 16px;
  flex: none;
}

.share-source-name {
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.share-picker-empty {
  padding: 24px 0;
  text-align: center;
  color: var(--text-subtle);
  font-size: 14px;
}

/* --- Confirm dialog (lib/confirm.ts) ------------------------------------ */

.confirm-dialog-card {
  width: 420px;
  max-width: calc(100vw - 64px);
  padding: 32px 36px;
}

.confirm-dialog-message {
  color: var(--text-primary);
  font-size: 16px;
}

/* Multi-line dialog copy (the focus/test rules): every bullet starts on
   its own line and the block reads left-aligned — a centered paragraph
   swallows the list structure. One-line confirms stay centered. */
.confirm-dialog-message.multiline {
  white-space: pre-line;
  text-align: left;
}

.confirm-dialog-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}

/* N-way choice variant (lib/confirm.ts choiceDialog): labels are full
   sentences — stack them so none truncates. */
.choice-dialog-actions {
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}

/* --- Number prompt dialog (lib/prompt-number.ts) ------------------------ */

.prompt-dialog-input {
  width: 96px;
  margin: 4px auto 0;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 18px;
  text-align: center;
}

.prompt-dialog-input:focus {
  outline: none;
  border-color: var(--color-accent);
}

/* --- Test-mode hint line ---------------------------------------------------
   The violet line under the header while THIS student's proctored exam is
   running (layout shared with the other hint elements — see .mode-hint-row
   above). Deliberately clock-free (a visible timer stresses test-takers). */

.test-banner {
  background: rgba(139, 92, 246, 0.12);
  border-bottom: 1px solid rgba(139, 92, 246, 0.4);
  color: var(--color-test-text);
}

/* --- Focus-mode hint line --------------------------------------------------
   Teal while THIS client is in focus mode (student-chosen, TEX-3962): what
   the mode does + how to leave. Clock-free like the test line (TEX-3959).
   Hidden while test mode is also on — the lines never stack. */

.focus-banner {
  background: rgba(20, 184, 166, 0.12);
  border-bottom: 1px solid rgba(20, 184, 166, 0.4);
  color: var(--color-focus-text);
}

/* --- Academic-block hint line ---------------------------------------------
   While a room's academic block binds THIS client. Students know the block
   as FOCUS MODE (user decision 2026-08-15), so it wears the focus line's
   exact look — only the copy tells the guide-imposed mode from the
   self-chosen one. Precedence: test > academic > focus. */

.academic-banner {
  background: rgba(20, 184, 166, 0.12);
  border-bottom: 1px solid rgba(20, 184, 166, 0.4);
  color: var(--color-focus-text);
}

/* --- Test-mode hand/chat notices ----------------------------------------------
   A testing/focused student's call-for-help reassurance, centered over the
   guide's video tile (the only tile they see in test mode), auto-hidden
   after a beat. Two variants sharing one treatment: the hand raise
   (.test-hand-notice), the chat redirect (.ask-chat-notice — a Timeback
   student's unmute attempt opened the chat instead), and the chat-first
   raise (.hand-chat-notice — a focused raise opened the chat alongside
   the hand; call-screen shows one at a time). Deliberately NOT top toasts: they must not cover the
   test-mode banner. */

.test-hand-notice,
.ask-chat-notice,
.hand-chat-notice {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 78%;
  padding: 16px 22px;
  border-radius: var(--radius-lg, 16px);
  border: 1px solid rgba(139, 92, 246, 0.55);
  background: rgba(16, 16, 30, 0.92);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.45;
  text-align: left;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  animation: test-hand-notice-in 180ms var(--ease-default, ease-out);
}

.test-hand-notice[hidden],
.ask-chat-notice[hidden],
.hand-chat-notice[hidden] {
  display: none;
}

.test-hand-notice-icon,
.ask-chat-notice-icon,
.hand-chat-notice-icon {
  flex: none;
  font-size: 26px;
  line-height: 1;
}

/* Reason-led ask-chat notice (user ask 2026-08-18): line one is WHY the
   mic refused, line two the shared chat recourse — the reason leads, so
   it carries the weight; the recourse reads as the follow-through. */
.ask-chat-notice-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ask-chat-notice-reason {
  font-weight: 700;
}

.ask-chat-notice-reason[hidden] {
  display: none;
}

.ask-chat-notice-recourse {
  font-weight: 500;
  opacity: 0.9;
}

@keyframes test-hand-notice-in {
  from {
    opacity: 0;
    transform: translate(-50%, calc(-50% + 8px));
  }
}

/* --- Ask card (components/ask-prompt.ts) -------------------------
   Centered over the STAGE, click-through around it: the toolbar keeps
   working. `position: fixed` deliberately: #video-stage is NOT a positioned
   ancestor and must not become one — the three centered notices
   (.test-hand-notice & co.) are absolute against the initial containing
   block and would re-center on the stage the moment it was. The card uses
   the same containing block they do; ask-prompt.ts then sizes this
   root to #video-stage's client rect from JS (inline left/top/width/height,
   re-read every render while the card is up and on window resize), so the
   card centers on the stage column rather than straddling an open side
   panel. `inset: 0` is the fallback when the stage is not found. z-index
   above those notices (40), below dialogs (60) and toasts (100). Font
   sizes in rem only; nothing here truncates — the question wraps. */
.ask-card-root {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 55;
}
/* Student card, design B (Rodrigo's pick, 2026-09-03): playful and
   energetic — a tilted amber "Your turn" tag with the mono clock beside it,
   the question in the display face, a segmented timer that drains, a chunky
   "Send it" pill, and a full-card celebration on Sent. Tuned so a senior
   does not roll their eyes: tokens only, one accent, no emoji. */
.ask-card {
  pointer-events: auto;
  position: relative;
  width: 440px;
  max-width: calc(100% - 48px);
  padding: 30px 28px 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  border: 2px solid var(--color-accent);
  box-shadow:
    var(--shadow-raised),
    0 0 24px var(--color-accent-glow);
  overflow: hidden;
  /* menuInPortal is components.css's shared translateY-only keyframe (both
     sheets load on the same page, so the name resolves). NOT menuIn: that
     one bakes .device-menu's translateX(-50%) into both frames, which
     would slide a flex-centered card in from 220px left of center. */
  animation: menuInPortal var(--duration-fast) var(--ease-out);
}
.ask-card[hidden] {
  display: none;
}
.ask-card.phase-ending {
  border-color: var(--color-error);
  box-shadow:
    var(--shadow-raised),
    0 0 24px rgba(220, 38, 38, 0.18);
}
.ask-card.flash-sent {
  border-color: var(--color-success);
  box-shadow: var(--shadow-raised);
}
.ask-card.flash-verdict {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-raised);
}
/* Decoration only: a dashed ring bleeding off the bottom-right corner, kept
   away from the × so it never crowds the escape target. */
.ask-card .ac-deco {
  position: absolute;
  right: -40px;
  bottom: -40px;
  color: var(--color-accent);
  opacity: 0.3;
  pointer-events: none;
}
.ask-card.phase-ending .ac-deco {
  color: var(--color-error);
}
.ask-card .ac-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-muted);
  font-size: 1.125rem;
  line-height: 1;
  z-index: 1;
}
.ask-card .ac-close:hover {
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.ask-card .ac-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-right: 40px; /* clear of the × */
}
.ask-card .ac-eyebrow {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--text-inverse);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transform: rotate(-2deg);
}
.ask-card.phase-ending .ac-eyebrow {
  background: var(--color-error);
  color: #ffffff;
}
.ask-card .ac-time {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 500;
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}
.ask-card.phase-ending .ac-time {
  color: var(--color-error-text);
}
/* Body face, bold — NOT the display face: IntegralCF is all-caps, and a
   200-character question in caps is unreadable. The tag and the Sent line
   keep the display face; they are short. */
.ask-card .ac-question {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 1.25rem;
  line-height: var(--leading-tight);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
/* The segmented timer: ONE fill element scaled by the remaining fraction
   inside a track masked into six pills, so it drains chunk by chunk while
   the fill itself stays continuous (the view model's `fraction` drives it). */
.ask-card .ac-countdown {
  position: relative;
  height: 8px;
  background: var(--surface-tile);
  overflow: hidden;
  border-radius: var(--radius-pill);
  -webkit-mask-image: repeating-linear-gradient(
    90deg,
    #000 0,
    #000 calc(100% / 6 - 6px),
    transparent calc(100% / 6 - 6px),
    transparent calc(100% / 6)
  );
  mask-image: repeating-linear-gradient(
    90deg,
    #000 0,
    #000 calc(100% / 6 - 6px),
    transparent calc(100% / 6 - 6px),
    transparent calc(100% / 6)
  );
}
.ask-card .ac-countdown-bar {
  position: absolute;
  inset: 0;
  transform-origin: left center;
  background: var(--color-accent);
  transition: transform 250ms linear;
}
.ask-card .ac-countdown.ending .ac-countdown-bar {
  background: var(--color-error);
}
.ask-card .ac-answer {
  width: 100%;
  /* A FIXED height, not `rows`: three lines at 1rem × 1.6 plus the 12px
     paddings and 2px borders. Listening swaps padding for the caption strip
     along the bottom edge; with the height pinned that trades text area for
     caption and the card stays exactly the same size (it visibly grew when
     the padding was allowed to add up — Rodrigo, 2026-09-04). */
  height: 6.55rem;
  box-sizing: border-box;
  resize: none;
  font: inherit;
  font-size: 1rem;
  line-height: var(--leading-normal);
  padding: 12px 14px;
  border-radius: var(--radius-md);
  border: 2px solid var(--border-strong);
  background: var(--surface-base);
  color: var(--text-primary);
}
.ask-card .ac-answer:focus {
  outline: none;
  border-color: var(--color-accent);
}
.ask-card .ac-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* LINK kind (direction 01, 2026-10-01): one row — destination + host on the
   left, Open on the right — a hint line beneath, and on a failed open the
   full url (wrapping, never truncated) with Copy link. Hovers brighten: the
   accent's own hover token is the DARKER amber, so Open lifts with a filter. */
.ask-card .ac-link {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ask-card .ac-link[hidden] {
  display: none;
}
.ask-card .ac-link-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: var(--surface-tile);
  border: 1px solid var(--border-subtle);
}
.ask-card .ac-link-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ask-card .ac-link-dest {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ask-card .ac-link-host {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.ask-card .ac-open {
  flex-shrink: 0;
  padding: 10px 18px;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  border-radius: var(--radius-pill);
  border: none;
  background: var(--color-accent);
  color: var(--text-inverse);
  transition: filter var(--duration-fast) var(--ease-default);
}
.ask-card .ac-open:hover:not(:disabled) {
  filter: brightness(1.12);
}
.ask-card .ac-open:disabled {
  opacity: 0.6;
}
.ask-card .ac-link-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.ask-card .ac-opened {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-success);
}
.ask-card .ac-opened[hidden],
.ask-card .ac-copy[hidden],
.ask-card .ac-link-url[hidden] {
  display: none;
}
.ask-card .ac-link-hint {
  font-size: 0.8125rem;
  color: var(--text-muted);
}
.ask-card .ac-link-hint.is-error {
  color: var(--color-error-text);
}
.ask-card .ac-copy {
  padding: 6px 12px;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-accent);
  background: transparent;
  color: var(--color-accent-text);
}
.ask-card .ac-copy:hover {
  background: var(--color-accent-glow);
}
.ask-card .ac-link-url {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--text-primary);
  overflow-wrap: anywhere;
  user-select: text;
}
.ask-card.kind-link .ac-footer {
  display: none;
}
/* Tap to talk, direction B (Rodrigo's pick 2026-09-04): the mic lives INSIDE
   the answer box, bottom-right — where the words will appear, the pattern a
   phone keyboard taught every student. Same states as .chat-mic
   (components.css) so the two read as one control: amber idle, error-red
   while listening. The textarea keeps a right gutter for it, and a bottom
   gutter while listening for the caption strip. */
.ask-card .ac-answer-wrap {
  position: relative;
}
.ask-card .ac-answer-wrap .ac-answer {
  padding-right: 62px;
}
.ask-card .ac-mic {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: var(--radius-pill);
  background: rgba(245, 158, 11, 0.18);
  color: var(--color-accent);
  border: 1px solid rgba(245, 158, 11, 0.4);
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease-default),
    color var(--duration-fast) var(--ease-default);
}
.ask-card .ac-mic svg {
  width: 18px;
  height: 18px;
}
.ask-card .ac-mic:hover:not(:disabled) {
  background: rgba(245, 158, 11, 0.32);
}
.ask-card .ac-mic:disabled {
  opacity: 0.45;
  cursor: default;
}
.ask-card .ac-mic.is-busy {
  color: var(--text-muted);
  border-color: var(--border-strong);
  background: var(--surface-raised);
}
/* Listening = the recording disclosure: the control becomes a stop square. */
.ask-card .ac-mic.is-listening {
  color: #ff8a80;
  border-color: rgba(220, 38, 38, 0.6);
  background: rgba(220, 38, 38, 0.22);
  animation: dictation-pulse 1.6s ease-in-out infinite;
}
.ask-card .ac-mic .ac-mic-on,
.ask-card .ac-mic .ac-mic-stop {
  display: grid;
  place-items: center;
}
.ask-card .ac-mic .ac-mic-stop,
.ask-card .ac-mic.is-listening .ac-mic-on {
  display: none;
}
.ask-card .ac-mic.is-listening .ac-mic-stop {
  display: grid;
}
/* The live transcript reads as the student's own words — normal face, full
   color; the BOX carries the recording state (red edge + soft ring). */
.ask-card .ac-answer.dictating {
  border-color: rgba(220, 38, 38, 0.6);
  box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.12);
  /* Room for the caption strip INSIDE the pinned height: the text area
     shrinks to about two lines and scrolls; the box and the card do not move. */
  padding-bottom: 36px;
}
/* The caption strip along the box's bottom edge: three breathing bars and
   the hint while listening; the failure hint alone, in red text, after. */
.ask-card .ac-listen {
  position: absolute;
  left: 14px;
  bottom: 12px;
  right: 62px;
  display: flex;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}
.ask-card .ac-listen[hidden] {
  display: none;
}
.ask-card .ac-wave {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 14px;
  color: #ff8a80;
}
.ask-card .ac-wave span {
  display: block;
  width: 3px;
  height: 6px;
  border-radius: 2px;
  background: currentColor;
  animation: ask-wave 1s ease-in-out infinite;
}
.ask-card .ac-wave span:nth-child(2) {
  animation-delay: 0.15s;
}
.ask-card .ac-wave span:nth-child(3) {
  animation-delay: 0.3s;
}
@keyframes ask-wave {
  0%,
  100% {
    height: 6px;
  }
  50% {
    height: 14px;
  }
}
.ask-card .ac-listen.is-error .ac-wave {
  display: none;
}
.ask-card .ac-hint {
  font-size: 0.75rem;
  color: var(--text-subtle);
}
.ask-card .ac-hint.is-error {
  color: var(--color-error-text);
}
@media (prefers-reduced-motion: reduce) {
  .ask-card .ac-mic.is-listening,
  .ask-card .ac-wave span {
    animation: none;
  }
}
.ask-card .ac-submit {
  padding: 12px 22px;
  font: inherit;
  font-size: 1rem;
  font-weight: 700;
  border-radius: var(--radius-pill);
  border: none;
  background: var(--color-accent);
  color: var(--text-inverse);
  box-shadow: 0 5px 0 var(--color-accent-hover);
  transition:
    transform var(--duration-fast) var(--ease-default),
    box-shadow var(--duration-fast) var(--ease-default);
}
.ask-card .ac-submit:hover:not(:disabled) {
  transform: translateY(1px);
  box-shadow: 0 4px 0 var(--color-accent-hover);
}
.ask-card .ac-submit:disabled {
  opacity: 0.55;
  box-shadow: none;
}
/* Flash states take the whole card: the form hides, the celebration shows. */
.ask-card .ac-celebrate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}
.ask-card .ac-celebrate[hidden] {
  display: none;
}
.ask-card .ac-celebrate-icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-success);
  color: var(--text-inverse);
}
.ask-card .ac-celebrate-icon:empty {
  display: none;
}
.ask-card.flash-verdict .ac-celebrate-icon {
  background: var(--surface-tile);
  color: var(--text-muted);
}
.ask-card .ac-status {
  font-family: var(--font-display);
  font-size: 1.25rem;
  letter-spacing: 0.04em;
  color: var(--text-primary);
}
.ask-card .ac-sub {
  font-size: 0.9375rem;
  color: var(--text-muted);
}
.ask-card .ac-sub:empty {
  display: none;
}
/* The WRAP, not the textarea alone: the tap-to-talk mic and the listening
   strip are absolutely positioned inside it, so hiding only the textarea
   left a mic floating top-right over the Sent flash (Rodrigo, 2026-09-08). */
.ask-card.flash .ac-head,
.ask-card.flash .ac-question,
.ask-card.flash .ac-countdown,
.ask-card.flash .ac-answer-wrap,
.ask-card.flash .ac-choices,
.ask-card.flash .ac-reveal,
.ask-card.flash .ac-link,
.ask-card.flash .ac-footer,
.ask-card.flash .ac-close {
  display: none;
}

/* --- Check-in card: "How are you feeling today?" (2026-09-10) -----------
   The Ask card's sibling, centered on the stage the same way (the root is
   the same viewport-fixed frame; components/checkin-card.ts sizes it to
   #video-stage like the Ask card does). Three face tiles, one tap; the
   thanks state borrows the tapped face's color for its edge. Sky · amber ·
   green — a low mood is never red on this card. */
.checkin-card-root,
.checkout-card-root {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 54; /* one under the Ask card, which outranks it at join */
}
/* The workshop CHECK-OUT (2026-09-11) shares the card's chrome; it stands in
   the Leave door (over the stage) and on the landing screen after the guide
   ends the session, so its root also sits above the landing content. */
.checkout-card-root {
  z-index: 60;
}
.checkin-card {
  pointer-events: auto;
  position: relative;
  width: 440px;
  max-width: calc(100% - 48px);
  padding: 30px 28px 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  border: 2px solid var(--color-accent);
  box-shadow:
    var(--shadow-raised),
    0 0 24px var(--color-accent-glow);
  animation: menuInPortal var(--duration-normal) var(--ease-out);
}
.checkin-card[hidden] {
  display: none;
}
.checkin-card .cc-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-right: 40px;
}
.checkin-card .cc-eyebrow {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--text-inverse);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.checkin-card .cc-question {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: var(--leading-tight);
  color: var(--text-primary);
}
.checkin-card .cc-faces {
  display: flex;
  gap: 12px;
}
.checkin-card .cc-face {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 22px 0 18px;
  border-radius: 14px;
  border: 1px solid var(--border-strong);
  background: var(--surface-base);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-default),
    transform var(--duration-fast) var(--ease-default);
}
.checkin-card .cc-face svg {
  width: 44px;
  height: 44px;
}
.checkin-card .cc-face.tone-sky svg {
  color: #38bdf8;
}
.checkin-card .cc-face.tone-amber svg {
  color: var(--color-accent);
}
.checkin-card .cc-face.tone-green svg {
  color: var(--color-success);
}
.checkin-card .cc-face.tone-gold svg {
  color: #fbbf24;
}
.checkin-card .cc-face:hover:not(:disabled) {
  transform: translateY(-2px);
}
.checkin-card .cc-face.tone-sky:hover:not(:disabled) {
  border-color: #38bdf8;
}
.checkin-card .cc-face.tone-amber:hover:not(:disabled) {
  border-color: var(--color-accent);
}
.checkin-card .cc-face.tone-green:hover:not(:disabled) {
  border-color: var(--color-success);
}
.checkin-card .cc-face.tone-gold:hover:not(:disabled) {
  border-color: #fbbf24;
}
.checkin-card .cc-face:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.checkin-card .cc-hint {
  font-size: 0.8125rem;
  color: var(--text-muted);
}
/* Thanks: the tapped face big, the card's edge in its color, the form gone. */
.checkin-card .cc-thanks {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
  text-align: center;
}
.checkin-card .cc-thanks[hidden] {
  display: none;
}
.checkin-card .cc-thanks-face svg {
  width: 56px;
  height: 56px;
}
.checkin-card .cc-thanks-title {
  font-size: 1.375rem;
  font-weight: 700;
  color: var(--text-primary);
}
.checkin-card .cc-thanks-sub {
  font-size: 0.875rem;
  color: var(--text-muted);
}
.checkin-card.thanks .cc-head,
.checkin-card.thanks .cc-question,
.checkin-card.thanks .cc-faces,
.checkin-card.thanks .cc-hint {
  display: none;
}
.checkin-card.thanks.tone-sky {
  border-color: #38bdf8;
  box-shadow: var(--shadow-raised);
}
.checkin-card.thanks.tone-sky .cc-thanks-face svg {
  color: #38bdf8;
}
.checkin-card.thanks.tone-amber {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-raised);
}
.checkin-card.thanks.tone-amber .cc-thanks-face svg {
  color: var(--color-accent);
}
.checkin-card.thanks.tone-green {
  border-color: var(--color-success);
  box-shadow: var(--shadow-raised);
}
.checkin-card.thanks.tone-gold {
  border-color: #fbbf24;
  box-shadow: var(--shadow-raised);
}
.checkin-card.thanks.tone-gold .cc-thanks-face svg {
  color: #fbbf24;
}
.checkin-card.thanks.tone-green .cc-thanks-face svg {
  color: var(--color-success);
}

/* --- Choose card: "Pick your room" (Let students join a room, 2026-09-15;
   restyled 2026-10 to Paper "UPDATE · Welcome, time to pick & presets").
   480 wide, one 4px amber bar draining across the top, a PINNED header
   (YOUR TURN · the countdown, today's Ns, m:ss from one minute) over a
   thin divider, and a BODY that
   scrolls when the guide's words and the rooms are taller than the stage:
   the card is capped to its stage-sized root (max-height), the header
   never scrolls, a fade marks "more below". Font sizes in rem (the
   student's font setting scales them, 0.75rem floor); nothing ever
   ellipsizes — long words wrap. Its root sits ONE over the Ask card's
   (56 > 55): a student's first seconds in a choose session belong to the
   room pick. */
.choose-card-root {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 56;
}
.choose-card {
  pointer-events: auto;
  position: relative;
  width: 480px;
  max-width: calc(100% - 48px);
  max-height: calc(100% - 48px);
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
  animation: menuInPortal var(--duration-normal) var(--ease-out);
}
.choose-card[hidden] {
  display: none;
}
/* The window, draining: full when the card shows, empty at the deadline. */
.choose-card .chc-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: rgba(255, 255, 255, 0.08);
  z-index: 1;
}
.choose-card .chc-bar-fill {
  height: 100%;
  width: 100%;
  background: var(--color-accent);
  transition: width 250ms linear;
}
.choose-card.phase-ending .chc-bar-fill {
  background: var(--color-danger);
}
.choose-card.phase-ending .chc-countdown {
  color: var(--color-danger);
}
.choose-card .chc-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 22px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.choose-card .chc-eyebrow {
  color: var(--color-accent-text);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.choose-card .chc-countdown {
  font-family: var(--font-mono);
  font-size: 1.125rem;
  font-weight: 500;
  color: var(--color-accent-text);
  font-variant-numeric: tabular-nums;
  min-width: 5ch;
  text-align: right;
}
.choose-card .chc-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 22px 22px;
}
.choose-card .chc-body::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.28);
}
.choose-card .chc-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 800;
  line-height: 1.2;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.choose-card .chc-desc {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.47;
  color: #c9d3e1;
  overflow-wrap: anywhere;
}
.choose-card .chc-desc[hidden],
.choose-card .chc-rooms[hidden],
.choose-card .chc-hint[hidden],
.choose-card .chc-status[hidden] {
  display: none;
}
.choose-card .chc-rooms {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.choose-card .chc-room {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--surface-overlay);
  color: var(--text-primary);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-default),
    transform var(--duration-fast) var(--ease-default);
}
.choose-card .chc-room.is-default {
  border-color: rgba(245, 158, 11, 0.55);
}
.choose-card .chc-room:hover:not(:disabled) {
  border-color: var(--color-accent);
  transform: translateY(-1px);
}
.choose-card .chc-room:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.choose-card .chc-room-avatar {
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.02em;
}
.choose-card .chc-room-avatar.tone-primary {
  background: var(--color-primary);
  color: #ffffff;
}
.choose-card .chc-room-avatar.tone-accent {
  background: var(--color-accent);
  color: var(--text-inverse);
}
.choose-card .chc-room-avatar.tone-focus {
  background: var(--color-focus);
  color: var(--text-inverse);
}
.choose-card .chc-room-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.choose-card .chc-room-name {
  font-size: 1rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}
.choose-card .chc-room-sub {
  font-size: 0.875rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
/* Today's separate tag on the right of the default row (unchanged rule:
   hidden on every row, shown only on .is-default; wraps, never cut). */
.choose-card .chc-room-default {
  display: none;
  flex-shrink: 0;
  font-size: 0.75rem;
  color: var(--text-muted);
  text-align: right;
  max-width: 45%;
}
.choose-card .chc-room.is-default .chc-room-default {
  display: block;
}
.choose-card .chc-room-chevron {
  flex: none;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--text-muted);
}
.choose-card .chc-hint {
  font-size: 0.8125rem;
  color: var(--text-muted);
}
.choose-card .chc-status {
  padding: 18px 0 10px;
  text-align: center;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--text-primary);
}
.choose-card .chc-fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 40px;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(16, 31, 58, 0),
    var(--surface-raised)
  );
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-default);
}
.choose-card.has-more .chc-fade {
  opacity: 1;
}
.choose-card.phase-placed {
  border-color: var(--color-success);
}
.choose-card.phase-placed .chc-bar-fill {
  background: var(--color-success);
}
.choose-card.phase-failed {
  border-color: var(--color-danger);
}
.choose-card.phase-failed .chc-bar-fill {
  background: var(--color-danger);
}

/* --- Switch panel: "Students switch rooms" (2026-10, Paper design 07
   "Current on top"). A STUDENT's 340px right column in the leaderboard /
   chat family (one right slot: chat > this > leaderboard). A big card for
   the room the student is in (YOU'RE IN, the name in the display face,
   "with <guides>", a status dot + line with the countdown in a reserved
   mono box, the ×), then OTHER ROOMS as compact rows and the hint. Names
   and guides only. Font sizes in rem (0.75rem floor — Paper's 11/11.5px
   labels sit on the floor); nothing ellipsizes; red text would be
   --color-error-text (never the "danger" color, which tokens.css does
   not define). */
.switch-panel {
  width: 340px;
  flex: none;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-left: 1px solid var(--border-subtle);
  background: rgba(10, 20, 38, 0.7);
}
.switch-panel .swp-current {
  flex: none;
  margin: 14px 14px 10px;
  padding: 16px 16px 14px;
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  border: 1px solid rgba(255, 255, 255, 0.12);
}
.switch-panel .swp-current-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.switch-panel .swp-eyebrow {
  color: var(--color-accent-text);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.switch-panel .swp-close {
  flex: none;
  min-width: 44px;
  min-height: 44px;
  margin: -12px -12px -12px 0;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font-size: 1.375rem;
  line-height: 1;
  cursor: pointer;
}
.switch-panel .swp-close:hover {
  color: var(--text-primary);
}
.switch-panel .swp-current-name {
  margin: 6px 0 0;
  font-family: var(--font-display);
  font-size: 1.625rem;
  font-weight: 800;
  line-height: 1.15;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.switch-panel .swp-current-guides {
  margin-top: 4px;
  font-size: 0.875rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.switch-panel .swp-status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-success);
}
.switch-panel .swp-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-success);
}
.switch-panel.is-frozen .swp-status {
  color: var(--color-accent-text);
}
.switch-panel.is-frozen .swp-dot {
  background: var(--color-accent);
}
.switch-panel .swp-countdown {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  min-width: 5ch;
}
.switch-panel .swp-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 6px 14px 18px;
}
.switch-panel .swp-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.switch-panel .swp-rooms {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.switch-panel .swp-room {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 50px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: var(--surface-overlay);
  color: var(--text-primary);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-default);
}
.switch-panel .swp-room:hover:not([disabled]) {
  border-color: var(--color-primary-text);
}
.switch-panel .swp-room:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
/* Frozen is the panel's default state: 0.8 keeps --text-muted names and
   "Locked" at ~4.76:1 (0.6 composited to ~3.3:1, below WCAG 1.4.3). */
.switch-panel .swp-room[disabled] {
  cursor: default;
  opacity: 0.8;
}
.switch-panel .swp-room-avatar {
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 800;
}
.switch-panel .swp-room-avatar.tone-primary {
  background: var(--color-primary);
  color: #ffffff;
}
.switch-panel .swp-room-avatar.tone-accent {
  background: var(--color-accent);
  color: var(--text-inverse);
}
.switch-panel .swp-room-avatar.tone-focus {
  background: var(--color-focus);
  color: var(--text-inverse);
}
.switch-panel .swp-room-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.switch-panel .swp-room-name {
  font-size: 0.84375rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}
.switch-panel .swp-room-guides {
  font-size: 0.75rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.switch-panel .swp-join {
  flex: none;
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--color-primary-text);
}
.switch-panel.is-frozen .swp-join {
  color: var(--text-muted);
}
.switch-panel .swp-empty {
  font-size: 0.8125rem;
  color: var(--text-muted);
}
.switch-panel .swp-hint {
  margin-top: 4px;
  font-size: 0.78125rem;
  line-height: 1.45;
  color: var(--text-muted);
}

/* --- Ask card: POLLS (Flow A, 2026-09-09) -----------------------------
   The answer box gives way to lettered choice rows. A tap picks (amber
   edge); Lock it in sends; the locked card greys the rest and fills the
   pick amber with a check in the letter's slot. */
.ask-card .ac-choices {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ask-card .ac-choices[hidden],
.ask-card .ac-answer-wrap[hidden],
.ask-card .ac-reveal[hidden],
.ask-card .ac-countdown[hidden],
.ask-card .ac-submit[hidden] {
  display: none;
}
.ask-card .ac-choice {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 52px;
  padding: 0 16px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-base);
  color: var(--text-primary);
  font: inherit;
  font-size: 1.0625rem;
  text-align: left;
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-default),
    background-color var(--duration-fast) var(--ease-default);
}
.ask-card .ac-choice:hover:not(:disabled) {
  border-color: var(--text-muted);
}
.ask-card .ac-choice-letter {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: var(--surface-raised);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 500;
}
.ask-card .ac-choice-label {
  overflow-wrap: anywhere;
}
.ask-card .ac-choice.picked {
  border: 2px solid var(--color-accent);
  background: rgba(245, 158, 11, 0.15);
  font-weight: 600;
}
/* A poll's footer: the hint wraps, the button never does. */
.ask-card.kind-poll .ac-footer .ac-counter {
  flex: 1;
  min-width: 0;
}
.ask-card.kind-poll .ac-submit {
  flex-shrink: 0;
  white-space: nowrap;
}
/* base.css dims every disabled button; a locked row is not a dead control,
   it is the record of a pick — the dimming below is deliberate per row. */
.ask-card .ac-choice.locked:disabled {
  opacity: 1;
  cursor: default;
}
.ask-card .ac-choice.picked .ac-choice-letter {
  background: var(--color-accent);
  color: var(--text-inverse);
}
/* Locked in: the pick fills amber and its letter becomes a check; the
   other rows dim. Nothing on the card is clickable until the clock ends. */
.ask-card .ac-choice.locked {
  cursor: default;
  border-color: var(--border-subtle);
  color: var(--text-subtle);
}
.ask-card .ac-choice.locked .ac-choice-letter {
  color: var(--text-subtle);
}
.ask-card .ac-choice.locked.picked {
  border-color: var(--color-accent);
  background: var(--color-accent);
  color: var(--text-inverse);
}
.ask-card .ac-choice.locked.picked .ac-choice-letter {
  background: var(--text-inverse);
  color: var(--color-accent);
  font-size: 0;
}
.ask-card .ac-choice.locked.picked .ac-choice-letter::after {
  content: "✓";
  font-size: 0.8125rem;
  font-weight: 700;
}
.ask-card.voted {
  border-color: var(--color-success);
}
.ask-card.voted .ac-eyebrow,
.ask-card.revealing .ac-eyebrow {
  background: var(--color-success);
  color: var(--text-inverse);
}
.ask-card.voted .ac-time {
  color: var(--text-muted);
}
.ask-card.voted .ac-countdown-bar {
  background: var(--text-muted);
}
.ask-card.voted .ac-counter {
  color: var(--color-success);
  font-weight: 600;
}
.ask-card.waiting .ac-counter {
  color: var(--text-muted);
}
/* The reveal: the room's bars, the student's own pick tagged. */
.ask-card .ac-reveal {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ask-card .ac-reveal-sub {
  font-size: 0.875rem;
  color: var(--text-muted);
}
.ask-card .ac-reveal-rows {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.ask-card .ac-reveal-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ask-card .ac-reveal-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ask-card .ac-reveal-label {
  flex: 1;
  min-width: 0;
  font-size: 1rem;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ask-card .ac-reveal-row.lead .ac-reveal-label {
  font-weight: 600;
}
.ask-card .ac-reveal-you {
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: rgba(87, 201, 155, 0.18);
  color: var(--color-success);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.ask-card .ac-reveal-pct {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.ask-card .ac-reveal-row.lead .ac-reveal-pct {
  color: var(--text-primary);
}
.ask-card .ac-reveal-track {
  height: 10px;
  border-radius: 5px;
  background: var(--surface-base);
  display: flex;
}
.ask-card .ac-reveal-bar {
  height: 10px;
  border-radius: 5px;
  background: var(--text-muted);
}
.ask-card .ac-reveal-row.lead .ac-reveal-bar {
  background: var(--color-accent);
}
.ask-card .ac-reveal-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 0.875rem;
  color: var(--text-muted);
}
/* A failed send: the form stays, the line sits beneath it in red, no icon. */
.ask-card.phase-failed .ac-celebrate {
  align-items: flex-start;
  text-align: left;
}
.ask-card.phase-failed .ac-status {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  letter-spacing: 0;
  color: var(--color-error-text);
}
