/* SavoryWolper v2 — voice/transcription utility page.
   Chromeless: no nav, no footer. Transcript grows to fill the viewport. */

:root {
  --sw-bg:         #FAF8F5;
  --sw-surface:    #FFFFFF;
  --sw-text:       #0B0B0B;
  --sw-text-2:     #2E2E2E;
  --sw-text-3:     #6B6B6B;
  --sw-text-4:     #A8A8A8;
  --sw-accent:     #C7895A;
  --sw-accent-2:   #B07748;
  --sw-system:     #3C9DCF;
  --sw-system-2:   #2A85B0;
  --sw-alert:      #C7515A;
  --sw-alert-2:    #B04848;
  --sw-surface-2:  #F2EFE9;
  --sw-system-soft:rgba(60, 157, 207, 0.12);
  --sw-rule:       rgba(11, 11, 11, 0.08);
  --sw-rule-strong:rgba(11, 11, 11, 0.16);
  --sw-ease:       cubic-bezier(0.22, 1, 0.36, 1);
  /* Copied verbatim from the site's css/tokens.css rather than reinvented.
     This page is on another subdomain and cannot link that stylesheet, so
     the copy drifts: it carried 17 of the 49 tokens and none of the scales,
     which is why everything below used to be written in raw pixels. */
  --sw-radius-sm:  6px;
  --sw-radius-md:  12px;
  --sw-radius-pill:999px;
  /* BlueHouse's own values, read off its compiled stylesheet — the app this
     call belongs to, so its buttons and ours line up. Tailwind v4
     blue-600/700 and slate-200/300, radius-lg, and the 44 px minimum height
     its primary button carries (`min-h-11`). */
  --bh-blue:       #155dfc;
  --bh-blue-hover: #1447e6;
  --bh-slate-200:  #e2e8f0;
  --bh-slate-300:  #cad5e2;
  --bh-radius:     0.5rem;
  --bh-btn-min-h:  2.75rem;
  /* Label colours. The transcript labels are 11 px uppercase, which WCAG
     counts as small text and wants 4.5:1 against the white transcript.
     `--sw-accent` measures 2.94:1 and `--sw-system` 3.04:1 — both were
     genuinely hard to read, which is what prompted this.
     `--sw-label-clay` is `--sw-accent` taken down 0.12 in oklch lightness
     with chroma and hue untouched, per DESIGN.md §3: derive, don't invent.
     The blue is BlueHouse's own, which already clears the bar. */
  --sw-label-clay: #A06536;   /* oklch(L-0.12) of --sw-accent — 4.76:1 */
  --sw-label-blue: #155dfc;   /* BlueHouse blue-600 — 5.25:1 */
  --sw-s-1:        0.5rem;
  --sw-s-2:        1rem;
  --sw-s-3:        1.5rem;
  --sw-dur-fast:   200ms;
  --sw-dur:        400ms;
  --sw-font-body:  "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --sw-font-mono:  "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }

body {
  font-family: var(--sw-font-body);
  background: var(--sw-bg);
  color: var(--sw-text-2);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Fixed viewport height (with dvh fallback for mobile chrome bars)
     so the transcript scrolls inside the body instead of the whole
     page scrolling and pushing the controls off-screen. */
  height: 100vh;
  height: 100dvh;
  padding: 1.25rem 1rem 1rem;
  -webkit-font-smoothing: antialiased;
}

/* ---------- Top label (no nav) ---------- */
.vc-topbar {
  width: 100%;
  max-width: 720px;
  margin-bottom: 0.875rem;
  flex: none;
  text-align: center;
}
.vc-eyebrow {
  font-family: var(--sw-font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sw-text-3);
}
#agent-label { color: var(--sw-accent); }

/* ---------- Call button (clay primary; blue meeting; red recording) ---------- */
#call-btn, #status, #mic-level { flex: none; }

#call-btn {
  width: 108px; height: 108px;
  border-radius: 50%;
  border: none;
  background: var(--sw-accent);
  color: #fff;
  font-family: var(--sw-font-body);
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  cursor: pointer;
  transition: background 200ms var(--sw-ease),
              box-shadow 200ms var(--sw-ease),
              transform 200ms var(--sw-ease);
  box-shadow: 0 10px 28px -8px rgba(199, 137, 90, 0.45);
  margin-top: 0.25rem;
}
#call-btn:hover {
  background: var(--sw-accent-2);
  transform: translateY(-1px);
  box-shadow: 0 12px 32px -8px rgba(199, 137, 90, 0.55);
}
#call-btn.active {
  background: var(--sw-alert);
  box-shadow: 0 10px 28px -8px rgba(199, 81, 90, 0.45);
  animation: pulse 2s infinite;
}
#call-btn.active:hover { background: var(--sw-alert-2); }
@keyframes pulse {
  0%, 100% { box-shadow: 0 10px 28px -8px rgba(199, 81, 90, 0.45); }
  50%      { box-shadow: 0 10px 40px -8px rgba(199, 81, 90, 0.65); }
}

/* Meeting mode: blue tint signals listen-only. Red still overrides on .active. */
/* Meeting mode is the system listening, so it takes the system colour —
   and that colour is now BlueHouse's, because this is a BlueHouse surface.
   Clay stays on the direct call: that is a conversation, not a system. */
#call-btn.meeting {
  background: var(--bh-blue);
  box-shadow: 0 10px 28px -8px rgba(21, 93, 252, 0.40);
}
#call-btn.meeting:hover { background: var(--bh-blue-hover); }
#call-btn.meeting.active {
  background: var(--sw-alert);
  box-shadow: 0 10px 28px -8px rgba(199, 81, 90, 0.45);
}

/* ---------- Status indicator ---------- */
#status {
  margin-top: 0.875rem;
  font-family: var(--sw-font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sw-text-3);
  height: 1.1rem;
}
#status.listening   { color: var(--sw-accent); }
#status.recording   { color: var(--sw-alert); }
#status.thinking    { color: var(--sw-accent-2); }
#status.speaking    { color: var(--sw-system); }
#status.calibrating { color: var(--sw-text-4); text-transform: none; font-style: italic; letter-spacing: 0; }

/* Sticky error banner that appears under the status line when the
   server signals voice.error (e.g. TTS backend returning HTTP 200 but
   zero audio bytes — VoxCPM post-CUDA-assert, Kokoro down, etc.).
   Doesn't touch the call-state machine; just sits visible until the
   server pushes voice.ok. */
#voice-error {
  margin-top: 0.4rem;
  font-family: var(--sw-font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sw-alert);
}

/* Shown when the speech services a mode depends on aren't answering. Same
   voice as #voice-error — this is the same class of bad news — but it is
   about the service rather than about the call, so it survives across
   calls and is not cleared by a new turn. */
#service-banner {
  margin-top: 0.4rem;
  max-width: 32rem;
  font-family: var(--sw-font-mono);
  font-size: 0.72rem;
  line-height: 1.5;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sw-alert);
}
#service-banner .detail {
  display: block;
  margin-top: 0.15rem;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--sw-text-3);
}
/* The button stays visible while gated — hiding it reads as "the page is
   broken", greying it reads as "not right now". */
#call-btn:disabled,
#call-btn:disabled:hover {
  opacity: 0.4;
  cursor: not-allowed;
  background: var(--sw-text-3);
}

/* ---------- Mic level ---------- */
#mic-level {
  width: 200px; height: 3px;
  background: var(--sw-rule);
  border-radius: 2px;
  margin-top: 0.625rem;
  overflow: hidden;
}
#mic-level-bar {
  height: 100%;
  width: 0%;
  background: var(--sw-accent);
  transition: width 50ms linear;
}

/* ---------- Transcript (the page's main real estate) ---------- */
/* On mobile this is the column the body always was. On desktop it becomes
   the left zone of a two-zone layout — see the min-width rule at the end. */
.vc-controls {
  display: flex; flex-direction: column; align-items: center;
  width: 100%; max-width: 720px;
  flex: none;
}
.vc-identity { order: 99; }

#transcript {
  margin-top: 1.125rem;
  width: 100%;
  max-width: 720px;
  flex: 1 1 auto;
  /* min-height: 0 lets the flex child shrink below its content size
     so overflow-y actually scrolls instead of pushing the parent. */
  min-height: 0;
  overflow-y: auto;
  padding: 1rem 1.125rem;
  background: var(--sw-surface);
  border: 1px solid var(--sw-rule);
  border-radius: 12px;
  font-size: 0.9375rem;
  line-height: 1.6;
  box-shadow:
    0 1px 0 rgba(11, 11, 11, 0.02),
    0 8px 24px -8px rgba(11, 11, 11, 0.06);
}

.turn { margin-bottom: 0.85rem; }
.turn:last-child { margin-bottom: 0; }
/* A name, in the body font and its own case — "McGyver", the way BlueHouse
   writes it. Mono small-caps made it read as a category rather than a
   person, and the letter-spacing only existed to keep all-caps legible. */
.turn .label {
  font-size: 0.75rem;
  font-weight: 600;
  margin-bottom: 0.2rem;
}
/* Each of these falls back to the page's own palette and is overridden per
   call by the agent's BlueHouse chat appearance — the colours its owner
   picked for it there, so a call looks like that agent's chat. */
.turn.user  .label { color: var(--sw-label-clay); }
.turn.agent .label { color: var(--sw-label-blue); }
.turn .text        { color: var(--vc-text-color, var(--sw-text-2)); }

/* ---------- Agent bubbles ----------
   On only when the agent has an appearance to draw (`vc-bubbles`), so a call
   without one keeps the plain transcript instead of growing empty padded
   boxes. Colours come from the agent's own BlueHouse design and are mixed
   by its own maths — see `applyAgentAppearance`. */
.vc-bubbles .turn .text {
  display: inline-block;
  max-width: 85%;
  padding: 0.4rem 0.7rem;
  border-radius: var(--bh-radius);
}
/* A user turn is a container for one or more speakers, so the bubble moves
   off it and onto the blocks inside. */
.turn.user .text {
  display: block; max-width: none; padding: 0;
  background: none; border: 0;
}
.turn.user .label { text-align: right; }
.vc-said { margin-top: 0.4rem; }
.vc-said:first-child { margin-top: 0; }
/* You, on the right — the side your own words sit on in BlueHouse. */
.vc-said--me { width: fit-content; max-width: 85%; margin-left: auto; }
.vc-bubbles .vc-said--me {
  padding: 0.4rem 0.7rem;
  border-radius: var(--bh-radius);
  background: var(--vc-user-bg, transparent);
  color: var(--vc-user-fg, inherit);
}
/* Everyone else in the room: left, no bubble, in the colour the agent
   picked for text on its own background. */
.vc-said--other {
  max-width: 85%; margin-right: auto;
  color: var(--vc-text-color, var(--sw-text-2));
}
.vc-said__who {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--sw-text-4);
  margin-bottom: 0.15rem;
}
/* Only present when its owner turned the assistant bubble on. */
.vc-bubbles .turn.agent .text {
  background: var(--vc-agent-bg, transparent);
  color: var(--vc-agent-fg, var(--vc-text-color, var(--sw-text-2)));
}
.vc-bubbles .turn.agent .text.partial { color: var(--sw-text-3); }
.vc-bubbles .turn.system .text {
  background: var(--vc-status-bg, transparent);
  border: 1px solid var(--vc-status-border, transparent);
  color: var(--vc-status-fg, var(--sw-text-3));
  opacity: 1;
}
.vc-bubbles .turn.error .text {
  background: var(--vc-error-bg, transparent);
  border: 1px solid var(--vc-error-border, transparent);
  color: var(--vc-error-fg, var(--sw-alert));
}
.turn.agent .text.partial { color: var(--sw-text-3); font-style: italic; }

/* System note — not a speaker. A turn that produced nothing (model timed
   out, agent errored) leaves a mark here rather than a gap in the record.
   Muted and monospaced so it reads as machine annotation, not speech. */
.turn.system .text {
  font-family: var(--sw-font-mono);
  font-size: 0.78rem;
  color: var(--sw-text-3);
  opacity: 0.85;
}

/* ---------- "Speaking" glyph on the live agent line ----------
   A KITT-style equalizer that floats at the start of the currently-
   spoken agent line; the text wraps to its right. Its bars react to the
   real TTS output (an AnalyserNode tapped onto the playback context), so
   they drop during the natural gaps between sentence-chunks. Tapping it
   stops the agent mid-sentence — the same abort a voice barge performs,
   but on demand. Only the line being spoken carries it; it's removed the
   instant playback ends. */
.spk-glyph {
  float: left;
  display: inline-flex;
  align-items: flex-end;   /* bars grow upward from a bottom on the baseline */
  gap: 2px;
  /* Span the full line height so the bars can swing the whole line, and
     drop the glyph down (top offset) so its bottom sits on the text
     baseline rather than floating near the top of the line. */
  height: 1.4rem;
  padding: 0 6px;
  margin: 0 0.4rem -0.15rem -0.35rem;
  position: relative;
  top: 0.32rem;
  background: none;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.spk-glyph i {
  display: block;
  width: 3px;
  height: 30%;
  background: var(--sw-system);
  border-radius: 2px;
  transition: height 50ms linear, background 150ms var(--sw-ease);
}
/* Hover/press = "this will silence it": bars turn alert-red. */
.spk-glyph:hover { background: rgba(199, 81, 90, 0.10); }
.spk-glyph:hover i, .spk-glyph:active i { background: var(--sw-alert); }

/* ---------- Login + agent-picker panels ---------- */
#login-panel, #agent-panel {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.875rem;
  width: 100%;
  max-width: 320px;
  flex: none;
}
#login-panel.show, #agent-panel.show { display: flex; }

#login-panel input, #agent-panel select {
  width: 100%;
  padding: 0.625rem 0.75rem;
  background: var(--sw-surface);
  border: 1px solid var(--sw-rule-strong);
  border-radius: 6px;
  color: var(--sw-text);
  font-family: inherit;
  font-size: 0.9375rem;
  transition: border-color 200ms var(--sw-ease),
              box-shadow 200ms var(--sw-ease);
}
#login-panel input:focus,
#agent-panel select:focus {
  outline: none;
  border-color: var(--sw-accent);
  box-shadow: 0 0 0 3px rgba(199, 137, 90, 0.14);
}

/* Agent selector reads as the "who" — clay accent, medium weight.
   Mode selector stays neutral; it's the "how". */
#agent-select { color: var(--sw-accent); font-weight: 500; }

/* Static agent name shown when the user only has one agent — visually
   matches the mode dropdown next to it (same border, padding, radius)
   but no chevron, no interaction. */
.vc-agent-name {
  flex: 1;
  min-width: 0;
  padding: 0.625rem 0.75rem;
  background: var(--sw-surface);
  border: 1px solid var(--sw-rule-strong);
  border-radius: 6px;
  color: var(--sw-accent);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}

#agent-panel .agent-row { display: flex; flex-wrap: wrap; gap: 0.5rem; width: 100%; }
#agent-panel .agent-row select { flex: 1; min-width: 0; }

/* Model picker — the third column, after "who" (agent) and "how" (mode).
   Unlike those two it stays live DURING a call: switching model mid-call is
   the point (a model can turn out to be rate-limited or just too slow, and
   the user shouldn't have to hang up to move off it). Muted and slightly
   smaller so it reads as a detail of the call, not a peer of the agent.
   Below 34rem it takes its own row rather than squeezing three selects. */
#model-select {
  color: var(--sw-text-3);
  font-size: 0.875rem;
}
#model-select:disabled { opacity: 0.5; }
/* An option the router found unusable this session — still selectable
   (the block is a cached observation, the user may know it's fixed). */
#model-select option[data-unavailable="1"] { color: var(--sw-text-3); }
/* "Include tab audio" — meeting-mode-only sub-option, sits inside the
   agent-panel directly under the blue banner. Subtle so it doesn't
   compete with the agent selector. */
#tab-audio-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  font-size: 0.85rem;
  color: var(--sw-text-3);
  cursor: pointer;
  user-select: none;
}
#tab-audio-row.show { display: flex; }

#tab-audio-status {
  width: 100%;
  font-family: var(--sw-font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.4rem 0.6rem;
  border-radius: 6px;
}
#tab-audio-status.ok {
  color: var(--sw-system-2);
  background: rgba(60, 157, 207, 0.10);
  border: 1px solid rgba(60, 157, 207, 0.22);
}
#tab-audio-status.err {
  color: var(--sw-alert);
  background: rgba(199, 81, 90, 0.10);
  border: 1px solid rgba(199, 81, 90, 0.22);
}
/* Neutral informational note (e.g. "mic only") — not an error. */
#tab-audio-status.tip {
  color: var(--sw-text-3);
  background: rgba(11, 11, 11, 0.04);
  border: 1px solid var(--sw-rule);
}

/* "Stay silent" — meeting-mode toggle. When on, NOTHING reaches the
   agent mid-meeting (not even when it's addressed by name): every
   utterance is still transcribed and buffered, and the agent only sees
   the transcript once, at call end, to write the minutes. A real toggle
   switch (not a checkbox) signals it's a sticky mode, not a one-shot. */
#respond-row {
  display: none;
  width: 100%;
  color: var(--sw-text-3);
  cursor: pointer;
  user-select: none;
}
#respond-row.show { display: flex; }
.switch { position: relative; flex: none; width: 34px; height: 20px; }
.switch input {
  position: absolute; inset: 0;
  opacity: 0; margin: 0; cursor: pointer;
}
.switch .slider {
  position: absolute; inset: 0;
  background: var(--sw-rule-strong);
  border-radius: 999px;
  transition: background 180ms var(--sw-ease);
}
.switch .slider::before {
  content: ""; position: absolute;
  width: 16px; height: 16px; left: 2px; top: 2px;
  background: #fff; border-radius: 50%;
  box-shadow: 0 1px 2px rgba(11, 11, 11, 0.25);
  transition: transform 180ms var(--sw-ease);
}
.switch input:checked + .slider { background: var(--sw-system); }
.switch input:checked + .slider::before { transform: translateX(14px); }
.switch input:focus-visible + .slider { box-shadow: 0 0 0 3px rgba(60, 157, 207, 0.25); }

/* The mode used to be a dropdown, which made the call's whole shape read
   like a technical setting. Two buttons say it is a choice between two
   things, and which one you are in. */
.vc-visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
@media (prefers-reduced-motion: reduce) {
  :root { --sw-dur-fast: 0ms; --sw-dur: 0ms; }
}
.vc-segmented {
  display: flex; width: 100%; gap: 2px; padding: 2px;
  background: var(--sw-surface-2);
  border: 1px solid var(--bh-slate-200);
  border-radius: var(--bh-radius);
}
.vc-seg {
  flex: 1; min-width: 0; padding: 0.45rem var(--sw-s-1);
  font: inherit; font-size: 0.8125rem; color: var(--sw-text-3);
  background: none; border: 0; border-radius: 4px; cursor: pointer;
  transition: background var(--sw-dur-fast) var(--sw-ease),
              color var(--sw-dur-fast) var(--sw-ease);
}
.vc-seg:hover:not([aria-checked="true"]):not(:disabled) { color: var(--sw-text-2); }
.vc-seg[aria-checked="true"] {
  background: var(--bh-blue); color: #fff; font-weight: 600;
}
.vc-seg:disabled { opacity: 0.5; cursor: default; }

/* Meeting settings as one card rather than a blue banner plus loose rows:
   the banner was drawing more attention than the call button it sat above. */
/* Geometry and elevation follow the site's `.sw-card`: 12 px radius, hairline
   rule, and the same two-layer soft shadow, so a card here reads as the same
   object as a card on savorywolper.com. */
.vc-card {
  width: 100%; display: flex; flex-direction: column; gap: 0.625rem;
  padding: var(--sw-s-2);
  background: var(--sw-surface);
  border: 1px solid var(--sw-rule);
  border-radius: var(--sw-radius-md);
  box-shadow:
    0 1px 0 rgba(11, 11, 11, 0.02),
    0 8px 24px -8px rgba(11, 11, 11, 0.06);
}
.vc-card-title { font-size: 0.8125rem; font-weight: 600; color: var(--sw-text); }
.vc-card-lede {
  margin: -0.25rem 0 0; font-size: 0.8125rem; line-height: 1.4;
  color: var(--sw-text-3);
}
.vc-option { display: flex; align-items: flex-start; gap: 0.625rem; cursor: pointer; }
.vc-option-text { display: flex; flex-direction: column; gap: 0.125rem; }
.vc-option-title { font-size: 0.8125rem; color: var(--sw-text-2); }
.vc-option-hint { font-size: 0.75rem; line-height: 1.35; color: var(--sw-text-4); }

/* Model choice is a repair tool, not a setting: it exists because a model
   the account cannot serve answers nothing at all (2026-08-05). Out of the
   way by default, opened for you when that actually happens. */
#advanced-panel { width: 100%; }
#advanced-panel > summary {
  font-size: 0.75rem; color: var(--sw-text-4); cursor: pointer;
  list-style: none; padding: 0.25rem 0;
}
#advanced-panel > summary::-webkit-details-marker { display: none; }
#advanced-panel > summary::before {
  content: '+'; display: inline-block; width: 1em;
  font-family: var(--sw-font-mono);
}
#advanced-panel[open] > summary::before { content: '\2212'; }
#advanced-panel > summary:hover { color: var(--sw-text-3); }
.vc-advanced-body {
  padding-top: 0.375rem;
  display: flex; flex-direction: column; gap: 0.625rem;
  font-size: 0.8125rem; color: var(--sw-text-3);
}
.vc-advanced-body select { width: 100%; }
.vc-advanced-body #diag-opt-in-label {
  display: flex; align-items: center; gap: 0.45rem;
  cursor: pointer; line-height: 1.35;
}
.vc-advanced-actions {
  display: flex; flex-wrap: wrap; gap: 0.75rem;
  padding-top: 0.125rem;
  border-top: 1px solid var(--sw-rule);
  margin-top: 0.125rem;
}
.vc-advanced-actions button { padding-top: 0.5rem; }

#login-panel button {
  width: 100%;
  padding: 0.625rem 1rem;
  background: var(--sw-accent);
  border: 1px solid var(--sw-accent);
  border-radius: 999px;
  color: #fff;
  font-family: inherit;
  font-weight: 500;
  font-size: 0.9375rem;
  cursor: pointer;
  transition: background 200ms var(--sw-ease),
              border-color 200ms var(--sw-ease),
              transform 200ms var(--sw-ease),
              box-shadow 200ms var(--sw-ease);
}
#login-panel button:hover {
  background: var(--sw-accent-2);
  border-color: var(--sw-accent-2);
  transform: translateY(-1px);
  box-shadow: 0 8px 20px -8px rgba(199, 137, 90, 0.45);
}

/* ---------- Waiting dots ----------
   Taken from the BlueHouse chat: three period characters, each bouncing on
   a 1 s cycle, started a tenth of a second apart by negative delays so they
   are already staggered on the first frame rather than jumping together.
   Keyframes are Tailwind's `bounce`, which is what BlueHouse renders. */
@keyframes vc-bounce {
  0%, 100% { animation-timing-function: cubic-bezier(0.8, 0, 1, 1); transform: translateY(-25%); }
  50%      { animation-timing-function: cubic-bezier(0, 0, 0.2, 1); transform: none; }
}
.vc-dot {
  display: inline-block;
  animation: vc-bounce 1s infinite;
}
.vc-dot:nth-child(1) { animation-delay: -0.2s; }
.vc-dot:nth-child(2) { animation-delay: -0.1s; }
@media (prefers-reduced-motion: reduce) {
  .vc-dot { animation: none; }
}

/* ---------- Identity line ----------
   Everything that is not "make a call" now lives behind Advanced; what is
   left out here is who you are signed in as. That is not an advanced
   question — signing out is, and it moved with the rest. */
.vc-footer__identity {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* On narrow viewports, centre each group on its own row so they stack
   cleanly rather than landing at uneven edges. */
@media (max-width: 560px) {
  .vc-footer__identity {
    width: 100%;
    justify-content: center;
  }
}

#user-tag {
  font-family: var(--sw-font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--sw-text-4);
}
#logout-btn, #vlog-copy-btn, #vad-trace-btn {
  background: transparent;
  border: none;
  color: var(--sw-text-3);
  font-family: var(--sw-font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.15rem 0.25rem;
  cursor: pointer;
  transition: color 200ms var(--sw-ease);
}
#logout-btn:hover, #vlog-copy-btn:hover, #vad-trace-btn:hover { color: var(--sw-text); }

#diag-opt-in-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--sw-text-4);
  font-family: var(--sw-font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  user-select: none;
  transition: color 200ms var(--sw-ease);
}
#diag-opt-in-label:hover { color: var(--sw-text-2); }
#diag-opt-in {
  margin: 0;
  /* Muted grey instead of stark black — the checkbox is an opt-out
     preference, not a primary action. */
  accent-color: var(--sw-text-3);
  cursor: pointer;
}

/* ---------- Mic-permission denied dialog ---------- */
#mic-denied-dialog {
  max-width: 360px;
  padding: 1.5rem 1.5rem 1.25rem;
  border: 1px solid var(--sw-rule-strong);
  border-radius: 12px;
  background: var(--sw-surface);
  color: var(--sw-text);
  font-family: var(--sw-font-body);
  box-shadow: 0 24px 48px -16px rgba(11, 11, 11, 0.25);
}
#mic-denied-dialog::backdrop {
  background: rgba(11, 11, 11, 0.45);
  backdrop-filter: blur(2px);
}
#mic-denied-dialog h2 {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--sw-alert);
  margin-bottom: 0.5rem;
}
#mic-denied-dialog p {
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--sw-text-2);
  margin-bottom: 0.75rem;
}
#mic-denied-dialog p.ios-note {
  font-size: 0.85rem;
  color: var(--sw-text-3);
  margin-bottom: 1.1rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--sw-rule);
}
#mic-denied-dialog button {
  width: 100%;
  padding: 0.55rem;
  background: var(--sw-accent);
  color: #fff;
  border: none;
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.9375rem;
  cursor: pointer;
  transition: background 200ms var(--sw-ease);
}
#mic-denied-dialog button:hover { background: var(--sw-accent-2); }

#login-error {
  color: var(--sw-alert);
  font-size: 0.85rem;
  min-height: 1.1rem;
  text-align: center;
}

/* ---------- Mobile: tighten chrome, hand the transcript every spare px ---------- */
@media (max-width: 560px) {
  body { padding: 0.625rem 0.75rem 0.5rem; }
  .vc-topbar { margin-bottom: 0.5rem; }
  #call-btn { width: 92px; height: 92px; font-size: 0.95rem; }
  #status { margin-top: 0.5rem; }
  #mic-level { margin-top: 0.5rem; width: 160px; }
  #transcript { margin-top: 0.75rem; padding: 0.875rem 1rem; }
  #login-panel, #agent-panel { margin-bottom: 0.625rem; }
}

/* ── Desktop: controls left, transcript right ───────────────────────────
   Below this width the page stays the stacked column it is on a phone.
   Above it, the transcript is the subject — you are here for the meeting
   record, not for the settings — so it takes the room and the controls
   become a fixed-width panel beside it.

   Only once signed in: with nothing to show, a two-column layout would be
   a login box next to an empty white rectangle. */
@media (min-width: 900px) {
  body.vc-signed-in {
    display: grid;
    grid-template-columns: 360px minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas:
      "head  head"
      "left  right";
    column-gap: var(--sw-s-3);
    row-gap: var(--sw-s-1);
    align-items: start;
    max-width: 1200px;          /* container max, per DESIGN.md §3 */
    margin: 0 auto;
    padding: 1.25rem var(--sw-s-2) var(--sw-s-2);
  }
  /* Left-aligned on desktop: it is a page title beside an account, not a
     centred masthead over a phone-width column. */
  body.vc-signed-in .vc-topbar {
    grid-area: head; margin-bottom: 0; text-align: left; max-width: none;
  }
  body.vc-signed-in .vc-identity {
    grid-area: head; justify-self: end; align-self: center; order: 0;
  }
  body.vc-signed-in .vc-controls { grid-area: left; align-self: start; }
  body.vc-signed-in #transcript {
    grid-area: right;
    margin-top: 0;
    max-width: none;
    height: 100%;
  }

  /* A 108 px circle is a phone affordance; on a desktop panel it eats the
     height the transcript wants and drags the eye back to the middle of the
     screen. Shape, height, padding and weight are BlueHouse's primary
     button, so the two apps' buttons are the same object. */
  body.vc-signed-in #call-btn {
    width: 100%; height: auto;
    min-height: var(--bh-btn-min-h);
    border-radius: var(--bh-radius);
    padding: 0.625rem 1rem;
    font-size: 0.875rem;
    font-weight: 600;
    margin-top: var(--sw-s-1);
  }
  body.vc-signed-in #status { margin-top: 0.5rem; }
}
