/* Small, opinionated CSS. System fonts, dark-ish neutral palette. */

:root {
  --bg: #0f1115;
  --surface: #171a21;
  --surface-2: #1f232c;
  --border: #2a2f3a;
  --text: #e7e9ee;
  --text-dim: #9aa3b2;
  --accent: #4f8cff;
  --accent-hover: #6aa0ff;
  --danger: #e05a5a;
  --ok: #3fb950;
  --warn: #f0b055;
  --radius: 10px;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Visually-hidden — used for the page-level <h1> on host/viewer so screen
   readers get a title without the chrome taking up layout space. */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* Consistent focus ring — keyboard users see it, mouse users don't. */
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}

* { box-sizing: border-box; }

html, body { height: 100%; margin: 0; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); }

header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 24px;
  border-bottom: 1px solid var(--border);
}

.brand {
  font-weight: 600;
  font-size: 17px;
  color: var(--text);
  letter-spacing: 0.01em;
}

/* Build-version pill next to the brand. Small + dim so it reads as
   metadata, not as part of the logo. Exists so you can eyeball which
   build is live on the VPS without digging into the footer. */
.brand-version {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
  background: var(--surface-2, rgba(255,255,255,0.04));
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 8px;
  letter-spacing: 0.02em;
  user-select: all;   /* click-to-select for easy copy into a bug report */
}

.role-chip {
  font-size: 12px;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 2px 8px;
  border-radius: 999px;
}

/* Host-only countdown chip. Inline-block next to Start/Stop. Colour
   ramps via `data-phase` set by session-countdown.js — normal/warn/
   urgent/ended. Kept subdued for most of the session so it doesn't
   visually compete with the primary controls. */
.session-countdown {
  display: inline-flex;
  align-items: center;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 4px 10px;
  border-radius: 999px;
  transition: color 200ms ease, background 200ms ease, border-color 200ms ease;
}
/* v0.21.11 — UA `[hidden] { display: none }` has specificity 0,1,0 and
   loses source-order to `.session-countdown { display: inline-flex }`
   above (same specificity). Without this explicit override, the
   hidden pill stays in the flex layout as an empty bubble in the
   pre-session state. Same pattern fixed previously for .option-row,
   .player-box, .reserve-btn, .restore-btn, .rc-helper-install. */
.session-countdown[hidden] { display: none; }
.session-countdown::before {
  content: '';
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: currentColor;
  margin-right: 6px;
  opacity: 0.6;
}
.session-countdown[data-phase="warn"] {
  color: #8a5a00;
  background: #ffe9b3;
  border-color: #f0c36d;
}
.session-countdown[data-phase="urgent"] {
  color: #8a1a1a;
  background: #ffd1d1;
  border-color: #f0a0a0;
  animation: session-countdown-pulse 1s ease-in-out infinite;
}
.session-countdown[data-phase="ended"] {
  color: #8a1a1a;
  background: #ffd1d1;
  border-color: #f0a0a0;
}
@keyframes session-countdown-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* Unlock-code disclosure — collapsed-by-default "Advanced" field on the
   host page. Kept intentionally low-contrast: most users never touch it,
   and making it blend in reduces the "what's this?" friction while still
   being findable for issued-voucher holders. */
/* v0.17.1 — "SHARE" section heading above the surface picker.
   Visually anchors the pre-flight setup as one logical section.
   Uppercase tracked treatment matches the section-h pattern from
   the design mockups; subtle, doesn't compete with content. */
.share-section-heading {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 0 0 10px;
}

/* v0.22.14 — Tagline lives in the header strap line right of the
   Host pill. Quiet positioning copy in the brand bar — never claims
   vertical space above the controls. Hidden on narrow screens
   (mobile/split-screen) where the brand bar is already tight. */
.header-tagline {
  font-size: 13px;
  color: var(--text-dim);
  font-weight: 400;
  margin-left: 8px;
  padding-left: 10px;
  border-left: 1px solid var(--border);
  line-height: 1.2;
}
@media (max-width: 720px) {
  .header-tagline { display: none; }
}

/* v0.22.15 — Session length row. Native <select> sized to content
   (auto-width) on the left, inline unlock-code input to its right
   when a paid tier is selected. Replaces v0.22.11's full-width
   dropdown that left the unlock input parked in a separate panel
   below — single row reads as one decision: "how long, with what
   code?" without the visual stutter. */
.session-length {
  margin: 0 0 12px;
}
.session-length__label {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 0 0 6px;
}
.session-length__row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.session-length__select {
  flex: 0 0 auto;
  /* Width fits content (no width:100%) so the input has room to
     breathe on the right. Min-width keeps the longest paid label
     ("10 hours — unlock code") readable without truncation. */
  min-width: 200px;
  padding: 9px 12px;
  font-size: 14px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  appearance: none;
  -webkit-appearance: none;
  /* Inline chevron via background-image — color-mixed so it tracks
     theme. Keeps the select native + accessible while looking custom. */
  background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
                    linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 32px;
  cursor: pointer;
}
.session-length__select:hover {
  border-color: var(--text-dim);
}
.session-length__select:focus {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 1px;
}
/* Inline unlock-code input — sits right of the dropdown, expands to
   fill the remaining space in the row. Hidden via [hidden] attr in
   Free mode (set by host.js syncSessionLengthUi). Explicit [hidden]
   rule per cerebrum DNR — display:flex on .unlock-inline would
   otherwise tie with the UA [hidden] rule. */
.unlock-inline {
  flex: 1 1 200px;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.unlock-inline[hidden] { display: none; }
.unlock-inline__input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 9px 12px;
  font-size: 14px;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  letter-spacing: 0.02em;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.unlock-inline__input:hover {
  border-color: var(--text-dim);
}
.unlock-inline__input:focus {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 1px;
}

/* v0.17.1 — persistent room sub-option inside the unlock card.
   Visible to everyone; helper text makes the unlock-code dependency
   explicit. UI + localStorage only in this release; the actual
   reclaim flow lands in v0.17.2 with task #24. */
.unlock-sub-option {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding-top: 8px;
  margin-top: 8px;
  border-top: 1px solid var(--border);
}
.unlock-sub-option input[type="checkbox"] {
  margin-top: 2px;
  flex-shrink: 0;
}
.unlock-sub-option label {
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
  line-height: 1.4;
  flex: 1;
}
.unlock-sub-option__title {
  display: block;
  font-weight: 500;
}
.unlock-sub-option__sub {
  display: block;
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 2px;
}

/* v0.17.1 — Start sharing button promoted as the dominant action.
   Solid accent background with white text + a bit more padding than
   the default .primary button so it visually anchors the controls.
   No effect on the existing .primary rules elsewhere on the page —
   this targets the Start button specifically by ID. */
#start.primary {
  padding: 11px 18px;
  font-size: 15px;
  font-weight: 500;
}

/* ───────────────────────────────────────────────────────────────
   v0.22.14 — Single-column layout. The previous two-column grid
   (Share setup left, Your room right) suffered from misaligned
   baselines + a pre-share right column dominated by a "No room yet"
   placeholder. Single column lets the eye flow naturally: picker →
   length → Start. The Your room heading + share-info card are
   hidden pre-share and only appear once .is-sharing or .is-reserved
   flips on (see #controls-section.is-sharing rules below).
   ─────────────────────────────────────────────────────────────── */
.controls-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  margin-top: 4px;
}
/* v0.22.17 — Matching column cards. Both columns get identical card
   styling (same background, border, radius, padding-top) so their
   first visible elements align at the same Y by construction. The
   previous version drifted because col 1 started with an <h3>
   share-section-heading and col 2 started with a .session-length
   wrapper whose internal label had a slightly different intrinsic
   line-height — wrapping both in identical-padding cards removes
   the drift without us having to normalize every typography rule. */
.controls-left,
.controls-right {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px;
}
.controls-right {
  gap: 8px;
}
/* v0.22.17 — Push Start (and any sibling action buttons after it) to
   the bottom of the right column. margin-top: auto in a flex column
   eats remaining vertical space, so a tall left column with picker +
   coach panel no longer leaves the right column visually short. The
   card bottoms align even when Free mode keeps the right column
   sparse (just Session length + Start). */
.controls-right #start.primary {
  margin-top: auto;
}
/* Pre-share: hide the Your room heading + share-info card entirely.
   They live in .controls-right and shouldn't compete for attention
   while the host is still setting up. Once the session starts
   (#controls-section gains .is-sharing or .is-reserved), they appear. */
.controls-right .your-room-section-heading,
.controls-right .share-info {
  display: none;
}
#controls-section.is-sharing .controls-right .your-room-section-heading,
#controls-section.is-sharing .controls-right .share-info,
#controls-section.is-reserved .controls-right .your-room-section-heading,
#controls-section.is-reserved .controls-right .share-info {
  display: block;
}
/* Start button stretches full width as the dominant pre-share CTA.
   Stop button (visible mid-share via the .is-sharing toggles below)
   inherits the same treatment for visual continuity. */
#start.primary,
#stop {
  width: 100%;
}
.your-room-section-heading {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 0 0 10px;
}
/* Inside the side card, share-info gets a different visual treatment —
   compact, no bottom margin (the column gap handles spacing), and
   relaxed padding to match the action buttons below. */
.controls-right .share-info {
  margin-bottom: 0;
  padding: 12px;
}
.controls-right .share-info .row {
  padding: 4px 0;
  flex-wrap: wrap;
}
.controls-right .share-info label {
  width: 80px;
  font-size: 12px;
}
.controls-right .share-info code {
  font-size: 14px;
}
.controls-right .share-info .link { font-size: 12px; }
.share-info__placeholder {
  font-size: 13px;
  color: var(--text-dim);
  font-style: italic;
  padding: 14px 8px;
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: 6px;
  margin: 0;
}
/* When the room becomes live, hide the placeholder and show the rows.
   The is-live class is toggled by host.js's setShareInfoLive(). */
.share-info.is-live .share-info__placeholder { display: none; }
.share-info:not(.is-live) .row { display: none; }

/* Reserve-room button (v0.17.2 placeholder; functional in #24).
   Outline-style secondary action — clearly subordinate to Start sharing
   so the hierarchy is unmistakable. Disabled state is visibly dimmed
   without looking broken; cursor and title hint that it's coming. */
.reserve-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius, 8px);
  padding: 10px 14px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 100%;
}
/* v0.21.2 — [hidden] specificity disambiguator. The UA rule
   `[hidden] { display: none }` is specificity 0,1,0 and ties with the
   class rule above; source order then favors the later rule (the class),
   so `el.hidden = true` was a silent visual no-op. Same bug class fixed
   earlier for .option-row and .rc-helper-install. */
.reserve-btn[hidden] { display: none; }
.reserve-btn:hover:not(:disabled) {
  border-color: var(--text-dim);
}
.reserve-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.reserve-btn__label { font-weight: 500; }
.reserve-btn__sub {
  display: block;
  font-size: 11px;
  color: var(--text-dim);
  font-weight: 400;
}

/* When the controls section is narrow (mobile / split-screen), collapse
   the grid to a single column. Share setup stacks on top, Your room
   below. The same UI on a wide screen renders side-by-side. */
@media (max-width: 720px) {
  .controls-grid {
    grid-template-columns: 1fr;
    gap: 14px;
  }
}

/* v0.17.4 — show only the state-relevant action button. Pre-share
   (no .is-sharing on the controls section): Start visible, Stop
   hidden. Mid-share (.is-sharing added by host.js's WELCOME handler):
   Stop visible, Start hidden. The class flips back on stop().

   Why this rule instead of the [hidden] attribute attempted in
   v0.17.2: [hidden] is 0,1,0 specificity and lost ties to class-based
   display rules in at least one user's environment, leaving Stop
   missing entirely mid-share. ID + class combined gives 0,2,1
   specificity which beats any reasonable competing rule.

   Brief "connecting" window between picker close and WELCOME: Start
   is still visible (no .is-sharing yet) but disabled (set at start()
   entry). User sees a greyed Start — natural "this is loading"
   feedback. */
#controls-section:not(.is-sharing):not(.is-reserved) #stop { display: none; }
#controls-section.is-sharing #start { display: none; }
/* v0.18.0 — Reserve mode: room is reserved but no real screen share yet.
   Start stays visible (label still "Start sharing" — same intent for the
   host) so they can click it to Go Live. Stop is visible so they can
   release the reservation. Reserve button hidden once we're in a session
   (managed by host.js syncReserveVisibility — toggles the `hidden` attr). */
#controls-section.is-reserved #reserve { display: none; }

/* v0.18.2 — Compact recovery-code copy control. The code itself is
   intentionally NOT rendered (avoids leaking it when the host is
   full-screen sharing including the host tab — viewers would see it
   in the captured frame). A small text-button at the bottom of the
   share-info card lets the host copy the code to clipboard on demand.
   Hidden by default; host.js's showRecoveryCode() flips the `hidden`
   attribute when a recovery code is available for the current room. */
.recovery-link-row {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--border);
  text-align: center;
}
.recovery-link-row__btn {
  background: transparent;
  border: none;
  color: var(--text-dim);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
  text-decoration: none;
}
.recovery-link-row__btn:hover {
  color: var(--text);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
}
/* v0.18.3 — Once the user clicks Copy and the "paste it somewhere safe"
   confirmation has shown for 5s, the row fades out before `hidden = true`
   removes it from layout. 350ms keeps the transition snappy but visible
   so the user understands the row is going away rather than vanishing. */
.recovery-link-row.is-fading,
.unlock-section.is-fading,
.unlock-inline.is-fading {
  opacity: 0;
  transition: opacity 350ms ease;
  pointer-events: none;
}
/* Explicit override so hidden actually hides even when the class sets display. */
.unlock-section[hidden] { display: none; }

/* v0.17.17 — Host approval gating prompt. Floating prompt that stacks
   above other host UI when a viewer is waiting for approval. Living
   outside the chat window so it stays visible even when chat is hidden
   or in PiP. Width-capped to match other host floats. */
.join-approval {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 1000;
  max-width: 320px;
  padding: 14px 14px 12px;
  background: var(--bg-elevated, var(--bg));
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  color: var(--text);
  font-size: 13px;
  line-height: 1.4;
}
.join-approval__title {
  font-weight: 600;
  margin-bottom: 6px;
}
.join-approval__body {
  margin-bottom: 12px;
}
.join-approval__name {
  font-weight: 500;
}
.join-approval__queue {
  display: inline-block;
  margin-left: 6px;
  font-size: 12px;
  color: var(--text-dim);
}
.join-approval__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.join-approval__btn {
  padding: 6px 14px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.join-approval__btn:hover {
  border-color: var(--text-dim);
}
.join-approval__btn--approve {
  background: var(--color-text-info, #2563eb);
  border-color: var(--color-text-info, #2563eb);
  color: white;
}
.join-approval__btn--approve:hover {
  filter: brightness(1.08);
}
.join-approval__btn--deny {
  /* Outlined; relies on default colors for clear visual hierarchy
     (approve is the primary action). */
}

/* v0.22.9 — Inline 'Enable browser notifications' link beneath the
   notifications toggle. Shown only when the user hasn't been asked yet
   (Notification.permission === 'default'). Tinted accent so it reads
   as an action, not body copy. */
.rc-notif-permission-link {
  display: inline-block;
  margin-top: 6px;
  font-size: 12px;
  color: var(--accent, #4fc3f7);
  text-decoration: underline;
}
.rc-notif-permission-link[hidden] { display: none; }
.rc-notif-permission-link:hover { text-decoration: none; }

/* v0.22.8 — Security toast for trust-related security events (PRD C-5).
   Lives at the bottom of the page as a one-off announcement. Reused
   for: 'auto-revoked after 5 wrong RC passcodes', future trust-related
   alerts. Auto-dismisses after 12 seconds; manual dismiss via ×. */
.security-toast {
  position: fixed;
  bottom: 16px;
  left: 16px;
  z-index: 9500;
  max-width: 380px;
  padding: 12px 14px;
  background: #fcebeb;
  border: 1px solid #f7c1c1;
  border-radius: 8px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  color: #791f1f;
  box-shadow: 0 6px 16px rgba(0,0,0,0.12);
}
.security-toast__close {
  margin-left: auto;
  width: 24px;
  height: 24px;
  border: 0;
  background: transparent;
  color: #791f1f;
  font-size: 18px;
  cursor: pointer;
  border-radius: 4px;
}
.security-toast__close:hover { background: rgba(0,0,0,0.06); }

/* v0.22.6 — Viewer RC unlock modal (PRD Phase C-2/3). Centered card
   with a dimmed backdrop. Shown when viewer clicks Request control
   in a room with enableTrustedDevices ON. Optional passcode + remember
   checkbox. Submit fires remote-control-request with the passcode. */
.rc-unlock-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 9000;
}
.rc-unlock-backdrop[hidden] { display: none; }
.rc-unlock-modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(400px, calc(100vw - 32px));
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  z-index: 9001;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
}
.rc-unlock-modal[hidden] { display: none; }
.rc-unlock-modal__title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  margin: 0;
}
.rc-unlock-modal__meta {
  margin: 0;
  font-size: 13px;
  color: var(--text-dim);
}
.rc-unlock-modal__host {
  color: var(--text);
  font-weight: 500;
}
.rc-unlock-modal__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.rc-unlock-modal__label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.rc-unlock-modal__input {
  height: 36px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 13px;
}
.rc-unlock-modal__hint {
  margin: 0;
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.5;
}
.rc-unlock-modal__remember {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
}
.rc-unlock-modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.rc-unlock-modal__btn {
  height: 34px;
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
}
.rc-unlock-modal__btn--primary {
  background: #185FA5;
  color: white;
  border-color: #185FA5;
  font-weight: 500;
}

/* v0.22.5 — RC passcode setup card (PRD Phase C-1). Same column-flow
   layout as the Manage Trusted Devices panel below it, so the two
   trust-related cards visually group together when both are shown. */
.option-row--rc-passcode { display: block; }
.option-row--rc-passcode[hidden] { display: none; }
.rc-passcode {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.rc-passcode__title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.rc-passcode__desc {
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.5;
}
.rc-passcode__input-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
}
.rc-passcode__input {
  flex: 1;
  height: 34px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 13px;
}
.rc-passcode__btn {
  height: 34px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
}
.rc-passcode__btn--primary {
  background: #185FA5;
  color: white;
  border-color: #185FA5;
  font-weight: 500;
}
.rc-passcode__btn--primary:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.rc-passcode__btn--secondary { color: var(--text-dim); }
.rc-passcode__btn--secondary:hover {
  border-color: #d05050;
  color: #d05050;
}
.rc-passcode__strength {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
}
.rc-passcode__strength[hidden] { display: none; }
.rc-passcode__strength-track {
  flex: 1;
  height: 4px;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.rc-passcode__strength-fill {
  height: 100%;
  width: 0;
  background: #d05050;
  transition: width 200ms ease, background 200ms ease;
}
.rc-passcode__strength-label {
  font-weight: 500;
  min-width: 44px;
  text-align: right;
}
.rc-passcode__actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.rc-passcode__status {
  font-size: 12px;
  color: var(--text-dim);
}
.rc-passcode__status--set {
  color: var(--accent, #4fc3f7);
  font-weight: 500;
}
.rc-passcode__spacer { flex: 1; }

/* v0.22.4 — Manage Trusted Devices panel (PRD Phase B-5). Lives at
   the bottom of the Joining section of Advanced room options. Only
   visible when (a) Trusted devices toggle is on AND (b) there's an
   active room. Re-uses option-row's flex layout but the content is
   a column-flowing card rather than a single row. */
.option-row--trusted-devices {
  display: block;
}
.option-row--trusted-devices[hidden] { display: none; }
.trusted-devices {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.trusted-devices__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.trusted-devices__title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.trusted-devices__revoke-all {
  font-size: 12px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}
.trusted-devices__revoke-all:hover:not(:disabled) {
  border-color: #d05050;
  color: #d05050;
}
.trusted-devices__revoke-all:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.trusted-devices__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.trusted-devices__list:empty { display: none; }
.trusted-devices__item {
  background: var(--bg);
  border-radius: 6px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.trusted-devices__item-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.trusted-devices__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trusted-devices__controls {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.trusted-devices__level {
  font-size: 12px;
  padding: 3px 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--text);
}
.trusted-devices__revoke {
  width: 24px;
  height: 24px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
}
.trusted-devices__revoke:hover {
  border-color: #d05050;
  color: #d05050;
}
.trusted-devices__fp {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 11px;
  color: var(--text-dim);
}
.trusted-devices__empty {
  margin: 0;
  font-size: 12px;
  color: var(--text-dim);
  font-style: italic;
}
.trusted-devices__empty[hidden] { display: none; }

/* v0.22.10 — PRD Phase C-8 activity log inside the Manage panel.
   Visually subordinated to the paired-devices list above (smaller
   text, dimmer marker) — it's diagnostic info, not a primary action.
   Newest-first; FIFO-capped at 20 in JS. */
.trusted-devices__activity {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px dashed var(--border);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.trusted-devices__activity-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.trusted-devices__activity-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.trusted-devices__activity-clear {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 11px;
  cursor: pointer;
}
.trusted-devices__activity-clear:hover:not(:disabled) {
  border-color: var(--text);
  color: var(--text);
}
.trusted-devices__activity-clear:disabled {
  opacity: 0.4;
  cursor: default;
}
.activity-log {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 240px;
  overflow-y: auto;
  font-size: 12px;
}
/* Explicit [hidden] rule per cerebrum DNR — [hidden] on a flex element
   needs specificity to overcome the display:flex declaration above. */
.activity-log:empty { display: none; }
.activity-log__row {
  display: grid;
  grid-template-columns: 72px 1fr;
  gap: 8px;
  align-items: baseline;
  padding: 4px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
}
.activity-log__row:last-child { border-bottom: none; }
.activity-log__time {
  color: var(--text-dim);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.activity-log__text {
  color: var(--text);
  word-break: break-word;
}
.activity-log__label {
  font-weight: 600;
}
.activity-log__meta {
  color: var(--text-dim);
  font-size: 11px;
}
/* Per-kind left-border accents. Subtle — the panel is supposed to be
   information-dense, not loud. Matches accent / danger CSS vars when
   available; falls back to text-dim. */
.activity-log__row--paired   { border-left: 3px solid var(--accent, var(--text-dim)); padding-left: 6px; }
.activity-log__row--unlock   { border-left: 3px solid var(--accent, var(--text-dim)); padding-left: 6px; opacity: 0.9; }
.activity-log__row--wrong    { border-left: 3px solid var(--danger, #c8553d); padding-left: 6px; }
.activity-log__row--revoke   { border-left: 3px solid var(--danger, #c8553d); padding-left: 6px; }
.activity-log__empty {
  margin: 0;
  font-size: 11px;
  color: var(--text-dim);
  font-style: italic;
}
.activity-log__empty[hidden] { display: none; }

/* v0.22.2 — Trusted-device UI section inside the approval popup
   (PRD docs/PRD-trusted-devices.md Phase B-3). Hidden by default
   via [hidden]; host.js shows it only when (a) enableTrustedDevices
   room option is ON, AND (b) the viewer presented an authPubkey
   in HELLO. Both checks gate via host.js — no CSS branching needed. */
.join-approval__trust {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 10px 0;
}
.join-approval__trust[hidden] { display: none; }
.join-approval__fp-box {
  background: var(--surface-2);
  border-radius: 6px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.join-approval__fp-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.join-approval__fp {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 14px;
  letter-spacing: 0.5px;
  color: var(--text);
}
.join-approval__fp-hint {
  font-size: 11px;
  color: var(--text-dim);
}
.join-approval__trust-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
}
.join-approval__trust-level {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
}

/* v0.17.9 — Browser limitations disclosure, nested inside the surface
   coach panel after the per-surface instruction list. Thin top-border
   visually separates the two conceptual categories (per-surface
   instructions vs per-browser caveats) sharing the same outer panel.
   Body uses a slightly-different background tint so it reads as a
   nested callout rather than yet another bordered block. Hidden
   entirely when the detected browser has no notes worth surfacing
   (Chrome/Edge desktop). */
.browser-limits {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  font-size: 12px;
}
.browser-limits__link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--text-dim);
  text-decoration: none;
  cursor: pointer;
  padding: 2px 0;
}
.browser-limits__link:hover { color: var(--text); }
.browser-limits__caret {
  transition: transform 120ms ease;
  display: inline-block;
}
.browser-limits.is-open .browser-limits__caret { transform: rotate(180deg); }
.browser-limits__body {
  margin-top: 6px;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border-radius: 4px;
  color: var(--text);
  line-height: 1.4;
}
.browser-limits__body p { margin: 0 0 6px; }
.browser-limits__body p:last-child { margin-bottom: 0; }
.browser-limits__body ul { margin: 0; padding-left: 16px; }
.browser-limits__body li { margin: 2px 0; }
.browser-limits__body em { color: var(--text); font-style: normal; font-weight: 500; }

/* v0.17.0 — unlock-code section. Replaces the prior collapsed
   .unlock-disclosure twisty (input is now always visible). Existing
   .unlock-input / .unlock-hint / .unlock-badge styles below are
   reused unchanged. The .unlock-disclosure block is kept dormant
   below for backward-compat with any cached HTML; new pages won't
   render those elements. */
.unlock-section {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.unlock-section__title {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
}
.unlock-section__optional {
  font-weight: 400;
  color: var(--text-dim);
  font-size: 12px;
}
.unlock-input-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
/* Inline state pill — pre-Start client-side format check. Distinct
   from the post-Start server-confirmed .unlock-badge (different
   semantics, different element). */
.unlock-state {
  font-size: 12px;
  font-weight: 500;
  padding: 3px 8px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  min-height: 22px;
}
.unlock-state:empty {
  padding: 0;
  border: 0;
}
.unlock-state--ok {
  background: color-mix(in srgb, var(--success, #3fb950) 18%, transparent);
  color: var(--success, #3fb950);
  border: 1px solid color-mix(in srgb, var(--success, #3fb950) 40%, transparent);
}
.unlock-state--pending {
  background: color-mix(in srgb, var(--text-dim) 15%, transparent);
  color: var(--text-dim);
  border: 1px solid color-mix(in srgb, var(--text-dim) 30%, transparent);
}
.unlock-state--bad {
  background: color-mix(in srgb, var(--danger, #ff6a6a) 18%, transparent);
  color: var(--danger, #ff6a6a);
  border: 1px solid color-mix(in srgb, var(--danger, #ff6a6a) 40%, transparent);
}

/* Kept for any cached HTML that still renders the old disclosure. New
   v0.17.0+ pages use .unlock-section instead. */
.unlock-disclosure {
  margin-top: 12px;
  padding: 8px 10px;
  border: 1px solid var(--border, #2a2e36);
  border-radius: 6px;
  background: var(--surface-alt, rgba(255,255,255,0.02));
  color: var(--text-dim, #9ba1ac);
  font-size: 13px;
}
.unlock-disclosure[open] {
  background: var(--surface, rgba(255,255,255,0.04));
}
.unlock-disclosure__summary {
  cursor: pointer;
  user-select: none;
  outline: none;
  list-style: none;
  padding: 2px 0;
}
.unlock-disclosure__summary::-webkit-details-marker { display: none; }
.unlock-disclosure__summary::before {
  content: "▸ ";
  display: inline-block;
  width: 1em;
  color: var(--text-dim, #9ba1ac);
  transition: transform 0.15s ease;
}
.unlock-disclosure[open] .unlock-disclosure__summary::before {
  content: "▾ ";
}
.unlock-disclosure__body {
  padding: 10px 0 4px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.unlock-label {
  font-weight: 500;
  color: var(--text, #e6e8ec);
}
.unlock-input {
  width: 14ch;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* Use --bg (same as .join-box input) so the field reads as active.
     The disclosure body's background is already --surface; putting
     --input-bg=rgba(0,0,0,0.25) on top of that made the field look
     inert / disabled. */
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.unlock-input:focus {
  outline: none;
  border-color: var(--accent, #4ea1ff);
}
.unlock-input:invalid:not(:placeholder-shown) {
  border-color: var(--danger, #ff6a6a);
}
.unlock-hint {
  margin: 0;
  font-size: 12px;
  color: var(--text-dim, #9ba1ac);
  line-height: 1.4;
}
.unlock-hint.is-error {
  color: var(--danger, #ff6a6a);
}
/* Success pill for a redeemed unlock code. Green, bordered, pre-disclosure
   so the user sees it the instant WELCOME arrives. Stays visible for the
   life of the session; hidden again on stop() or on a fresh beta session.
   NB: :not([hidden]) is required — an explicit display:inline-block rule
   would otherwise override the browser's UA [hidden]{display:none} and
   leave an empty green pill visible on fresh page loads. */
.unlock-badge:not([hidden]) {
  margin: 10px 0 0;
  padding: 6px 10px;
  border: 1px solid var(--success-border, rgba(46,160,67,0.45));
  background: var(--success-bg, rgba(46,160,67,0.12));
  color: var(--success, #3fb950);
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  display: inline-block;
  user-select: all;
}
.unlock-hint code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  padding: 1px 4px;
  background: var(--surface, rgba(255,255,255,0.04));
  border-radius: 3px;
}

main {
  max-width: 900px;
  margin: 0 auto;
  padding: 32px 24px;
}

/* Landing page */
.landing main { text-align: center; }
.landing h1 { font-size: 44px; margin: 48px 0 8px; }
.landing .tagline { color: var(--text-dim); margin: 0 0 40px; }

.card-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  text-align: left;
}
@media (max-width: 640px) {
  .card-row { grid-template-columns: 1fr; }
}

.card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 24px;
  border-radius: var(--radius);
  transition: border-color 120ms, transform 120ms;
  color: var(--text);
}
.card:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
  color: var(--text);
}
.card h2 { margin: 0 0 6px; font-size: 18px; }
.card p { margin: 0; color: var(--text-dim); font-size: 14px; }

footer {
  margin-top: 60px;
  color: var(--text-dim);
}

/* Version stamp in page footers. Small and unobtrusive, but visible so a
   quick glance confirms which build is actually loaded — service workers
   and HTTP caches both happily serve stale JS without warning. */
.hdr-version {
  display: block;
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-dim);
  opacity: 0.55;
  letter-spacing: 0.04em;
}

/* Buttons */
button {
  font: inherit;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 10px 16px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 100ms, border-color 100ms;
}
button:hover:not(:disabled) { background: var(--surface-2); filter: brightness(1.1); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}
button.primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
button.primary:disabled {
  background: var(--surface-2, #444);
  border-color: var(--surface-2, #444);
  color: var(--text-dim, #888);
  opacity: 0.6;
}

/* Host page */
.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 24px;
}
.status {
  margin: 0;
  flex: 1 1 100%;
  color: var(--text-dim);
  min-height: 1.5em;
}
.share-info {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 24px;
}
.share-info .row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
}
.share-info label {
  width: 110px;
  color: var(--text-dim);
  font-size: 13px;
}
.share-info code {
  font-family: var(--mono);
  font-size: 18px;
  letter-spacing: 0.1em;
  color: var(--text);
}
.share-info .link {
  flex: 1;
  font-family: var(--mono);
  font-size: 13px;
  word-break: break-all;
}

.preview-section h3, .viewers h3 { color: var(--text-dim); font-size: 13px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }

video#preview, video#remote {
  width: 100%;
  max-height: 520px;
  background: #000;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

/* Quality controls overlay on preview video. */
.preview-wrap {
  position: relative;
}
.preview-mode-toggle {
  position: absolute;
  bottom: 10px;
  left: 10px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  background: color-mix(in srgb, var(--surface) 75%, transparent);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 11px;
  color: var(--text-dim);
  cursor: pointer;
  transition: background 120ms, color 120ms, border-color 120ms;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 2;
}
.preview-mode-toggle:hover {
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  color: var(--text);
  border-color: var(--accent);
}
.preview-mode-toggle.is-encoded {
  border-color: var(--accent);
  color: var(--accent);
}
.preview-mode-toggle[hidden] { display: none; }

.quality-badge {
  position: absolute;
  bottom: 10px;
  right: 10px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  background: color-mix(in srgb, var(--surface) 75%, transparent);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 11px;
  color: var(--text-dim);
  cursor: pointer;
  transition: background 120ms, color 120ms, border-color 120ms;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 2;
}
.quality-badge:hover {
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  color: var(--text);
  border-color: var(--accent);
}
.quality-badge svg { opacity: 0.6; transition: opacity 120ms; }
.quality-badge:hover svg { opacity: 1; }
.quality-badge[hidden] { display: none; }

.quality-dropdown {
  position: absolute;
  bottom: 42px;
  right: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;
  min-width: 190px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  z-index: 3;
}
.quality-dropdown[hidden] { display: none; }

.quality-dropdown__item {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 8px 10px;
  font-size: 12px;
  color: var(--text-dim);
  background: transparent;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  text-align: left;
  filter: none;
  transition: background 150ms, color 150ms;
}
.quality-dropdown__item:hover,
.quality-dropdown__item:hover:not(:disabled) {
  background: var(--surface-2);
  color: var(--text);
  filter: none;
}
.quality-dropdown__item.is-active {
  color: var(--accent);
  font-weight: 600;
}
.quality-dropdown__name { min-width: 36px; }
.quality-dropdown__detail {
  flex: 1;
  font-size: 10px;
  color: var(--text-extra-dim);
}
.quality-dropdown__check {
  font-size: 14px;
  visibility: hidden;
}
.quality-dropdown__item.is-active .quality-dropdown__check {
  visibility: visible;
}
.quality-dropdown__divider {
  height: 1px;
  margin: 4px 6px;
  background: var(--border);
}
.quality-dropdown__fps {
  padding: 8px 10px 6px;
}
.quality-dropdown__fps-label {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--text-dim);
  margin-bottom: 6px;
}
.quality-dropdown__fps-value {
  font-weight: 600;
  color: var(--accent);
  min-width: 32px;
  text-align: right;
}
.quality-dropdown__fps-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  border-radius: 2px;
  background: var(--surface-2);
  outline: none;
  cursor: pointer;
}
.quality-dropdown__fps-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--surface);
  cursor: pointer;
  transition: transform 80ms;
}
.quality-dropdown__fps-slider::-webkit-slider-thumb:hover {
  transform: scale(1.2);
}
.quality-dropdown__fps-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--surface);
  cursor: pointer;
}
.quality-dropdown__fps-slider::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: var(--surface-2);
}

.viewers ul { list-style: none; padding: 0; margin: 0; }
.viewers li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 13px;
}
.viewers li:last-child { border-bottom: none; }
.viewers .viewer-state {
  margin-left: auto;
  color: var(--text-dim);
  font-family: system-ui, sans-serif;
  font-size: 12px;
}

/* Health dot — one-glance status per viewer. Color AND aria-label so the
   state is conveyed non-visually too. */
.health-dot {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--text-dim);
  flex-shrink: 0;
}
.health-dot--good    { background: var(--ok);     box-shadow: 0 0 0 2px rgba(63,185,80,0.15); }
.health-dot--fair    { background: var(--warn);   box-shadow: 0 0 0 2px rgba(240,176,85,0.15); }
.health-dot--poor    { background: var(--danger); box-shadow: 0 0 0 2px rgba(224,90,90,0.15); }
.health-dot--offline { background: var(--text-dim); opacity: 0.5; }

/* Share-info: placeholder state vs live state. Dims the code/link until
   the host is actually sharing, so first-time users don't think the
   session is already active. */
.share-info:not(.is-live) { opacity: 0.6; }
.share-info:not(.is-live) code,
.share-info:not(.is-live) .link { color: var(--text-dim); }

/* Viewer page */
.join-box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px;
  max-width: 420px;
  margin: 48px auto;
  text-align: center;
}
.join-box h2 { margin-top: 0; }
/* Vertical stack: screenname, room-code, Join button — each on its own
   row. v0.16.0 added the screenname input above the room code; v0.16.2
   simplified to a flat stack so the button has its own row instead of
   sharing the room-code line. Form children inherit the flex container's
   default `align-items: stretch`, so each row is full-width. */
.join-box form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 16px;
}
.join-box input {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 10px 12px;
  border-radius: 8px;
  font: inherit;
  font-family: var(--mono);
  font-size: 16px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  min-width: 0;
}
/* Screenname is a name, not a code — reset the room-code styling so
   it reads in normal case and the page font. */
.join-box input.screenname {
  font-family: inherit;
  letter-spacing: normal;
  text-transform: none;
}
.join-box input:focus { outline: none; border-color: var(--accent); }
.join-box form button[type="submit"] { margin-top: 4px; }

/* v0.17.20 — Inline name prompt shown when a deep-link viewer hits a
   USERNAME_REQUIRED room. Same visual chrome as .join-box (so the page
   feels consistent) but only has the name input — the room is already
   pinned from the URL and shown in read-only form. */
.name-prompt {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px;
  max-width: 420px;
  margin: 48px auto;
  text-align: center;
}
.name-prompt h2 { margin-top: 0; }
.name-prompt__sub { margin: 8px 0 0; color: var(--text); }
.name-prompt__sub code {
  background: var(--bg);
  padding: 2px 6px;
  border-radius: 4px;
  font-family: var(--mono);
}
.name-prompt form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 20px;
}
.name-prompt input {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 10px 12px;
  border-radius: 8px;
  font: inherit;
  font-size: 16px;
  min-width: 0;
}
.name-prompt input:focus { outline: none; border-color: var(--accent); }
.name-prompt form button[type="submit"] { margin-top: 4px; }
.name-prompt__hint {
  margin: 12px 0 0;
  font-size: 12px;
  color: var(--text-dim);
}

.player-box {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* v0.21.2 — [hidden] specificity disambiguator (see .reserve-btn above). */
.player-box[hidden] { display: none; }
.player-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: flex-end;
}

/* Viewer connection-quality indicator. Mirrors the host's health dot
   semantics but sits in the player controls row. State is set via
   data-state on the element: idle | good | fair | poor. */
.quality-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: auto;
  font-size: 12px;
  color: var(--text-dim);
  font-family: system-ui, sans-serif;
  user-select: none;
}
.quality-indicator::before {
  content: '';
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--text-dim);
  opacity: 0.5;
}
.quality-indicator[data-state="good"]::before { background: var(--ok);     opacity: 1; }
.quality-indicator[data-state="fair"]::before { background: var(--warn);   opacity: 1; }
.quality-indicator[data-state="poor"]::before { background: var(--danger); opacity: 1; }
.quality-indicator[data-state="good"]::after  { content: 'Good'; }
.quality-indicator[data-state="fair"]::after  { content: 'Fair'; }
.quality-indicator[data-state="poor"]::after  { content: 'Poor'; }
.quality-indicator[data-state="idle"]::after  { content: ''; }

/* ─────────────────────────────────────────────────────────
   Audio status indicator (speaker icon + VU meter + label)
   ─────────────────────────────────────────────────────────
   Sits next to the Unmute/Mute button in .player-controls. Three states
   set via data-state: "live", "muted", "none". viewer.js mirrors the
   audio button's state here AND drives the --level custom property on
   each bar from an AnalyserNode so the meter tracks the actual signal.

   Why duplicate the information? The mute button tells the user what
   clicking it will do (it says "Unmute" when muted); the indicator tells
   the user what state the audio is currently in. Two different questions. */
.audio-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-dim);
  font-family: system-ui, sans-serif;
  user-select: none;
  padding: 2px 6px;
}
.audio-indicator__icon {
  display: block;
  color: var(--text-dim);
  flex: 0 0 auto;
}
/* Sound waves only when audio is on. */
.audio-indicator__waves { opacity: 0; transition: opacity 120ms linear; }
.audio-indicator[data-state="live"] .audio-indicator__waves { opacity: 1; }
/* Slash only when muted or no track. */
.audio-indicator__slash { opacity: 0; transition: opacity 120ms linear; }
.audio-indicator[data-state="muted"] .audio-indicator__slash,
.audio-indicator[data-state="none"]  .audio-indicator__slash { opacity: 1; }

/* 4-bar VU meter. Height comes from --level (updated every rAF from
   AnalyserNode). Default 30% = the flat "resting" height when muted or
   when no audio track exists. */
.audio-indicator__meter {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 14px;
  width: 18px;
}
.audio-indicator__bar {
  width: 3px;
  height: var(--level, 30%);
  background: currentColor;
  border-radius: 1px;
  /* Smooth transitions between rAF updates. Not too slow — the ear
     and eye don't line up if the meter lags the audio noticeably. */
  transition: height 60ms linear;
}
/* Color tokens per state. */
.audio-indicator[data-state="live"]  { color: var(--ok); }
.audio-indicator[data-state="muted"] { color: var(--text-dim); }
.audio-indicator[data-state="none"]  { color: var(--text-dim); opacity: 0.55; }
.audio-indicator[data-state="live"]  .audio-indicator__icon { color: var(--ok); }
.audio-indicator[data-state="muted"] .audio-indicator__icon,
.audio-indicator[data-state="none"]  .audio-indicator__icon { color: var(--text-dim); }
.audio-indicator__label { font-variant-numeric: tabular-nums; }
/* Clickable affordance — indicator doubles as a mute toggle when a track
   is present. In "none" state (no audio track) we leave it as plain text
   so users don't get the misleading impression that clicking would help. */
.audio-indicator[data-state="live"],
.audio-indicator[data-state="muted"] {
  cursor: pointer;
  border-radius: 6px;
  transition: background 120ms linear;
}
.audio-indicator[data-state="live"]:hover,
.audio-indicator[data-state="muted"]:hover {
  background: var(--surface-2);
}

/* Service-worker update toast. Shown when a new build is available; click
   the reload button to activate. Bottom-right, unobtrusive. */
.sw-toast {
  position: fixed;
  right: 16px; bottom: 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  color: var(--text);
  font-size: 14px;
  z-index: 100;
}
.sw-toast__reload {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
  padding: 6px 12px;
  font-size: 13px;
}
.sw-toast__reload:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }

/* ─────────────────────────────────────────────────────────
   Mobile responsive layout.
   Breakpoint at 720px covers phones in portrait + landscape
   and small tablets. The video becomes the dominant element
   and controls grow to meet Apple HIG (44px) and Material
   (48px) minimum tap sizes.
   ───────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  body { font-size: 16px; }  /* Prevent iOS Safari zoom on input focus. */

  header {
    padding: 10px 16px;
    gap: 8px;
  }
  .brand { font-size: 16px; }

  main { padding: 16px 12px; }

  /* Landing page on phones: stack, big tappable cards. */
  .landing h1 { font-size: 34px; margin: 24px 0 6px; }
  .landing .tagline { margin: 0 0 24px; }
  .card { padding: 20px; }

  /* Host: stack controls, bigger primary button. */
  .controls { gap: 8px; }
  .controls button { flex: 1 1 auto; min-height: 48px; padding: 12px 16px; }

  .share-info { padding: 12px; }
  .share-info .row {
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
  }
  .share-info label { width: auto; font-size: 12px; }
  .share-info code { font-size: 22px; text-align: center; padding: 8px; background: var(--bg); border-radius: 6px; }
  .share-info .link { font-size: 12px; }

  /* Let video breathe on small screens. */
  video#preview, video#remote {
    max-height: 60vh;
  }

  /* Viewer join form: stack, big input + button. */
  .join-box { padding: 20px; margin: 16px auto; }
  .join-box form { flex-direction: column; gap: 12px; }
  .join-box input { font-size: 20px; min-height: 52px; text-align: center; }
  .join-box button { min-height: 52px; }

  /* Player controls: full-width tappable. */
  .player-controls {
    justify-content: stretch;
    flex-wrap: wrap;
  }
  .player-controls button { flex: 1; min-height: 48px; }
  /* On phones, hide the audio-indicator text label to keep the row
     compact — the icon + VU bars still tell the story, and the Unmute/
     Mute button carries the action. */
  .audio-indicator__label { display: none; }
  .audio-indicator { padding: 0; }
}

/* Viewer in landscape on a phone: maximize the video, hide chrome. */
@media (max-width: 900px) and (orientation: landscape) {
  .viewer header { display: none; }
  .viewer main { padding: 0; max-width: none; }
  .viewer .player-box { gap: 0; }
  .viewer video#remote {
    width: 100vw;
    height: 100vh;
    max-height: none;
    border-radius: 0;
    border: none;
    object-fit: contain;
    background: #000;
  }
  .viewer .player-controls {
    position: fixed;
    bottom: 12px;
    right: 12px;
    z-index: 5;
  }
  .viewer .player-controls button {
    background: color-mix(in srgb, var(--bg) 85%, transparent);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
  }
}

/* ─────────────────────────────────────────────────────────
   Tap-to-unmute / tap-to-play overlay for iOS viewers.
   iOS Safari blocks autoplay of streams with audio until the
   user taps. Even muted playback can fail on older iOS if the
   video element doesn't have explicit user gesture context.
   ───────────────────────────────────────────────────────── */
.tap-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Translucent on purpose. iOS renders the first WebRTC frame as soon as
     any gesture lands on the page; the dimmed preview peeking through
     lets the viewer confirm what's being shared before they commit to
     tapping. The document-level pointerdown handler in viewer.js makes
     a tap anywhere count as "start", so users are never stuck staring
     at the preview.

     IMPORTANT: keep the hover/active/focus rules below in sync with this
     background. The generic `button:hover:not(:disabled)` rule earlier
     in this file has higher specificity (0,2,1 vs our 0,1,0) and will
     hijack the overlay to an opaque dark-gray — on iOS the :hover state
     "sticks" after a tap, so the overlay would turn solid gray until
     the user taps elsewhere. Match our background on all interaction
     states to prevent that. */
  background: rgba(0, 0, 0, 0.55);
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
  z-index: 3;
  border-radius: var(--radius);
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  /* Button reset — the overlay is a <button> for iOS click reliability. */
  appearance: none;
  -webkit-appearance: none;
  border: none;
  margin: 0;
  padding: 0;
  font: inherit;
  width: 100%;
  height: 100%;
  /* Silences the ~300ms iOS double-tap zoom delay so tap feels instant. */
  touch-action: manipulation;
}
/* Pin the overlay background on every interaction state. Specificity
   0,3,0 beats `button:hover:not(:disabled)` at 0,2,1 — without this,
   iOS "sticky hover" after a tap turns the overlay opaque dark-gray
   and hides the video until the user taps somewhere else. */
.tap-overlay:hover:not(:disabled),
.tap-overlay:active:not(:disabled),
.tap-overlay:focus,
.tap-overlay:focus-visible {
  background: rgba(0, 0, 0, 0.55);
  /* And kill the focus ring — this is a full-bleed element, a ring
     around it looks like a bug. */
  outline: none;
  box-shadow: none;
}

/* The UA `[hidden] { display: none }` rule ties our `.tap-overlay
   { display: flex }` on specificity and loses on source order, so the
   `hidden` attribute alone is not enough to hide the overlay. Force it
   here explicitly. Without this, `els.tapOverlay.hidden = true` in JS
   is a no-op and the overlay stays visible forever. */
.tap-overlay[hidden] { display: none; }

/* Intentionally NO :active darken — on iOS Chrome, if the click event
   is ever suppressed, :active can stick and make the page look frozen. */
.tap-overlay .tap-inner {
  text-align: center;
  padding: 24px;
  pointer-events: none;  /* so clicks always count as "on the button" */
}
.tap-overlay .tap-icon {
  font-size: 44px;
  line-height: 1;
  margin-bottom: 12px;
  display: block;
}
.tap-overlay .tap-label { display: block; }
.player-box { position: relative; }  /* anchor for .tap-overlay */

/* ─────────────────────────────────────────────────────────
   Platform-specific messaging (iOS host unsupported, etc.)
   ───────────────────────────────────────────────────────── */
.platform-notice {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 16px 20px;
  margin: 16px 0;
  color: var(--text-dim);
  font-size: 14px;
  line-height: 1.5;
}
.platform-notice strong { color: var(--text); }
.platform-notice a { font-weight: 500; }
.platform-notice[hidden] { display: none; }

/* Inline browser-quirk hint rendered above the Start button — lighter
   than .platform-notice because it's informational, not a hard block.
   Currently used for the "Firefox doesn't share audio" warning. */
.browser-hint {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin: 0 0 4px 0;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.4;
}
.browser-hint strong { color: var(--text); }
/* Defensive — UA `[hidden] { display: none }` has only 0,1,0 specificity
   and can lose ties to any later class rule that sets display. */
.browser-hint[hidden] { display: none; }
/* v0.16.4 dismiss + expanded list variant. */
.browser-hint { position: relative; }
.browser-hint__dismiss {
  position: absolute;
  top: 6px;
  right: 8px;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 20px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}
.browser-hint__dismiss:hover { color: var(--text); }
.browser-hint__list {
  margin: 6px 0 0;
  padding-left: 20px;
  color: var(--text-dim);
}
.browser-hint__list li { margin-bottom: 2px; }
.browser-hint__cta {
  margin-top: 8px;
  color: var(--text-dim);
  font-style: italic;
}

/* Firefox post-pick audio toast (v0.16.4). Fires once after the user
   clicks Start if isFirefox() AND audio was requested AND the resolved
   stream has zero audio tracks — i.e. Firefox silently dropped audio
   per the long-standing Bugzilla 1541425. Shown at the moment of
   frustration (right after picking a surface) so the nudge to switch
   browsers lands with maximum conversion. */
.firefox-audio-toast {
  position: fixed;
  right: 16px; bottom: 16px;
  max-width: 360px;
  padding: 10px 14px 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  color: var(--text);
  font-size: 13px;
  line-height: 1.45;
  z-index: 100;
}
.firefox-audio-toast strong { color: var(--text); }
.firefox-audio-toast__close {
  position: absolute;
  top: 6px;
  right: 8px;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 18px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

/* ───────────────────────────────────────────────────────────────
   Advanced room options twisty (v0.16.5 — host.html / host.js)
   ─────────────────────────────────────────────────────────────── */
/* Was a modal opened post-Start via a waffle-menu item; now an inline
   <details> in the controls section so hosts can configure features
   BEFORE clicking Start. Collapsed by default — the summary line stays
   one-row tall so the visual cost of having it always present is small.
   When open, contains the same sections + checkboxes the modal used to
   show; styling for those rows (.option-list, .option-row, .switch, etc.)
   is shared with the rest of the page and unchanged. */
.advanced-options {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin: 8px 0 12px;
  padding: 0;
}
.advanced-options__summary {
  cursor: pointer;
  list-style: none;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  user-select: none;
  position: relative;
}
.advanced-options__summary::-webkit-details-marker { display: none; }
.advanced-options__summary::before {
  content: "";
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  border-style: solid;
  border-width: 4px 0 4px 6px;
  border-color: transparent transparent transparent var(--text-dim);
  transition: transform 150ms ease;
}
.advanced-options[open] .advanced-options__summary::before {
  transform: translateY(-50%) rotate(90deg);
}
.advanced-options__title {
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
}
.advanced-options__sub {
  font-size: 12px;
  color: var(--text-dim);
}
.advanced-options[open] .advanced-options__summary {
  border-bottom: 1px solid var(--border);
}
.advanced-options > :not(summary) {
  padding-left: 14px;
  padding-right: 14px;
}
.advanced-options > :first-of-type:not(summary) { padding-top: 10px; }
.advanced-options > :last-child:not(summary) { padding-bottom: 12px; }
/* Reset h3 spacing inside the twisty since it was sized for the modal's
   wider canvas. */
.advanced-options .options-section-title {
  margin-top: 14px;
  margin-bottom: 6px;
  font-size: 13px;
}
.advanced-options .room-options-hint {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--text-dim);
}


/* ─────────────────────────────────────────────────────────
   Waffle menu (3×3 grid icon, top-right header)
   ───────────────────────────────────────────────────────── */
.waffle-menu { position: relative; margin-left: auto; }
.waffle-btn {
  background: transparent;
  border: 1px solid transparent;
  padding: 6px;
  border-radius: 8px;
  cursor: pointer;
  display: grid;
  grid-template-columns: repeat(3, 5px);
  gap: 4px;
  transition: background 120ms;
}
.waffle-btn:hover { background: var(--surface-2); }
.waffle-btn .dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--text-dim);
}
.waffle-btn:hover .dot { background: var(--text); }
.waffle-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow, 0 4px 24px rgba(0,0,0,0.4));
  min-width: 180px;
  padding: 6px;
  z-index: 50;
}
.waffle-dropdown[hidden] { display: none; }
.waffle-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  background: none;
  border: none;
  border-radius: 6px;
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
  transition: background 100ms;
  text-align: left;
}
.waffle-item:hover { background: var(--surface-2); filter: none; }
.waffle-item svg { flex-shrink: 0; color: var(--text-dim); }

/* ─────────────────────────────────────────────────────────
   Modal overlay — shared base for theme picker + room options
   ───────────────────────────────────────────────────────── */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.modal-backdrop[hidden] { display: none; }
.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  width: 100%;
  max-width: 520px;
  max-height: 85vh;
  overflow-y: auto;
  padding: 24px;
  box-shadow: var(--shadow, 0 4px 24px rgba(0,0,0,0.4));
}
.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.modal__title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}
.modal__close {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 20px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  line-height: 1;
}
.modal__close:hover { background: var(--surface-2); color: var(--text); filter: none; }

/* ─────────────────────────────────────────────────────────
   Theme picker grid
   ───────────────────────────────────────────────────────── */
.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px;
}
.theme-card {
  background: var(--bg);
  border: 2px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  cursor: pointer;
  transition: border-color 120ms, transform 100ms;
  text-align: center;
}
.theme-card:hover { transform: translateY(-1px); border-color: var(--text-dim); filter: none; }
.theme-card.is-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.theme-card__swatches {
  display: flex;
  gap: 4px;
  justify-content: center;
  margin-bottom: 8px;
}
.theme-card__swatch {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid rgba(128,128,128,0.3);
}
.theme-card__name {
  font-size: 12px;
  font-weight: 500;
  color: var(--text);
}
.theme-card__mode {
  font-size: 10px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.theme-section-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 16px 0 8px;
}
.theme-section-label:first-child { margin-top: 0; }

/* ─────────────────────────────────────────────────────────
   Room options (inside modal)
   ───────────────────────────────────────────────────────── */
.room-options-hint {
  margin: 0 0 12px;
  color: var(--text-dim);
  font-size: 13px;
}
.option-list {
  list-style: none;
  margin: 0;
  padding: 0 16px 12px;
}
.option-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}
.option-row[hidden] { display: none; }
.join-approval__queue[hidden] { display: none; }
.option-row:first-child { border-top: none; }
.option-label {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  cursor: pointer;
}
.option-row--disabled .option-label { cursor: not-allowed; }
.option-title {
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
}
.option-row--disabled .option-title { color: var(--text-dim); }
.option-desc {
  color: var(--text-dim);
  font-size: 12px;
}

/* Toggle switch. Checkbox-based; the checkbox itself is visually hidden
   but remains focusable so keyboard users land on it naturally. */
.switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
  flex-shrink: 0;
}
.switch input {
  position: absolute;
  opacity: 0;
  inset: 0;
  margin: 0;
  cursor: pointer;
  width: 100%;
  height: 100%;
}
.switch__slider {
  position: absolute;
  inset: 0;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  transition: background 120ms, border-color 120ms;
}
.switch__slider::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform 140ms ease-out, background 120ms;
}
.switch input:checked + .switch__slider {
  background: var(--accent);
  border-color: var(--accent);
}
.switch input:checked + .switch__slider::before {
  transform: translateX(18px);
  background: white;
}
.switch input:focus-visible + .switch__slider {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.switch input:disabled + .switch__slider { opacity: 0.5; cursor: not-allowed; }
.switch--inline {
  vertical-align: middle;
  margin-left: 8px;
  width: 32px;
  height: 18px;
}
.switch--inline .switch__slider::before {
  width: 12px; height: 12px;
}
.switch--inline input:checked + .switch__slider::before {
  transform: translateX(14px);
}

/* ─────────────────────────────────────────────────────────
   Floating chat window
   ─────────────────────────────────────────────────────────
   Hidden by default; revealed by JS when roomOptions.chat is true.
   A floating panel in the bottom-right corner — like a mini child
   window with title bar, messages, and composer.

   States:
     .chat-fab[hidden]        — chat disabled (not shown at all)
     .chat-fab                — chat enabled, window closed (FAB visible)
     .chat-window[hidden]     — window closed
     .chat-window             — window open                              */

/* ── Chat FAB (floating action button) ── */
.chat-fab {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 50;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--accent);
  color: white;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 16px rgba(0,0,0,0.25);
  transition: background 0.15s, transform 0.15s;
}
.chat-fab:hover { background: var(--accent-hover); transform: scale(1.06); }
.chat-fab:active { transform: scale(0.96); }
.chat-fab[hidden] { display: none; }
.chat-fab svg { width: 24px; height: 24px; fill: currentColor; }

/* Unread badge on the FAB */
.chat-fab__badge {
  position: absolute;
  top: -2px;
  right: -2px;
  background: var(--danger);
  color: white;
  font-size: 11px;
  font-weight: 700;
  border-radius: 999px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
.chat-fab__badge[hidden] { display: none; }

/* ── Floating chat window ── */
.chat-window {
  position: fixed;
  bottom: 88px;
  right: 24px;
  z-index: 51;
  width: 360px;
  max-height: 480px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow, 0 4px 24px rgba(0,0,0,0.3));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: chat-window-enter 0.2s ease-out;
}
.chat-window[hidden] { display: none; }
/* While being dragged, suppress the enter animation and add a subtle
   translucency so the content beneath peeks through slightly — gives
   a tactile "I'm holding this" feel. */
.chat-window.is-dragging { animation: none; opacity: 0.92; }
/* After a drag, suppress the enter animation so dropping the window
   doesn't replay the fade-in. Removing is-dragging would re-trigger
   the base animation rule. This class sticks until the window is hidden. */
.chat-window.was-dragged { animation: none; }

@keyframes chat-window-enter {
  from { opacity: 0; transform: translateY(12px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Title bar */
.chat-window__header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
  user-select: none;
}
.chat-window.is-dragging .chat-window__header { cursor: grabbing; }
.chat-window__title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* Click-to-edit display name pill in chat header. */
.chat-name {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 10px;
  background: var(--surface-2, #2a2e38);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  transition: background 120ms, color 120ms;
  max-width: 140px;
  overflow: hidden;
}
.chat-name:hover, .chat-name:focus-visible {
  background: var(--surface-3, #353a46);
  color: var(--text);
}
.chat-name__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-name__pencil {
  flex-shrink: 0;
  opacity: 0.5;
}
.chat-name:hover .chat-name__pencil { opacity: 0.8; }

.chat-window__close {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  margin-left: auto;
  padding: 0;
}
.chat-window__close:hover { background: var(--surface); color: var(--text); filter: none; }

/* Header action group (pip + close) */
.chat-window__actions {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}
.chat-window__pip {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
}
.chat-window__pip:hover { background: var(--surface); color: var(--text); filter: none; }
.chat-window__pip[hidden] { display: none; }

/* Badge inside the title bar (shown while window is open) */
.chat-window__badge {
  background: var(--accent);
  color: white;
  font-size: 11px;
  font-weight: 600;
  border-radius: 999px;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.chat-window__badge[hidden] { display: none; }

/* Message list */
.chat-list {
  list-style: none;
  margin: 0;
  padding: 12px;
  flex: 1;
  min-height: 120px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.chat-message {
  padding: 6px 10px;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 14px;
  line-height: 1.4;
  max-width: 85%;
  word-wrap: break-word;
  overflow-wrap: break-word;
}
.chat-message--local {
  align-self: flex-end;
  background: var(--surface-2);
  border-color: var(--accent);
}
.chat-message__author {
  display: block;
  font-size: 11px;
  color: var(--text-dim);
  margin-bottom: 2px;
  letter-spacing: 0.02em;
}
.chat-message__text {
  display: block;
  color: var(--text);
  white-space: pre-wrap;
}
.chat-message__text a {
  color: var(--accent, #4ea1d3);
  text-decoration: underline;
  word-break: break-all;
}
.chat-message__text a:hover {
  opacity: .8;
}

/* Composer */
.chat-form {
  display: flex;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}
.chat-input {
  flex: 1;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 10px 12px;
  border-radius: 8px;
  font: inherit;
  font-size: 14px;
}
.chat-input:focus { outline: none; border-color: var(--accent); }
.chat-form button { min-width: 64px; }

/* ── Document PiP chat (host only) ── */
/* Styles for the popped-out chat window. The PiP window loads
   styles.css so these rules apply inside it. */
.pip-chat-body {
  margin: 0;
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  overflow: hidden;
}
.pip-chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  user-select: none;
}
.pip-chat-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.pip-chat-dock {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
}
.pip-chat-dock:hover { background: var(--surface); color: var(--text); filter: none; }

/* When chat-list and chat-form live inside the PiP body, fill the
   remaining height so the form sticks to the bottom. */
.pip-chat-body .chat-list {
  flex: 1 1 auto;
  max-height: none;
}
.pip-chat-body .chat-form {
  flex: 0 0 auto;
}

/* ─────────────────────────────────────────────────────────
   Voice controls (host side — in chat window + PiP)
   ───────────────────────────────────────────────────────── */
.voice-controls {
  border-bottom: 1px solid var(--border);
  padding: 0;
}
.voice-controls[hidden] { display: none; }
.voice-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--surface-2);
}
.voice-bar__status {
  flex: 1;
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.voice-bar__btn {
  width: 32px;
  height: 32px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border);
  cursor: pointer;
  padding: 0;
  font-size: 16px;
  color: var(--text);
}
.voice-bar__btn:hover:not(:disabled) { background: var(--surface); filter: none; }
.voice-bar__btn:disabled { opacity: 0.4; cursor: not-allowed; }
.voice-icon { display: block; }
.voice-icon__slash { color: var(--text-dim, #e55); }

/* Chat viewer roster — compact viewer list inside host chat window */
.chat-viewer-roster {
  flex-shrink: 0;
  border-bottom: 1px solid var(--border, #333);
  padding: 6px 12px;
  font-size: 12px;
  max-height: 120px;
  overflow-y: auto;
}
.roster-header {
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 4px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.roster-count {
  background: var(--surface-2, #2a2f3a);
  border-radius: 8px;
  padding: 0 6px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-dim);
}
.roster-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 0;
}
.roster-row--speaker {
  background: color-mix(in srgb, var(--ok, #4caf50) 12%, transparent);
  border-radius: 4px;
  padding: 3px 4px;
  margin: 0 -4px;
}
.roster-id {
  font-family: monospace;
  color: var(--text);
  min-width: 36px;
}
.roster-btn {
  border: 1px solid var(--border, #444);
  background: var(--surface-2, #2a2f3a);
  color: var(--text);
  border-radius: 4px;
  cursor: pointer;
  padding: 2px 6px;
  font-size: 11px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.roster-btn:hover { background: var(--surface, #333); }
.roster-btn--speaker.is-active {
  background: var(--ok, #4caf50);
  color: #fff;
  border-color: var(--ok, #4caf50);
}
.roster-btn--join {
  margin-left: auto;
}

/* Mic / voice level meter bars (shared pattern for host + viewer) */
.voice-bar__meter {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 20px;
  flex-shrink: 0;
}
.voice-bar__meter-bar {
  width: 3px;
  border-radius: 1px;
  background: var(--accent, #4f9cf7);
  height: var(--level, 25%);
  transition: height 80ms ease-out;
}

/* Mic picker dropdown inside voice controls */
.voice-mic-picker {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  margin: 4px 12px 8px;
}
.voice-mic-picker__item {
  text-align: left;
  font-size: 13px;
  padding: 8px 10px;
  border-radius: 6px;
  background: transparent;
  border: none;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.voice-mic-picker__item:hover { background: var(--surface-2); filter: none; }
.voice-mic-picker__item.is-active {
  background: var(--accent);
  color: white;
}

/* ─────────────────────────────────────────────────────────
   Voice bar (viewer side — above player controls)
   ───────────────────────────────────────────────────────── */
.viewer-voice-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.viewer-voice-bar[hidden] { display: none; }
.viewer-voice-bar__label {
  flex: 1;
  font-size: 13px;
  color: var(--ok);
  font-weight: 500;
}
.viewer-voice-bar__mic {
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.viewer-voice-bar__mic:hover:not(:disabled) { background: var(--surface); filter: brightness(1.15); }

/* ─────────────────────────────────────────────────────────
   Room options modal — section titles
   ───────────────────────────────────────────────────────── */
.options-section-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 16px 0 4px;
  padding: 0 16px;
}

/* ─────────────────────────────────────────────────────────
   File transfer: paperclip button, inline cards, progress,
   drag-drop overlay, file list panel
   ───────────────────────────────────────────────────────── */

/* Paperclip attach button in chat form */
.chat-attach {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  cursor: pointer;
  color: var(--text-dim);
  border-radius: 6px;
  transition: color 0.15s, background 0.15s;
  flex-shrink: 0;
}
.chat-attach[hidden] { display: none; }
.chat-attach:hover { color: var(--text); background: var(--surface-2); }
.chat-attach__icon { display: block; }

/* Drag-drop overlay on chat window */
.chat-window.is-drop-target::after {
  content: 'Drop files here';
  position: absolute;
  inset: 0;
  background: rgba(79, 140, 255, 0.12);
  border: 2px dashed var(--accent, #4f9cf7);
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent, #4f9cf7);
  font-size: 14px;
  font-weight: 600;
  z-index: 10;
  pointer-events: none;
}

/* Inline file card in chat list */
.file-card {
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 6px;
  list-style: none;
  margin: 4px 0;
}
.file-card--local { border-left: 3px solid var(--accent, #4f9cf7); }
.file-card__header {
  display: flex;
  align-items: center;
  gap: 8px;
}
.file-card__icon { flex-shrink: 0; font-size: 16px; }
.file-card__name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-card__meta {
  font-size: 11px;
  color: var(--text-dim);
}
.file-card__progress {
  height: 4px;
  background: var(--surface-2);
  border-radius: 2px;
  overflow: hidden;
}
.file-card__progress[hidden] { display: none; }
.file-card__progress-bar {
  height: 100%;
  background: var(--accent, #4f9cf7);
  border-radius: 2px;
  transition: width 0.2s;
  width: 0%;
}
.file-card__actions {
  display: flex;
  gap: 8px;
}
.file-card__btn {
  font-size: 12px;
  padding: 4px 12px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.file-card__btn:hover { filter: brightness(1.1); }
.file-card__btn--accept { border-color: var(--ok, #4caf50); color: var(--ok, #4caf50); }
.file-card__btn--reject { border-color: var(--danger, #ef5350); color: var(--danger, #ef5350); }
.file-card__btn--download { border-color: var(--accent, #4f9cf7); color: var(--accent, #4f9cf7); }
/* v0.17.19 — Sender's "Cancel" button on the outbound card. Subtler than
   reject (which is the receiver's "refuse to accept") since cancel is a
   normal action a sender might take to recall a not-yet-accepted file
   or abort a slow transfer. Default styling (neutral border + dim text). */
.file-card__btn--cancel { color: var(--text-dim, #888); }

/* Collapsible file list panel (re-download) */
.file-list-panel {
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
  overflow: hidden;
}
.file-list-panel__toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  font-size: 12px;
  color: var(--text-dim);
  cursor: pointer;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
}
.file-list-panel__toggle:hover { color: var(--text); }
.file-list-panel__items {
  padding: 0 12px 8px;
  max-height: 0;
  overflow-y: auto;
  transition: max-height 0.25s ease;
}
.file-list-panel.is-open .file-list-panel__items {
  max-height: 200px;
}
.file-list-panel__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 0;
  font-size: 12px;
  color: var(--text);
}
.file-list-panel__item small { color: var(--text-dim); }
.file-list-panel__saved { color: var(--accent, #4fc3f7); font-size: 11px; }
/* LRU-evicted RAM entries (v0.21.4): the user can no longer download
   the file from cache — the byte blob was revoked to free memory once
   newer arrivals pushed cumulative usage past FILE_CACHE_MAX_BYTES. */
.file-list-panel__expired,
.file-card__expired {
  color: var(--text-dim);
  font-size: 11px;
  font-style: italic;
}

/* System messages in chat (warnings, file-transfer notices) */
.chat-message--system {
  font-size: 11px;
  color: var(--text-dim);
  font-style: italic;
  padding: 2px 0;
}

/* ── Recording controls (host chat window) ── */
.rec-controls { flex-shrink: 0; }
.rec-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  font-size: 12px;
  border-bottom: 1px solid var(--border, rgba(255,255,255,0.08));
}
.rec-bar__spacer { flex: 1; }

/* Record button (idle state) */
.rec-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  color: var(--text);
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.rec-btn:hover { background: var(--surface, rgba(255,255,255,0.06)); }
.rec-btn--record { color: var(--text-dim); }
.rec-btn--record:hover { color: #ff4444; }
.rec-btn__dot {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #ff4444;
  opacity: 0.7;
}
.rec-btn--record:hover .rec-btn__dot { opacity: 1; }

/* Active/paused recording indicator */
.rec-indicator {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: #ff4444;
}
.rec-indicator__dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: #ff4444;
  animation: rec-pulse 1.2s ease-in-out infinite;
}
.rec-indicator--paused { color: var(--text-dim); }
.rec-indicator__dot--paused {
  background: var(--text-dim);
  animation: none;
}
@keyframes rec-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

.rec-timer {
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--text);
}
.rec-size {
  font-size: 11px;
  color: var(--text-dim);
}
.rec-saved {
  color: var(--accent, #4fc3f7);
  font-weight: 600;
  font-size: 12px;
}
/* v0.21.10 — Save-failed state when writeError fired during streaming
   recording. Visually distinct from the Saved ✓ state so the user
   doesn't lean on a possibly-truncated file. */
.rec-error {
  color: #d05050;
  font-weight: 600;
  font-size: 12px;
  cursor: help;
}
.rec-meta {
  font-size: 11px;
  color: var(--text-dim);
}

/* Share and new-recording buttons */
.rec-btn--share {
  background: var(--surface-2, #1e2028);
  color: var(--text, #e0e0e0);
  border: 1px solid var(--border, #333);
  border-radius: 4px;
  font-size: 11px;
  padding: 2px 8px;
}
.rec-btn--share:hover { filter: brightness(1.15); }
.rec-btn--stop { color: #ff4444; }
.rec-btn--pause { color: var(--text); }
.rec-btn--resume { color: #4fc3f7; }
.rec-btn--new { color: var(--text-dim); font-size: 11px; }
.rec-btn--chat { color: var(--text-dim); font-size: 11px; }
.rec-btn--chat:hover { color: var(--text); }

/* ── Viewer recording indicator ── */
.recording-indicator {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  background: rgba(255, 68, 68, 0.08);
  color: var(--text-dim, #888);
  font-size: 11px;
  font-weight: 400;
  border-bottom: 1px solid rgba(255, 68, 68, 0.1);
}
.recording-indicator[hidden] { display: none; }
.recording-indicator__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #ff4444;
  animation: rec-pulse 1.2s ease-in-out infinite;
}

/* ── Stream pause/resume controls ── */
.stream-controls { flex-shrink: 0; }
.stream-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px;
  font-size: 12px;
  border-bottom: 1px solid var(--border, #333);
}
.stream-bar__spacer { flex: 1; }
.stream-bar__status {
  color: #ffb74d;
  font-weight: 500;
  font-size: 11px;
}
.stream-bar__btn {
  background: none;
  border: 1px solid var(--border, #333);
  color: var(--text, #e0e0e0);
  font: inherit;
  font-size: 11px;
  padding: 2px 10px;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
.stream-bar__btn:hover { filter: brightness(1.15); background: var(--surface-2, #1e2028); }

/* ── Viewer: stream paused overlay ── */
.paused-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: rgba(0, 0, 0, 0.65);
  color: #ccc;
  font-size: 14px;
  z-index: 5;
  pointer-events: none;
}
.paused-overlay[hidden] { display: none; }
.paused-overlay__icon { font-size: 32px; opacity: 0.7; }
.paused-overlay__text { font-weight: 500; }

/* ── Host stats panel ── */
.host-stats { font-size: 11px; color: var(--text-dim, #888); }
.host-stats__toggle {
  background: none; border: none; color: var(--text-dim, #888);
  font: inherit; font-size: 11px; cursor: pointer; padding: 4px 0;
  display: flex; align-items: center; gap: 4px;
}
.host-stats__toggle:hover { color: var(--text, #e0e0e0); }
.host-stats__toggle-icon { font-size: 8px; width: 10px; text-align: center; }
.host-stats__body {
  padding: 6px 0 2px;
}
.host-stats__grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 12px;
  line-height: 1.6;
}
.host-stats__label { color: var(--text-dim, #888); }
.host-stats__value { color: var(--text, #e0e0e0); font-variant-numeric: tabular-nums; }
.host-stats__good { color: #81c784; }
.host-stats__warn { color: #ffb74d; }
.host-stats__info { color: var(--text-dim, #888); }
.host-stats__empty { color: var(--text-dim, #888); font-style: italic; }

/* ───────────────────────────────────────────────────────────────
   Connection diagnostics twisty. Lives under the preview, collapsed
   by default. Uses tone attributes for a four-color status vocabulary
   (good/warn/bad/idle) so every field can carry semantic meaning
   without caller code having to thread classes around.
   ─────────────────────────────────────────────────────────────── */
.conn-diag {
  margin-top: 8px;
  border: 1px solid var(--border, #2a2e36);
  border-radius: 6px;
  background: var(--surface-alt, rgba(255,255,255,0.02));
  font-size: 13px;
}
.conn-diag[open] {
  background: var(--surface, rgba(255,255,255,0.04));
}
.conn-diag__summary {
  cursor: pointer;
  user-select: none;
  outline: none;
  padding: 8px 12px;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 10px;
}
.conn-diag__summary::-webkit-details-marker { display: none; }
.conn-diag__summary::before {
  content: "▸";
  color: var(--text-dim);
  transition: transform 0.15s ease;
}
.conn-diag[open] .conn-diag__summary::before { content: "▾"; }
.conn-diag__summary-label {
  color: var(--text);
  font-weight: 500;
}
.conn-diag__headline {
  margin-left: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-2, rgba(255,255,255,0.06));
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.conn-diag__body {
  padding: 4px 12px 8px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.conn-diag__actions {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 6px 12px 10px;
  border-top: 1px dashed var(--border);
}
.conn-diag__btn {
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--surface-2, rgba(255,255,255,0.04));
  color: var(--text);
  cursor: pointer;
}
.conn-diag__btn:hover { background: var(--surface, rgba(255,255,255,0.08)); }
.conn-diag__status {
  font-size: 12px;
  color: var(--text-dim);
  margin-left: auto;
}
/* "Show full IPs" opt-in. Sits in the actions row next to the
   Refresh/Copy buttons. Kept visually secondary — it's a tool,
   not a decision the user wants to make every session. */
.conn-diag__opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}
.conn-diag__opt input[type="checkbox"] {
  margin: 0;
  accent-color: var(--accent, #4ea1ff);
}
/* Candidate-type legend — only rendered when unredact is on. Less
   data-dense than the per-peer rows; prose-ish, since the content
   is definitional. */
.conn-diag__legend .conn-diag__row {
  grid-template-columns: 70px 1fr;  /* narrower label col for single-word keys */
  align-items: start;
  padding: 4px 0;
}
.conn-diag__legend .conn-diag__label {
  font-weight: 600;
  color: var(--text);
}
.conn-diag__legend .conn-diag__val {
  font-family: inherit;   /* prose, not mono */
  font-size: 12px;
  line-height: 1.4;
  word-break: normal;
}
.conn-diag__legend .conn-diag__val code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
}
.conn-diag__note {
  margin-top: 8px;
  padding: 6px 8px;
  border-radius: 4px;
  background: var(--surface-2, rgba(255,255,255,0.04));
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-dim);
}
.conn-diag__section {
  border-top: 1px solid var(--border);
  padding-top: 8px;
}
.conn-diag__section:first-child { border-top: 0; padding-top: 0; }
.conn-diag__h {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin: 0 0 4px;
}
.conn-diag__row {
  display: grid;
  grid-template-columns: minmax(180px, 200px) 1fr;
  gap: 10px;
  padding: 2px 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}
.conn-diag__label { color: var(--text-dim); }
.conn-diag__val { color: var(--text); word-break: break-all; }
.conn-diag__val code {
  background: var(--surface-2, rgba(255,255,255,0.04));
  padding: 1px 5px;
  border-radius: 3px;
}
.conn-diag__empty { color: var(--text-dim); font-style: italic; margin: 0; }
.conn-diag__peer {
  margin-top: 4px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: rgba(255,255,255,0.02);
}
.conn-diag__peer-summary {
  cursor: pointer;
  padding: 6px 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  list-style: none;
}
.conn-diag__peer-summary::-webkit-details-marker { display: none; }
.conn-diag__peer-summary::before {
  content: "▸ ";
  color: var(--text-dim);
}
.conn-diag__peer[open] .conn-diag__peer-summary::before { content: "▾ "; }
.conn-diag__peer-body {
  padding: 4px 10px 8px;
  border-top: 1px dashed var(--border);
}
/* Tone tokens — apply to any element with data-tone=... for a
   consistent four-color status vocabulary. */
[data-tone="good"]  { color: #81c784; }
[data-tone="warn"]  { color: #ffb74d; }
[data-tone="bad"]   { color: #ef6b6b; }
[data-tone="idle"]  { color: var(--text-dim); }
.conn-diag__headline[data-tone="good"] {
  border-color: rgba(129,199,132,0.4); background: rgba(129,199,132,0.12);
}
.conn-diag__headline[data-tone="warn"] {
  border-color: rgba(255,183,77,0.4); background: rgba(255,183,77,0.12);
}
.conn-diag__headline[data-tone="bad"] {
  border-color: rgba(239,107,107,0.4); background: rgba(239,107,107,0.12);
}

/* ───────────────────────────────────────────────────────────────
   mDNS stuck-connection helper (viewer + host).
   Appears near the top of the page when a peer connection stays in
   'checking' with .local mDNS remote candidates. Mobile-friendly:
   big tap targets, monospace URL, prominent Copy button.
   ─────────────────────────────────────────────────────────────── */
.mdns-help-container:empty { display: none; }
.mdns-help-container {
  margin: 12px 16px;
}
.mdns-help {
  border: 1px solid var(--warn, #f0b055);
  background: color-mix(in srgb, var(--warn, #f0b055) 10%, var(--surface, #1a1d24));
  color: var(--text);
  border-radius: 8px;
  padding: 14px 16px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.25);
  max-width: 640px;
}
.mdns-help__head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.mdns-help__title {
  margin: 0 auto 0 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--warn, #f0b055);
}
.mdns-help__close {
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: 20px;
  line-height: 1;
  padding: 4px 8px;
  cursor: pointer;
  border-radius: 4px;
}
.mdns-help__close:hover { background: var(--surface-2); color: var(--text); }
.mdns-help__desc {
  margin: 6px 0 10px;
  font-size: 14px;
  line-height: 1.45;
}
.mdns-help__url-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
  margin: 8px 0;
}
.mdns-help__url {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text);
  overflow-x: auto;
  white-space: nowrap;
  user-select: all;
  min-width: 0;
}
.mdns-help__copy {
  flex-shrink: 0;
  background: var(--accent, #4f8cff);
  color: white;
  border: 0;
  border-radius: 6px;
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  min-height: 44px;   /* iOS-recommended min tap target */
}
.mdns-help__copy:hover { background: var(--accent-hover, #6aa0ff); }
.mdns-help__copy--full {
  width: 100%;
  margin: 6px 0 2px;
}
.mdns-help__steps {
  margin: 8px 0 8px 18px;
  padding: 0;
  font-size: 14px;
  line-height: 1.5;
}
.mdns-help__steps li { margin: 6px 0; }
.mdns-help__aside {
  margin: 10px 0 0;
  padding: 8px 10px;
  background: var(--surface-2);
  border-radius: 6px;
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.45;
}
.mdns-help__copied {
  display: block;
  margin-top: 6px;
  min-height: 18px;
  font-size: 13px;
  color: var(--text-dim);
  font-weight: 500;
}
.mdns-help__copied--ok { color: var(--ok, #3fb950); }

/* Primary fix: mic-grant button. Visually the most prominent option
   in the banner because it's the one-tap fix. Multi-line button with
   a subtitle so the user knows what to expect from the prompt. */
.mdns-help__primary {
  margin: 10px 0 4px;
}
.mdns-help__grant {
  width: 100%;
  padding: 14px 16px;
  border: 0;
  border-radius: 8px;
  background: var(--accent, #4f8cff);
  color: white;
  cursor: pointer;
  text-align: center;
  min-height: 56px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: center;
  justify-content: center;
}
.mdns-help__grant:hover { background: var(--accent-hover, #6aa0ff); }
.mdns-help__grant:disabled { opacity: 0.6; cursor: progress; }
.mdns-help__grant-label {
  font-size: 15px;
  font-weight: 600;
}
.mdns-help__grant-hint {
  font-size: 12px;
  opacity: 0.85;
  font-weight: 400;
}
.mdns-help__grant-why {
  margin: 6px 2px 0;
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.45;
}

/* "More options" disclosure — the chrome://flags path becomes a
   collapsed fallback since it's the slower, more technical option. */
.mdns-help__more {
  margin-top: 10px;
  padding: 8px 10px;
  background: var(--surface-2, rgba(255,255,255,0.04));
  border-radius: 6px;
}
.mdns-help__more > summary {
  cursor: pointer;
  user-select: none;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-dim);
  padding: 2px 0;
  list-style: none;
}
.mdns-help__more > summary::-webkit-details-marker { display: none; }
.mdns-help__more > summary::before {
  content: "▸ ";
  color: var(--text-dim);
}
.mdns-help__more[open] > summary::before { content: "▾ "; }
.mdns-help__more[open] > summary { color: var(--text); margin-bottom: 6px; }

@media (max-width: 640px) {
  .mdns-help-container { margin: 10px 12px; }
  .mdns-help { padding: 12px; }
  .mdns-help__title { font-size: 15px; }
  .mdns-help__url-row { flex-direction: column; }
  .mdns-help__url { font-size: 12px; padding: 10px; }
  .mdns-help__copy { width: 100%; padding: 14px; }
}

/* ── Mobile: chat window fills bottom sheet style ── */
@media (max-width: 720px) {
  .chat-fab { bottom: 16px; right: 16px; width: 48px; height: 48px; }
  .chat-window {
    bottom: 72px;
    right: 8px;
    left: 8px;
    width: auto;
    max-height: 60vh;
    border-radius: 14px;
  }
  .chat-list { max-height: 40vh; }
  .chat-input { min-height: 44px; }
  .chat-form button { min-height: 44px; }
  .option-row { gap: 12px; }
}

/* ─── Group sessions (v0.8.0) ────────────────────────────── */

/* Sub-options indented under a parent toggle. */
.option-row--sub {
  padding-left: 24px;
  border-left: 2px solid var(--border, #2a2f3a);
  margin-left: 8px;
}
/* v0.22.25 — Limitation hint sub-row under the Remote control toggle.
   Pure informational text, no toggle. Slightly muted with subtle
   info-tinted background so it reads as a "this is what you need to
   know about RC" panel, not a row of controls. */
.rc-limit-hint {
  display: block;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-dim);
  background: color-mix(in srgb, var(--accent, #4ea1ff) 8%, transparent);
  border-radius: 6px;
  padding: 8px 10px;
}
.option-row--sub:has(.rc-limit-hint) {
  border-left-color: color-mix(in srgb, var(--accent, #4ea1ff) 40%, transparent);
}

/* Dropdown select in room options. */
.option-select {
  background: var(--surface, #171a21);
  color: var(--text, #e0e0e0);
  border: 1px solid var(--border, #333);
  border-radius: 4px;
  padding: 4px 8px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

/* Viewer list "Join room" button. */
.viewer-join-btn {
  background: none;
  border: 1px solid var(--accent, #4f8cff);
  color: var(--accent, #4f8cff);
  font: inherit;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 4px;
  cursor: pointer;
  margin-left: auto;
}
.viewer-join-btn:hover {
  background: var(--accent, #4f8cff);
  color: var(--bg, #0f1115);
}
/* "Switch to room" state — host already has a tab open for this sub-room.
   Green to match the viewer tab's favicon so the visual story lines up:
   green button → you have a green-favicon tab open somewhere. */
.viewer-join-btn.is-joined,
.roster-btn--join.is-joined {
  border-color: var(--ok, #10b981);
  color: var(--ok, #10b981);
}
.viewer-join-btn.is-joined:hover,
.roster-btn--join.is-joined:hover {
  background: var(--ok, #10b981);
  color: var(--bg, #0f1115);
}

/* Viewer list items: flex row so the join button can align right. */
.viewers ul li {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Group host bar in viewer chat window. */
.group-host-bar {
  flex-shrink: 0;
  border-bottom: 1px solid var(--border, #333);
  padding: 6px 12px;
  font-size: 12px;
}
.group-host-bar[hidden] { display: none; }

.group-host-bar__created {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.group-host-bar__code {
  color: var(--text-dim, #9aa3b2);
}
.group-host-bar__code code {
  color: var(--text, #e0e0e0);
  font-family: var(--mono);
}

.group-host-bar__btn {
  background: none;
  border: 1px solid var(--border, #333);
  color: var(--text, #e0e0e0);
  font: inherit;
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 4px;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.group-host-bar__btn:hover {
  border-color: var(--accent, #4f8cff);
  color: var(--accent, #4f8cff);
}
.group-host-bar__btn--create {
  width: 100%;
  text-align: center;
  padding: 6px 12px;
  border-color: var(--accent, #4f8cff);
  color: var(--accent, #4f8cff);
}
.group-host-bar__btn--start {
  border-color: var(--ok, #3fb950);
  color: var(--ok, #3fb950);
}
.group-host-bar__btn--start:hover {
  background: var(--ok, #3fb950);
  color: var(--bg, #0f1115);
}

/* ─────────────────────────────────────────────────────────
   Remote control (host + viewer)
   ─────────────────────────────────────────────────────────
   Three surfaces:
     - Host approval modal (reuses .modal / .modal-backdrop skeleton).
     - Host active-control banner (slim strip at the top of the viewport).
     - Viewer status chip + Request/Release buttons in the player controls.
   Palette pulls from the shared --danger / --accent tokens so themes work.
*/

/* ─── Host approval modal ─────────────────────────────── */
.rc-approval__body {
  margin: 0 0 16px;
  color: var(--text, #e0e0e0);
  line-height: 1.45;
}
.rc-approval__body strong {
  color: var(--accent, #4f8cff);
}
.rc-approval__actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
button.primary.danger {
  background: transparent;
  border-color: var(--danger, #e05a5a);
  color: var(--danger, #e05a5a);
}
button.primary.danger:hover:not(:disabled) {
  background: var(--danger, #e05a5a);
  color: #fff;
}

/* ─── Host active-control banner ──────────────────────── */
.rc-active-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px;
  background: var(--danger, #e05a5a);
  color: #fff;
  font-size: 13px;
  font-weight: 500;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}
.rc-active-banner[hidden] { display: none; }
.rc-active-banner svg {
  flex: 0 0 auto;
}
.rc-active-banner span {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rc-active-banner button {
  background: rgba(255, 255, 255, 0.15);
  border: 1px solid rgba(255, 255, 255, 0.35);
  color: #fff;
  font: inherit;
  font-size: 12px;
  padding: 2px 10px;
  border-radius: 3px;
  cursor: pointer;
}
.rc-active-banner button:hover {
  background: rgba(255, 255, 255, 0.25);
}

/* ─── Viewer status chip + capture cursor cue ─────────── */
.rc-status-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  font-size: 11px;
  font-weight: 500;
  border-radius: 10px;
  border: 1px solid var(--border, #2a2f3a);
  color: var(--text-dim, #9aa3b2);
  background: transparent;
}
.rc-status-chip[hidden] { display: none; }
.rc-status-chip[data-state="pending"] {
  border-color: var(--warn, #f0b055);
  color: var(--warn, #f0b055);
}
.rc-status-chip[data-state="granted"] {
  border-color: var(--ok, #3fb950);
  color: var(--ok, #3fb950);
}
.rc-status-chip[data-state="pending"]::before,
.rc-status-chip[data-state="granted"]::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}

/* While the viewer holds control, the video shows a crosshair cursor so
   the user understands that clicks go through to the host. We can't use
   `cursor: none` because the OS pointer is what lands at the target; the
   crosshair is a visual reinforcement. */
video.rc-capturing {
  cursor: crosshair;
  outline: 2px solid var(--ok, #3fb950);
  outline-offset: -2px;
}

/* ─── rc-helper install banner (v0.12.0) ─────────────────────
   Shown in the Room Options modal when the "Allow remote control"
   toggle is on but the native helper isn't connected on 127.0.0.1:8765.
   Acts as the one-click install path without bouncing the user to docs. */

/* v0.22.24 — Helper Status row warning state, raised when the helper has
   connected but its OS-level permission probe reports injection won't
   work (e.g. macOS Accessibility revoked). The row stays in flex layout;
   we only tint the background + add a left border so the user's eye is
   drawn there. Sized by the existing .option-row--sub padding. */
.option-row.rc-stub-status--warning {
  background: color-mix(in srgb, var(--warn, #f0b055) 12%, transparent);
  border-left: 3px solid var(--warn, #f0b055);
  border-radius: 4px;
  padding-left: 9px;  /* compensate for the 3px left border so text stays aligned */
}
.option-row.rc-stub-status--warning .option-title::before {
  content: '⚠ ';
  color: var(--warn, #f0b055);
}
.option-row.rc-stub-status--warning .option-desc {
  color: var(--text, #e0e0e0);
}

.rc-helper-install {
  display: block; /* override option-row's flex so the banner body fills width */
}
/* Explicit [hidden] override — .rc-helper-install's display:block at 0,1,0
   ties with the UA's [hidden]{display:none} and wins by source order,
   silently making the hidden attribute a no-op. Pin the hidden state so
   renderRcUi() can toggle it reliably. Cerebrum 2026-04-15. */
.rc-helper-install[hidden] {
  display: none;
}
.rc-helper-install__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 0 4px;
}
.rc-helper-install__lede {
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted, #a0a6b1);
}
.rc-helper-install__lede strong {
  color: var(--text, #e0e0e0);
}
.rc-helper-install__lede code {
  background: var(--surface, #171a21);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 12px;
  white-space: nowrap;
}
.rc-helper-install__cta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.rc-helper-install__cta .btn--primary {
  font-size: 13px;
  padding: 8px 14px;
  text-decoration: none;
}
.rc-helper-install__os {
  font-size: 12px;
  color: var(--muted, #a0a6b1);
  font-style: italic;
}
.rc-helper-install__more {
  font-size: 12px;
}
.rc-helper-install__more summary {
  cursor: pointer;
  color: var(--accent, #4f8cff);
  user-select: none;
  padding: 4px 0;
}
.rc-helper-install__list {
  list-style: none;
  padding: 8px 0 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rc-helper-install__list a {
  color: var(--text, #e0e0e0);
  text-decoration: none;
  border-bottom: 1px dotted var(--border, #2a2f3a);
  padding: 2px 0;
}
.rc-helper-install__list a:hover {
  color: var(--accent, #4f8cff);
  border-bottom-color: var(--accent, #4f8cff);
}
.rc-helper-install__howto {
  font-size: 12px;
  color: var(--muted, #a0a6b1);
  line-height: 1.5;
  margin: 10px 0 0;
}
.rc-helper-install__howto code {
  background: var(--surface, #171a21);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 4px;
  padding: 1px 4px;
  font-size: 11px;
  word-break: break-all;
}

/* ─── Interactive Reading (v0.12.2) ──────────────────────────
   Ghost-cursor overlay, inline session toggles, zoom +/- buttons,
   and the persistent-prefs modal.

   Interaction model recap:
   - Host page mounts a full-bleed fixed overlay container; the
     host-side module appends one ring per granted viewer.
   - Viewer page mounts a row of compact toggle buttons next to the
     existing remote-control button. The row hides when the host
     hasn't advertised support, so non-interactive-reading sessions
     have zero UI surface area added.
   - The +/- zoom buttons share visual weight with the toggles but
     only appear while zoom is both advertised and enabled, to avoid
     orphan controls that do nothing.
*/

/* Host: the ghost container sits above the viewport. We don't put it
   inside .preview because we want the overlay to always cover the
   entire host page regardless of what the host is doing (preview off,
   another modal open, etc.). Children get positioned by the JS using
   `transform: translate(Xpx, Ypx)` — see host-side module. */
.interact-cursor-container {
  position: fixed;
  inset: 0;
  pointer-events: none;
  /* High but not maximum z-index — we want modals to still win so a
     ghost ring doesn't bleed through an approval dialog and confuse
     the host. */
  z-index: 9000;
  /* The container itself has no box; it's just a positioning root.
     Hidden when interactive-reading is off on the room. */
}
.interact-cursor-container[hidden] { display: none; }

/* The ring itself. 22px is comfortably "see me from across the room"
   without being obnoxious. We use a coloured border rather than a
   filled circle so it stays readable on busy backgrounds — a white-
   on-white video frame won't hide it. */
.interact-cursor-ghost {
  position: absolute;
  top: 0;
  left: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid var(--accent, #4f8cff);
  box-shadow: 0 0 0 2px rgba(0,0,0,0.35), 0 0 6px rgba(79,140,255,0.6);
  /* JS sets transform for x,y; subtract half the size so the ring
     centres on the pointer rather than sitting below-right of it. */
  margin: -11px 0 0 -11px;
  pointer-events: none;
  opacity: 0.92;
  transition: opacity 260ms ease-out;
  /* Gentle pulse so a stationary viewer still shows presence. */
  animation: interact-cursor-pulse 1.8s ease-in-out infinite;
}
.interact-cursor-ghost.is-faded { opacity: 0; }
@keyframes interact-cursor-pulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(0,0,0,0.35), 0 0 6px rgba(79,140,255,0.6); }
  50%      { box-shadow: 0 0 0 2px rgba(0,0,0,0.35), 0 0 12px rgba(79,140,255,0.9); }
}
/* Respect reduced-motion — some users find pulsing distracting. */
@media (prefers-reduced-motion: reduce) {
  .interact-cursor-ghost { animation: none; }
}

/* Host: off-screen forwardWheel proxy video. Must be in the DOM and
   have non-zero size for forwardWheel() to accept it; we hide via
   off-screen positioning rather than display:none / visibility:hidden
   (both of which can make the browser treat it as ineligible). */
.ir-forward-proxy {
  position: fixed;
  left: -10000px;
  top: 0;
  width: 2px;
  height: 2px;
  opacity: 0;
  pointer-events: none;
}

/* Viewer: inline toggle row on player-controls. Uses gap + wrap so the
   buttons reflow onto a second line on narrow screens. */
.ir-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.ir-row[hidden] { display: none; }

/* Toggle buttons: same general shape as other player-controls buttons,
   with a clear pressed state because the whole point of these is to
   show "this feature is live RIGHT NOW". */
.ir-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  border-radius: 14px;
  border: 1px solid var(--border, #2a2f3a);
  background: transparent;
  color: var(--text-dim, #9aa3b2);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.ir-toggle:hover {
  border-color: var(--accent, #4f8cff);
  color: var(--text, #e0e0e0);
}
.ir-toggle[hidden] { display: none; }
.ir-toggle.is-pressed,
.ir-toggle[aria-pressed="true"] {
  background: var(--accent, #4f8cff);
  color: var(--on-accent, #fff);
  border-color: var(--accent, #4f8cff);
}

/* Zoom +/- buttons: compact, fixed-width so the two buttons line up
   visually when both are visible. */
.ir-zoom-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 26px;
  padding: 0 6px;
  font-size: 14px;
  line-height: 1;
  font-weight: 600;
  border-radius: 13px;
  border: 1px solid var(--border, #2a2f3a);
  background: transparent;
  color: var(--text, #e0e0e0);
  cursor: pointer;
}
.ir-zoom-btn:hover { border-color: var(--accent, #4f8cff); }
.ir-zoom-btn[hidden] { display: none; }

/* Status chip variant — inherits base .rc-status-chip styling from
   the remote-control rules above; we only add a class selector so
   theming can override the IR variant specifically if needed. */
.ir-status-chip { /* piggybacks on .rc-status-chip */ }

/* Interactive-reading preferences modal body. Reuses the generic
   .modal / .option-list / .option-row structure, so here we just
   tighten spacing on the intro paragraph. */
.interact-modal__body {
  padding: 14px 18px 18px;
}
.interact-modal__intro {
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted, #a0a6b1);
  margin: 0 0 12px;
}

/* ──────────────────────────────────────────────────────────────────
   Audio Settings (gear button + modal + diagnostics)
   ────────────────────────────────────────────────────────────────── */

/* Gear button: appears next to the voice controls on host (in chat
   window) and inline in the viewer voice bar. Hidden until voice is
   active so we don't clutter the UI when audio doesn't apply. */
.audio-settings-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  margin: 0 12px 8px;
  font-size: 12px;
  background: transparent;
  color: var(--text-dim, #9aa3b2);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  cursor: pointer;
  transition: color 120ms, border-color 120ms;
}
.audio-settings-btn:hover:not(:disabled) {
  color: var(--text, #e7e9ee);
  border-color: var(--accent, #4f8cff);
  background: transparent;     /* override generic button:hover */
}
.audio-settings-btn[hidden] { display: none; }

/* Inline variant for the viewer voice bar: tighter padding, no border, icon-only. */
.audio-settings-btn--inline {
  padding: 4px 6px;
  margin: 0 0 0 4px;
  border: none;
}
.audio-settings-btn--inline span { display: none; }

/* Modal container piggybacks on .modal-backdrop / .modal but the body
   gets a lot of content, so we widen it and let it scroll. */
.audio-settings-modal .modal {
  max-width: 720px;
  width: 92vw;
  max-height: 88vh;
  overflow-y: auto;
}

.audio-settings__intro {
  margin: 8px 18px 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim, #9aa3b2);
}
.audio-settings__intro--small {
  font-size: 12px;
  margin-top: 0;
}

.audio-settings__filters {
  margin: 6px 0 16px;
}

.audio-settings__gain {
  padding: 4px 18px 14px;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "slider value" "desc desc";
  gap: 6px 12px;
  align-items: center;
}
.audio-settings__gain input[type="range"] { grid-area: slider; width: 100%; }
.audio-settings__gain-value {
  grid-area: value;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-dim, #9aa3b2);
  white-space: nowrap;
}
.audio-settings__gain .option-desc {
  grid-area: desc;
  margin: 0;
}

.audio-settings__speaker {
  padding: 4px 18px 14px;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "label sel" "desc desc";
  gap: 6px 12px;
  align-items: center;
}
.audio-settings__speaker label { grid-area: label; }
.audio-settings__speaker select {
  grid-area: sel;
  width: 100%;
  padding: 6px 8px;
  background: var(--surface-2, #1f232c);
  color: var(--text, #e7e9ee);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
}
.audio-settings__speaker .option-desc { grid-area: desc; margin: 0; }

/* Diagnostics block — vertical stack of card-ish sections. */
.audio-diag {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 4px 18px 8px;
}
.audio-diag__section {
  background: var(--surface-2, #1f232c);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  padding: 10px 14px;
}
.audio-diag__section[hidden] { display: none; }
.audio-diag__section h4 {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text, #e7e9ee);
  letter-spacing: 0.2px;
}
.audio-diag__empty {
  font-size: 12px;
  color: var(--text-dim, #9aa3b2);
  font-style: italic;
}
.audio-diag__note {
  margin-top: 8px;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-dim, #9aa3b2);
  background: color-mix(in srgb, var(--warn, #f0b055) 12%, transparent);
  border-left: 3px solid var(--warn, #f0b055);
  border-radius: 4px;
}

/* Two-column key/value grid — narrow left column for labels. */
.audio-diag__table {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(120px, 32%) 1fr;
  gap: 4px 12px;
}
.audio-diag__table dt {
  font-size: 12px;
  color: var(--text-dim, #9aa3b2);
}
.audio-diag__table dd {
  margin: 0;
  font-size: 12px;
  font-family: var(--mono);
  color: var(--text, #e7e9ee);
  word-break: break-word;
}

/* Filter pills — visual signal for the four states. */
.audio-pill {
  display: inline-block;
  padding: 1px 7px;
  font-size: 11px;
  font-family: var(--mono);
  border-radius: 999px;
  border: 1px solid var(--border);
  letter-spacing: 0.3px;
}
.audio-pill--on {
  color: var(--ok, #3fb950);
  border-color: color-mix(in srgb, var(--ok, #3fb950) 50%, transparent);
  background: color-mix(in srgb, var(--ok, #3fb950) 12%, transparent);
}
.audio-pill--off {
  color: var(--text-dim, #9aa3b2);
  background: var(--surface, #171a21);
}
.audio-pill--unknown {
  color: var(--warn, #f0b055);
  border-color: color-mix(in srgb, var(--warn, #f0b055) 50%, transparent);
  background: color-mix(in srgb, var(--warn, #f0b055) 12%, transparent);
}
.audio-pill--unsupp {
  color: var(--danger, #e05a5a);
  border-color: color-mix(in srgb, var(--danger, #e05a5a) 50%, transparent);
  background: color-mix(in srgb, var(--danger, #e05a5a) 12%, transparent);
}

/* Footer actions — copy / reset / status. */
.audio-settings__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px 16px;
  border-top: 1px solid var(--border);
  margin-top: 8px;
}
.audio-copy-status {
  font-size: 12px;
  color: var(--ok, #3fb950);
  opacity: 0;
  transition: opacity 200ms;
}
.audio-copy-status.is-visible { opacity: 1; }

/* ───────────────────────────────────────────────────────────────
   Capability diagnostic (host.js showPlatformNotice — non-iOS branch)
   ─────────────────────────────────────────────────────────────── */
/* The inferred-cause paragraph is the user-actionable line; the
   <details> twisty hides the raw signals (isSecureContext, UA, etc.)
   for users who want to copy-paste them into a bug report. Native
   <details>/<summary> gives keyboard + a11y for free. */
.capability-cause {
  margin: 8px 0 12px;
}
.capability-diagnostic {
  margin-top: 12px;
  font-size: 0.9em;
}
.capability-diagnostic summary {
  cursor: pointer;
  color: color-mix(in srgb, var(--text) 65%, transparent);
  user-select: none;
}
.capability-diagnostic pre {
  margin: 8px 0;
  padding: 10px 12px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  border-radius: 4px;
  overflow-x: auto;
  font-size: 0.85em;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
}
.cap-diag-copy {
  font-size: 0.85em;
  padding: 4px 10px;
}

/* ───────────────────────────────────────────────────────────────
   Pre-flight surface picker (v0.16.3 — host.html / host.js)
   ─────────────────────────────────────────────────────────────── */
/* Lets the host pre-declare what they want to share (whole screen,
   a window, or a browser tab) before clicking Start. The selection
   is passed to getDisplayMedia as a `displaySurface` hint so Chrome
   opens the picker on the matching tab. The coaching strip below
   names the three clicks the picker requires. */
.surface-picker {
  margin: 4px 0 12px;
}
.surface-picker__label {
  font-size: 13px;
  color: var(--text-dim);
  margin: 0 0 8px;
}
.surface-picker__tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 10px;
}
.surface-tile {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  color: var(--text);
  font: inherit;
  text-align: center;
  transition: border-color 150ms ease, background-color 150ms ease;
}
.surface-tile:hover { border-color: var(--text-dim); }
.surface-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.surface-tile.is-selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--bg));
}
.surface-tile__icon {
  width: 24px;
  height: 24px;
  color: var(--text-dim);
}
.surface-tile.is-selected .surface-tile__icon { color: var(--accent); }
.surface-tile__name {
  font-size: 13px;
  font-weight: 500;
  line-height: 1.2;
}
.surface-tile__sub {
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.2;
}
.surface-picker__coach {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
}
.surface-picker__coach ol {
  margin: 4px 0 0;
  padding-left: 20px;
}
.surface-picker__coach li { margin-bottom: 2px; }

/* ───────────────────────────────────────────────────────────────
   Restore previous room (v0.18.6 — host page)
   ─────────────────────────────────────────────────────────────── */
/* Secondary-style button matching .reserve-btn but visually more
   subdued so it doesn't compete with the primary Start action. */
.restore-btn {
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--text-dim);
  border-radius: var(--radius, 8px);
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-align: center;
  width: 100%;
}
/* v0.21.2 — [hidden] specificity disambiguator (see .reserve-btn above). */
.restore-btn[hidden] { display: none; }
.restore-btn:hover:not(:disabled) {
  border-color: var(--text-dim);
  color: var(--text);
}
.restore-btn__label { font-weight: 500; }

/* Inline restore-key input section — sits between the restore button
   and the reserve button. Matches .unlock-section pattern. */
.restore-section {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 8px;
}
.restore-section[hidden] { display: none; }
.restore-section__input {
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.restore-section__input:focus {
  outline: none;
  border-color: var(--accent, #4ea1ff);
}
.restore-section__input::placeholder {
  text-transform: none;
  letter-spacing: normal;
  font-family: inherit;
  color: var(--text-dim);
  opacity: 0.7;
}
.restore-section__submit {
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}
.restore-section__submit:hover:not(:disabled) {
  background: var(--surface);
  border-color: var(--text-dim);
}

/* v0.22.16 — Restore 2-col 50/50 top layout. v0.22.14 went single-column;
   v0.22.16 puts WHAT TO SHARE left, SESSION LENGTH + Start right, with
   the room features panel moved full-width below the preview. */
.controls-grid {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
}
@media (max-width: 720px) {
  .controls-grid {
    grid-template-columns: 1fr;
  }
}

/* v0.22.16 — Tinted sub-card grid inside Advanced room options. Four
   sections (Joining / Communications / Sharing / Network), each a
   tinted block with a small uppercase label. 2-col grid on desktop,
   1-col on narrow. */
.advanced-options__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 10px;
}
@media (max-width: 720px) {
  .advanced-options__grid {
    grid-template-columns: 1fr;
  }
}
.feature-section {
  background: var(--bg);
  border-radius: 8px;
  padding: 10px 12px;
}
.feature-section__label {
  font-size: 10px;
  font-weight: 500;
  color: var(--text-dim);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin-bottom: 6px;
}
/* Inside each feature-section, the option-list rows should be
   slightly more compact than the old wide row. */
.feature-section .option-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* v0.22.21 — TURN relay toggle: locked-by-default, unlocked only
   when the server confirms TURN was granted.

   Earlier versions (v0.22.18-20) gated the lock on body.is-free-tier
   (set by syncSessionLengthUi when the dropdown is on Free). That
   logic missed M-class codes: M codes set the dropdown to a paid
   tier so is-free-tier got dropped, but M is STUN-only — TURN was
   never actually granted server-side. The toggle visually said ON
   while the iceServers said STUN-only. Misleading.

   New logic: lock is the BASE state for the TURN row (greyed, pill
   shown, slider OFF, unclickable). The ONLY escape is when
   applyUnlockConfirmation receives unlockApplied.turnEnabled = true
   from the WELCOME, which adds body.has-turn-available. That class
   undoes every piece of the lock styling.

   This means the toggle starts locked at boot, stays locked through
   Free + paid-but-unconfirmed states, and only unlocks when server
   says TURN is in the iceServers. body.is-free-tier is no longer
   used here (kept around for any future toggle that needs the
   dropdown-only signal — see syncSessionLengthUi in host.js). */
.lock-hint {
  display: inline-block;
  margin-left: 8px;
  font-size: 10px;
  font-weight: 500;
  color: var(--text-dim);
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--border);
  border-radius: 99px;
  padding: 2px 8px;
  letter-spacing: 0.02em;
  vertical-align: middle;
  white-space: nowrap;
}
#turn-relay-row {
  opacity: 0.7;
}
#turn-relay-row .switch {
  pointer-events: none;
  cursor: not-allowed;
}
#turn-relay-row .option-label {
  cursor: default;
}
/* Force the slider to render in the OFF position even when the
   underlying <input> stays checked. We don't reset .checked itself
   because that would lose the host's previous preference — when
   they unlock with an X code we want the toggle to come back at
   their last value. */
#turn-relay-row .switch input:checked + .switch__slider {
  background: var(--surface-2);
  border-color: var(--border);
}
#turn-relay-row .switch input:checked + .switch__slider::before {
  transform: translateX(0);
  background: var(--text-dim);
}
/* Escape hatch: server confirmed TURN is granted. Undo every lock. */
body.has-turn-available .lock-hint {
  display: none;
}
body.has-turn-available #turn-relay-row {
  opacity: 1;
}
body.has-turn-available #turn-relay-row .switch {
  pointer-events: auto;
  cursor: pointer;
}
body.has-turn-available #turn-relay-row .option-label {
  cursor: pointer;
}
body.has-turn-available #turn-relay-row .switch input:checked + .switch__slider {
  background: var(--accent);
  border-color: var(--accent);
}
body.has-turn-available #turn-relay-row .switch input:checked + .switch__slider::before {
  transform: translateX(18px);
  background: white;
}
