/* ══════════════════════════════════════════════════════════════════
   Voice Mode — NON-BLOCKING floating pill (2026-05-17 redesign).

   Mirrors the file-viewer drawer pattern (file-viewer.css):
     - container is pointer-events: none with safe-area padding
     - panel is pointer-events: auto and slides into view
     - NO backdrop, NO body overflow lock
     - page underneath remains scrollable / clickable / typeable

   Layout breakpoints:
     - phone (≤480px):   top-pinned full-width pill, ~64px collapsed
     - tablet (481-768): top-right floating panel, 360px × ~88px
     - desktop (>768px): top-right floating panel, 400px × ~96px

   States:
     - collapsed (default): pill shows speaker/status/controls only
     - expanded (tap shell): transcript + dispatch stack + error panel
       slide down below the pill, still non-blocking, capped at 50vh

   z-index 350 keeps the pill above file-viewer (300) but below auth
   modals / tag drawers. ══════════════════════════════════════════ */

.voice-mode-overlay {
  position: fixed;
  top: 0;
  right: 0;
  /* Container hugs the panel so clicks outside fall through to Floor / Chat
     underneath — the whole point of the redesign. Width auto + pointer-events
     none means the container itself never intercepts a click. */
  width: auto;
  z-index: 350;
  pointer-events: none;
  padding-top: env(safe-area-inset-top);
  padding-right: env(safe-area-inset-right);
  font-family: var(--font-body, "Outfit", "Inter", sans-serif);
  color: #f0e8d8;
}

.voice-mode__shell {
  pointer-events: auto;
  width: 400px;
  max-width: calc(100vw - 32px);
  margin: 16px;
  display: flex;
  flex-direction: column;
  background: #1a1a1a;
  border: 1px solid #d4a574;
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  overflow: hidden;
  /* Slide-in animation: transform-only so we don't repaint anything. */
  transform: translateY(-110%);
  opacity: 0;
  transition: transform 200ms ease-out, opacity 200ms ease-out;
  will-change: transform, opacity;
}

.voice-mode-overlay[data-open="true"] .voice-mode__shell {
  transform: translateY(0);
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .voice-mode__shell { transition: none; }
}

.voice-mode__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  background: linear-gradient(180deg, #221d18 0%, #1a1a1a 100%);
  cursor: pointer;
  min-height: 64px;
  box-sizing: border-box;
}

.voice-mode__header:focus-visible {
  outline: 2px solid #d4a574;
  outline-offset: -2px;
}

/* When the shell is expanded, the header sits over a transcript area
   so we want a clear separator. When collapsed, no separator. */
.voice-mode-overlay[data-expanded="true"] .voice-mode__header {
  border-bottom: 1px solid #2a2520;
}

.voice-mode__title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-body, "Outfit", "Inter", sans-serif);
  font-size: 14px;
  color: #d4a574;
}

.voice-mode__title-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.2;
}

.voice-mode__title-status {
  color: #c8b896;
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.voice-mode__indicator {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #888;
  flex-shrink: 0;
}
.voice-mode__indicator[data-state="connecting"] {
  background: #d4a574;
  animation: vm-pulse 1.2s ease-in-out infinite;
}
.voice-mode__indicator[data-state="live"] {
  background: #7ba86f;
  box-shadow: 0 0 8px rgba(123, 168, 111, 0.6);
}
.voice-mode__indicator[data-state="error"]   { background: #c56b6b; }
.voice-mode__indicator[data-state="idle"]    { background: #888; }

@keyframes vm-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.85); }
}

.voice-mode__agent-select {
  background: #221d18;
  color: #f0e8d8;
  border: 1px solid #3a2f22;
  border-radius: 6px;
  padding: 4px 6px;
  font-family: inherit;
  font-size: 12px;
  min-height: 28px;
  max-width: 110px;
  flex-shrink: 1;
}
.voice-mode__agent-select:focus { outline: 2px solid #d4a574; outline-offset: 1px; }
/* Header click should toggle expand/collapse; the select shouldn't.
   stopPropagation is also wired in JS as belt-and-suspenders, but
   pointer-events: auto on a child is the canonical isolation pattern. */
.voice-mode__agent-select { pointer-events: auto; }

.voice-mode__header-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.voice-mode__expand {
  background: transparent;
  color: #c8b896;
  border: 1px solid #3a2f22;
  border-radius: 6px;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  transition: transform 150ms ease-out, border-color 150ms ease-out;
}
.voice-mode__expand:hover,
.voice-mode__expand:focus-visible {
  border-color: #d4a574;
  outline: none;
}
.voice-mode-overlay[data-expanded="true"] .voice-mode__expand {
  transform: rotate(180deg);
}

.voice-mode__close {
  background: #c56b6b;
  color: #fff;
  border: 0;
  border-radius: 6px;
  padding: 0 12px;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  min-height: 44px;
  min-width: 44px;
}
.voice-mode__close:hover  { background: #b85a5a; }
.voice-mode__close:focus  { outline: 2px solid #fff; outline-offset: 2px; }

/* Body collapses by default; expands only when the shell carries
   data-expanded="true" (set by the JS toggle). Keeping the body in the
   DOM at all times preserves all existing query-selectors in voice-mode.js. */
.voice-mode__body {
  display: none;
  flex-direction: column;
  align-items: stretch;
  padding: 12px 14px;
  text-align: left;
  min-height: 0;
  max-height: 50vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  border-top: 1px solid #2a2520;
}
.voice-mode-overlay[data-expanded="true"] .voice-mode__body {
  display: flex;
}

.voice-mode__active {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
}

/* Status text duplicates the header's title-status when expanded; show
   it only in expanded mode so the pill row stays tight. JS still writes
   to #vm-status (and also to the header's status node) so we don't break
   the existing setStatus() callers. */
.voice-mode__status {
  font-size: 13px;
  color: #c8b896;
  margin-bottom: 10px;
  text-align: center;
}

.voice-mode__waveform {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 6px;
  height: 40px;
  width: 100%;
  margin-bottom: 10px;
}
.voice-mode__bar {
  width: 8px;
  background: linear-gradient(180deg, #d4a574, #b08555);
  border-radius: 4px;
  height: 20%;
  animation: vm-bar 1.4s ease-in-out infinite;
}
.voice-mode__bar:nth-child(2) { animation-delay: 0.15s; }
.voice-mode__bar:nth-child(3) { animation-delay: 0.30s; }
.voice-mode__bar:nth-child(4) { animation-delay: 0.45s; }
.voice-mode__bar:nth-child(5) { animation-delay: 0.60s; }

@keyframes vm-bar {
  0%, 100% { height: 20%; }
  50%      { height: 90%; }
}
@media (prefers-reduced-motion: reduce) {
  .voice-mode__bar { animation: none; height: 50%; }
  .voice-mode__indicator[data-state="connecting"] { animation: none; }
}

.voice-mode__footer {
  display: none;
  border-top: 1px solid #2a2520;
  padding: 8px 14px;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
}
.voice-mode-overlay[data-expanded="true"] .voice-mode__footer {
  display: flex;
}

.voice-mode__meter {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  color: #a89880;
}
.voice-mode__meter-value {
  font-family: var(--font-mono, ui-monospace, "SF Mono", monospace);
  color: #d4a574;
  font-weight: 600;
}

.voice-mode__error {
  color: #f0a8a8;
  background: rgba(197, 107, 107, 0.12);
  border: 1px solid rgba(197, 107, 107, 0.4);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 13px;
}

/* ── Inline error panel (Fix C, 2026-05-11) ───────────────────────
   Body-level error state. When the WS closes with a non-clean code
   or with a Fix-A-enriched error payload, voice-mode.js calls
   enterErrorState() which hides .voice-mode__active and reveals
   this panel. User gets a clear failure title + detail + meta line,
   plus two 44×44 buttons (Try again / Close) — no navigation away.
   ─────────────────────────────────────────────────────────────── */

.voice-mode__active[hidden],
.voice-mode__error-panel[hidden] {
  display: none !important;
}

.voice-mode__error-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 8px 4px;
  max-width: 100%;
}

.voice-mode__error-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(197, 107, 107, 0.16);
  border: 1px solid var(--color-red, #c56b6b);
  color: var(--color-red, #c56b6b);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading, "Playfair Display", serif);
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
}

.voice-mode__error-title {
  font-family: var(--font-heading, "Playfair Display", serif);
  font-size: 20px;
  color: var(--color-red, #c56b6b);
}

.voice-mode__error-detail {
  font-size: 16px;
  line-height: 1.45;
  color: #f0e8d8;
  max-width: 420px;
  word-break: break-word;
}

.voice-mode__error-meta {
  font-family: var(--font-mono, ui-monospace, "SF Mono", monospace);
  font-size: 12px;
  color: #a89880;
  opacity: 0.85;
  word-break: break-all;
}
.voice-mode__error-meta:empty { display: none; }

.voice-mode__error-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 4px;
  width: 100%;
}

.voice-mode__btn {
  min-height: 44px;
  min-width: 132px;
  padding: 10px 22px;
  border-radius: 6px;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
}
.voice-mode__btn--primary {
  background: #d4a574;
  color: #1a1a1a;
  border-color: #d4a574;
}
.voice-mode__btn--primary:hover {
  background: #e0b888;
  border-color: #e0b888;
}
.voice-mode__btn--primary:focus-visible {
  outline: 2px solid #f0e8d8;
  outline-offset: 2px;
}
.voice-mode__btn--ghost {
  background: transparent;
  color: #c8b896;
  border-color: #3a2f22;
}
.voice-mode__btn--ghost:hover {
  background: #221d18;
  color: #f0e8d8;
  border-color: #d4a574;
}
.voice-mode__btn--ghost:focus-visible {
  outline: 2px solid #d4a574;
  outline-offset: 2px;
}

/* Mobile: stack the buttons full-width so 44px touch targets clear
   at 375px without the modal feeling cramped. */
@media (max-width: 480px) {
  .voice-mode__error-actions {
    flex-direction: column;
    gap: 10px;
  }
  .voice-mode__btn {
    width: 100%;
  }
}

/* ══════════════════════════════════════════════════════════════════
   Voice mode pill — responsive overrides.

   Tablet (481-768px): keep top-right anchor, narrower 360px width.
   Phone (≤480px): top-pinned, full-width pill. Floor scrolls underneath. */

@media (min-width: 481px) and (max-width: 768px) {
  .voice-mode__shell {
    width: 360px;
  }
}

@media (max-width: 480px) {
  /* Container becomes a top-anchored row spanning full viewport width. */
  .voice-mode-overlay {
    top: 0;
    right: 0;
    left: 0;
    width: 100%;
    padding-left: env(safe-area-inset-left);
  }
  .voice-mode__shell {
    width: 100%;
    max-width: 100%;
    margin: 0;
    border-radius: 0 0 12px 12px;
    border-top: none;
    border-left: none;
    border-right: none;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  }
  /* Tighter header padding so 64px collapsed height holds at narrow widths. */
  .voice-mode__header {
    padding: 8px 10px;
    gap: 6px;
  }
  .voice-mode__agent-select {
    max-width: 90px;
  }
  /* Cap expanded-body max-height so Floor stays usable in the lower half
     of the viewport when the pill is open. */
  .voice-mode__body {
    max-height: 45vh;
  }
  /* Slide direction stays vertical (translateY) — works for both phone and
     tablet/desktop since both are top-anchored. */
}

/* ══════════════════════════════════════════════════════════════════
   Voice mode entry button (chat composer + Floor toolbar)
   ══════════════════════════════════════════════════════════════════ */

/* Tokenized 2026-07-12 (Brian: "phone icon next to send is still black")
   — hardcoded dark hexes ignored the light theme. */
.chat-voice-btn,
.floor-voice-btn {
  background: var(--color-panel2);
  color: var(--color-amber);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  min-height: 44px;
  min-width: 44px;
  padding: 0 10px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  margin-right: 4px;
}
.chat-voice-btn:hover,
.floor-voice-btn:hover {
  background: var(--color-panel);
  border-color: var(--color-amber);
}
.chat-voice-btn:focus,
.floor-voice-btn:focus {
  outline: 2px solid var(--color-amber);
  outline-offset: 2px;
}
.chat-voice-btn[aria-pressed="true"],
.floor-voice-btn[aria-pressed="true"] {
  background: var(--color-amber-dim);
  color: var(--color-text);
}

/* ══════════════════════════════════════════════════════════════════
   Settings → Voice — sub-section visual hierarchy + cost meter
   ══════════════════════════════════════════════════════════════════ */

.settings-section--subgroup {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--color-border);
}
.settings-subsection-title {
  font-family: var(--font-heading, "Playfair Display", serif);
  font-size: 17px;
  color: var(--color-amber);
  margin: 0 0 10px;
}

.voice-cost-meter {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 14px;
  margin-top: 12px;
}
.voice-cost-meter__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
  font-size: 14px;
}
.voice-cost-meter__label { color: var(--color-dim); }
.voice-cost-meter__value {
  font-family: var(--font-mono, ui-monospace, monospace);
  color: var(--color-amber);
}
.voice-cost-meter__bar {
  height: 8px;
  background: var(--color-panel2);
  border-radius: 4px;
  overflow: hidden;
}
.voice-cost-meter__fill {
  height: 100%;
  background: linear-gradient(90deg, #7ba86f, #d4a574);
  transition: width 0.3s ease;
}
.voice-cost-meter__fill[data-warn="true"] {
  background: linear-gradient(90deg, #d4a574, #c56b6b);
}
.voice-cost-meter__fill[data-exhausted="true"] {
  background: #c56b6b;
}

/* ══════════════════════════════════════════════════════════════════
   Voice picker rows (Gemini Live + Coqui broadcast)
   ══════════════════════════════════════════════════════════════════ */

.voice-picker-row {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) minmax(180px, 1.5fr) auto auto;
  gap: 10px;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
}
.voice-picker-row:last-child { border-bottom: 0; }
.voice-picker-row__agent {
  font-weight: 600;
  color: var(--color-text);
}
.voice-picker-row__select {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 14px;
  min-height: 44px;
  width: 100%;
}
.voice-picker-row__preview,
.voice-picker-row__clear {
  background: var(--color-bg);
  color: var(--color-amber);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 0 12px;
  min-height: 44px;
  cursor: pointer;
  font-size: 13px;
}
.voice-picker-row__preview:hover,
.voice-picker-row__clear:hover {
  border-color: var(--color-amber);
  background: var(--color-panel2);
}
.voice-picker-row__preview:disabled {
  opacity: 0.6;
  cursor: progress;
}
.voice-picker-row__clear { color: var(--color-red); }

@media (max-width: 600px) {
  .voice-picker-row {
    grid-template-columns: 1fr;
    gap: 6px;
  }
}

/* ══════════════════════════════════════════════════════════════════
   Default voice + agent dropdowns (full-width fields)
   ══════════════════════════════════════════════════════════════════ */

.voice-default-field {
  display: grid;
  grid-template-columns: minmax(140px, 220px) 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 8px 0;
}
@media (max-width: 600px) {
  .voice-default-field {
    grid-template-columns: 1fr;
  }
}

/* ══════════════════════════════════════════════════════════════════
   Settings → Voice sub-tabs (2026-05-10 voice-settings-tab-restructure)
   Three sub-tabs: Overview / Gemini Live / Coqui. Horizontal pill-style
   on desktop; collapses to a <select> dropdown <600px so the page
   stops being super-long on mobile. Each .voice-tab-panel renders
   under the sub-tab nav and uses the 2-column .voice-tab-grid where
   it makes sense (Gemini config primarily).
   ══════════════════════════════════════════════════════════════════ */

.voice-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--color-border);
  margin: 0 0 16px;
  padding: 0 0 0 0;
  flex-wrap: wrap;
}
.voice-tabs__tab {
  background: transparent;
  border: 1px solid transparent;
  border-bottom: 2px solid transparent;
  color: var(--color-dim);
  padding: 8px 14px;
  font: inherit;
  cursor: pointer;
  border-radius: 4px 4px 0 0;
  min-height: 44px; /* mobile-first touch target */
}
.voice-tabs__tab:hover {
  background: var(--color-panel2);
  color: var(--color-text);
}
.voice-tabs__tab.is-active,
.voice-tabs__tab[aria-selected="true"] {
  color: var(--color-amber);
  border-bottom-color: var(--color-amber);
  background: var(--color-bg);
}
.voice-tabs__tab:focus-visible {
  outline: 2px solid var(--color-amber);
  outline-offset: 2px;
}

/* Mobile <select> alternative — visible <600px, hidden ≥600px. */
.voice-tabs-mobile {
  display: none;
  margin: 0 0 16px;
}
@media (max-width: 600px) {
  .voice-tabs {
    display: none;
  }
  .voice-tabs-mobile {
    display: block;
  }
}

/* Tab panels: defaults to visible, hidden when hidden attribute set. */
.voice-tab-panel[hidden] { display: none; }

/* 2-column grid inside a tab panel for the Gemini Live config.
   Falls back to single column <800px so Gemini still fits on mobile. */
.voice-tab-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: 24px;
  align-items: start;
}
.voice-tab-grid__col { min-width: 0; }
@media (max-width: 800px) {
  .voice-tab-grid {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

/* Removes the default settings-section padding for the tab nav row so
   the tabs sit flush against the section title above, but keeps the
   bottom border for the tabs themselves. */
.settings-section--no-pad { padding-bottom: 8px; }

/* ── Overview tab: active-mode radio group ─────────────────── */
.voice-active-mode-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}
.voice-active-mode-opt {
  display: grid;
  grid-template-columns: 24px 1fr;
  grid-template-rows: auto auto;
  column-gap: 10px;
  align-items: start;
  padding: 10px 12px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  cursor: pointer;
  min-height: 44px;
}
.voice-active-mode-opt:hover { background: var(--color-panel2); }
.voice-active-mode-opt input[type="radio"] {
  grid-row: 1 / span 2;
  align-self: center;
  width: 18px;
  height: 18px;
  accent-color: var(--color-amber);
}
.voice-active-mode-opt__title {
  color: var(--color-text);
  font-weight: 500;
}
.voice-active-mode-opt__desc {
  color: #9a8c75;
  font-size: 0.875rem;
}

/* ── Overview tab: 2-column status snapshot ──────────────── */
.voice-overview-snapshot { margin-top: 16px; }
.voice-overview-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  align-items: start;
}
.voice-overview-col { min-width: 0; }
@media (max-width: 800px) {
  .voice-overview-grid {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}
.voice-overview-links {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.voice-overview-links .btn {
  min-height: 44px;
}

/* ══════════════════════════════════════════════════════════════════
   Live transcript area (2026-05-11 — voice-mode-live-transcript)

   Sits between the waveform and the footer. Shows each finalized
   utterance (user + agent) as a row with speaker name, role badge,
   relative timestamp, and the transcribed text. Color-coded per agent
   so multi-voice routing bugs are visible — see decision doc.
   ══════════════════════════════════════════════════════════════════ */

.voice-mode__transcript {
  width: 100%;
  flex: 1 1 auto;
  min-height: 80px;
  /* Cap on tall viewports so the transcript never crowds out the
     waveform indicator at the top. The flex-grow shrinks it on small
     screens; the cap protects against runaway growth on desktop. */
  max-height: 45vh;
  margin-top: 18px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: rgba(34, 29, 24, 0.5);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 10px 12px;
  text-align: left;
  /* `font-size: 16px` on the LINES (not here) — iOS auto-zooms inputs
     smaller than 16px on focus; we don't have inputs in this surface
     but the 16px floor on text remains best-practice for legibility. */
}

.voice-mode__transcript-empty {
  color: #6b6052;
  font-size: 13px;
  text-align: center;
  padding: 18px 8px;
  font-style: italic;
}
.voice-mode__transcript-empty[hidden] { display: none; }

.voice-mode__transcript-lines {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.voice-mode__transcript-line {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 10px;
  border-radius: 6px;
  background: rgba(20, 20, 20, 0.55);
  border-left: 3px solid var(--color-amber, #d4a574);
}
.voice-mode__transcript-line[data-role="user"] {
  border-left-color: #d4a574;
  background: rgba(212, 165, 116, 0.06);
}
.voice-mode__transcript-line[data-role="agent"] {
  /* Agent rows take their accent from the inline `color` style applied
     to .voice-mode__transcript-speaker — but the border falls back to
     a neutral if no agent color was found. */
  border-left-color: #5a8a7a;
  background: rgba(90, 138, 122, 0.05);
}
.voice-mode__transcript-line[data-final="true"] {
  /* Finalized lines are slightly less prominent than the live row. */
  opacity: 0.96;
}
/* Narration rows = Coqui-synthesized status clips ("got it", "still
   working"). Rendered dimmer + italic so the user can distinguish them
   from a live Gemini reply, while still preserving the transcript anchor
   for what was spoken. See
   `docs/decisions/accepted/voice-narration-transcript-coverage.md`. */
.voice-mode__transcript-line[data-source="narration"] {
  opacity: 0.68;
  border-left-style: dashed;
  background: rgba(20, 20, 20, 0.35);
}
.voice-mode__transcript-line[data-source="narration"] .voice-mode__transcript-text {
  font-style: italic;
  color: #b0a890;
}

.voice-mode__transcript-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  flex-wrap: wrap;
}
.voice-mode__transcript-speaker {
  font-weight: 600;
  font-size: 13px;
}
.voice-mode__transcript-role {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.06);
  color: #a89880;
}
.voice-mode__transcript-relTime {
  color: #6b6052;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}

.voice-mode__transcript-text {
  color: #f0e8d8;
  font-size: 16px; /* 16px floor — readable on mobile, no iOS auto-zoom */
  line-height: 1.4;
  /* Allow long words / URLs to wrap rather than overflow the row */
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* Narrow-viewport tuning. On iPhone-class widths the transcript becomes
   the dominant content area, so we shrink the waveform's effective
   vertical footprint by trimming gaps rather than the readable text. */
@media (max-width: 480px) {
  .voice-mode__transcript {
    margin-top: 12px;
    padding: 8px 10px;
    max-height: 55vh;
  }
  .voice-mode__transcript-line {
    padding: 7px 9px;
  }
  .voice-mode__transcript-text {
    font-size: 16px; /* explicit at this breakpoint — no shrinking */
  }
}

/* ══════════════════════════════════════════════════════════════════
   Mute toggle (2026-05-12 — voice-mute-toggle)
   Sits between waveform and transcript. 64×64 thumb-accessible target,
   icon + label stack, red-tinted background when active. Brian directive
   2026-05-12 01:28: "at the least i think we need a mute button so its
   not trying to dictate surroundings etc."
   ══════════════════════════════════════════════════════════════════ */

.voice-mode__controls {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 14px;
}

/* Mute is now in the pill HEADER (44×44 inline icon button) rather than
   the expanded body — gives Brian one-tap mute without opening the pill.
   The .voice-mode__mute-icon stays visible; the .voice-mode__mute-label
   is screen-reader-only so the aria-label reads correctly but the icon
   doesn't get a redundant text caption in a 44px-tall button. */
.voice-mode__mute {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 6px;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.voice-mode__mute:hover,
.voice-mode__mute:focus-visible {
  background: rgba(212, 165, 116, 0.18);
  border-color: rgba(212, 165, 116, 0.6);
  outline: none;
}

.voice-mode__mute:focus-visible {
  outline: 2px solid #d4a574;
  outline-offset: 2px;
}

.voice-mode__mute--active {
  background: rgba(197, 107, 107, 0.22);
  border-color: rgba(197, 107, 107, 0.7);
  color: #f0a8a8;
}
.voice-mode__mute--active:hover,
.voice-mode__mute--active:focus-visible {
  background: rgba(197, 107, 107, 0.32);
  border-color: rgba(197, 107, 107, 0.9);
}

.voice-mode__mute-icon {
  font-size: 18px;
  line-height: 1;
}

/* Mute label is screen-reader only now — the icon + aria-label carry the
   semantic content for users; sighted users get the icon alone in the
   44×44 pill header button. */
.voice-mode__mute-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Voice call card (chat-side, 2026-05-12 Brian UX) ──────────────
 * Renders a single tappable card per voice session in the chat thread,
 * replacing what would otherwise have been N indistinguishable per-turn
 * bubbles. 44px min-height meets the touch-target floor; preview lines
 * truncate cleanly so the card stays compact on 375px viewports.
 */

.message-row--voice-card {
  padding: 6px 12px;
}

.voice-call-card {
  width: 100%;
  display: block;
  padding: 0;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(212, 165, 116, 0.3);
  border-radius: 8px;
  color: inherit;
  text-align: left;
  cursor: pointer;
  font: inherit;
  transition: background-color 120ms ease, border-color 120ms ease;
  min-height: 44px;
}

.voice-call-card:hover,
.voice-call-card:focus-visible {
  background: rgba(212, 165, 116, 0.1);
  border-color: rgba(212, 165, 116, 0.6);
  outline: none;
}

.voice-call-card__header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  flex-wrap: wrap;
}

.voice-call-card__icon { font-size: 16px; }
.voice-call-card__title { font-weight: 600; }
.voice-call-card__meta { opacity: 0.7; font-size: 0.85em; }
.voice-call-card__time { margin-left: auto; opacity: 0.5; font-size: 0.8em; }

.voice-call-card__preview {
  padding: 8px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.voice-call-card__preview-line {
  display: flex;
  gap: 6px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 0.92em;
  line-height: 1.4;
}

.voice-call-card__preview-who {
  font-weight: 600;
  flex-shrink: 0;
  opacity: 0.8;
}

.voice-call-card__preview-text {
  overflow: hidden;
  text-overflow: ellipsis;
}

.voice-call-card__hint {
  margin-top: 4px;
  font-size: 0.78em;
  opacity: 0.5;
  font-style: italic;
}

/* ── Voice transcript viewer (read-only overlay) ──────────────────
 * Full-screen modal listing every turn of a completed voice session.
 * Lazy-loaded by chat.js when the user taps a voice-call card.
 */

.voice-transcript-viewer {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  display: flex;
  align-items: stretch;
  justify-content: center;
  z-index: 9999;
}

.voice-transcript-viewer__shell {
  background: #1a1a1a;
  width: 100%;
  max-width: 720px;
  display: flex;
  flex-direction: column;
  height: 100%;
}

.voice-transcript-viewer__header {
  padding: 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.voice-transcript-viewer__title {
  font-size: 1.05em;
  font-weight: 600;
  display: flex;
  gap: 8px;
}

.voice-transcript-viewer__meta {
  opacity: 0.6;
  font-size: 0.85em;
  flex: 1;
}

.voice-transcript-viewer__close {
  background: rgba(212, 165, 116, 0.18);
  color: var(--color-amber);
  border: 1px solid rgba(212, 165, 116, 0.4);
  padding: 8px 14px;
  min-height: 44px;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
}

.voice-transcript-viewer__body {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.voice-transcript-viewer__turn {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  border-radius: 8px;
}

.voice-transcript-viewer__turn--user {
  background: rgba(212, 165, 116, 0.08);
  align-self: flex-end;
  max-width: 85%;
}

.voice-transcript-viewer__turn--agent {
  background: rgba(255, 255, 255, 0.04);
  align-self: flex-start;
  max-width: 85%;
}

.voice-transcript-viewer__turn-who {
  font-weight: 600;
  opacity: 0.7;
  font-size: 0.85em;
}

.voice-transcript-viewer__turn-text {
  white-space: pre-wrap;
  line-height: 1.5;
}

/* ══════════════════════════════════════════════════════════════════
   Voice Mode — dispatch confirm cards (Phase 1 item 8).

   Bottom-anchored stack of amber-bordered cards above the modal footer.
   Each card surfaces a pending `pending_dispatches` row routed via the
   `delegation_dispatched` Feed event. Buttons are ≥44×44 for thumb-zone
   tap-confirm on 375px viewports; token shows monospace below the
   buttons as a visual disambiguator (Chief never speaks it aloud).
   ══════════════════════════════════════════════════════════════════ */

.voice-mode__dispatch-stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  max-height: 40vh;
  overflow-y: auto;
  margin-top: 12px;
  flex: 0 0 auto;
}
.voice-mode__dispatch-stack[hidden] { display: none; }

/* Pending indicator state — reuses the vm-pulse keyframes already used by
   the connecting state, but in amber so it reads as "awaiting action"
   rather than "still loading." */
.voice-mode__indicator[data-state="pending"] {
  background: #d4a574;
  box-shadow: 0 0 8px rgba(212, 165, 116, 0.65);
  animation: vm-pulse 1.4s ease-in-out infinite;
}

.vm-dispatch-card {
  background: #221d18;
  border: 2px solid #d4a574;
  border-radius: 8px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: left;
  animation: vm-dispatch-glow 1.2s ease-out 1;
}
.vm-dispatch-card--busy { opacity: 0.65; }

@keyframes vm-dispatch-glow {
  0%   { box-shadow: 0 0 0 0 rgba(212, 165, 116, 0.0); }
  40%  { box-shadow: 0 0 0 6px rgba(212, 165, 116, 0.28); }
  100% { box-shadow: 0 0 0 0 rgba(212, 165, 116, 0.0); }
}
@media (prefers-reduced-motion: reduce) {
  .vm-dispatch-card { animation: none; }
}

.vm-dispatch-card__head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-heading, "Playfair Display", serif);
  font-size: 15px;
  color: #d4a574;
}
.vm-dispatch-card__arrow { font-weight: 700; }
.vm-dispatch-card__lead { font-weight: 600; }

.vm-dispatch-card__directive {
  color: #f0e8d8;
  font-size: 14px;
  line-height: 1.4;
  word-break: break-word;
  overflow-wrap: anywhere;
}

.vm-dispatch-card__muted {
  font-size: 13px;
  color: #f0c898;
  background: rgba(212, 165, 116, 0.12);
  border: 1px solid rgba(212, 165, 116, 0.3);
  border-radius: 4px;
  padding: 6px 8px;
}

.vm-dispatch-card__hint {
  font-size: 13px;
  color: #c8b896;
}

.vm-dispatch-card__actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.vm-dispatch-card__btn {
  min-height: 44px;
  min-width: 44px;
  padding: 10px 18px;
  border-radius: 6px;
  border: 1px solid transparent;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.vm-dispatch-card__btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

.vm-dispatch-card__btn--confirm {
  background: #7ba86f;
  color: #1a1a1a;
  border-color: #7ba86f;
}
.vm-dispatch-card__btn--confirm:hover:not(:disabled) {
  background: #8db97f;
  border-color: #8db97f;
}
.vm-dispatch-card__btn--confirm:focus-visible {
  outline: 2px solid #f0e8d8;
  outline-offset: 2px;
}

.vm-dispatch-card__btn--cancel {
  background: transparent;
  color: #f0a8a8;
  border-color: #c56b6b;
}
.vm-dispatch-card__btn--cancel:hover:not(:disabled) {
  background: rgba(197, 107, 107, 0.12);
}
.vm-dispatch-card__btn--cancel:focus-visible {
  outline: 2px solid #f0e8d8;
  outline-offset: 2px;
}

.vm-dispatch-card__token {
  font-family: var(--font-mono, ui-monospace, "SF Mono", monospace);
  font-size: 12px;
  color: #a89880;
  letter-spacing: 0.04em;
  text-align: center;
  opacity: 0.85;
}

/* 375px viewport tightening — pack the actions to keep buttons readable. */
@media (max-width: 420px) {
  .vm-dispatch-card { padding: 10px 12px; }
  .vm-dispatch-card__btn { padding: 10px 12px; font-size: 14px; }
  .vm-dispatch-card__directive { font-size: 13px; }
}

/* ══════════════════════════════════════════════════════════════════
   Voice-mode multi-party participant strip (2026-05-17 — Spawn 3d).

   Renders a horizontal row of small circular avatars in the pill
   header below the title row, plus a current-speaker label. Each
   avatar:
     - Pulses (vm-pulse keyframes, reused from indicator) when its
       participant is the current speaker
     - Dims via opacity when NOT speaking
     - Carries an aria-label with the agent's display name
   Layout caps:
     - ≤480px: 4 visible + "+N" chip
     - 481-768px: 5 visible
     - >768px: 6 visible
   Speaker label takes precedence visually; strip is secondary.
   ══════════════════════════════════════════════════════════════════ */

.voice-mode__participants-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 12px 8px;
  border-bottom: 1px solid #2a2520;
  background: linear-gradient(180deg, #1f1a14 0%, #1a1a1a 100%);
}
.voice-mode__participants-row[hidden] { display: none; }

.voice-mode__participants {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
  min-height: 28px;
}

.voice-mode__participant {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  font-size: 11px;
  font-weight: 600;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.04);
  border: 1.5px solid rgba(255, 255, 255, 0.18);
  color: #c8b896;
  /* Non-speaking participants render dimmer so the active speaker pops. */
  opacity: 0.55;
  filter: grayscale(0.4);
  transition: opacity 160ms ease, filter 160ms ease, box-shadow 160ms ease;
}

.voice-mode__participant[data-speaking="true"] {
  opacity: 1;
  filter: none;
  box-shadow: 0 0 0 2px rgba(212, 165, 116, 0.55), 0 0 8px rgba(212, 165, 116, 0.4);
  /* Reuses vm-pulse keyframes from the indicator (defined earlier). */
  animation: vm-pulse 1.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .voice-mode__participant[data-speaking="true"] {
    animation: none;
  }
}

.voice-mode__participants-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #a89880;
  font-size: 11px;
  font-weight: 600;
  flex-shrink: 0;
}

.voice-mode__current-speaker {
  font-size: 12px;
  color: #d4a574;
  line-height: 1.2;
  /* Truncate on narrow viewports so the row stays single-line. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-height: 14px;
}
.voice-mode__current-speaker:empty { display: none; }

/* Visually-hidden helper for aria announcements. */
.voice-mode__sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Responsive caps ───────────────────────────────────────────── */
/* Default (desktop): up to 6 avatars rendered by JS. */
/* Tablet 481-768px: up to 5; phone ≤480px: up to 4. JS reads the
   matchMedia breakpoint and trims the list before render so the row
   doesn't overflow the pill width. The CSS just sets the visual cap
   on icon sizes; the count cap is enforced in JS. */
@media (max-width: 480px) {
  .voice-mode__participants-row {
    padding: 5px 10px 7px;
  }
  .voice-mode__participant,
  .voice-mode__participants-more {
    width: 22px;
    height: 22px;
    min-width: 22px;
    font-size: 10px;
  }
}
