:root {
  color-scheme: dark;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: #111;
  color: #eee;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { background: #111; color: #eee; overflow: hidden; }
button, input { font: inherit; }
.hidden { display: none !important; }
button {
  border: 1px solid #444;
  border-radius: 6px;
  background: #242424;
  color: #eee;
  padding: 0.45rem 0.75rem;
  cursor: pointer;
}
button:hover { background: #303030; }
button.active { background: #305d35; border-color: #5d9b63; }
button.danger { background: #6a2424; border-color: #a64848; }
button:disabled { opacity: 0.55; cursor: default; }

.host-shell {
  display: grid;
  grid-template-columns: 1fr;
  /* Row 1 (auto) is the golden notification area: the contender waiting
     banner (a new host waiting to be accepted) or the takeover prompt (the
     current host deciding accept/decline). At most one is visible at a time
     and the row collapses to 0 while both are hidden. Row 4 is auto (not a
     fixed 56px) so the control bar can wrap to a second row on narrow
     screens instead of clipping its buttons. The bar's natural height is
     exactly 56px when it fits on one row, so the desktop layout is
     unchanged. */
  grid-template-rows: auto 150px 1fr auto;
  height: 100vh;
  gap: 8px;
  padding: 8px;
  position: relative;
}

.broadcast-strip {
  grid-column: 1;
  grid-row: 2;
  display: flex;
  gap: 8px;
  overflow-x: auto;
  min-height: 0;
  border: 1px solid #242424;
  background: #151515;
  padding: 6px;
}
.broadcast-strip:empty::before {
  content: "Broadcast-only video sources appear here";
  color: #666;
  margin: auto;
}
.broadcast-tile {
  position: relative;
  height: 100%;
  min-width: 280px;
  background: #050505;
}
.broadcast-tile video { width: 100%; height: 100%; object-fit: contain; display: block; }
.video-label {
  position: absolute;
  left: 5px;
  bottom: 5px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(0,0,0,.65);
  color: #ddd;
  font-size: 12px;
}

/* Wraps the recording canvas so the split divider can be positioned over it.
   The divider is a DOM element, so it is visible to the host but never ends
   up in the recorded pixels (the recording captures the canvas content only). */
.canvas-wrap {
  position: relative;
  min-height: 0;
  /* Width is capped so the 16:9 canvas fits the row below the 150px
     broadcast strip (same vertical budget the row gets; keeps the old
     280px constant shifted by the 50px strip reduction). */
  width: min(100%, calc((100vh - 230px) * 16 / 9));
  height: auto;
  max-height: 100%;
  aspect-ratio: 16 / 9;
}
#recordCanvas {
  display: block;
  width: 100%;
  height: 100%;
  background: #000;
}
/* The host canvas has a uniform dark background, so the full-height divider
   line (fine over the viewer's wallpaper) would stand out. Keep only the
   grip pill — the whole seam stays draggable (cursor + pill highlight). */
.canvas-wrap .split-divider::before {
  content: none;
}
.recording-preview {
  grid-column: 1;
  grid-row: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-height: 0;
  padding: 6px 8px 0;
}
.recording-preview label {
  font-size: 11px;
  color: #888;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.control-bar {
  grid-column: 1;
  grid-row: 4;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px;
  border: 1px solid #242424;
  background: #181818;
  position: relative;
}
.control-bar button {
  position: relative;
}
#sourcesButton { background: #305d35; border-color: #5d9b63; }
#sourcesButton:hover { background: #3a6e42; }

/* Broadcast state chip (host control bar): off (grey), ready (amber —
   broadcasting but nobody watching), live (green, gently pulsing). */
.live-state {
  white-space: nowrap;
  font-size: 13px;
  font-weight: 700;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid #3a3a3a;
  background: #1c1c1c;
  color: #9a9a9a;
}
.live-state.ready {
  color: #ffd97a;
  border-color: #8a6d1f;
  background: #26200f;
}
.live-state.live {
  color: #c9f7cd;
  border-color: #4d8d54;
  background: #14301b;
  animation: livepulse 2s ease-in-out infinite;
}
@keyframes livepulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(77, 141, 84, .45); }
  50% { box-shadow: 0 0 0 5px rgba(77, 141, 84, 0); }
}

.viewer-list {
  list-style: none;
  padding: 0;
  margin: 0 2px;
  max-height: min(40vh, 400px);
  overflow-y: auto;
}
.viewer-list li { padding: 5px 6px; border-bottom: 1px solid #282828; font-size: 13px; }

.menu {
  position: absolute;
  bottom: 52px;
  left: 8px;
  min-width: 360px;
  max-width: min(620px, calc(100vw - 24px));
  max-height: min(70vh, 620px);
  overflow: auto;
  z-index: 10;
  padding: 8px;
  border: 1px solid #4a4a4a;
  border-radius: 8px;
  background: #202020;
  box-shadow: 0 8px 26px rgba(0,0,0,.55);
}
.menu.hidden { display: none; }
/* Menus that anchor to the right edge of the bar (pages + viewers). */
.menu.menu-right { left: auto; right: 8px; }
#navMenu { min-width: 240px; }
.chat-panel {
  bottom: 52px;
  right: 8px;
  left: auto;
}
.chat-panel ul {
  list-style: none;
  padding: 0;
  margin: 0 0 6px 0;
  max-height: min(320px, calc(70vh - 100px));
  overflow-y: auto;
}
.chat-panel li {
  padding: 4px 0;
  font-size: 13px;
  border-bottom: 1px solid #282828;
  line-height: 1.35;
}
.chat-panel li .chat-name {
  font-weight: 600;
}
.chat-panel form {
  display: flex;
  gap: 4px;
}
.chat-panel input[type="text"] {
  flex: 1;
  padding: 5px 8px;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #eee;
  font-size: 13px;
  outline: none;
}
.chat-panel input[type="text"]:focus {
  border-color: #5d9b63;
}
.chat-panel button {
  padding: 5px 12px;
  border-radius: 6px;
}
.chat-panel.hidden { display: none; }
.menu h3 { margin: 4px 4px 8px; font-size: 14px; }
.menu-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 12px;
  align-items: center;
  padding: 7px 6px;
  border-top: 1px solid #303030;
}
.menu-row.mic { grid-template-columns: 1fr auto; }
/* Remote source rows carry a third button (Fullscreen) after the
   Broadcast/Record pair. */
.menu-row.remote { grid-template-columns: 1fr auto auto auto; }
.menu-row label { white-space: nowrap; font-size: 13px; }
/* Local mic rows grow a middle input-level meter column while capturing. */
.menu-row.mic.metered { grid-template-columns: 1fr auto auto; }
/* Input-level meter (host Sources menu mic rows): a width-animating fill
   inside a thin frame; the fill color is zone-based (green -> yellow -> red)
   via the data-zone attribute set by the meter. Fixed width in menu rows so
   the buttons don't shift as the level moves. */
.mic-meter {
  position: relative;
  height: 8px;
  border: 1px solid #333;
  border-radius: 4px;
  background: #161616;
  overflow: hidden;
}
.mic-meter-fill {
  height: 100%;
  width: 0%;
  background: #2ed573;
}
.mic-meter-fill[data-zone="mid"] { background: #ffd700; }
.mic-meter-fill[data-zone="high"] { background: #ff6348; }
.menu-row .mic-meter { width: 90px; }
/* Source-menu toggle buttons: grey when off, green when on or hovered.
   Hovering Record also lights the Broadcast button next to it, previewing
   that recording always broadcasts. On-state always stays green. */
.menu-row .src-btn {
  font-size: 13px;
  white-space: nowrap;
  color: #aaa;
}
.src-btn:hover,
.src-btn:focus-visible,
.src-btn[aria-pressed="true"] {
  background: #305d35;
  border-color: #5d9b63;
  color: #d9f2dc;
}
.menu-row:has(.src-btn-record:hover) .src-btn-broadcast {
  background: #305d35;
  border-color: #5d9b63;
  color: #d9f2dc;
}
/* Hovering a button that would turn its source OFF (pressing the button for
   the mode that is already active) shows red instead of green, warning that
   the source is about to be disabled. Hovering Record while recording also
   lights the Broadcast button next to it (recording always broadcasts). */
.menu-row:has(.src-btn-record[aria-pressed="true"]:is(:hover, :focus-visible)) .src-btn-record,
.menu-row:has(.src-btn-record[aria-pressed="true"]:is(:hover, :focus-visible)) .src-btn-broadcast,
.menu-row:has(.src-btn-broadcast[aria-pressed="true"]:is(:hover, :focus-visible)):not(:has(.src-btn-record[aria-pressed="true"])) .src-btn-broadcast,
.src-btn-use[aria-pressed="true"]:is(:hover, :focus-visible) {
  background: #5d3030;
  border-color: #9b5d5d;
  color: #f2d9d9;
}
.menu-empty { color: #888; padding: 8px; }
.small-note { color: #888; font-size: 12px; margin: 6px; }
.spacer { flex: 1; }
.nav-links {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 2px 4px 4px;
}
.nav-links a {
  text-align: center;
  font-size: 13px;
  padding: 8px 12px;
  border: 1px solid #2c2c2c;
  border-radius: 8px;
  background: #141414;
  text-decoration: none;
  transition: border-color .15s ease, color .15s ease;
}
.nav-links a.viewer-page { color: #8fd69a; }
.nav-links a.viewer-page:hover { border-color: #5d9b63; color: #c4f0c8; }
.nav-links a.contrib-page { color: #c98fd6; }
.nav-links a.contrib-page:hover { border-color: #9b5d9b; color: #eec8ef; }
.nav-links a.home-page { color: #8fb8d6; }
.nav-links a.home-page:hover { border-color: #5d8db3; color: #cfe6f5; }
/* Recordings/Passwords actions in the pages menu: same card look as the
   page links. */
.nav-links button {
  text-align: center;
  font-size: 13px;
  padding: 8px 12px;
  border: 1px solid #2c2c2c;
  border-radius: 8px;
  background: #141414;
  color: #ddd;
  transition: border-color .15s ease, color .15s ease;
}
.nav-links button:hover { border-color: #4a4a4a; color: #eee; }

/* Host takeover prompt (golden banner, top of the host page) */
.takeover-prompt {
    grid-column: 1;
    grid-row: 1;
    padding: 10px;
    border: 2px solid #9c7a24;
    border-radius: 8px;
    background: #2a2310;
}
.takeover-prompt .takeover-header {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
}
.takeover-prompt .takeover-header .pulse-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ffd700;
    animation: pulse 1.2s ease-in-out infinite;
}
@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.3; }
}
.takeover-prompt .takeover-title {
    font-size: 13px;
    font-weight: 700;
    color: #ffd700;
}
.takeover-prompt .takeover-subtitle {
    font-size: 12px;
    color: #aaa;
    margin-bottom: 8px;
}
.takeover-buttons {
    display: flex;
    gap: 6px;
}
.takeover-buttons button {
    flex: 1;
    padding: 6px 10px;
    font-size: 13px;
    font-weight: 600;
}
.takeover-buttons .takeover-accept {
    background: #2a4a2a;
    border-color: #4d8d54;
    color: #dff5df;
}
.takeover-buttons .takeover-accept:hover {
    background: #3a6a3a;
}
.takeover-buttons .takeover-decline {
    background: #4a2a2a;
    border-color: #8d4d4d;
    color: #f5dfdf;
}
.takeover-buttons .takeover-decline:hover {
    background: #6a3a3a;
}

/* Contender waiting banner (golden, top of the host page) */
.contender-wait-banner {
    grid-column: 1;
    grid-row: 1;
    padding: 10px;
    border: 2px solid #9c7a24;
    border-radius: 8px;
    background: #2a2310;
    text-align: center;
}
.contender-wait-banner .spinner {
    display: inline-block;
    width: 16px;
    height: 16px;
    border: 2px solid #ffd700;
    border-top-color: transparent;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    vertical-align: middle;
    margin-right: 6px;
}
@keyframes spin {
    to { transform: rotate(360deg); }
}
.contender-wait-banner .waiting p {
    display: inline;
    font-size: 13px;
    color: #ffd700;
}

/* Request state: the golden CTA button that queues the takeover. Gold fill
   (the banner's accent) marks it as the primary action. */
.contender-wait-banner .request p {
    font-size: 13px;
    color: #ffd700;
}
.contender-wait-banner .takeover-request {
    margin-top: 10px;
    padding: 6px 14px;
    font-size: 13px;
    font-weight: 600;
    background: #9c7a24;
    border-color: #c9a227;
    color: #fff8dc;
}
.contender-wait-banner .takeover-request:hover {
    background: #b8922e;
}
.contender-wait-banner .declined-links .takeover-request {
    margin-top: 0;
}

/* Admin force-takeover button on the contender waiting banner: styled like
   the decline button — it takes the host away from the current host. */
.contender-wait-banner .takeover-force {
    margin-top: 10px;
    padding: 6px 14px;
    font-size: 13px;
    font-weight: 600;
    background: #4a2a2a;
    border-color: #8d4d4d;
    color: #f5dfdf;
}
.contender-wait-banner .takeover-force:hover {
    background: #6a3a3a;
}

.join-shell {
  position: relative;
  height: 100vh;
  display: grid;
  place-items: center;
  /* Reserve the top strip for the room bar / host-contrib links row. */
  padding: 76px 16px 16px;
  background: radial-gradient(circle at 50% 30%, #222, #090909 70%);
}
/* The top row of the landing page: the room bar, centered on the page, with
   the host/contrib links on the same row to the right. */
.join-topbar {
  position: absolute;
  top: 16px;
  left: 16px;
  right: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.join-topbar .join-links {
  position: absolute;
  right: 8px;
  display: flex;
  gap: 8px;
}
/* The room bar: a compact, distinct pill. */
.room-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border: 1px solid #333;
  border-radius: 999px;
  background: rgba(25, 25, 25, 0.72);
}
.room-bar label {
  font-size: 13px;
  color: #999;
}
.room-bar input {
  width: 210px;
  padding: 0.45rem 0.6rem;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #eee;
  font-size: 14px;
}
.takeover-prompt.hidden, .contender-wait-banner.hidden {
    display: none !important;
}

/* Contender declined banner */
.contender-wait-banner .declined-text {
    font-size: 13px;
    color: #f5dfdf;
    text-align: center;
}
.contender-wait-banner .declined-links {
    margin-top: 8px;
    display: flex;
    gap: 8px;
    justify-content: center;
}
.contender-wait-banner .declined-links a {
    font-size: 13px;
    font-weight: 600;
    padding: 5px 14px;
    border-radius: 6px;
    text-decoration: none;
}
.contender-wait-banner .declined-links a.viewer-link {
    background: #2a4a2a;
    color: #dff5df;
}
.contender-wait-banner .declined-links a.viewer-link:hover {
    background: #3a6a3a;
}
.contender-wait-banner .declined-links a.contrib-link {
    background: #4a2a4a;
    color: #f5dff5;
}
.contender-wait-banner .declined-links a.contrib-link:hover {
    background: #6a3a6a;
}

.join-shell .host-link {
  padding: 6px 14px;
  border: 1px solid #444;
  border-radius: 6px;
  background: #242424;
  color: #ccc;
  text-decoration: none;
  font-size: 13px;
}
.join-shell .host-link:hover {
  background: #303030;
  color: #eee;
}
.join-card {
  width: min(420px, calc(100vw - 32px));
  padding: 24px;
  border: 1px solid #333;
  border-radius: 12px;
  background: #191919;
  box-shadow: 0 10px 32px rgba(0,0,0,.5);
}
.join-card h1 { margin: 0 0 16px; font-size: 24px; }
.join-card input {
  width: 100%;
  margin-bottom: 12px;
  padding: 0.65rem;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #eee;
}
.join-card .login-error {
  margin: 0 0 12px;
  color: #ff8f8f;
  font-size: 14px;
}
.join-card button { width: 100%; }
/* Join + "request access" (waiting room) on one row: Join keeps its full
   width, the request button hugs its label. */
.join-buttons {
  display: flex;
  gap: 8px;
}
.join-buttons #joinButton {
  flex: 1 1 auto;
}
.join-buttons .request-button {
  flex: 0 0 auto;
  width: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0.65rem 0.9rem;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #ccc;
  font-size: 13px;
  white-space: nowrap;
  cursor: pointer;
}
.join-buttons .request-button:hover:not(:disabled) {
  color: #eee;
  border-color: #666;
}
.join-buttons .request-button:disabled {
  opacity: 0.6;
  cursor: default;
}
/* Button spinner (the request is in the host's queue). Reuses the shared
   spin keyframes (contender-wait banner). */
.request-button .spinner,
.request-access-button .spinner {
  display: none;
  width: 12px;
  height: 12px;
  border: 2px solid #9fd0ff;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
.request-button.pending .spinner,
.request-access-button.pending .spinner {
  display: inline-block;
}
.join-hint {
  margin: 12px 0 0;
  font-size: 13px;
  color: #888;
  text-align: center;
}

.viewer-shell {
  height: 100vh;
  display: grid;
  grid-template-rows: 1fr auto;
  background: #050505;
}
.viewer-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border-top: 1px solid #242424;
  background: #181818;
  height: 42px;
}
.chat-toggle {
  flex-shrink: 0;
  font-size: 13px;
  position: relative;
}
.viewer-mic-wrap {
  display: flex;
  align-items: stretch;
  flex-shrink: 0;
}
.mic-button, .mic-menu-button {
  position: relative;
  min-width: 34px;
  font-size: 14px;
}
.mic-button { border-radius: 6px 0 0 6px; }
.mic-menu-button { min-width: 24px; border-radius: 0 6px 6px 0; margin-left: -1px; }
.mic-button.active::after {
  content: '';
  position: absolute;
  top: -4px;
  right: -4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #ff4757;
  border: 2px solid #181818;
}
.mic-button.muted-by-host {
  opacity: .45;
  filter: grayscale(1);
}
.mic-device-menu {
  min-width: 260px;
  max-width: min(360px, calc(100vw - 24px));
}
.mic-device-row {
  display: block;
  width: 100%;
  text-align: left;
  margin: 3px 0;
}
.viewer-list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.viewer-mute-button { padding: 2px 8px; }
.viewer-mute-all-row button { width: 100%; margin-bottom: 4px; }
.unread-dot {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #2ed573;
  border: 2px solid #181818;
}
.status-bar-text {
  color: #bbb;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1;
}
.viewer-shell.hidden, .join-shell.hidden, .contrib-shell.hidden { display: none; }
#videoStage {
  min-height: 0;
  display: grid;
  gap: 0;
  position: relative; /* containing block for the absolutely-positioned split divider */
  background: #000 url("./statue2.webp") center / cover no-repeat;
}
/* Waiting lobby card, shown in the stage while no video is present.
   viewer.js keeps its text in sync with the current viewer state. */
.lobby-card {
  display: grid;
  place-items: center;
  color: #aaa;
  font-size: 18px;
  line-height: 1.5;
  text-align: center;
  padding: 24px;
  max-width: 520px;
}
#videoStage.single {
  grid-template-columns: 1fr;
  align-items: center;
  justify-items: stretch;
}
#videoStage.multi {
  grid-template-columns: var(--main-pct, 70%) calc(100% - var(--main-pct, 70%));
}
.main-slot, .side-slot { min-width: 0; min-height: 0; background: transparent; }
.main-slot {
  display: grid;
  align-items: center;
  justify-items: stretch;
  overflow: hidden;
}
#videoStage.single .viewer-tile,
.main-slot .viewer-tile {
  width: 100%;
  height: auto;
}
#videoStage.single .viewer-tile {
  justify-self: center;
  width: min(100%, calc((100vh - 42px) * 16 / 9));
  aspect-ratio: 16 / 9;
}
.side-slot {
  display: flex;
  flex-direction: column;
  gap: 4px;
  justify-content: center;
  align-items: stretch;
  overflow: hidden;
}
.side-slot .viewer-tile {
  flex: 0 1 min(calc(100% / var(--side-count, 1)), calc(var(--side-pct, 30vw) * 9 / 16));
}
/* Draggable divider between the main and side columns (viewer page).
   Centered exactly on the grid boundary via --main-pct; the 18px element is
   the hit area, the visible part is a thin line with a small grip pill.
   --divider-top/--divider-bottom are set by viewer.js so the bar matches the
   main video's vertical extent instead of floating over the wallpaper. */
.split-divider {
  position: absolute;
  top: var(--divider-top, 0);
  bottom: var(--divider-bottom, 0);
  left: var(--main-pct, 70%);
  width: 18px;
  margin-left: -9px;
  cursor: col-resize;
  touch-action: none;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
}
.split-divider::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  margin-left: -1px;
  background: rgba(255, 255, 255, .22);
  transition: background .15s;
}
.split-divider::after {
  content: '';
  width: 5px;
  height: 34px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .35);
  border: 1px solid rgba(255, 255, 255, .25);
  transition: background .15s;
}
.split-divider:hover::before,
.split-divider:focus-visible::before,
body.split-dragging .split-divider::before {
  background: #2ed573;
}
.split-divider:hover::after,
.split-divider:focus-visible::after,
body.split-dragging .split-divider::after {
  background: rgba(46, 213, 115, .8);
}
.split-divider:focus-visible { outline: none; }
/* Lock the cursor and text selection while a drag is in progress */
body.split-dragging,
body.split-dragging * {
  user-select: none !important;
  -webkit-user-select: none !important;
  cursor: col-resize !important;
}
.viewer-tile { position: relative; min-height: 0; background: transparent; }
.viewer-tile video { width: 100%; height: 100%; object-fit: contain; display: block; }
.viewer-tile .tile-controls {
  position: absolute;
  top: 6px;
  right: 6px;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.15s;
  z-index: 2;
}
.viewer-tile:hover .tile-controls { opacity: 1; }
.tile-controls button {
  width: 32px;
  height: 32px;
  border-radius: 6px;
  border: 1px solid rgba(255,255,255,.2);
  background: rgba(0,0,0,.55);
  color: #fff;
  font-size: 13px;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.tile-controls button:hover { background: rgba(0,0,0,.8); }
.tile-controls .btn-max {
  content: '⛶';
}
.tile-controls .btn-promote {
  font-size: 11px;
}
.tile-controls .btn-promote.active {
  background: rgba(0,0,0,.85);
  border-color: rgba(255,255,255,.4);
}
.tile-controls .btn-fs {
  font-size: 12px;
}
.max-overlay .tile-controls {
  position: absolute;
  top: 62px;
  right: 12px;
  opacity: 0;
  transition: opacity 0.15s;
  z-index: 2;
}
.max-overlay:hover .tile-controls { opacity: 1; }
.max-overlay .tile-controls button {
  width: 40px;
  height: 40px;
  font-size: 16px;
}
.max-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #000;
  display: grid;
  place-items: center;
}
.max-overlay.hidden { display: none; }
.max-tile {
  width: 100%;
  height: 100%;
  position: relative;
  background: #000;
}
.max-tile video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.max-tile .video-label {
  position: absolute;
  left: 16px;
  bottom: 70px;
  padding: 4px 10px;
  border-radius: 4px;
  background: rgba(0,0,0,.65);
  color: #ddd;
  font-size: 14px;
}
.close-max {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,.6);
  color: #fff;
  font-size: 18px;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.close-max:hover { background: rgba(255,255,255,.2); }

.contrib-shell {
  height: 100vh;
  display: grid;
  grid-template-columns: 1fr 300px;
  grid-template-rows: 1fr 56px;
  gap: 8px;
  padding: 8px;
  background: #050505;
}
.contrib-stage {
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
  gap: 8px;
  overflow: auto;
  background: #050505;
  border: 1px solid #222;
  border-radius: 6px;
  padding: 8px;
}
.contrib-empty {
  display: grid;
  place-items: center;
  color: #777;
  min-height: 100%;
  text-align: center;
  padding: 24px;
}
.contrib-tile {
  position: relative;
  min-height: 220px;
  background: #000;
  border: 1px solid #222;
  border-radius: 4px;
}
.contrib-tile video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.contrib-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  padding: 3px 8px;
  border: 1px solid #555;
  border-radius: 999px;
  background: rgba(0,0,0,.65);
  color: #bbb;
  font-size: 12px;
  font-weight: 700;
}
/* Audio-only sources (a shared microphone): a compact tile with an icon,
   the label, and an input-level meter instead of a video element. */
.contrib-tile.audio-tile {
  min-height: 150px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 16px;
  background: #101010;
}
.contrib-tile.audio-tile .audio-tile-icon {
  font-size: 36px;
  line-height: 1;
}
.contrib-tile.audio-tile .mic-meter {
  width: min(60%, 180px);
}
/* ON AIR: the host has selected this source — the audience sees it. */
.contrib-badge.onair {
  color: #d8ffd9;
  background: rgba(28,74,33,.92);
  border-color: #57a85f;
  animation: onairpulse 2s ease-in-out infinite;
}
@keyframes onairpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(87, 168, 95, .5); }
  50% { box-shadow: 0 0 0 6px rgba(87, 168, 95, 0); }
}
.contrib-tile.onair {
  border-color: #57a85f;
  box-shadow: 0 0 0 3px rgba(77, 141, 84, .3);
}
.slides-picker {
  margin: 0 0 10px;
}
.slides-picker button {
  width: 100%;
  padding: 8px 10px;
  border: 1px dashed #4a4a4a;
  border-radius: 6px;
  background: #101010;
  color: #ccc;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.slides-picker button:hover:not(:disabled) {
  border-color: #6aa8c4;
  color: #fff;
}
.slides-picker button:disabled {
  opacity: .55;
  cursor: wait;
}
/* Slide transition picker (only meaningful while a deck is shared, so it
   starts disabled). */
.slides-transition {
  margin: 0;
}
.slides-transition label {
  display: block;
  margin-bottom: 4px;
  color: #bbb;
  font-size: 12px;
}
.slides-transition select {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 8px;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #eee;
  font-size: 13px;
  outline: none;
}
.slides-transition select:disabled { opacity: .5; cursor: not-allowed; }
/* Fullscreen toggle for the live slide canvas (only meaningful while a
   deck is shared, so it starts disabled). */
.slides-fullscreen {
  margin-top: 10px;
}
.slides-fullscreen button {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 8px;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #eee;
  font-size: 13px;
  cursor: pointer;
}
.slides-fullscreen button:hover:not(:disabled) { border-color: #6aa8c4; color: #fff; }
.slides-fullscreen button:disabled { opacity: .5; cursor: not-allowed; }
/* Slide deck tile: the whole tile is the navigation surface (tap left half
   = previous slide, right half = next slide), so it gets a pointer cursor.
   The HUD overlay is purely visual (pointer-events: none). */
.slide-tile {
  cursor: pointer;
}
.slide-hud {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 8px;
  pointer-events: none;
}
.slide-hint {
  color: rgba(255,255,255,.5);
  font-size: 34px;
  font-weight: 700;
  line-height: 1;
  text-shadow: 0 1px 4px rgba(0,0,0,.8);
}
.slide-counter {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(0,0,0,.65);
  color: #ddd;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}
/* Fullscreen host for the slide canvas: a stable, always-in-document
   container in <body> (never touched by renderStage() re-renders) holding
   the live canvas + a HUD. The Slides panel's Fullscreen button makes it
   the fullscreen element; until then it is invisible and inert. */
.slide-fs-host {
  position: fixed;
  inset: 0;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
}
.slide-fs-host canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.slide-fs-host:fullscreen {
  opacity: 1;
  pointer-events: auto;
  background: #000;
  cursor: pointer;
}
/* ✕ in the corner of the fullscreen view: the primary way to exit without
   a keyboard (mobile). Lives in the host, so it is invisible and
   non-interactive unless the host is the fullscreen element. */
.slide-fs-exit {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1;
  width: 34px;
  height: 34px;
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 999px;
  background: rgba(0, 0, 0, .55);
  color: #eee;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  pointer-events: none;
  touch-action: manipulation;
}
.slide-fs-host:fullscreen .slide-fs-exit { pointer-events: auto; }
.slide-fs-host:fullscreen .slide-fs-exit:hover {
  background: rgba(0, 0, 0, .8);
  color: #fff;
}
/* Stable host for the dedicated fullscreen source player (see host.js).
   It remains a rendered 1×1 element until its container enters fullscreen;
   the source element used by WebGL is never moved or fullscreened. */
.video-fs-host {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  z-index: 0;
  overflow: hidden;
}
.video-fs-host:fullscreen {
  width: 100%;
  height: 100%;
  background: #000;
}
.video-fs-host video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.sources-panel {
  grid-column: 2;
  grid-row: 1 / 3;
  min-height: 0;
  overflow: auto;
  border: 1px solid #242424;
  background: #181818;
  padding: 10px;
}
.sources-panel h2 {
  font-size: 16px;
  margin: 0 0 8px;
}
.sources-panel .sources-hint {
  margin: 0 0 10px;
  color: #888;
  font-size: 12px;
  line-height: 1.45;
}
.sources-panel .sources-empty {
  color: #777;
  font-size: 13px;
  padding: 8px 0;
}
/* Slides section: deck upload + transition picker grouped together,
   visually separated from the live source list above. */
.sources-panel .slides-group {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid #2c2c2c;
}
/* Source rows reuse the host page's Sources-menu row/button styles
   (.menu-row + .src-btn); panel-specific bits are just the label ellipsis
   and the blue on-air glow on the Share button. */
#sourceList { margin: 0 0 8px; }
/* Section headers inside the source list (e.g. the "Audio" group for
   shared microphones). */
#sourceList h3 {
  margin: 10px 0 4px;
  padding: 0 6px;
}
#sourceList h3 + .menu-row { border-top: none; }
#sourceList .menu-row { font-size: 13px; }
#sourceList .menu-row:first-child { border-top: none; }
#sourceList .source-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* On air: the host is broadcasting this source. A blue glow around the
   Share button replaces the old "ON AIR" chip. */
#sourceList .src-btn.onair {
  box-shadow: 0 0 0 1px rgba(74, 158, 255, .9), 0 0 12px 2px rgba(74, 158, 255, .5);
}
.contrib-bar {
  grid-column: 1;
  grid-row: 2;
}

/* ==========================================================================
   Mobile optimizations
   Everything below is scoped to touch input and/or narrow viewports, so the
   desktop layout and behavior above are unaffected.
   ========================================================================== */

/* Touch devices have no :hover — controls that rely on it must stay visible */
@media (hover: none) and (pointer: coarse) {
  .tile-controls { opacity: 1; }
  .tile-controls button {
    width: 40px;
    height: 40px;
    font-size: 15px;
  }
  .max-overlay .tile-controls { opacity: 1; }
  .max-overlay .tile-controls button {
    width: 46px;
    height: 46px;
  }
  .close-max {
    width: 46px;
    height: 46px;
  }
  /* Contrib page: the slide tile is the whole navigation surface on mobile —
     drop the double-tap-zoom pause so tap-to-page feels instant — and give
     the source-row Share buttons a more thumb-friendly hit area. */
  .slide-tile { touch-action: manipulation; }
  #sourceList .src-btn { padding: 8px 14px; }
}

@media (max-width: 768px), (max-height: 500px) and (orientation: landscape) {
  /* Dynamic viewport height avoids mobile browser chrome clipping/jumping content.
     Browsers without dvh support simply ignore this line and keep the vh above. */
  .join-shell,
  .viewer-shell,
  .max-overlay {
    height: 100vh;
    height: 100dvh;
  }

  /* Landing page: stack the top row so the centered room bar and the
     host/contrib links don't collide on narrow screens. */
  .join-shell { padding-top: 108px; }
  .join-topbar {
    top: 12px;
    flex-direction: column;
    gap: 10px;
  }
  .join-topbar .join-links { position: static; }

  /* Chat becomes a full-width bottom sheet and tracks the on-screen keyboard
     via --kb-inset (set by viewer.js from the visualViewport API) */
  .chat-panel {
    left: 0;
    right: 0;
    width: 100%;
    max-width: none;
    min-width: 0;
    bottom: calc(42px + var(--kb-inset, 0px));
    border-radius: 12px 12px 0 0;
    max-height: min(60vh, 60dvh);
  }
  .chat-panel ul {
    max-height: calc(min(60vh, 60dvh) - 90px);
  }

  .viewer-bar {
    height: calc(42px + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}

@media (max-width: 768px) and (orientation: portrait) {
  /* Video stage: one main speaker full-width, everyone else as a horizontal
     filmstrip below (tap a thumbnail to swap it into the main slot).
     Portrait only — landscape keeps the side-by-side desktop-style layout,
     since there's no spare vertical room to stack a filmstrip under it. */
  #videoStage.multi {
    display: flex;
    flex-direction: column;
    grid-template-columns: none;
    min-width: 0;
  }
  #videoStage.multi .main-slot {
    flex: 1;
    min-height: 0;
    min-width: 0;
  }
  #videoStage.multi .side-slot {
    flex: 0 0 auto;
    flex-direction: row;
    justify-content: flex-start;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    height: 108px;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    padding: 4px 0;
  }
  #videoStage.multi .side-slot .viewer-tile {
    flex: 0 0 auto;
    width: 148px;
    min-width: 148px;
    height: 100%;
    cursor: pointer;
  }
  /* Promote button is redundant once tapping a filmstrip thumbnail does the same job */
  #videoStage.multi .side-slot .tile-controls .btn-promote {
    display: none;
  }

  /* The main/side split ratio is meaningless once side videos become a filmstrip */
  #videoStage .split-divider { display: none; }

  /* Portrait phones have vertical room to spare — make filmstrip thumbnails
     bigger and more legible than the landscape default set above. */
  #videoStage.multi .side-slot {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    height: 40%;
  }
  #videoStage.multi .side-slot .viewer-tile {
    width: 50%;
    min-width: 224px;
  }
  /* Exactly one secondary video (2 total) — give it the full filmstrip width
     instead of the multi-video 50% sizing */
  #videoStage.multi .side-slot .viewer-tile:only-child {
    width: 100%;
    min-width: 100%;
  }
}

/* ==========================================================================
   Recordings management
   ========================================================================== */

.recordings-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
}
.recordings-modal.hidden {
  display: none !important;
}
.recordings-modal-content {
  width: min(640px, calc(100vw - 32px));
  max-height: min(70vh, 560px);
  background: #1e1e1e;
  border: 1px solid #4a4a4a;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.recordings-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid #333;
  flex-shrink: 0;
}
.recordings-modal-header h3 {
  margin: 0;
  font-size: 16px;
}
.recordings-close-btn {
  background: none;
  border: 1px solid #444;
  border-radius: 6px;
  color: #aaa;
  cursor: pointer;
  padding: 2px 8px;
  font-size: 14px;
}
.recordings-close-btn:hover {
  background: #303030;
  color: #eee;
}
.recordings-list-wrap {
  overflow: auto;
  flex: 1;
}
#recordingsList {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 13px;
}
#recordingsList th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #1e1e1e;
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #888;
  padding: 10px 16px 8px;
  border-bottom: 1px solid #333;
}
#recordingsList th.rec-col-date {
  width: 120px;
}
#recordingsList th.rec-col-actions {
  width: 290px;
  text-align: right;
}
#recordingsList td {
  padding: 8px 16px;
  border-bottom: 1px solid #2a2a2a;
  vertical-align: middle;
}
#recordingsList tbody tr:last-child td {
  border-bottom: none;
}
.rec-filecell {
  display: flex;
  align-items: center;
  gap: 8px;
}
.rec-filename {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: monospace;
  font-size: 12px;
}
.rec-date {
  color: #888;
  font-size: 11px;
  white-space: nowrap;
}
.rec-indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.rec-indicator.uploaded {
  background: #2ed573;
}
.rec-indicator.processed {
  background: #48dbfb;
}
.rec-buttons {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
}
.rec-buttons button {
  padding: 4px 10px;
  font-size: 12px;
  border-radius: 5px;
  white-space: nowrap;
}
.rec-empty {
  color: #888;
  text-align: center;
  padding: 32px 16px;
  font-size: 14px;
}

/* Set-passwords modal (admin) — reuses the recordings-modal frame. Wide
   enough for a full access-link URL on one line in the access-link table. */
.passwords-modal-content {
  width: min(640px, calc(100vw - 32px));
  max-height: min(80vh, 640px);
}
.passwords-modal-body {
  padding: 16px;
  overflow: auto;
}
.passwords-hint {
  margin: 0 0 16px;
  color: #999;
  font-size: 13px;
}
.passwords-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
.passwords-row span {
  width: 100px;
  flex-shrink: 0;
  font-size: 13px;
  color: #ccc;
}
.passwords-row input {
  flex: 1;
  min-width: 0;
  padding: 0.55rem;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #eee;
  font-size: 14px;
}
.passwords-toggle {
  flex-shrink: 0;
  width: 52px;
  padding: 0.55rem 0;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #999;
  font-size: 12px;
  cursor: pointer;
}
.passwords-toggle:hover {
  color: #eee;
  border-color: #666;
}
.passwords-toggle.shown {
  color: #ffd54f;
}
#passwordsError {
  color: #ff8f8f;
  font-size: 13px;
  margin: 4px 0 8px;
}
.passwords-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 8px;
}
.passwords-actions button {
  padding: 8px 18px;
  border-radius: 6px;
}
/* Password-free access links (set-passwords modal) */
.passwords-section-title {
  margin: 20px 0 8px;
  font-size: 14px;
  color: #eee;
}
.access-links {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 8px;
  font-size: 12px;
}
.access-links th,
.access-links td {
  padding: 5px 6px;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid #2c2c2c;
}
.access-links th {
  color: #888;
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.access-links td:first-child {
  color: #ccc;
  white-space: nowrap;
  width: 1%;
}
.access-link-cell {
  min-width: 0;
}
.access-link-text {
  display: block;
  /* The full link on one line (the modal is sized to fit it); on a narrow
     viewport the cell shrinks and the link ellipsizes rather than wrapping.
     The copy button always carries the full URL. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #9fd0ff;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
}
.access-link-copy,
.access-link-toggle {
  padding: 4px 8px;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #ccc;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.access-link-copy:hover:not(:disabled),
.access-link-toggle:hover {
  color: #eee;
  border-color: #666;
}
.access-link-copy:disabled {
  opacity: 0.45;
  cursor: default;
}
.access-link-copy svg {
  display: block;
}
/* Brief checkmark state shown after a copy (overrides the hover color). */
.access-link-copy.copied,
.access-link-copy.copied:hover:not(:disabled) {
  color: #2ed573;
  border-color: #2ed573;
}

/* Access methods (set-passwords modal): one row with a radio per mode
   (password required / waiting room / admit all). */
.access-methods {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 8px;
  font-size: 12px;
}
.access-methods td {
  padding: 6px 8px;
  text-align: center;
  vertical-align: middle;
  border-bottom: 1px solid #2c2c2c;
  color: #ccc;
  white-space: nowrap;
}
.access-methods input[type="radio"] {
  cursor: pointer;
  margin-right: 6px;
}
/* Waiting-room admission notice (host page): a slim floating pill,
   Zoom-style, anchored above the control bar. It never dims or blocks the
   page — the host keeps working while they decide. One line: the
   requester, then Deny / Admit. It floats above the modals (z-index 1000)
   so a pending request can be answered without closing an open dialog. */
.access-dialog {
  position: fixed;
  bottom: 72px; /* 8px above the one-row control bar (it ends 64px up) */
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: calc(100vw - 24px);
  padding: 8px 8px 8px 16px;
  border: 1px solid #444;
  border-radius: 999px;
  background: #1e1e1e;
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
}
.access-dialog.hidden {
  display: none;
}
.access-dialog-text {
  min-width: 0;
  font-size: 14px;
  color: #eee;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.access-dialog-buttons {
  display: flex;
  gap: 6px;
}
.access-dialog-buttons button {
  padding: 5px 14px;
  border: 1px solid #444;
  border-radius: 999px;
  background: #0d0d0d;
  color: #ccc;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
}
.access-dialog-buttons button:hover {
  color: #eee;
  border-color: #666;
}
.access-dialog-buttons .access-admit {
  background: #1c4a2a;
  border-color: #2e7d4f;
  color: #c8f0d4;
}
.access-dialog-buttons .access-admit:hover {
  background: #226037;
}
/* Contributor requests render in a brighter palette so the host can tell
   the two apart at a glance. */
.access-dialog.access-dialog-contrib {
  background: #2a2a3f;
  border-color: #7a7ac0;
}
.access-dialog.access-dialog-contrib .access-dialog-text {
  color: #e6e6ff;
}
/* At-capacity indicator on the host control bar (the room has reached its
   viewer limit). */
.capacity-warning {
  color: #ffb347;
  font-size: 12px;
  white-space: nowrap;
  padding: 2px 8px;
  border: 1px solid #6b4e1f;
  border-radius: 10px;
  background: #2a2010;
}
/* The login page's contributor "request access" button (waiting room): a
   secondary full-width button under the Sign in form. */
.request-access-button {
  width: 100%;
  margin-top: 8px;
  padding: 0.6rem;
  border: 1px solid #444;
  border-radius: 6px;
  background: #0d0d0d;
  color: #ccc;
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
}
.request-access-button:hover:not(:disabled) {
  color: #eee;
  border-color: #666;
}
.request-access-button:disabled {
  opacity: 0.6;
  cursor: default;
}

/* Logout link on the viewer join screen */
.logout-link {
  display: inline-block;
  margin-top: 12px;
  color: #888;
  font-size: 13px;
  text-decoration: none;
}
.logout-link:hover {
  color: #ccc;
}

/* Confirm deletion modal */
.confirm-modal {
  position: fixed;
  inset: 0;
  z-index: 250;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
}
.confirm-modal.hidden {
  display: none !important;
}
.confirm-modal-content {
  width: min(400px, calc(100vw - 32px));
  background: #1e1e1e;
  border: 1px solid #4a4a4a;
  border-radius: 10px;
  padding: 20px 24px;
  text-align: center;
}
.confirm-modal-content p {
  margin: 0 0 16px;
  font-size: 14px;
  color: #ddd;
}
.confirm-modal-buttons {
  display: flex;
  gap: 10px;
  justify-content: center;
}
.confirm-modal-buttons button {
  padding: 8px 20px;
  font-size: 13px;
  border-radius: 6px;
}

/* Recording save dialog popup */
.save-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 180;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
}
.save-dialog-overlay.hidden {
  display: none !important;
}
.save-dialog {
  width: min(420px, calc(100vw - 32px));
  background: #202020;
  border: 1px solid #4a4a4a;
  border-radius: 10px;
  padding: 20px 24px;
}
.save-dialog h3 {
  margin: 0 0 16px;
  font-size: 15px;
}
.save-dialog-buttons {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}
.save-dialog-buttons button {
  flex: 1;
  padding: 8px 12px;
  font-size: 13px;
  border-radius: 6px;
  text-align: center;
}
.save-dialog-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  font-size: 13px;
  color: #bbb;
}
.save-dialog-checkbox input[type="checkbox"] {
  accent-color: #2ed573;
}
.save-dialog-cancel {
  width: 100%;
  padding: 8px 12px;
  font-size: 13px;
  border-radius: 6px;
  text-align: center;
}

/* Upload progress bar: fixed toast at the top of the page while a
   recording uploads. */
#uploadProgress {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  width: min(420px, calc(100vw - 32px));
  z-index: 400;
  display: none;
}
#uploadProgress.active {
  display: block;
}
.upload-progress-bar {
  height: 4px;
  border-radius: 2px;
  background: #333;
  overflow: hidden;
  margin-bottom: 4px;
}
.upload-progress-fill {
  height: 100%;
  width: 0%;
  background: #48dbfb;
  transition: width 0.2s ease;
}
.upload-progress-text {
  font-size: 11px;
  color: #aaa;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ==========================================================================
   Host page mobile fix
   Same underlying problem as the viewer had: the Sources/chat/pages/viewers
   popups anchor with a fixed min-width and no keyboard awareness, so on a
   narrow screen they get clipped or covered. This does not change the
   desktop grid layout (golden banner row, broadcast strip, recording
   canvas).
   ========================================================================== */

@media (max-width: 768px), (max-height: 500px) and (orientation: landscape) {
  .host-shell {
    height: 100vh;
    height: 100dvh;
  }

  /* Both the Sources menu and chat panel share the .menu popup pattern —
     turn them into full-width bottom sheets that track the on-screen
     keyboard via --kb-inset (set by host.js from the visualViewport API). */
  .control-bar .menu {
    position: fixed;
    left: 0;
    right: 0;
    width: 100%;
    max-width: none;
    min-width: 0;
    bottom: calc(52px + var(--kb-inset, 0px));
    border-radius: 12px 12px 0 0;
    max-height: min(60vh, 60dvh);
  }
  .control-bar .chat-panel ul {
    max-height: calc(min(60vh, 60dvh) - 90px);
  }

  /* Hide the keyboard-only screen-capture hint on narrow screens. */
  #screenHint {
    display: none;
  }
}

/* ==========================================================================
   Contrib page mobile fix
   The desktop layout is a two-column grid (stage + fixed 300px sources
   sidebar). On a narrow phone that leaves the stage — where the slide tile
   the presenter actually watches — a sliver. On narrow/short screens the
   sidebar becomes a full-width bottom sheet above the control bar (the same
   pattern as the host page's Sources menu and the viewer's chat panel),
   toggled by the Sources button in the bar and starting closed so the stage
   gets the full screen. The chat panel gets the viewer page's bottom-sheet +
   keyboard-tracking treatment. Desktop layout and behavior are unaffected.
   ========================================================================== */

.sources-toggle {
  display: none;
  flex-shrink: 0;
}

@media (max-width: 768px), (max-height: 500px) and (orientation: landscape) {
  .contrib-shell {
    height: 100vh;
    height: 100dvh;
    grid-template-columns: 1fr;
    grid-template-rows: 1fr auto;
    /* The control bar's rendered height on mobile; the bottom sheets below
       share it so they always sit exactly above the bar (including the
       home-indicator safe area). */
    --contrib-bar-h: calc(44px + env(safe-area-inset-bottom, 0px));
  }
  .contrib-stage {
    grid-column: 1;
    grid-row: 1;
    min-width: 0;
    /* One full-width column. (The desktop auto-fit minmax() becomes circular
       on a stage narrower than 420px — the track's 100% fallback grows the
       stage past the viewport — so pin an explicit single track.) */
    grid-template-columns: 1fr;
    /* A single source (the slide tile) fills the whole stage; several
       sources keep a usable size, stack, and the stage scrolls. */
    grid-auto-rows: minmax(220px, 1fr);
  }
  .contrib-bar {
    grid-column: 1;
    grid-row: 2;
    height: var(--contrib-bar-h);
    padding: 4px 8px;
  }
  .sources-toggle {
    display: inline-block;
  }

  /* Sources sidebar → full-width bottom sheet above the bar, tracking the
     on-screen keyboard via --kb-inset (set by contrib.js from the
     visualViewport API). */
  .sources-panel {
    position: fixed;
    left: 0;
    right: 0;
    width: 100%;
    max-width: none;
    bottom: calc(var(--contrib-bar-h) + var(--kb-inset, 0px));
    max-height: min(60vh, 60dvh);
    border-radius: 12px 12px 0 0;
    box-shadow: 0 -8px 26px rgba(0, 0, 0, .55);
    z-index: 20;
  }

  /* Chat: the viewer's global mobile rule above already makes .chat-panel a
     full-width sheet, but the base .menu is position:absolute — which inside
     the control bar would anchor the sheet to the bar instead of the
     viewport. Pin it to the viewport above the bar. */
  .contrib-bar .chat-panel {
    position: fixed;
    bottom: calc(var(--contrib-bar-h) + var(--kb-inset, 0px));
  }
}

/* Remote source indicator dot on the Sources button */
.remote-dot {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #ffa94d;
  border: 2px solid #181818;
}

/* ==========================================================================
   Host help modal
   ========================================================================== */
.help-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
}
.help-modal.hidden { display: none !important; }
.help-modal-content {
  width: min(720px, calc(100vw - 32px));
  max-height: min(80vh, 640px);
  background: #1e1e1e;
  border: 1px solid #4a4a4a;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.help-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid #333;
  flex-shrink: 0;
}
.help-modal-header h3 {
  margin: 0;
  font-size: 16px;
}
.help-body {
  overflow: auto;
  flex: 1;
  padding: 14px 18px 18px;
  font-size: 13.5px;
  line-height: 1.5;
  color: #ddd;
}
.help-body h4 {
  margin: 18px 0 6px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #8fd69a;
}
.help-body h4:first-child { margin-top: 0; }
.help-body p, .help-body li { margin: 0 0 8px; }
.help-body ul, .help-body ol { margin: 0 0 8px; padding-left: 22px; }
.help-body li:last-child, .help-body p:last-child { margin-bottom: 0; }
.help-body strong { color: #eee; }
.help-body a { color: #8fd69a; }

/* --- Rooms ----------------------------------------------------------- */

/* Room picker on the viewer join card (landing page). Matches the card's
   input styling; the label text is dim and the dropdown fills the row. */

/* "Back to landing page" link on the right of the active pages' bottom
   bars. Dim by default, brightens on hover (the bars are dark). */
.landing-link {
  margin-left: auto;
  flex-shrink: 0;
  color: #888;
  font-size: 13px;
  text-decoration: none;
  white-space: nowrap;
}
.landing-link:hover {
  color: #ccc;
}
.control-bar .landing-link {
  margin-left: 0;
}

/* Recordings modal: the all-rooms checkbox (admin only) and the room
   column it reveals. */
.recordings-all-rooms {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  font-size: 13px;
  color: #aaa;
  border-bottom: 1px solid #2a2a2a;
  background: #181818;
  position: sticky;
  top: 0;
  z-index: 2;
}
#recordingsList th.rec-col-room {
  width: 110px;
}
#recordingsList td.rec-room {
  color: #9ab;
  font-size: 12px;
  word-break: break-all;
}

/* Passwords modal: the global-admin note (shown only to an admin in the
   main room, next to the admin field). */
.passwords-note {
  margin: 4px 0 12px;
  font-size: 12px;
  line-height: 1.4;
  color: #999;
}
