:root{
  /* Light theme for the projected viewer page — a townhall room stays lit,
     so a dark background would wash out under normal room lighting. */
  --ink:      #F3F6F8;
  --panel:    #FFFFFF;
  --rule:     #D6DEE4;
  --text:     #10202B;
  --dim:      #5C6E79;
  --tally:    #86BC25; /* Pantone 365 C */
  --tally-off:#DCE9BE;
  --tally-tint: #F0F6E3; /* soft tint of --tally, used behind the live/interim line */

  --gap: 10px;
  --pad: clamp(14px, 1.5vw, 30px);

  /* /speak page palette — kept dark on purpose (handheld phone screen, not
     projected), fully separate from the viewer's light theme above. Uses
     its own --speak-bg/--speak-text rather than --ink/--text, which are
     scoped to the projected viewer page. */
  --speak-bg: #0b0f14;
  --speak-text: #f2f5f8;
  --accent: #4fd1c5;
  --text-dim: #9aa7b4;
  --box-bg: #151b23;
  --box-border: #2a333e;
  --error: #f85149;
}

*{ box-sizing:border-box; margin:0; padding:0; }

html,body{ height:100%; }

body{
  background: var(--ink);
  color: var(--text);
  font-family:"IBM Plex Sans",system-ui,sans-serif;
  -webkit-font-smoothing: antialiased;
}

body.speak-body{
  background: var(--speak-bg);
  color: var(--speak-text);
}

/* ── Viewer page ("floor" + channel grid) ───────────────────────────────── */

body.viewer{
  display:flex;
  flex-direction:column;
  gap: var(--gap);
  padding: var(--gap);
  overflow:hidden;
}

.floor{
  flex: 0 0 27%;
  position:relative;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--tally);
  box-shadow: 0 1px 2px rgba(16,32,43,.05), 0 8px 24px rgba(16,32,43,.06);
  padding: var(--pad);
  display:flex;
  flex-direction:row;
  gap: var(--pad);
  min-height:0;
}
/* Always-on column, capped at a fixed width share rather than "100%
   height, real aspect ratio" — at the floor box's actual height a 16:9
   box computes very wide and crowds the transcript. Centering within a
   capped share instead means it shrinks to fit, never dictates the row's
   proportions. */
.floor-video-col{
  flex: 0 0 20%;
  min-width:0;
  display:flex;
  align-items:center;
  justify-content:center;
}
.floor-video-placeholder{
  width:100%;
  max-height:100%;
  aspect-ratio: 16 / 9;
  background: repeating-linear-gradient(135deg, #1b232b, #1b232b 8px, #222c35 8px, #222c35 16px);
  border: 1px solid var(--rule);
  object-fit: cover; /* fills the box rather than letterboxing on a near-16:9 camera feed */
}
/* Solo layouts (exactly one thing full-screen) move this same element
   (viewer.js) into whatever text container is showing, floated in the
   top-left corner so the transcript/translation text wraps around it —
   not a rigid side-by-side column split, which is what the row layout
   above does for every other view. Fixed width instead of the row
   layout's height-driven sizing, since there's no fixed-height row to
   match anymore. */
.floor-video-placeholder.floated{
  float:left;
  width: clamp(160px, 22vw, 280px);
  max-height:none;
  margin: 0 clamp(16px,2vw,28px) 14px 0;
}
/* Mobile has its own separate, always-visible video row (.mobile-video-row
   below) — if the desktop element above ends up moved inside a text
   container (.body/.ch-final) by viewer.js, hide it here rather than
   showing both at once. */
@media (max-width: 640px){
  .body > .floor-video-placeholder, .ch-final > .floor-video-placeholder{
    display:none;
  }
}
.floor-main{
  flex:1 1 60%;
  min-width:0;
  min-height:0;
  display:flex;
  flex-direction:column;
}
/* Phone-only row (shown instead — see the mobile media query below), a
   sibling of .floor and .grid rather than nested inside .floor: on a
   phone, floor needs a generous height budget for the transcript alone,
   and squeezing video into that same budget left neither one enough
   room. */
.mobile-video-row{
  display:none;
}
/* Shown by default on desktop (Q toggles .hidden — see viewer.js).
   Unlike the video column, this doesn't need a width cap — a QR is
   square and small enough that sizing it by height alone (no fixed
   share of the row) never crowds the transcript the way an uncapped
   16:9 video would. Plain image, no padding/border/background chrome
   around it. Never shown on mobile regardless of the toggle — see the
   mobile media query below, which forces display:none unconditionally. */
.floor-qr{
  display:flex;
  flex: 0 0 auto;
  align-self:stretch;
}
.floor-qr.hidden{ display:none; }
.floor-qr img{
  height:100%;
  width:auto;
  aspect-ratio: 1 / 1;
}

/* 3 channels total (en/ms/zh) — default is a single row of 3. When the
   presenter's language matches one of them, that channel is hidden
   entirely (.ch.hidden below) rather than shown with a placeholder, and
   the remaining 2 stack full-width instead of squeezing into 2 narrow
   columns (.grid.two-row) — same shape as the floor box above them.
   viewer.js refits each visible box's font size off its own new height
   whenever this toggles. */
.grid{
  flex:1 1 auto;
  display:grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 1fr;
  gap: var(--gap);
  min-height:0;
}
.grid.two-row{
  grid-template-columns: 1fr;
  grid-template-rows: 1fr 1fr;
}

.ch{
  position:relative;
  background: var(--panel);
  border: 1px solid var(--rule);
  box-shadow: 0 1px 2px rgba(16,32,43,.05), 0 8px 24px rgba(16,32,43,.06);
  padding: var(--pad);
  display:flex;
  flex-direction:column;
  min-height:0;
  min-width:0;
}
.ch.hidden{
  display:none;
}

.tag{
  flex:0 0 auto;
  display:flex;
  align-items:baseline;
  gap: 12px;
  margin-bottom: .6em;
  font-family:"IBM Plex Mono",monospace;
  font-size: clamp(13px, 1vw, 19px);
  letter-spacing:.18em;
  text-transform:uppercase;
  color: var(--dim);
}

.tag .num{
  color:#fff;
  background: var(--tally);
  padding: .2em .6em;
  letter-spacing: .1em;
}

.tag .native{
  letter-spacing:.04em;
  text-transform:none;
  color:#3F5A6C;
}

.tag .dot{
  width:7px; height:7px; border-radius:50%;
  background: var(--tally-off);
  margin-left:auto;
  flex:0 0 auto;
  align-self:center;
}
.tag .dot.live{ background: var(--tally); }

/* Floor's live indicator: a 3-bar equalizer instead of a plain dot — same
   pattern as dc_mp_web's Stream page "Speaking Now" panel (EqualizerBars).
   Idle: bars rest at different low heights, no animation. Live: each bar
   bounces scaleY on its own staggered duration for an organic (not
   robotic/synced) feel. */
.tag .eq{
  display:flex;
  align-items:center;
  gap:3px;
  height:14px;
  margin-left:auto;
  flex:0 0 auto;
}
.tag .eq i{
  display:block;
  width:3px;
  height:100%;
  border-radius:1.5px;
  background: var(--tally);
  transform-origin:center;
  transition: transform .3s ease;
}
.tag .eq:not(.live) i:nth-child(1){ transform: scaleY(.4); }
.tag .eq:not(.live) i:nth-child(2){ transform: scaleY(.7); }
.tag .eq:not(.live) i:nth-child(3){ transform: scaleY(.25); }
@keyframes eqBounce{
  0%,100%{ transform: scaleY(.3); }
  50%{ transform: scaleY(1); }
}
.tag .eq.live i:nth-child(1){ animation: eqBounce .8s ease-in-out infinite; }
.tag .eq.live i:nth-child(2){ animation: eqBounce 1.1s ease-in-out infinite; }
.tag .eq.live i:nth-child(3){ animation: eqBounce .7s ease-in-out infinite; }

.body{
  flex:1 1 auto;
  min-height:0;
  overflow:hidden;
  line-height:1.42;
  font-weight:400;
  /* no transition on text — a fade reads as lag on a projector */
}

/* Channels split into two stacked areas: the still-live/changing
   translation up top (.ch-interim, always italic — that's how a viewer
   knows it hasn't settled), a divider, then the locked history below
   (.ch-final) that each committed sentence drops down into. Font size on
   both is set inline by fitLinesBox() (height-based, sized off .ch-final's
   line count), so this clamp() is only a pre-JS-load fallback. */
.ch .body{
  display:flex;
  flex-direction:column;
  font-size: clamp(24px, 2.7vw, 50px);
  font-weight:500;
  color: var(--text);
}
/* The live/still-translating line gets its own tinted card — reads as an
   active, distinct zone rather than a lone italic line floating above a
   plain rule. Collapses to a transparent placeholder (same reserved
   height, so nothing jumps) when there's nothing live to show. */
.ch-interim{
  flex:0 0 auto;
  font-style: italic;
  min-height: 1.42em;
  background: var(--tally-tint);
  padding: .5em .7em;
  margin-bottom: .7em;
}
.ch-interim:empty{
  background: transparent;
}
/* Spacer only now — the interim card's own background/margin does the
   visual separating, a flat gray rule underneath it read as a stray line. */
.ch-divider{
  display:none;
}
/* Final history: newest committed sentence appears at the TOP and pushes
   older ones down, oldest fading off the bottom (viewer.js's addLineTo()
   inserts at the front here to match) — the opposite direction from
   floor, which grows upward from the bottom instead. The newest line is
   bolded so the most recent sentence stays the clear focal point. */
.ch-final{
  flex:1 1 auto;
  min-height:0;
  overflow:hidden;
  display:flex;
  flex-direction:column;
  justify-content:flex-start;
  gap: 1em; /* one blank line's worth of breathing room between sentences */
}
.ch-final .floor-line:first-child{
  font-weight: 600;
}

/* Floor shows finals only and never clears — it's always "done"-styled,
   no live/dim variant needed. Text wraps normally left-to-right within a
   sentence; a new line only starts between sentences. It clips from the
   TOP once there are more lines than fit: a flex column with
   justify-content:flex-end keeps the newest .floor-line pinned to the
   box's bottom edge. viewer.js keeps at most FLOOR_TARGET_LINES elements
   here — when a new one would exceed that, the oldest fades out first
   (.exiting) instead of just vanishing when clipped. */
.floor .body{
  display:flex;
  flex-direction:column;
  justify-content:flex-end;
  font-size: clamp(30px, 3.8vw, 74px);
  font-weight:500;
  color: var(--text);
}
.floor-line{
  display:block;
  transition: opacity 0.3s ease;
}
.floor-line.exiting{
  opacity: 0;
}
/* Floor-only: the words just appended to the growing line get a brief
   highlight that fades on its own (a running spotlight a viewer's eye can
   follow) — this alone signals "still live," no separate italic treatment
   needed (the two together read as fighting each other). Pure CSS
   animation — plays once automatically when the span is created, no JS
   cleanup needed. */
@keyframes newWordFade{
  0%   { background: rgba(134, 188, 37, 0.45); }
  100% { background: transparent; }
}
.new-word{
  animation: newWordFade 1100ms ease-out forwards;
  border-radius: 3px;
}

/* script-specific tuning: SC needs less line-height than the Latin default */
#zh .body{ font-family:"Noto Sans SC","IBM Plex Sans",sans-serif; line-height:1.34; }

.diag{
  position:fixed; right:14px; bottom:14px;
  background:rgba(255,255,255,.96);
  box-shadow: 0 2px 14px rgba(16,32,43,.15);
  border:1px solid var(--rule);
  padding:10px 14px;
  font-family:"IBM Plex Mono",monospace;
  font-size:14px;
  line-height:1.7;
  color:var(--dim);
  display:none;
  white-space:pre;
}
.diag.show{ display:block; }
.diag b{ color:var(--tally); font-weight:400; }

/* Announcement overlay — server-driven (frame.popup), operator-controlled
   only, see index.html. Sits above everything, including the layout
   picker, since an operator showing this mid-show wants it seen
   immediately regardless of what a viewer currently has open. */
.popup-overlay{
  position:fixed;
  inset:0;
  z-index:1000;
  align-items:center;
  justify-content:center;
  padding: 5vw;
  background: rgba(16,32,43,.72);
}
/* Our own display:flex below would otherwise beat the browser's default
   [hidden]{display:none} — same specificity, but author CSS always wins
   over the UA stylesheet regardless. Gate it on :not([hidden]) instead
   of setting display unconditionally. */
.popup-overlay:not([hidden]){ display:flex; }
.popup-box{
  max-width: 900px;
  background: var(--panel);
  border: 3px solid var(--tally);
  border-radius: 20px;
  padding: clamp(24px, 4vw, 56px);
  box-shadow: 0 20px 60px rgba(16,32,43,.35);
  font-size: clamp(20px, 3.2vw, 42px);
  font-weight: 600;
  line-height:1.4;
  color: var(--text);
  text-align:center;
  white-space: pre-wrap;
}


@media (prefers-reduced-motion: reduce){
  .tag .dot{ box-shadow:none !important; }
  .tag .eq.live i{ animation:none !important; }
  .new-word{ animation:none !important; background: var(--tally-tint) !important; }
}

/* ── Compact viewer layout ───────────────────────────────────────────────
   Phone portrait (narrow width) AND phone landscape (short height, which
   can still be >700px wide on some phones) both fail with the venue-screen
   2x2 grid — not enough room for 5 boxes side by side. Below either
   threshold, stack everything in one column instead — still filling the
   screen exactly like the venue layout does (no scroll): video, floor, and
   every channel box are equal flex items of the same column, so they all
   end up the same height. Text auto-shrinks (viewer.js's fit()) to
   whatever each row actually gets. */
@media (max-width: 700px), (max-height: 520px){
  /* Floor, video, and every channel box are equal flex items of the same
     body.viewer column — all get flex:1 1 0, so they all end up the same
     height. .grid switches to display:contents so its .ch children
     become direct flex items of body.viewer instead of a nested grid —
     that also means a hidden channel (.ch.hidden, when it matches the
     speaker's own language) just removes one flex item and the rest
     expand automatically; no separate "two-row" sizing needed here. */
  .floor{ flex:1 1 0; min-height:0; }
  .floor-video-col{ display:none; }
  /* Never shown on mobile, regardless of the Q toggle's .hidden class —
     a phone screen is too small to spare room for it, and there's no
     keyboard to press Q with anyway. */
  .floor-qr{ display:none; }

  .mobile-video-row{
    order: -1; /* renders above .floor, regardless of DOM position */
    display:flex;
    align-items:center;
    justify-content:center;
    flex: 1 1 0;
    min-height:0;
  }
  .mobile-video-row .floor-video-placeholder{
    width:auto;
    height:100%;
    max-width:100%;
  }

  .grid{ display:contents; }

  .ch{ flex: 1 1 0; min-height:0; }

  .floor .body{ font-size: clamp(18px, 6.5vw, 56px); }
  .ch    .body{ font-size: clamp(13px, 5.5vw, 40px); }

  /* .tag never wrapped as flex ITEMS before — on a narrow box, that meant
     the browser fell back to wrapping mid-TEXT inside whichever span ran
     out of room ("CH 1" splitting into "CH" / "1" on separate lines).
     flex-wrap lets whole spans move to a new line intact instead. */
  .tag{
    flex-wrap: wrap;
    row-gap: 2px;
    gap: 6px 10px;
    font-size: clamp(11px, 3vw, 14px);
  }
  .tag .dot{ margin-left:0; }
}

/* Very short landscape (phone lying flat) — trim padding/gaps further so
   more of the limited height goes to actual text instead of chrome. No
   min-heights here on purpose: the fill-exactly-and-shrink-text model
   above already handles this, a min-height would just force a scrollbar
   back in at the extreme end. */
@media (max-height: 420px){
  :root{ --gap: 6px; --pad: clamp(8px, 2vw, 16px); }
  .tag{ font-size: clamp(9px, 2.6vw, 12px); }
}

/* ── Layout picker ───────────────────────────────────────────────────────
   Shown on every visit (viewer.js), light-only like the rest of this page
   — no dark-mode block, deliberately, so it can't drift from the app's
   actual palette. Every option applies on click (no separate confirm
   step, no persistent radio state) — see the body.view-* rules below. */

/* Always visible (unlike .diag, which is hidden until D is pressed) —
   this is the primary way phone viewers reopen the picker, since
   keyboard shortcuts aren't discoverable/usable on a touchscreen. */
.picker-reopen{
  position:fixed; left:14px; bottom:14px;
  z-index:900;
  width:40px; height:40px;
  background: var(--panel);
  border: 1px solid var(--rule);
  box-shadow: 0 2px 8px rgba(16,32,43,.12);
  color: var(--dim);
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  padding:0;
}
.picker-reopen:hover{ color: var(--tally); border-color: color-mix(in srgb, var(--tally) 40%, var(--rule)); }
.picker-reopen svg{ width:19px; height:19px; }

.picker-backdrop{
  position:fixed; inset:0;
  z-index:1000;
  background: rgba(16,32,43,.55);
  backdrop-filter: blur(3px);
  display:none;
  align-items:center;
  justify-content:center;
  padding:24px;
}
.picker-backdrop.show{ display:flex; }

.picker-modal{
  background: var(--panel);
  border: 1px solid var(--rule);
  box-shadow: 0 2px 6px rgba(16,32,43,.06), 0 32px 80px rgba(16,32,43,.20);
  width: min(920px, 100%);
  max-height: 92vh;
  overflow-y: auto;
}

.picker-head{
  padding: clamp(28px,5vw,42px) clamp(28px,5vw,42px) 24px;
}
.picker-eyebrow{
  display:flex;
  align-items:center;
  gap:8px;
  font-family:"IBM Plex Mono",monospace;
  font-size:11.5px;
  letter-spacing:.16em;
  text-transform:uppercase;
  color: var(--dim);
  font-weight:500;
  margin:0 0 16px;
}
.picker-dot{
  width:7px; height:7px;
  background: var(--tally);
  flex:0 0 auto;
}
.picker-modal h1{
  font-size: clamp(27px,4vw,36px);
  font-weight:600;
  letter-spacing:-.01em;
  margin:0 0 11px;
  line-height:1.15;
}
.picker-sub{
  color: var(--dim);
  font-size:16px;
  line-height:1.55;
  margin:0;
  max-width:52ch;
}

.picker-options{
  display:grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 36px;
  padding: 0 clamp(28px,5vw,42px) 4px;
}

/* Step 2 (language pick for "Transcript + one") — swaps in for
   .picker-options rather than sitting inside a shrinking sub-picker, so
   it gets the same full-width, legible treatment as every other choice
   in this popup. */
.picker-step2{
  display:none;
  flex-direction:column;
  padding: 0 clamp(28px,5vw,42px) 8px;
}
.picker-modal.step-lang .picker-options{ display:none; }
.picker-modal.step-lang .picker-step2{ display:flex; }

.picker-back{
  align-self:flex-start;
  font-family:inherit;
  font-size:13px;
  color: var(--dim);
  background:none;
  border:none;
  cursor:pointer;
  padding:0;
  margin-bottom:14px;
}
.picker-back:hover{ color: var(--text); }
.picker-suggested{ margin-left:auto; }
.picker-group{ display:flex; flex-direction:column; }
.picker-group-label{
  font-family:"IBM Plex Mono",monospace;
  font-size:11px;
  letter-spacing:.12em;
  text-transform:uppercase;
  color: var(--dim);
  padding: 4px 4px 12px;
}
.picker-opt{
  position:relative;
  display:flex;
  align-items:center;
  gap:18px;
  text-align:left;
  background:none;
  border:none;
  border-top: 1px solid var(--rule);
  padding:18px 4px;
  cursor:pointer;
  font-family:inherit;
  color:inherit;
  width:100%;
}
.picker-group .picker-opt:last-of-type{ border-bottom: 1px solid var(--rule); }
.picker-opt:hover .picker-label{ color: var(--tally); }

.picker-diagram{
  flex:0 0 auto;
  width:76px; height:56px;
  background: var(--ink);
  border: 1px solid var(--rule);
  display:flex;
  flex-direction:column;
  gap:4px;
  padding:5px;
}
.pd-floor{
  background: var(--panel);
  border: 1px solid var(--rule);
  border-left: 2px solid var(--tally);
  flex:0 0 auto;
  display:flex;
  align-items:center;
  padding:0 4px;
}
.pd-floor i{ display:block; height:2px; width:60%; background: color-mix(in srgb, var(--text) 30%, transparent); }
.pd-row{ display:flex; gap:3px; flex:1 1 auto; min-height:0; }
.pd-ch{
  background: var(--panel);
  border: 1px solid var(--rule);
  flex:1 1 auto;
  display:flex;
  flex-direction:column;
  justify-content:center;
  gap:2px;
  padding:3px;
}
.pd-ch i{ display:block; height:2px; background: color-mix(in srgb, var(--text) 22%, transparent); }
.pd-ch i:first-child{ background: var(--tally); }

.picker-text{ flex:1 1 auto; min-width:0; }
.picker-label{
  font-weight:600;
  font-size:17px;
  display:flex;
  align-items:center;
  gap:8px;
  transition: color .15s ease;
}
.picker-desc{
  font-size:14px;
  color: var(--dim);
  line-height:1.45;
  margin-top:3px;
}
.picker-tag{
  font-family:"IBM Plex Mono",monospace;
  font-size:10px;
  letter-spacing:.08em;
  text-transform:uppercase;
  color: var(--tally);
  background: var(--tally-tint);
  padding:2px 8px;
  font-weight:600;
}

@media (max-width: 640px){
  .picker-backdrop{ padding:0; align-items:flex-end; }
  .picker-modal{
    width:100%;
    max-height: 88vh;
    border-left:none; border-right:none; border-bottom:none;
  }
  .picker-options{ grid-template-columns: 1fr; gap:0; }
  .picker-group:last-child{ margin-top:18px; }
  .picker-opt{ padding:14px 2px; gap:12px; }
  .picker-diagram{ width:52px; height:38px; }
  .picker-label{ font-size:14.5px; }
  .picker-desc{ font-size:12.5px; }
}

/* ── Layout picker view modes ─────────────────────────────────────────────
   Applied as a class on <body> by viewer.js when a picker option is
   clicked. No class = "Everything" — today's normal dynamic behavior,
   untouched. ID selectors here give these enough specificity to override
   the existing per-channel .hidden / .grid.two-row logic regardless of
   its current state, so the user's explicit choice always wins. */
/* Solo layouts (exactly one thing full-screen): floor's box is hidden
   entirely for the channel-only views, and its video-column is hidden for
   the transcript-only view — in both cases the video moves (viewer.js)
   into whatever text container IS showing instead, floated in its
   top-left corner so text wraps around it. See .floor-video-placeholder
   .floated above for the video's own styling in this mode. */
body.view-floor .grid{ display:none; }
body.view-floor .floor{ flex:1 1 auto; }
body.view-floor .floor-video-col{ display:none; }
body.view-floor .floor-main{ flex:1 1 auto; }
body.view-floor .floor .body{ display:block; }
body.view-floor .floor .body .floor-line{ margin-bottom:1em; }

body.view-en .floor, body.view-ms .floor, body.view-zh .floor{ display:none; }
body.view-en .grid, body.view-ms .grid, body.view-zh .grid{
  flex:1 1 auto;
  grid-template-columns:1fr;
  grid-template-rows:1fr;
}
body.view-en #ms, body.view-en #zh,
body.view-ms #en, body.view-ms #zh,
body.view-zh #en, body.view-zh #ms{ display:none; }
body.view-en .ch-final, body.view-ms .ch-final, body.view-zh .ch-final{
  display:block;
  gap:0;
}
body.view-en .ch-final .floor-line, body.view-ms .ch-final .floor-line, body.view-zh .ch-final .floor-line{
  margin-bottom:1em;
}
/* The interim/divider strip above .ch-final doesn't fit the "text wraps
   around a floated video" composition — the video sits inside .ch-final
   itself in this mode instead. */
body.view-en .ch-interim, body.view-ms .ch-interim, body.view-zh .ch-interim{ display:none; }

body.view-floor-en .grid, body.view-floor-ms .grid, body.view-floor-zh .grid{
  grid-template-columns:1fr;
  grid-template-rows:1fr;
}
body.view-floor-en #ms, body.view-floor-en #zh,
body.view-floor-ms #en, body.view-floor-ms #zh,
body.view-floor-zh #en, body.view-floor-zh #ms{ display:none; }

/* ── Speak page ──────────────────────────────────────────────────────────── */

.speak-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 24px;
  text-align: center;
}

.speak-status {
  font-size: 36px;
  font-weight: 700;
}
.speak-status.idle { color: var(--text-dim); }
.speak-status.connecting { color: var(--accent); }
.speak-status.live { color: #3fb950; }
.speak-status.error { color: var(--error); }

.muted-badge {
  font-family: "IBM Plex Mono", monospace;
  font-size: 15px;
  font-weight: 600;
  color: var(--error);
}

.mic-select {
  font-size: 16px;
  font-weight: 500;
  padding: 12px 16px;
  border-radius: 10px;
  border: 2px solid var(--box-border);
  background: var(--box-bg);
  color: var(--speak-text);
  width: 100%;
  max-width: 340px;
  cursor: pointer;
}

.speak-start-btn {
  font-size: 28px;
  font-weight: 600;
  padding: 26px 40px;
  border-radius: 14px;
  border: none;
  background: var(--accent);
  color: #06201d;
  cursor: pointer;
}
.speak-start-btn:active { opacity: 0.85; }

.speak-stop-btn {
  font-size: 22px;
  font-weight: 600;
  padding: 16px 34px;
  border-radius: 12px;
  border: 2px solid var(--error);
  background: transparent;
  color: var(--error);
  cursor: pointer;
}
.speak-stop-btn:active { opacity: 0.85; }

.lang-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  width: 100%;
  max-width: 420px;
}

.lang-btn {
  font-size: 25px;
  font-weight: 600;
  padding: 24px 14px;
  border-radius: 12px;
  border: 2px solid var(--box-border);
  background: var(--box-bg);
  color: var(--speak-text);
  cursor: pointer;
}
.lang-btn.active {
  border-color: var(--accent);
  background: rgba(79, 209, 197, 0.15);
  color: var(--accent);
}
/* Portuguese (first button) spans both columns — it's the primary language. */
.lang-btn:first-child { grid-column: 1 / -1; }

.popup-textarea {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  cursor: text;
  resize: vertical;
  max-width: 420px;
}
.popup-btn-row {
  display: flex;
  gap: 12px;
}
.popup-btn-row button.active {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
.popup-btn-row button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
