/* One sans family for Persian and Latin (the operator, 5 Nov 2026: Noto Naskh's Latin is a heavy
   serif beside its Persian); the unicode ranges are the subsets' own, so a Latin name does not
   pull the Arabic file and the reverse. */
@font-face {
  font-family: Vazirmatn;
  src: url("/vendor/vazirmatn-arabic.woff2") format("woff2");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
}

@font-face {
  font-family: Vazirmatn;
  src: url("/vendor/vazirmatn-latin.woff2") format("woff2");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --colour-background: #f4f5f7;
  --colour-surface: #ffffff;
  --colour-surface-raised: #eaecf0;
  --colour-text: #1a1d24;
  --colour-text-muted: #5b6270;
  --colour-accent: #c8102e;
  /* The logo's red, measured on its neon ring (brand-images.sh writes it into the web manifest). */
  --colour-brand: #f70303;
  --colour-accent-text: #ffffff;
  --colour-border: #d7dbe2;
  --colour-success: #1b7f3b;
  --colour-player-background: #000000;
  --colour-poster-start: #2a2f3a;
  --colour-poster-end: #0f1115;
  --radius-small: 6px;
  --radius-medium: 12px;
  --radius-circle: 50%;
  --radius-pill: 999px;
  --shadow-panel: 0 1px 3px rgba(0, 0, 0, 0.08);
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --focus-outline-offset: 2px;
  /* Stacking: the sound button stands over a room's scene, which fills the frame after it. */
  --layer-above-scene: 2;
  --font-family: Vazirmatn, Tahoma, system-ui, sans-serif;
  --font-size-small: 0.875rem;
  --font-size-body: 1rem;
  --font-size-heading: 1.25rem;
  --font-size-brand: 1.5rem;
  --card-placeholder-mark-size: 44cqh;
  /* A poster's live badge and the band it takes from the edge: inset, then its height (one line
     of text and its padding) or its width (dot, gap, the word and padding, measured at 52px). */
  --card-badge-inset: var(--space-2);
  --card-badge-font-size: var(--font-size-small);
  --card-badge-padding: var(--space-1) var(--space-2);
  --card-badge-band-block: calc(var(--card-badge-inset) + var(--card-badge-font-size) * 1.2 + 2 * var(--space-1));
  --card-badge-band-inline: calc(var(--card-badge-inset) + 3.25rem);
  --brand-mark-size: 36px;
  --brand-mark-size-phone: 32px;
  --list-panel-width: 360px;
  --overlay-size: 72px;
  --overlay-background: rgba(0, 0, 0, 0.55);
  --player-control-bar-height: 48px;
  --motion-flash: 600ms;
  --card-min-width: 280px;
  --page-max-width: 2000px;
  --contact-max-width: 720px;
  --opacity-disabled: 0.6;
  /* The player's live or replay badge: a tinted glass chip like the muted microphone's, not an
     alarm; the red is the dot and the word, over a faint wash of it. */
  --stream-state-live-background: rgba(200, 16, 46, 0.1);
  --stream-state-live-border: rgba(200, 16, 46, 0.32);
  --stream-state-live-ink: var(--colour-accent);
  --stream-state-live-dot: var(--colour-accent);
  --stream-state-quiet-background: rgba(26, 29, 36, 0.06);
  --stream-state-quiet-border: rgba(26, 29, 36, 0.16);
  --stream-state-quiet-dot: var(--colour-text-muted);
  --stream-state-blur: 6px;
  --stream-state-dot-size: 8px;
}

/* The dark values are declared once; the system preference and an explicit dark theme both map
   to them, since a media query and a selector cannot share one rule. */
:root {
  --dark-colour-background: #0f1115;
  --dark-colour-surface: #181b21;
  --dark-colour-surface-raised: #23272f;
  --dark-colour-text: #eef0f3;
  --dark-colour-text-muted: #9aa3b2;
  --dark-colour-border: #2e333c;
  --dark-colour-success: #5cc982;
  --dark-shadow-panel: 0 1px 3px rgba(0, 0, 0, 0.4);
  --dark-stream-state-live-background: rgba(247, 3, 3, 0.14);
  --dark-stream-state-live-border: rgba(255, 110, 110, 0.38);
  --dark-stream-state-live-ink: #ff9a9a;
  --dark-stream-state-live-dot: #ff5a5a;
  --dark-stream-state-quiet-background: rgba(255, 255, 255, 0.06);
  --dark-stream-state-quiet-border: rgba(255, 255, 255, 0.16);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --colour-background: var(--dark-colour-background);
    --colour-surface: var(--dark-colour-surface);
    --colour-surface-raised: var(--dark-colour-surface-raised);
    --colour-text: var(--dark-colour-text);
    --colour-text-muted: var(--dark-colour-text-muted);
    --colour-border: var(--dark-colour-border);
    --colour-success: var(--dark-colour-success);
    --shadow-panel: var(--dark-shadow-panel);
    --stream-state-live-background: var(--dark-stream-state-live-background);
    --stream-state-live-border: var(--dark-stream-state-live-border);
    --stream-state-live-ink: var(--dark-stream-state-live-ink);
    --stream-state-live-dot: var(--dark-stream-state-live-dot);
    --stream-state-quiet-background: var(--dark-stream-state-quiet-background);
    --stream-state-quiet-border: var(--dark-stream-state-quiet-border);
  }
}

:root[data-theme="dark"] {
  --colour-background: var(--dark-colour-background);
  --colour-surface: var(--dark-colour-surface);
  --colour-surface-raised: var(--dark-colour-surface-raised);
  --colour-text: var(--dark-colour-text);
  --colour-text-muted: var(--dark-colour-text-muted);
  --colour-border: var(--dark-colour-border);
  --colour-success: var(--dark-colour-success);
  --shadow-panel: var(--dark-shadow-panel);
  --stream-state-live-background: var(--dark-stream-state-live-background);
  --stream-state-live-border: var(--dark-stream-state-live-border);
  --stream-state-live-ink: var(--dark-stream-state-live-ink);
  --stream-state-live-dot: var(--dark-stream-state-live-dot);
  --stream-state-quiet-background: var(--dark-stream-state-quiet-background);
  --stream-state-quiet-border: var(--dark-stream-state-quiet-border);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--colour-background);
  color: var(--colour-text);
  font-family: var(--font-family);
  font-size: var(--font-size-body);
  line-height: 1.6;
}

a {
  color: inherit;
}

.top-bar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-5);
  padding: var(--space-3) var(--space-4);
  background: var(--colour-surface);
  border-bottom: 1px solid var(--colour-border);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--font-size-brand);
  font-weight: 700;
  text-decoration: none;
}

/* The logo as a round mark before the name (brand-images.sh makes its 1x and 2x copies). */
.brand-mark {
  width: var(--brand-mark-size);
  height: var(--brand-mark-size);
  border-radius: var(--radius-circle);
}

@media (max-width: 700px) {
  .brand-mark {
    width: var(--brand-mark-size-phone);
    height: var(--brand-mark-size-phone);
  }
}

.nav {
  display: flex;
  gap: var(--space-3);
}

.nav-link {
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-small);
  text-decoration: none;
  color: var(--colour-text-muted);
}

.nav-link[aria-current="page"],
.nav-link:hover {
  background: var(--colour-surface-raised);
  color: var(--colour-text);
}

.status-line {
  margin: 0;
  margin-inline-start: auto;
  color: var(--colour-text-muted);
  font-size: var(--font-size-small);
}

.page {
  max-width: var(--page-max-width);
  margin: 0 auto;
  padding: var(--space-5) var(--space-4);
}

.section-title {
  margin: 0 0 var(--space-4);
  font-size: var(--font-size-heading);
}

.section-empty {
  margin: 0;
  color: var(--colour-text-muted);
}

.card-grid,
.card-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
}

.card-grid {
  grid-template-columns: repeat(auto-fill, minmax(var(--card-min-width), 1fr));
}

.card-list {
  gap: var(--space-3);
}

.stream-card {
  display: grid;
  gap: var(--space-2);
  text-decoration: none;
  border-radius: var(--radius-medium);
}

/* The list's small posters: a smaller badge, tighter to its corner, so the mark keeps its room. */
.card-list .card-poster {
  --card-badge-inset: var(--space-1);
  --card-badge-font-size: 0.75rem;
  --card-badge-padding: calc(var(--space-1) / 2) var(--space-1);
  --card-badge-band-inline: calc(var(--card-badge-inset) + 2.75rem);
}

.card-list .stream-card {
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  align-items: start;
}

.stream-card:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

.card-poster {
  position: relative;
  container-type: size;
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-medium);
  background: linear-gradient(135deg, var(--colour-poster-start), var(--colour-poster-end));
  box-shadow: var(--shadow-panel);
}

.card-placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

/* The mark centred, never under the live badge: as large as the poster allows once the badge's
   band is kept clear above and below it, or beside it, whichever leaves more (review of 4 Nov
   2026: on the small list cards the mark ran under «زنده»). */
.card-placeholder img {
  width: auto;
  height: min(var(--card-placeholder-mark-size),
    max(calc(100cqh - 2 * (var(--card-badge-band-block) + var(--space-1))),
        calc(100cqw - 2 * (var(--card-badge-band-inline) + var(--space-1)))));
  aspect-ratio: 1;
  border-radius: var(--radius-circle);
}

.card-image[hidden] {
  display: none;
}

.card-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The whole frame, letterboxed on the poster: the pods' scene is tall and cover showed its
     middle, a list of names cut top and bottom (operator, 3 Nov 2026). */
  object-fit: contain;
  border-radius: var(--radius-medium);
}

.stream-card:hover .card-poster {
  outline: 2px solid var(--colour-accent);
}

.live-badge {
  position: absolute;
  inset-block-end: var(--card-badge-inset);
  inset-inline-start: var(--card-badge-inset);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--card-badge-padding);
  border-radius: var(--radius-small);
  background: var(--colour-accent);
  color: var(--colour-accent-text);
  font-size: var(--card-badge-font-size);
  font-weight: 700;
  line-height: 1.2;
}

.live-badge::before {
  content: "";
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-circle);
  background: var(--colour-accent-text);
}

.replay-badge {
  position: absolute;
  inset-block-end: var(--space-2);
  inset-inline-start: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-small);
  background: var(--colour-surface);
  color: var(--colour-text);
  font-size: var(--font-size-small);
  font-weight: 700;
  line-height: 1.2;
}

.card-text {
  display: grid;
  min-width: 0;
}

.stream-title {
  display: block;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.stream-meta {
  display: block;
  color: var(--colour-text-muted);
  font-size: var(--font-size-small);
}

.layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--list-panel-width);
  gap: var(--space-5);
  max-width: var(--page-max-width);
  margin: 0 auto;
  padding: var(--space-5) var(--space-4);
}

@media (max-width: 1100px) {
  .layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* With no other stream to list, the player takes the page, centred, and the empty list goes. */
.layout:has(#live-grid:empty) {
  grid-template-columns: minmax(0, var(--stage-max-width));
  justify-content: center;
}

.layout:has(#live-grid:empty) .list-panel {
  display: none;
}

.stage {
  min-width: 0;
}

.player-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--colour-player-background);
  border-radius: var(--radius-medium);
  overflow: hidden;
}

.player {
  display: block;
  width: 100%;
  height: 100%;
}

.player-overlay {
  /* Centred over the picture, lifted by half the native control bar so it never covers the bar;
     only the glyph's circle takes clicks. Physical offsets: a centred thing has no direction. */
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--overlay-size);
  height: var(--overlay-size);
  margin-top: calc(var(--player-control-bar-height) / -2);
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-circle);
  background: var(--overlay-background);
  color: var(--colour-accent-text);
  cursor: pointer;
}

.unmute-button {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-start: var(--space-3);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-weight: 700;
  color: var(--colour-accent-text);
  background: var(--overlay-background);
  border: 0;
  border-radius: var(--radius-small);
  cursor: pointer;
}

.player-frame[data-kind="room"] .unmute-button {
  z-index: var(--layer-above-scene);
  inset-block-start: auto;
  inset-block-end: var(--space-3);
}

.unmute-button:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

.player-overlay:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

.player-overlay[data-state="flash-pause"],
.player-overlay[data-state="flash-play"] {
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .player-overlay[data-state="flash-pause"],
  .player-overlay[data-state="flash-play"] {
    animation: overlay-fade var(--motion-flash) ease-out forwards;
  }
}

@keyframes overlay-fade {
  to {
    opacity: 0;
    transform: translate(-50%, -50%) scale(1.4);
  }
}

.overlay-glyph {
  width: 50%;
  height: 50%;
  fill: currentColor;
}

.live-timer {
  font-variant-numeric: tabular-nums;
}

/* A count and its word, or a span of time and its words, never part on one line and part on the next. */
.meta-viewers,
.meta-timer {
  white-space: nowrap;
}

.player-notice {
  position: absolute;
  inset: auto 0 0 0;
  margin: 0;
  padding: var(--space-2) var(--space-4);
  background: var(--colour-surface);
  color: var(--colour-text);
  font-size: var(--font-size-small);
}

.now-playing-title {
  margin: var(--space-4) 0 0;
  font-size: var(--font-size-heading);
}

.now-playing-meta {
  margin: var(--space-1) 0 0;
  color: var(--colour-text-muted);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.action-button {
  padding: var(--space-2) var(--space-4);
  font: inherit;
  color: var(--colour-text);
  background: var(--colour-surface-raised);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-small);
  cursor: pointer;
}

.action-button:hover {
  border-color: var(--colour-accent);
}

.action-button:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

.actions-label {
  color: var(--colour-text-muted);
  font-size: var(--font-size-small);
}

.speed-group {
  display: inline-flex;
  gap: var(--space-1);
}

.speed-button[aria-pressed="true"] {
  border-color: var(--colour-accent);
  background: var(--colour-accent);
  color: var(--colour-accent-text);
}

.share-notice {
  margin: 0;
  color: var(--colour-text-muted);
  font-size: var(--font-size-small);
  overflow-wrap: anywhere;
}

.list-panel {
  align-self: start;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-medium);
  box-shadow: var(--shadow-panel);
  padding: var(--space-4);
}

.list-title {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-heading);
}

[hidden] {
  display: none !important;
}

@media (prefers-reduced-motion: no-preference) {
  .live-badge::before,
  .stream-state[data-state="live"] .stream-state-dot {
    animation: pulse 2s ease-in-out infinite;
  }
}

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

/* The footer on every page: the one way to reach the operators (contact page). */
.site-footer {
  max-width: var(--page-max-width);
  margin: 0 auto;
  padding: var(--space-5) var(--space-4);
  border-top: 1px solid var(--colour-border);
  font-size: var(--font-size-small);
}

.footer-link {
  color: var(--colour-text-muted);
}

.footer-link:hover,
.footer-link:focus-visible {
  color: var(--colour-text);
}

.footer-link:focus-visible,
.contact-form input:focus-visible,
.contact-form textarea:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

.contact-page {
  max-width: var(--contact-max-width);
}

.contact-intro,
.contact-note {
  color: var(--colour-text-muted);
}

.contact-note {
  font-size: var(--font-size-small);
}

.contact-form {
  display: grid;
  gap: var(--space-4);
}

.contact-kinds {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-small);
}

.contact-field {
  display: grid;
  gap: var(--space-1);
}

.contact-field[hidden] {
  display: none;
}

.contact-label {
  font-weight: 700;
}

.contact-form input[type="email"],
.contact-form input[type="url"],
.contact-form textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  color: var(--colour-text);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-small);
}

/* The hidden field a person never fills (announcer ContactInbox drops a message that has it). */
.contact-trap {
  position: absolute;
  inset-inline-start: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.contact-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.action-button:disabled {
  cursor: progress;
  opacity: var(--opacity-disabled);
}

.contact-status {
  margin: 0;
}

.contact-status[data-state="sent"] {
  color: var(--colour-success);
}

.contact-status[data-state="error"] {
  color: var(--colour-accent);
}

/* The room scene (room-scene.js): a phase 2 stream drawn from its events, the same design on the
   watch page and in the broadcast frame. Its sizes are in container units of the frame, so one
   layout scales from a phone's frame to a 1920 by 1080 render; the text has a floor, so the page's
   smaller frame stays readable. A skin is a second set of these values. */
:root {
  --scene-background: #15171c;
  --scene-background-glow: #1d2330;
  --scene-panel: rgba(255, 255, 255, 0.04);
  --scene-panel-border: rgba(255, 255, 255, 0.07);
  --scene-ink: #eeece6;
  --scene-muted: #9aa1ad;
  --scene-accent: #7fb2e0;
  --scene-accent-secondary: #e8b64c;
  --scene-accent-ink: #05070d;
  --scene-ring: #7fb2e0;
  /* The muted microphone is a quiet chip, not an alarm (operator, 4 Nov 2026: the red badge was
     too harsh); the speaking ring is the signal that stands out. */
  --scene-mute-mark: rgba(12, 14, 18, 0.62);
  --scene-mute-mark-ink: rgba(238, 236, 230, 0.86);
  --scene-mute-mark-border: rgba(255, 255, 255, 0.14);
  --scene-mute-mark-blur: 6px;
  --scene-muted-photo-saturation: 0.55;
  --scene-muted-photo-brightness: 0.78;
  --scene-ring-halo: rgba(127, 178, 224, 0.55);
  --scene-ring-halo-spread: 4.2cqmin;
  --scene-avatar-saturation: 34%;
  --scene-avatar-lightness: 26%;
  --scene-shadow: 0 1.2cqmin 3.6cqmin rgba(0, 0, 0, 0.35);
  --scene-radius: 1.8cqmin;
  --scene-radius-small: 0.9cqmin;
  --scene-gap: 2.2cqmin;
  --scene-gap-small: 1cqmin;
  /* The chat: about 13px on a 1440 page, more lines at once (operator, 4 Nov 2026); it grows
     with the frame, so a 1080p broadcast frame gets 16px. */
  --scene-chat-size: max(0.8125rem, 1.5cqmin);
  --scene-chat-line-height: 1.45;
  --scene-chat-avatar-size: max(1.125rem, 2.2cqmin);
  --scene-padding: 3.2cqmin;
  --scene-title-size: max(1.1rem, 3.6cqmin);
  --scene-text-size: max(0.875rem, 2.2cqmin);
  --scene-small-size: max(0.75rem, 1.8cqmin);
  --scene-house-icon-size: max(1.5rem, 5.2cqmin);
  --scene-ring-width: max(2px, 0.6cqmin);
  --scene-ring-glow: 3.2cqmin;
  /* How far a speaking photo's halo shows past its edge at full voice: the spread in the speaking
     rule below at --speaking-level 1 (4.5 ring widths) and three quarters of its blur (1.25 halo
     spreads), beyond which the blur's Gaussian is under 4% of the halo's colour. The stage keeps
     this much room inside its edges, so a ring in the first row or an outer column is never cut
     (the demo, 5 Nov 2026); room-scene.js fits the tiles inside it. */
  --scene-ring-reach: calc(var(--scene-ring-width) * 4.5 + var(--scene-ring-halo-spread) * 0.94);
  --scene-chat-width: 38cqw;
  --scene-chat-fade: 2.5rem;
  --scene-watermark-size: 6cqmin;
  --scene-watermark-opacity: 0.9;
  --scene-chat-soft-edge: 0.75rem;
  --scene-chat-meta-gap: 0.45em;
  --scene-filter-background: rgba(127, 178, 224, 0.14);
  /* The clock under a chat name: about 10px on the page, scaled with the chat in broadcast. */
  --scene-chat-time-size: calc(var(--scene-chat-size) * 0.77);
  --scene-chat-meta-width: 9em;
  --scene-watermark-inset: calc(var(--scene-padding) * 0.35);
  /* The corner the watermark keeps to itself: nothing else is drawn inside it. */
  --scene-watermark-safe: calc(var(--scene-watermark-inset) + var(--scene-watermark-size) + var(--scene-gap-small));
  --scene-live-badge-width: 4.5rem;
  --scene-chat-height-tall: 46cqh;
  /* The least a phone's chat gives up to a crowded stage: three lines and its title (room-scene.js
     shareTallFrame); at 26cqh the halo's room above the stage put eight speakers at 390 px into a
     scroll by 3 px (5 Nov 2026). */
  --scene-chat-height-smallest: 25cqh;
  --scene-scrollbar: rgba(255, 255, 255, 0.18);
  --scene-scrollbar-hover: rgba(255, 255, 255, 0.32);
  --scene-ring-change: 80ms;
  --timeline-height: 28px;
  --timeline-track-height: 4px;
  --timeline-track-height-hover: 6px;
  --timeline-chapter-gap: 3px;
  --timeline-thumb-size: 14px;
  --timeline-played: var(--colour-accent);
  --timeline-buffered: rgba(255, 255, 255, 0.35);
  --timeline-rest: rgba(255, 255, 255, 0.16);
  --timeline-tip-offset: 10px;
  --chapter-tip-offset: 6px;
  /* Solid enough that the rows under a tip never read through it. */
  --tip-background: rgba(12, 14, 18, 0.95);
  --tip-secondary-opacity: 0.8;
  --chapter-current-border: 3px;
  --control-size: 44px;
  --overlay-play-size: 88px;
  --motion-fade: 160ms;
  --motion-spin: 900ms;
  --stage-max-width: 1120px;
  /* The page's height above and below a room's frame: the top bar, the controls and the title. */
  --room-chrome-height: 260px;
  /* On a phone the frame takes the screen's height less the top bar and the controls (390x844:
     a 4:5 frame left the stage 120px for the speakers). */
  --room-chrome-height-phone: 220px;
  --room-frame-phone-smallest: 480px;
  --room-frame-phone-largest: 760px;
}

/* The other skins' values live in their folders (skins/<name>/tokens.css, loaded by scene-skins.js),
   each scoped to [data-skin], so a page can hold frames in different skins. */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A room stream: the scene stands where the picture was; the media element only plays the sound. */
.player-frame[data-kind="room"] .player {
  display: none;
}

/* The scene, as the room's app lays a room out: the house and the title at the top, the pinned
   link as one compact card, the stage as a centred grid of round photos with the names under
   them, the chat beside it (below it in a portrait frame, room-scene.js data-frame). The tiles'
   sizes are computed at run time for the stage's box (room-scene.js fitTiles). */
.room-scene {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--scene-chat-width);
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas:
    "header chat"
    "stage chat";
  gap: var(--scene-gap) var(--scene-gap);
  padding: var(--scene-padding);
  overflow: hidden;
  color: var(--scene-ink);
  background: radial-gradient(120% 90% at 50% 0%, var(--scene-background-glow) 0%, var(--scene-background) 70%);
  font-size: var(--scene-text-size);
  line-height: 1.5;
}

.room-scene[data-frame="portrait"],
.room-scene[data-frame="tall"] {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) clamp(var(--scene-chat-height-smallest), var(--scene-chat-height-fit, var(--scene-chat-height-tall)), var(--scene-chat-height-tall));
  grid-template-areas:
    "header"
    "stage"
    "chat";
}

/* The body only groups the stage and the chat in the markup; each takes its own place. */
.scene-body {
  display: contents;
}

.scene-header {
  grid-area: header;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "house counts"
    "title title"
    "pinned pinned";
  align-items: center;
  gap: var(--scene-gap-small) var(--scene-gap);
  min-width: 0;
}

.scene-header p {
  margin: 0;
}

/* A narrow frame has no room for the house and the counts side by side. */
.room-scene[data-frame="portrait"] .scene-header,
.room-scene[data-frame="tall"] .scene-header {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "house"
    "title"
    "counts"
    "pinned";
}

.room-scene[data-frame="portrait"] .scene-counts,
.room-scene[data-frame="tall"] .scene-counts {
  flex-wrap: wrap;
  gap: 0 var(--scene-gap);
}

.scene-house {
  grid-area: house;
  display: flex;
  align-items: center;
  gap: var(--scene-gap-small);
  min-width: 0;
  color: var(--scene-muted);
  font-size: var(--scene-small-size);
  font-weight: 600;
}

.house-name {
  min-width: 0;
  overflow-wrap: break-word;
}

.house-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--scene-house-icon-size);
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--scene-radius-small);
  background: hsl(var(--avatar-hue, 210) var(--scene-avatar-saturation) var(--scene-avatar-lightness));
  color: var(--scene-ink);
}

/* The live mark and the room's counts share the header's far corner. */
.scene-counts {
  grid-area: counts;
  display: flex;
  align-items: center;
  gap: var(--scene-gap);
  color: var(--scene-muted);
  font-size: var(--scene-small-size);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.scene-live-badge.live-badge {
  position: static;
  grid-area: counts;
  justify-self: end;
  font-size: var(--scene-small-size);
}

:root[data-mode="broadcast"] .room-scene[data-live="true"] .scene-counts {
  margin-inline-end: calc(var(--scene-live-badge-width) + var(--scene-gap));
}

/* On the page the player's own badge says live or replay (.stream-state); one badge, not two. */
:root:not([data-mode="broadcast"]) .scene-live-badge {
  display: none;
}

.room-scene:not([data-live="true"]) .scene-live-badge {
  display: none;
}

/* Two lines at most, cut with an ellipsis, never broken inside a word. */
.scene-title {
  grid-area: title;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  margin: 0;
  font-size: var(--scene-title-size);
  font-weight: 700;
  line-height: 1.35;
  overflow-wrap: normal;
}

/* The pinned link as the room's app shows it: one card, its title and the site, its sharer's face. */
.scene-pinned {
  grid-area: pinned;
  min-width: 0;
}

.scene-pinned[hidden] {
  display: none;
}

.pinned-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.pinned-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--scene-gap-small);
  margin: 0;
  padding: 0;
  list-style: none;
}

.pinned-links li {
  display: flex;
  align-items: center;
  gap: var(--scene-gap-small);
  min-width: 0;
  max-width: 100%;
  padding: var(--scene-gap-small) var(--scene-gap);
  border: 1px solid var(--scene-panel-border);
  border-radius: var(--scene-radius-small);
  background: var(--scene-panel);
  font-size: var(--scene-small-size);
}

.pinned-link {
  display: grid;
  min-width: 0;
  color: var(--scene-accent);
  font-weight: 600;
  text-decoration: none;
}

.pinned-title,
.pinned-domain {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pinned-domain {
  color: var(--scene-muted);
  font-weight: 400;
}

.pinned-sharer {
  order: -1;
  display: flex;
  align-items: center;
  margin: 0;
  font-size: 0;
}

.pinned-sharer bdi {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.pinned-sharer-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--scene-house-icon-size);
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--radius-circle);
  background: hsl(var(--avatar-hue, 210) var(--scene-avatar-saturation) var(--scene-avatar-lightness));
  color: var(--scene-ink);
}

.pinned-sharer-avatar .avatar-initial,
.house-icon .avatar-initial {
  font-size: calc(var(--scene-house-icon-size) * 0.5);
}

/* The stage: a centred grid of equal tiles, rows as even as they can be (room-scene.js). */
.scene-stage {
  grid-area: stage;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 0;
  /* Above the rows. Below them the last row's names already lie under its halo, and beside them
     the tiles' own margin covers part of the reach and the fit leaves the rest (room-scene.js
     fitTiles). */
  padding-block-start: var(--scene-ring-reach);
  overflow: hidden;
}

/* A crowd too big to fit at a readable size scrolls, faded at both edges (room-scene.js); the
   fade lies in the padding, so the first and last rows are whole when scrolled to. */
.scene-stage[data-scrolls="on"] {
  align-items: flex-start;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--scene-muted) transparent;
  padding-block-end: var(--scene-ring-reach);
  mask-image: linear-gradient(to bottom, transparent, var(--scene-ink) var(--scene-ring-reach), var(--scene-ink) calc(100% - var(--scene-ring-reach)), transparent);
}

.scene-stage-title,
.stage-talking {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.stage-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-content: center;
  gap: var(--tile-gap, 16px);
  width: calc(var(--stage-columns, 4) * var(--tile-width, 120px) + (var(--stage-columns, 4) - 1) * var(--tile-gap, 16px));
  max-width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.stage-empty {
  margin: 0;
  color: var(--scene-muted);
}

.speaker-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--avatar-size, 80px) * 0.16);
  width: var(--tile-width, 120px);
  height: var(--tile-height, 140px);
  min-width: 0;
  text-align: center;
}

/* The portrait carries the round photo, its ring, the muted microphone and the moderator's
   pill, so they stay on the photo at every size. */
.speaker-portrait {
  position: relative;
  flex: none;
  width: var(--avatar-size, 80px);
  aspect-ratio: 1;
}

.speaker-avatar {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--radius-circle);
  background: hsl(var(--avatar-hue, 210) var(--scene-avatar-saturation) var(--scene-avatar-lightness));
  transition: box-shadow var(--scene-ring-change) linear;
}

/* Speaking: a ring and a soft halo whose reach follows the voice's level (--speaking-level, 0 to
   1, set by room-scene.js from the speaking lines); still in the broadcast frame and under
   reduced motion, where the level is not drawn. */
.speaker-card[data-speaking="true"] .speaker-avatar {
  box-shadow:
    0 0 0 var(--scene-ring-width) var(--scene-background),
    0 0 0 calc(var(--scene-ring-width) * 2) var(--scene-ring),
    0 0 calc(var(--scene-ring-halo-spread) * (0.15 + 1.1 * var(--speaking-level, 0.5))) calc(var(--scene-ring-width) * (0.5 + 4 * var(--speaking-level, 0.5))) var(--scene-ring-halo);
}

/* Muted: the photo goes quieter, so a glance tells a closed microphone from an open one that is
   silent even before the glyph is read. */
.speaker-card[data-muted="true"] .speaker-avatar {
  filter: saturate(var(--scene-muted-photo-saturation)) brightness(var(--scene-muted-photo-brightness));
}

.avatar-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar-initial {
  font-size: calc(var(--avatar-size, 80px) * 0.4);
  font-weight: 700;
  line-height: 1;
  color: var(--scene-ink);
}

/* Two lines at most; a word wider than the tile ends in an ellipsis of its own, and no word is
   ever broken (the demo of 5 Nov 2026 broke a long Latin name letter by letter). */
.speaker-name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  max-width: 100%;
  font-size: var(--name-size, 14px);
  font-weight: 500;
  line-height: 1.25;
  overflow-wrap: normal;
  word-break: keep-all;
}

.name-word {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: top;
}

.stage-list[data-names="off"] .speaker-name {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* A small glass chip at the photo's corner, translucent and blurred (the operator's pick of
   three, 4 Nov 2026). */
.mute-mark {
  position: absolute;
  inset-block-end: 2%;
  inset-inline-end: 2%;
  display: grid;
  place-items: center;
  width: max(18px, calc(var(--avatar-size, 80px) * 0.3));
  aspect-ratio: 1;
  border: 1px solid var(--scene-mute-mark-border);
  border-radius: var(--radius-circle);
  background: var(--scene-mute-mark);
  color: var(--scene-mute-mark-ink);
  -webkit-backdrop-filter: blur(var(--scene-mute-mark-blur));
  backdrop-filter: blur(var(--scene-mute-mark-blur));
}

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

.mute-mark svg {
  width: 62%;
  height: 62%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
}

/* A small pill on the photo's lower edge, centred, as the room's app marks a moderator. */
.moderator-mark {
  position: absolute;
  inset-block-start: 100%;
  left: 50%;
  transform: translate(-50%, -60%);
  padding: 0 0.6em;
  border-radius: var(--radius-pill);
  background: var(--scene-accent-secondary);
  color: var(--scene-accent-ink);
  font-size: max(9px, calc(var(--avatar-size, 80px) * 0.13));
  font-weight: 700;
  line-height: 1.6;
  white-space: nowrap;
}

.moderator-mark[hidden] {
  display: none;
}

/* The chat: a quiet panel, a thin bar that shows it scrolls, the oldest lines fading at the top. */
.scene-chat {
  grid-area: chat;
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
  padding: var(--scene-gap) var(--scene-gap-small) var(--scene-gap) var(--scene-gap);
  gap: var(--scene-gap-small);
  border: 1px solid var(--scene-panel-border);
  border-radius: var(--scene-radius);
  background: var(--scene-panel);
  cursor: auto;
}

.room-scene[data-chat="none"] .scene-chat {
  display: none;
}

/* The chat's title, and under it the bar of a chat filtered to one person. */
.scene-chat-head {
  display: grid;
  gap: var(--scene-gap-small);
  min-width: 0;
}

.chat-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--scene-gap-small);
  padding: calc(var(--scene-gap-small) * 0.6) var(--scene-gap-small);
  border-radius: var(--scene-radius-small);
  background: var(--scene-filter-background);
  font-size: var(--scene-small-size);
}

.chat-filter[hidden] {
  display: none;
}

.chat-filter-label {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.chat-filter-all,
.chat-filter-clear {
  padding: 0 var(--scene-gap-small);
  border: 1px solid var(--scene-panel-border);
  border-radius: var(--radius-pill);
  background: none;
  color: inherit;
  font: inherit;
  line-height: 1.6;
  cursor: pointer;
}

.chat-filter-clear {
  padding: 0;
  width: 1.6em;
  border-radius: var(--radius-circle);
}

.chat-filter-all:focus-visible,
.chat-filter-clear:focus-visible,
.chat-author:focus-visible,
.speaker-card[role="button"]:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

/* A chat author's name is the filter's button; the pressed one is underlined. */
.chat-author {
  display: block;
  max-width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.chat-author .chat-name {
  display: block;
}

.chat-author[aria-pressed="true"] .chat-name,
.speaker-card[aria-pressed="true"] .speaker-name {
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.chat-avatar-button,
.speaker-card[role="button"] {
  cursor: pointer;
}

.scene-chat-title {
  margin: 0;
  color: var(--scene-muted);
  font-size: var(--scene-small-size);
  font-weight: 600;
}

.scene-chat-scroller {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  overflow-anchor: none;
  padding-inline-end: var(--scene-gap-small);
  scrollbar-width: thin;
  scrollbar-color: var(--scene-scrollbar) transparent;
  scrollbar-gutter: stable;
  /* Hides a message cut by the top edge and fades into the first whole one (room-scene.js
     fadeChat): a cut message under a fade still showed half a name (coordinator, 4 Nov 2026). */
  mask-image: linear-gradient(to bottom, transparent 0, transparent max(0px, calc(var(--chat-fade-end, var(--scene-chat-fade)) - var(--scene-chat-soft-edge))), var(--scene-ink) var(--chat-fade-end, var(--scene-chat-fade)), var(--scene-ink) 100%);
}

.scene-chat-scroller::-webkit-scrollbar {
  width: 6px;
}

.scene-chat-scroller::-webkit-scrollbar-track {
  background: transparent;
}

.scene-chat-scroller::-webkit-scrollbar-thumb {
  border-radius: var(--radius-pill);
  background: var(--scene-scrollbar);
}

.scene-chat-scroller::-webkit-scrollbar-thumb:hover {
  background: var(--scene-scrollbar-hover);
}

/* The newest line sits at the bottom of a short chat; justify-content: flex-end would stop a
   long one from scrolling up. */
.scene-chat-scroller > .chat-list {
  margin-top: auto;
}

.chat-new {
  position: absolute;
  inset-inline: 0;
  bottom: var(--scene-gap);
  width: max-content;
  margin-inline: auto;
  padding: var(--scene-gap-small) var(--scene-gap);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--scene-accent);
  color: var(--scene-accent-ink);
  font: inherit;
  font-size: var(--scene-small-size);
  font-weight: 700;
  cursor: pointer;
}

.chat-new[hidden] {
  display: none;
}

.chat-new:focus-visible,
.chat-offset:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

.chat-list {
  display: grid;
  gap: 0.5em;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--scene-chat-size);
  line-height: var(--scene-chat-line-height);
}

/* One row per message: the small photo, the name with when it was said in tiny type under it,
   then the text (operator, 4 Nov 2026). The name and its time no longer share a line, so a Latin
   name never runs into Persian digits ("۱۰:۰۰Ali", review of 4 Nov 2026). */
.chat-message {
  display: grid;
  grid-template-columns: var(--scene-chat-avatar-size) minmax(0, max-content) minmax(0, 1fr);
  align-items: start;
  column-gap: var(--scene-gap-small);
  overflow-wrap: anywhere;
}

.chat-avatar {
  display: grid;
  place-items: center;
  width: var(--scene-chat-avatar-size);
  aspect-ratio: 1;
  margin-block-start: calc((1lh - var(--scene-chat-avatar-size)) / 2);
  overflow: hidden;
  border-radius: var(--radius-circle);
  background: hsl(var(--avatar-hue, 210) var(--scene-avatar-saturation) var(--scene-avatar-lightness));
}

.chat-avatar .avatar-initial {
  font-size: calc(var(--scene-chat-avatar-size) * 0.5);
}

/* The name, and under it the clock in the viewer's own zone and (a replay) the moment in the
   stream; the column is no wider than the name needs, and never more than its share. */
.chat-meta {
  display: grid;
  max-width: var(--scene-chat-meta-width);
  min-width: 0;
}

.chat-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--scene-accent);
  font-weight: 600;
}

.chat-when {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--scene-chat-meta-gap);
  color: var(--scene-muted);
  font-size: var(--scene-chat-time-size);
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.chat-when > * {
  unicode-bidi: isolate;
}

.chat-offset {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.chat-offset:hover {
  color: var(--scene-accent);
}

/* The text keeps its own direction (bdi dir=auto). */
.chat-text {
  min-width: 0;
}

/* Where the chat skipped ahead to catch up with the room (chat-stream.js). */
.chat-marker {
  padding: 0.2em 0.6em;
  border-radius: var(--radius-pill);
  background: var(--scene-panel-border);
  color: var(--scene-muted);
  font-size: 0.9em;
  text-align: center;
}

.chat-empty {
  margin: 0;
  color: var(--scene-muted);
  font-size: var(--scene-small-size);
}

.chat-list:not(:empty) + .chat-empty {
  display: none;
}

/* Under a filter on someone who has written nothing, the way back to everyone's lines. */
.chat-empty-clear {
  margin-inline-start: var(--scene-gap-small);
  padding: 0 var(--scene-gap);
  border: 1px solid var(--colour-accent);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--scene-ink);
  font: inherit;
  line-height: 1.8;
  cursor: pointer;
}

.chat-empty-clear:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

/* A speaker's tile filters the chat (its title says so): the pointer says it is a control. */
.speaker-card[role="button"] {
  cursor: pointer;
}

.scene-notice,
.scene-room-notice {
  position: absolute;
  inset-inline: var(--scene-padding);
  inset-block-end: var(--scene-padding);
  margin: 0;
  padding: var(--scene-gap-small) var(--scene-gap);
  border-radius: var(--scene-radius-small);
  background: var(--scene-panel);
  color: var(--scene-ink);
  font-size: var(--scene-small-size);
  text-align: center;
}

.scene-notice[hidden],
.scene-room-notice[hidden] {
  display: none;
}

/* The halo moves with the voice instead of a timed breath; reduced motion holds it still. */
@media (prefers-reduced-motion: reduce) {
  .speaker-avatar {
    transition: none;
  }
}

/* The broadcast frame animates nothing: every painted frame is captured and encoded, and the live
   dot's pulse and the speaking ring's breath kept Chrome painting 60 frames a second, which with
   the encode took the demo pod to 142% of two CPUs and starved its audio (5 Nov 2026). The ring
   still shows who speaks; it only holds still. */
:root[data-mode="broadcast"] .live-badge::before {
  animation: none;
}

:root[data-mode="broadcast"] .speaker-avatar {
  transition: none;
}

/* A blur over a moving page costs the broadcast encode its frames; the chip is opaque enough
   without it. */
:root[data-mode="broadcast"] .mute-mark {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Our logo in a corner of the frames sent to video (both formats); the page has it in its header. */
.scene-watermark {
  display: none;
}

:root[data-mode="broadcast"] .scene-watermark {
  position: absolute;
  z-index: var(--layer-above-scene);
  inset-block-start: var(--scene-watermark-inset);
  inset-inline-end: var(--scene-watermark-inset);
  display: block;
  width: var(--scene-watermark-size);
  height: var(--scene-watermark-size);
  border-radius: var(--radius-circle);
  opacity: var(--scene-watermark-opacity);
}

/* The watermark's corner is the chat's top in a wide frame and the header's far side in a tall
   one; neither runs under it (review of 4 Nov 2026: it sat on the chat panel's corner). */
:root[data-mode="broadcast"] .room-scene:not([data-frame="portrait"]):not([data-frame="tall"]) .scene-chat {
  margin-block-start: max(0px, calc(var(--scene-watermark-safe) - var(--scene-padding)));
}

:root[data-mode="broadcast"] .room-scene[data-frame="portrait"] .scene-header,
:root[data-mode="broadcast"] .room-scene[data-frame="tall"] .scene-header {
  padding-inline-end: max(0px, calc(var(--scene-watermark-safe) - var(--scene-padding)));
}

/* The broadcast chat: larger, for a 1080p or a tall frame seen on a phone; nothing to scroll. */
:root[data-mode="broadcast"] .room-scene {
  --scene-chat-size: max(1rem, 2.2cqmin);
  --scene-chat-avatar-size: max(1.5rem, 3.2cqmin);
}

:root[data-mode="broadcast"] .scene-chat-scroller {
  overflow: hidden;
  scrollbar-width: none;
}

/* The watch page's frame for a room: 16:9, as large as the window allows with the controls still
   in view; taller on a phone. The frame is the container the scene's units count in. */
.player-frame[data-kind="room"] {
  container: frame / size;
  width: min(100%, calc((100dvh - var(--room-chrome-height)) * 16 / 9));
  margin-inline: auto;
  background: var(--scene-background);
}

@media (max-width: 700px) {
  .player-frame[data-kind="room"] {
    width: 100%;
    aspect-ratio: auto;
    height: clamp(var(--room-frame-phone-smallest), calc(100dvh - var(--room-chrome-height-phone)), var(--room-frame-phone-largest));
  }
}

/* While paused: a large play button over the stage; a click on it, or on the scene, plays. Nothing
   dims: a pause only stops the sound and the clock, and the chat, stage and links stay as they
   were (the operator, 5 Nov 2026: "we don't need to hide the chat when paused. just pause!"). */
.player-frame[data-kind="room"] .player-overlay {
  z-index: var(--layer-above-scene);
  width: var(--overlay-play-size);
  height: var(--overlay-play-size);
  left: var(--stage-centre-x, 50%);
  top: var(--stage-centre-y, 50%);
  margin-top: 0;
  background: var(--colour-accent);
}

@media (prefers-reduced-motion: no-preference) {
  .player-frame[data-kind="room"] .player-overlay[data-state="paused"] {
    animation: overlay-in var(--motion-fade) ease-out;
  }
}

@keyframes overlay-in {
  from {
    opacity: 0;
  }
}

.player-frame[data-kind="room"] .room-scene {
  cursor: pointer;
}

/* The controls under the frame: the timeline across, then one bar. */
.room-controls {
  width: min(100%, calc((100dvh - var(--room-chrome-height)) * 16 / 9));
  margin: var(--space-2) auto 0;
}

.room-controls[hidden] {
  display: none;
}

.control-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-1);
}

.control-button {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: var(--control-size);
  height: var(--control-size);
  padding: 0;
  border: 0;
  border-radius: var(--radius-circle);
  background: var(--colour-accent);
  color: var(--colour-accent-text);
  cursor: pointer;
}

.control-glyph,
.control-spinner {
  grid-area: 1 / 1;
  width: 50%;
  height: 50%;
}

.control-glyph {
  fill: currentColor;
}

.control-glyph[hidden],
.control-spinner[hidden],
.overlay-glyph[hidden] {
  display: none;
}

/* Waiting for sound: a ring that turns, in place of the glyph. */
.control-spinner {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-dasharray: 40 60;
}

@media (prefers-reduced-motion: no-preference) {
  .control-spinner {
    animation: spin var(--motion-spin) linear infinite;
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Live, live but behind (a way back, as on the channel's player) or replay, beside the play button
   on every width (operator, 5 Nov 2026: "we need a clear LIVE or REPLAY label on the player"). */
.stream-state {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--stream-state-quiet-border);
  border-radius: var(--radius-pill);
  background: var(--stream-state-quiet-background);
  color: var(--colour-text);
  font: inherit;
  font-size: var(--font-size-small);
  font-weight: 700;
  line-height: 1.6;
  -webkit-backdrop-filter: blur(var(--stream-state-blur));
  backdrop-filter: blur(var(--stream-state-blur));
  cursor: default;
}

.stream-state[data-state="loading"] {
  display: none;
}

.stream-state-dot {
  width: var(--stream-state-dot-size);
  height: var(--stream-state-dot-size);
  border-radius: var(--radius-circle);
  background: var(--stream-state-quiet-dot);
}

.stream-state[data-state="live"] {
  border-color: var(--stream-state-live-border);
  background: var(--stream-state-live-background);
  color: var(--stream-state-live-ink);
}

.stream-state[data-state="live"] .stream-state-dot {
  background: var(--stream-state-live-dot);
}

.stream-state[data-state="replay"] {
  color: var(--colour-text-muted);
}

.stream-state[data-state="replay"] .stream-state-dot {
  display: none;
}

.stream-state[data-state="behind"] {
  cursor: pointer;
}

.stream-state[data-state="behind"]:hover {
  border-color: var(--stream-state-live-border);
}

.stream-state:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

/* The sound offered beside the play button, never over the scene; gone once the sound is on. */
.control-bar .unmute-button {
  position: static;
  flex: none;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-pill);
  background: var(--colour-surface-raised);
  color: var(--colour-text);
  font-size: var(--font-size-small);
  font-weight: 600;
}

.control-bar .unmute-button[hidden] {
  display: none;
}

/* The chapter's name wraps rather than end in an ellipsis: on-screen text is never a fragment. */
.room-time {
  flex: 1 1 12rem;
  min-width: 0;
  overflow-wrap: break-word;
  color: var(--colour-text-muted);
  font-size: var(--font-size-small);
  font-variant-numeric: tabular-nums;
}

.room-time-chapter:not(:empty)::before {
  content: "·";
  margin-inline: var(--space-2);
}

.room-time-chapter {
  color: var(--colour-text);
}

.room-time-clock {
  white-space: nowrap;
}

.control-bar .speed-group {
  flex-wrap: wrap;
}

.control-bar .speed-button {
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-size-small);
  font-variant-numeric: tabular-nums;
}

/* The timeline as the channel's player draws it: one thin track, the played part in the accent,
   what is buffered lighter, the rest dark, chapters as small gaps; the thumb shows on hover, focus
   and drag. Time runs left to right in every language, as the channel's player and the media
   controls of the viewer's own browser do in a right-to-left page (docs/v2-design.md). */
.room-timeline {
  position: relative;
  height: var(--timeline-height);
  cursor: pointer;
  touch-action: none;
  border-radius: var(--radius-small);
}

.room-timeline[aria-disabled="true"] {
  cursor: progress;
  opacity: var(--opacity-disabled);
}

.timeline-segments {
  position: absolute;
  inset-inline: 0;
  top: 50%;
  height: var(--timeline-track-height);
  transform: translateY(-50%);
  transition: height var(--motion-fade) ease-out;
}

.room-timeline:hover .timeline-segments,
.room-timeline:focus-visible .timeline-segments,
.room-timeline[data-dragging="true"] .timeline-segments {
  height: var(--timeline-track-height-hover);
}

.timeline-segment {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--segment-start) * 100%);
  width: calc(var(--segment-width) * 100% - var(--timeline-chapter-gap));
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--timeline-rest);
}

.timeline-segment:last-child {
  width: calc(var(--segment-width) * 100%);
}

.timeline-segment::before,
.timeline-segment::after {
  content: "";
  position: absolute;
  inset-block: 0;
  left: 0;
}

.timeline-segment::before {
  width: calc(var(--segment-buffered, 0) * 100%);
  background: var(--timeline-buffered);
}

.timeline-segment::after {
  width: calc(var(--segment-played, 0) * 100%);
  background: var(--timeline-played);
}

.timeline-thumb {
  position: absolute;
  top: 50%;
  left: calc(var(--timeline-progress, 0) * 100%);
  width: var(--timeline-thumb-size);
  height: var(--timeline-thumb-size);
  transform: translate(-50%, -50%) scale(0);
  border-radius: var(--radius-circle);
  background: var(--timeline-played);
  transition: transform var(--motion-fade) ease-out;
}

.room-timeline:hover .timeline-thumb,
.room-timeline:focus-visible .timeline-thumb,
.room-timeline[data-dragging="true"] .timeline-thumb {
  transform: translate(-50%, -50%) scale(1);
}

.timeline-tip {
  position: absolute;
  bottom: calc(100% + var(--timeline-tip-offset));
  left: calc(var(--tip-at, 0) * 100%);
  display: grid;
  justify-items: center;
  max-width: 18rem;
  padding: var(--space-1) var(--space-2);
  transform: translateX(-50%);
  border-radius: var(--radius-small);
  background: var(--overlay-background);
  color: var(--colour-accent-text);
  font-size: var(--font-size-small);
  line-height: 1.4;
  pointer-events: none;
  white-space: nowrap;
  z-index: var(--layer-above-scene);
}

.timeline-tip[hidden],
.timeline-tip-title[hidden] {
  display: none;
}

.timeline-tip-title {
  display: grid;
  justify-items: center;
  max-width: 100%;
  white-space: normal;
  text-align: center;
}

/* A chapter's tooltip: its speakers, its time range and the room's title then (room-player.js). */
.tip-speakers {
  font-weight: 700;
}

.tip-time {
  font-variant-numeric: tabular-nums;
  opacity: var(--tip-secondary-opacity);
}

.tip-room {
  opacity: var(--tip-secondary-opacity);
}

.room-chapters {
  position: relative;
}

.chapter-tip {
  position: absolute;
  z-index: var(--layer-above-scene);
  top: var(--tip-top, 0px);
  inset-inline-start: var(--space-4);
  display: grid;
  gap: var(--space-1);
  max-width: min(28rem, calc(100% - 2 * var(--space-4)));
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-small);
  background: var(--tip-background);
  box-shadow: var(--shadow-panel);
  color: var(--colour-accent-text);
  font-size: var(--font-size-small);
  line-height: 1.5;
  pointer-events: none;
}

.chapter-tip[hidden] {
  display: none;
}

/* Beside the row's title: one line, the pieces apart by a dot. */
.chapter-tip[data-side="beside"] {
  display: flex;
  gap: var(--space-2);
  left: var(--tip-left, 0px);
  inset-inline-start: auto;
  max-width: none;
  white-space: nowrap;
}

.chapter-tip[data-side="beside"] > * + *::before {
  content: "·";
  margin-inline-end: var(--space-2);
  opacity: var(--tip-secondary-opacity);
}

.timeline-tip-time {
  font-variant-numeric: tabular-nums;
}

.room-chapters {
  width: min(100%, calc((100dvh - var(--room-chrome-height)) * 16 / 9));
  margin: var(--space-4) auto var(--space-3);
}

.room-chapters[hidden] {
  display: none;
}

.room-chapters-title {
  margin: 0 0 var(--space-2);
  font-size: var(--font-size-body);
}

.chapter-list {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-medium);
  overflow: hidden;
}

.chapter-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: stretch;
  border-block-start: 1px solid var(--colour-border);
}

.chapter-row:first-child {
  border-block-start: 0;
}

.chapter-button {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3);
  align-items: baseline;
  width: 100%;
  min-height: var(--control-size);
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-inline-start: var(--chapter-current-border) solid transparent;
  background: var(--colour-surface);
  color: var(--colour-text);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.chapter-button:hover,
.chapter-copy:hover {
  background: var(--colour-surface-raised);
}

.chapter-button[aria-current="true"] {
  border-inline-start-color: var(--colour-accent);
  background: var(--colour-surface-raised);
  font-weight: 700;
}

.chapter-button:focus-visible,
.chapter-copy:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: calc(var(--focus-outline-offset) * -1);
}

.chapter-time {
  color: var(--colour-accent);
  font-variant-numeric: tabular-nums;
}

/* A Latin title keeps the list's alignment: on its own direction it went to the far side of the
   row ("Tech Night: part three", 4 Nov 2026). */
.chapter-title {
  min-width: 0;
  overflow-wrap: anywhere;
  text-align: match-parent;
}

.chapter-copy {
  display: grid;
  place-items: center;
  width: var(--control-size);
  padding: 0;
  border: 0;
  border-inline-start: 1px solid var(--colour-border);
  background: var(--colour-surface);
  color: var(--colour-text-muted);
  cursor: pointer;
}

.chapter-copy svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chapter-copy[data-copied="true"] {
  color: var(--colour-success);
}

.room-timeline:focus-visible,
.control-button:focus-visible,
.pinned-link:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: var(--focus-outline-offset);
}

/* A room's heading lines up with its frame. */
.player-frame[data-kind="room"] ~ .now-playing-title,
.player-frame[data-kind="room"] ~ .now-playing-meta,
.player-frame[data-kind="room"] ~ .actions {
  width: min(100%, calc((100dvh - var(--room-chrome-height)) * 16 / 9));
  margin-inline: auto;
}

/* Broadcast: the frame a headless browser renders to video. The scene alone, the whole canvas,
   nothing to operate and no cursor; ratio=9x16 is the tall frame. */
:root[data-mode="broadcast"] body {
  overflow: hidden;
  background: var(--scene-background);
  cursor: none;
}

:root[data-mode="broadcast"] * {
  cursor: none !important;
}

:root[data-mode="broadcast"] .top-bar,
:root[data-mode="broadcast"] .list-panel,
:root[data-mode="broadcast"] .site-footer,
:root[data-mode="broadcast"] .now-playing-title,
:root[data-mode="broadcast"] .now-playing-meta,
:root[data-mode="broadcast"] .actions,
:root[data-mode="broadcast"] .room-controls,
:root[data-mode="broadcast"] .room-chapters,
:root[data-mode="broadcast"] .chat-new,
:root[data-mode="broadcast"] .unmute-button,
:root[data-mode="broadcast"] .player-overlay,
:root[data-mode="broadcast"] .player-notice {
  display: none !important;
}

:root[data-mode="broadcast"] .layout {
  display: grid;
  place-items: center;
  max-width: none;
  min-height: 100vh;
  margin: 0;
  padding: 0;
}

:root[data-mode="broadcast"] .stage {
  width: 100%;
}

:root[data-mode="broadcast"] .player-frame {
  width: min(100vw, calc(100vh * 16 / 9));
  aspect-ratio: 16 / 9;
  margin: 0 auto;
  border-radius: 0;
}

:root[data-mode="broadcast"][data-ratio="9x16"] .player-frame {
  width: min(100vw, calc(100vh * 9 / 16));
  aspect-ratio: 9 / 16;
}

/* A broadcast picture's format (scene-skins.js; data-format on the frame or the page). The safe
   area is what the platform's own player leaves uncovered: nothing of the scene is drawn under its
   overlays. The horizontal player's controls hide while a stream plays, so it keeps the whole
   frame; the vertical is watched in the phone player, whose title, buttons and chat lie over its
   top, bottom and right edge. The vertical's insets are an agent's estimate until they are seen
   on YouTube's phone player (docs/v2-design.md, OPEN). Physical sides: the overlays do not turn
   with the text's direction. */
:root {
  --scene-safe-top: 0px;
  --scene-safe-right: 0px;
  --scene-safe-bottom: 0px;
  --scene-safe-left: 0px;
}

/* The safe area takes a fifth of the vertical's height; the stage fits its tiles to what is left
   (room-scene.js fitTiles), and the chat gives up a third of its share. */
[data-format="vertical"] {
  --scene-safe-top: 6cqh;
  --scene-safe-right: 9cqw;
  --scene-safe-bottom: 15cqh;
  --scene-safe-left: 3cqw;
  --scene-chat-height-tall: 30cqh;
}

:root[data-mode="broadcast"] .room-scene {
  padding: calc(var(--scene-padding) + var(--scene-safe-top)) calc(var(--scene-padding) + var(--scene-safe-right)) calc(var(--scene-padding) + var(--scene-safe-bottom)) calc(var(--scene-padding) + var(--scene-safe-left));
}

/* The watermark keeps its corner, the top of the inline end (left, in the page's right-to-left),
   moved into the safe area; the corner rules above clear it from the padding, which grows with
   the same insets. */
:root[data-mode="broadcast"] .scene-watermark {
  inset-block-start: calc(var(--scene-safe-top) + var(--scene-watermark-inset));
  inset-inline-end: auto;
  left: calc(var(--scene-safe-left) + var(--scene-watermark-inset));
}

/* The parts a format leaves out (skin.json "hide"). */
[data-hide~="house"] .scene-house,
[data-hide~="pinned"] .scene-pinned,
[data-hide~="counts"] .scene-counts,
[data-hide~="talking"] .stage-talking,
[data-hide~="stageTitle"] .scene-stage-title,
[data-hide~="chat"] .scene-chat {
  display: none;
}

[data-hide~="chat"] .scene-body {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
}

/* The thumbnail of a room stream (thumbnail.html), 1280 by 720, in the portal's design: its poster
   gradient, accent and typeface. */
:root {
  --thumbnail-width: 1280px;
  --thumbnail-height: 720px;
  --thumbnail-padding: 64px;
  --thumbnail-gap: 28px;
  --thumbnail-title-short: 76px;
  --thumbnail-title-medium: 60px;
  --thumbnail-title-long: 44px;
  --thumbnail-house-size: 36px;
  --thumbnail-house-icon: 72px;
  --thumbnail-face-size: 150px;
  --thumbnail-face-border: 6px;
  --thumbnail-brand-size: 28px;
  --thumbnail-ink: #f4f1ea;
  --thumbnail-muted: #b8bfcc;
  --thumbnail-glow: #24324a;
  --thumbnail-avatar-saturation: 34%;
  --thumbnail-avatar-lightness: 30%;
}

.thumbnail-body {
  margin: 0;
  overflow: hidden;
  background: var(--colour-poster-end);
}

.thumbnail {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: var(--thumbnail-gap);
  box-sizing: border-box;
  width: var(--thumbnail-width);
  height: var(--thumbnail-height);
  padding: var(--thumbnail-padding);
  overflow: hidden;
  color: var(--thumbnail-ink);
  background: radial-gradient(120% 100% at 85% 0%, var(--thumbnail-glow) 0%, var(--colour-poster-start) 40%, var(--colour-poster-end) 100%);
}

.thumbnail-house {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin: 0;
  color: var(--thumbnail-muted);
  font-size: var(--thumbnail-house-size);
  font-weight: 700;
}

.thumbnail-house-icon {
  display: grid;
  place-items: center;
  width: var(--thumbnail-house-icon);
  height: var(--thumbnail-house-icon);
  overflow: hidden;
  border-radius: var(--radius-medium);
  background: hsl(var(--avatar-hue, 210) var(--thumbnail-avatar-saturation) var(--thumbnail-avatar-lightness));
}

.thumbnail-title {
  align-self: center;
  margin: 0;
  font-size: var(--thumbnail-title-short);
  font-weight: 800;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.thumbnail[data-title-length="medium"] .thumbnail-title {
  font-size: var(--thumbnail-title-medium);
}

.thumbnail[data-title-length="long"] .thumbnail-title {
  font-size: var(--thumbnail-title-long);
}

.thumbnail-faces {
  display: flex;
  gap: var(--thumbnail-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.thumbnail-face-image {
  display: grid;
  place-items: center;
  width: var(--thumbnail-face-size);
  height: var(--thumbnail-face-size);
  overflow: hidden;
  border: var(--thumbnail-face-border) solid var(--thumbnail-ink);
  border-radius: var(--radius-circle);
  background: hsl(var(--avatar-hue, 210) var(--thumbnail-avatar-saturation) var(--thumbnail-avatar-lightness));
}

.thumbnail-face-image img,
.thumbnail-house-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.thumbnail-face-image .avatar-initial {
  font-size: calc(var(--thumbnail-face-size) * 0.42);
  color: var(--thumbnail-ink);
}

.thumbnail-house-icon .avatar-initial {
  font-size: calc(var(--thumbnail-house-icon) * 0.5);
  color: var(--thumbnail-ink);
}

.thumbnail-brand {
  position: absolute;
  inset-block-end: var(--thumbnail-padding);
  inset-inline-end: var(--thumbnail-padding);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  font-size: var(--thumbnail-brand-size);
  font-weight: 700;
}

.thumbnail-brand::before {
  content: "";
  width: var(--space-4);
  height: var(--space-4);
  border-radius: var(--radius-circle);
  background: var(--colour-accent);
}
