/* Live voice (voice.js, voice-view.js, voice-awake.js) and dictation (dictate.js): the
 * composer's mic and call buttons, the call bar above the composer with the
 * camera and the shared screen, and a request said on a call in the
 * transcript. Tokens from brand.css and tokens.css only.
 * The call bar says the truth: a transport-backed connection chip, a
 * status under it, a red labeled "End call" and a labeled mute. Red ends a
 * call and nothing else; the mic-level ring is the working green only while
 * real audio flows. On a phone, a call's bar is one slim row (the time and
 * status, mute, More, End) until More opens the rest: the conversation and
 * what Share hands over keep the screen (Hamet, 2026-10-27). */

.composer-options :is(.voice-call, .voice-dictate) { min-width: 32px; color: var(--sv-ink-tertiary); }
.composer-options :is(.voice-call, .voice-dictate):hover { color: var(--sv-ink); }
/* Dictating: the mic is lit in ink (never red: red ends a call), and its
 * ring follows the voice. It writes in the message box; nothing is recorded. */
.composer-options .voice-dictate.on {
  --level: 0;
  color: var(--sv-surface-raised, #fff);
  background: var(--sv-emphasis-surface);
  border-radius: 999px;
  box-shadow: 0 0 0 calc(var(--level) * 6px) color-mix(in srgb, var(--ws-working) 35%, transparent);
}
.composer-options .voice-dictate.on:hover { color: var(--sv-surface-raised, #fff); background: var(--sv-emphasis-surface); }
@media (prefers-reduced-motion: reduce) {
  .composer-options .voice-dictate.on { box-shadow: none; }
}
@media (max-width: 760px), (hover: none) and (pointer: coarse) {
  .composer-options :is(.voice-call, .voice-dictate) { min-width: 44px; min-height: 44px; }
}
/* The call button sits in the chat's top bar, at the right, away from Send:
 * a call is about the whole chat, and a thumb aimed at Send must never start
 * one. It is the header's one green control (Reyansh, 2026-10-29): a 32px
 * accent disc with an 18px outline handset, the size of the icons beside it.
 * On a touch screen the button is the header's 44px target and the disc stays
 * 32px inside it, so it never dominates the bar. The handset is on-accent ink
 * (black on green, 10.78:1 in both themes); red stays the end of a call only.
 * During a call it becomes a red End call action. */
.topbar .voice-call {
  --call-disc: var(--sv-accent);
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  min-width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--sv-on-accent);
}
.topbar .voice-call::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 50%;
  background: var(--call-disc);
  transition: background-color var(--sv-dur-instant) var(--sv-ease-out), transform var(--sv-dur-instant) var(--sv-ease-out);
}
.topbar .voice-call > .icon { width: 18px; height: 18px; fill: none; stroke-width: 2; opacity: 1; }
.topbar .voice-call:hover { background: none; color: var(--sv-on-accent); --call-disc: color-mix(in srgb, var(--sv-accent) 85%, var(--sv-on-accent)); }
.topbar .voice-call:active { --call-disc: color-mix(in srgb, var(--sv-accent) 72%, var(--sv-on-accent)); }
.topbar .voice-call:active::before { transform: scale(0.92); }
.topbar .voice-call:focus-visible { outline: 2px solid var(--sv-ink); outline-offset: 2px; }
/* The active top button is a hang-up action, not a green connection claim.
 * Connection words in the bar follow the peer and data channel (voice.js). */
.topbar .voice-call[aria-pressed="true"] { --call-disc: var(--sv-danger); color: var(--sv-surface-raised, #fff); }
.topbar .voice-call[aria-pressed="true"]:is(:hover, :active) { --call-disc: color-mix(in srgb, var(--sv-danger) 85%, #000); }
/* During a call the top button stays the same round disc, red, with the
 * phone-off handset: the same face as the bar's End call on a phone. A wide
 * labeled pill crowded the phone's top bar (Hamet, 2026-11-05); the word
 * stays in aria-label and the title, and the bar below names End call. */
.topbar .voice-call .voice-call-label { display: none; }
@media (prefers-reduced-motion: reduce) {
  .topbar .voice-call:active::before { transform: none; }
}
@media (max-width: 760px), (hover: none) and (pointer: coarse) {
  .topbar .voice-call { width: 44px; height: 44px; }
  .topbar .voice-call::before { inset: 6px; }
  .topbar .voice-call:hover { --call-disc: var(--sv-accent); }
  .topbar .voice-call:active { --call-disc: color-mix(in srgb, var(--sv-accent) 72%, var(--sv-on-accent)); }
  .topbar .voice-call:focus-visible { outline-offset: -2px; }
}

.voice-bar {
  align-self: center;
  width: min(100% - 2 * var(--sv-space-5), var(--reading));
  display: flex;
  align-items: center;
  gap: var(--sv-space-3);
  margin-top: var(--sv-space-1);
  padding: var(--sv-space-2) var(--sv-space-3);
  border: var(--sv-hairline-w) solid var(--sv-hairline);
  border-radius: calc(var(--sv-radius) * 1.5);
  background: var(--sv-surface-raised);
  font-size: var(--ui-text);
  animation: sv-enter-msg var(--sv-dur-fast) var(--sv-ease-out) both;
}
.voice-bar[hidden] { display: none; }
/* A second row only while a screen is shared (.voice-sharing). */
.voice-bar { flex-wrap: wrap; row-gap: var(--sv-space-1); }

/* Share's mark grows with its voice; the ring follows the person's real
 * microphone level (flat while muted: no audio flows, so none is drawn). */
.voice-orb {
  --out: 0;
  --in: 0;
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--sv-emphasis-surface, #000);
  color: var(--sv-surface-raised, #fff);
  box-shadow: 0 0 0 calc(var(--in) * 8px) color-mix(in srgb, var(--ws-working) 60%, transparent);
  transform: scale(calc(1 + var(--out) * 0.18));
}
.voice-orb svg { width: 15px; height: 17px; overflow: visible; }
/* A muted microphone is named over the mark, not only colored. */
.voice-mic-badge { display: none; }
.voice-orb.muted { box-shadow: none; }
.voice-orb.muted .voice-mic-badge {
  position: absolute;
  top: -6px;
  right: -8px;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--sv-surface-raised);
  border: var(--sv-hairline-w) solid var(--sv-hairline);
  color: var(--sv-ink-secondary);
}
.voice-orb.muted .voice-mic-badge .icon { width: 10px; height: 10px; }

.voice-info { flex: 1; min-width: 0; overflow: hidden; display: grid; gap: 1px; }
/* The connection state with the length: there from the session's first moment,
 * quiet (no live region) because it changes every second. */
.voice-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--sv-ink);
  font-size: var(--ui-meta);
  font-weight: 500;
}
.voice-chip[hidden] { display: none; }
.voice-chip-text { white-space: nowrap; }
.voice-chip-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--ws-working); }
.voice-chip.quiet .voice-chip-dot { background: var(--sv-ink-tertiary); }
.voice-chip .voice-clock { font-variant-numeric: tabular-nums; color: var(--sv-ink-secondary); }
.voice-status { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--sv-ink); font-weight: 500; }
/* Share is not listening (muted, the mic or the line down): the status says
 * why in heavier words and the dot dims. A nudge (voice-cues.js) is heavier. */
.voice-bar.deaf .voice-status { font-weight: 600; }
.voice-bar.nudge .voice-status { font-weight: 700; }
/* The newest words: a caption too long for the bar is clipped at its
 * start (voice.js marks it .long), not its end. */
.voice-caption {
  min-width: 0;
  display: flex;
  overflow: hidden;
  white-space: nowrap;
  color: var(--sv-ink-tertiary);
  font-size: var(--ui-meta);
}
.voice-caption > span { flex: none; }
.voice-caption.long { justify-content: flex-end; mask-image: linear-gradient(to right, transparent, #000 24px); }
.voice-caption:empty { display: none; }
.voice-back { font-size: var(--ui-meta); }
.voice-preview {
  flex: none;
  width: 72px;
  height: 54px;
  object-fit: cover;
  border-radius: calc(var(--sv-radius) * 0.75);
  background: var(--sv-surface-sunken);
  transform: scaleX(-1); /* a mirror, as people expect to see themselves */
}
.voice-preview[hidden] { display: none; }
/* A shared screen is shown whole and the right way round. */
.voice-preview.screen { width: 96px; object-fit: contain; transform: none; }

/* Sharing a screen: its own row under the status, a plain label and Stop sharing. */
.voice-sharing { flex: 1 0 100%; display: flex; align-items: center; gap: var(--sv-space-2); padding-left: calc(36px + var(--sv-space-3)); }
.voice-sharing[hidden] { display: none; }
.voice-sharing-label { display: inline-flex; align-items: center; gap: 6px; color: var(--sv-ink); font-size: var(--ui-meta); font-weight: 500; white-space: nowrap; }
.voice-sharing-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--sv-danger); }
.voice-sharing-stop { font-size: var(--ui-meta); }

/* Before a first screen share: what Share sees and what is kept. */
.voice-notice {
  align-self: center;
  width: min(100% - 2 * var(--sv-space-5), var(--reading));
  display: grid;
  gap: var(--sv-space-2);
  margin-top: var(--sv-space-1);
  padding: var(--sv-space-3);
  border: var(--sv-hairline-w) solid var(--sv-hairline);
  border-radius: calc(var(--sv-radius) * 1.5);
  background: var(--sv-surface-raised);
  font-size: var(--ui-text);
}
.voice-notice[hidden] { display: none; }
.voice-notice p { margin: 0; color: var(--sv-ink-secondary); line-height: var(--sv-leading-body); }
.voice-notice-actions { display: flex; flex-wrap: wrap; gap: var(--sv-space-2); }
/* The controls: their own cluster, so on a phone they take a row of 44 px
 * targets with room for their words. */
.voice-controls { flex: none; display: flex; align-items: center; gap: var(--sv-space-2); margin-left: auto; }
/* A control keeps its size and its words: the status gives way, never a control. */
.voice-controls > * { flex: none; }
/* A control's word rides beside its icon (mute, End call): meaning never
 * hangs on color or icon alone. */
.voice-label { font-size: var(--ui-meta); white-space: nowrap; }
/* .voice-bar first: button.ghost (tokens.css) would otherwise win and keep the controls 28 px tall. */
.voice-bar .voice-control { min-width: 36px; min-height: 36px; color: var(--sv-ink-secondary); }
.voice-control[aria-pressed="true"] { color: var(--sv-ink); background: var(--ws-selected); }
/* Red is the end of a call and nothing else. */
.voice-control.voice-end {
  color: var(--sv-surface-raised, #fff);
  background: var(--sv-danger);
  border-radius: 999px;
  padding-inline: var(--sv-space-3);
}
.voice-control.voice-end:hover { color: var(--sv-surface-raised, #fff); background: color-mix(in srgb, var(--sv-danger) 85%, #000); }
/* Muted is unmistakable (Hamet, 2026-10-28): a filled attention-color
 * button with the slashed mic and the word "Muted" (white on --sv-attention
 * 5.0:1; dark mode black on it 9.2:1). Attention, not red: red ends a call. */
.voice-bar .voice-control.voice-mute[aria-pressed="true"],
.voice-bar .voice-control.voice-mute[aria-pressed="true"]:hover {
  color: var(--sv-emphasis-ink);
  background: var(--sv-attention);
  border-radius: 999px;
  padding-inline: var(--sv-space-3);
  font-weight: 500;
}
.voice-mute.pulse { animation: voice-mute-pulse var(--sv-dur-fast, 160ms) var(--sv-ease-out, ease-out); }
@keyframes voice-mute-pulse { 50% { transform: scale(1.08); } }
/* "You're muted" at the top of the chat while the call is muted: large
 * enough to read at arm's length without glasses. */
.voice-muted-pill {
  position: absolute;
  top: var(--sv-space-3);
  left: 50%;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--sv-space-2);
  padding: var(--sv-space-2) var(--sv-space-4);
  border-radius: 999px;
  background: var(--sv-attention);
  color: var(--sv-emphasis-ink);
  font-size: var(--sv-text-h4);
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.18);
  transform: translateX(-50%);
  pointer-events: none;
  animation: sv-enter-msg var(--sv-dur-fast) var(--sv-ease-out) both;
}
.voice-muted-pill[hidden] { display: none; }
.voice-muted-pill .icon { width: 20px; height: 20px; }
@media (prefers-reduced-motion: reduce) {
  .voice-mute.pulse, .voice-muted-pill { animation: none; }
}
.voice-control.voice-end[aria-pressed="true"] { color: var(--sv-surface-raised, #fff); background: var(--sv-danger); }

/* A request said on a call: a call mark over the person's words; a camera
 * or screen frame it carried is a small thumbnail. */
.turn-user.turn-voice .voice-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 2px;
  color: var(--sv-ink-tertiary);
  font-size: var(--ui-meta);
}
.turn-user.turn-voice .turn-image { width: 120px; max-width: 120px; height: auto; margin-bottom: 4px; }

/* Listening for the wake word (wake.js): the ear beside the phone; the
 * hearing screen over the whole page while the phone listens (the solid
 * mark, the words as they are heard, the status), which becomes the
 * conversation while a call runs (the mark moves with the voices); and what
 * was heard before the wake word as context in the transcript. */
.wake-screen {
  position: fixed;
  inset: 0;
  z-index: var(--sv-z-overlay);
  display: grid;
  grid-template-rows: 1fr auto auto;
  align-items: center;
  justify-items: center;
  padding: max(var(--sv-space-6), env(safe-area-inset-top)) max(var(--sv-space-5), env(safe-area-inset-right)) max(var(--sv-space-6), env(safe-area-inset-bottom)) max(var(--sv-space-5), env(safe-area-inset-left));
  gap: var(--sv-space-5);
  background: var(--sv-surface);
  color: var(--sv-ink);
  overflow: hidden;
  animation: sv-enter-msg var(--sv-dur-fast) var(--sv-ease-out) both;
}
.wake-screen[hidden] { display: none; }
.wake-screen .wake-control { position: absolute; top: max(var(--sv-space-4), env(safe-area-inset-top)); min-width: 44px; min-height: 44px; color: var(--sv-ink-secondary); }
.wake-screen .wake-close { right: max(var(--sv-space-4), env(safe-area-inset-right)); }
.wake-screen .wake-mute { left: max(var(--sv-space-4), env(safe-area-inset-left)); }
.wake-screen .wake-mute[aria-pressed="true"] { color: var(--sv-ink); background: var(--ws-selected); border-radius: 999px; }
/* The mark: large, breathing; a ring for the person's voice, growth for Share's; a glow while speech is heard. */
.wake-stage {
  --out: 0;
  --in: 0;
  display: grid;
  place-items: center;
  padding: var(--sv-space-6);
  border-radius: 50%;
  box-shadow: 0 0 0 calc(var(--in) * 24px) color-mix(in srgb, var(--ws-working) 35%, transparent);
  transform: scale(calc(1 + var(--out) * 0.25));
  transition: box-shadow var(--sv-dur-fast) linear;
}
.wake-stage .mark3d { width: 148px; height: 166px; margin-bottom: var(--sv-space-5); --mark3d-step: 2.4px; }
.wake-stage.speech .mark3d { color: var(--ws-working-ink, var(--sv-ink)); }
.wake-words {
  width: min(100%, var(--reading));
  min-height: 7lh;
  max-height: 40vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--sv-space-2);
  overflow: hidden;
  mask-image: linear-gradient(transparent, #000 30%);
  font-size: calc(var(--ui-text) * 1.15);
  line-height: 1.45;
  text-align: center;
}
.wake-line { color: var(--sv-ink-secondary); animation: sv-enter-msg var(--sv-dur-fast) var(--sv-ease-out) both; }
.wake-line.heard:last-child, .wake-line.person { color: var(--sv-ink); }
.wake-line.share { color: var(--sv-ink); font-weight: 500; }
.wake-line.share::before { content: "Share · "; font-weight: 400; color: var(--sv-ink-tertiary); }
.wake-line.partial { color: var(--sv-ink-tertiary); letter-spacing: 0.2em; }
.wake-foot { display: flex; align-items: center; gap: var(--sv-space-3); min-height: 44px; font-size: var(--ui-text); }
.wake-status { color: var(--sv-ink); font-weight: 500; }
.wake-clock { color: var(--sv-ink-tertiary); font-variant-numeric: tabular-nums; }
.wake-foot .wake-end { position: static; gap: var(--sv-space-2); padding-inline: var(--sv-space-3); color: var(--sv-surface-raised, #fff); background: var(--sv-danger); border-radius: 999px; }
.turn-user.turn-heard .text { color: var(--sv-ink-secondary); }
@media (prefers-reduced-motion: reduce) {
  .wake-stage { transform: none; box-shadow: none; }
}

@media (prefers-reduced-motion: reduce) {
  .voice-orb { transform: none; box-shadow: none; }
}
@media (max-width: 760px), (pointer: coarse) and (max-width: 960px) and (max-height: 500px) {
  .voice-bar, .voice-notice { width: min(100% - 2 * var(--sv-space-4), var(--reading)); }
  .voice-bar { gap: var(--sv-space-2); }
  /* The controls take their own row: 44 px targets, spread edge to edge,
   * never over the composer, and the chip keeps its clock. */
  .voice-controls { flex: 1 0 100%; flex-wrap: wrap; justify-content: space-between; gap: var(--sv-space-1); margin-left: 0; }
  .voice-bar .voice-control, .voice-notice-actions button { min-width: 44px; min-height: 44px; }
  .voice-control .voice-label { font-size: var(--ui-meta); }
  .voice-sharing { padding-left: 0; }
  .voice-preview.screen { width: 72px; }
  .voice-bar .voice-controls > .voice-more { display: inline-flex; }
  /* Slim: one row of 44 px targets. The status names the state in words
   * (Share is speaking), so End may be an icon here; its word comes back
   * with More. Mute keeps its word (Mute / Muted): Hamet could not tell
   * the state from the icon alone (2026-10-28). */
  .voice-bar:not(.open) { padding-block: 2px; }
  .voice-bar:not(.open) .voice-orb { width: 28px; height: 28px; }
  .voice-bar:not(.open) .voice-info { display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--sv-space-2); }
  .voice-bar:not(.open) :is(.voice-caption, .voice-control:not(.voice-mute) .voice-label) { display: none; }
  .voice-bar:not(.open) .voice-info { min-width: 8rem; }
  .voice-bar:not(.open) .voice-clock { display: none; }
  .voice-bar:not(.open) .voice-controls { flex: none; gap: var(--sv-space-1); margin-left: auto; }
  .voice-bar:not(.open) .voice-controls > .voice-control:not(.voice-mute, .voice-more, .voice-end) { display: none; }
  .voice-bar:not(.open) .voice-end { padding-inline: 0; }
  /* The mute's word sits under its icon, so it fits next to the connection word. */
  .voice-bar:not(.open) .voice-control.voice-mute { flex-direction: column; gap: 0; padding: 2px var(--sv-space-2); border-radius: var(--sv-radius); line-height: 1.1; }
  .voice-bar:not(.open) .voice-mute .voice-label { font-size: 12px; }
  .voice-bar:not(.open) .voice-preview { width: 44px; height: 44px; }
}
/* More is a phone's: a wide screen shows every control (.voice-controls first: button.ghost sets display). */
.voice-controls > .voice-more { display: none; }
/* A small phone: tighter spacing leaves the status a few more words. */
@media (max-width: 380px) {
  .voice-bar { gap: var(--sv-space-1); padding-inline: var(--sv-space-2); }
}

@media (pointer: coarse) {
  .voice-bar .voice-control { min-width: 44px; min-height: 44px; }
}

/* A phone on its side cannot stack a large mark, seven caption lines and
 * the call foot. Keep the controls above a compact two-column stage, with
 * End always in its own visible bottom row, clear of the home indicator. */
@media (pointer: coarse) and (max-height: 500px) {
  .wake-screen {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    grid-template-rows: minmax(0, 1fr) auto;
    padding: max(64px, calc(env(safe-area-inset-top) + 48px)) max(var(--sv-space-4), env(safe-area-inset-right)) max(var(--sv-space-2), env(safe-area-inset-bottom)) max(var(--sv-space-4), env(safe-area-inset-left));
    gap: var(--sv-space-2) var(--sv-space-4);
  }
  .wake-stage { grid-column: 1; grid-row: 1; min-height: 0; padding: var(--sv-space-1); }
  .wake-stage .mark3d { width: 72px; height: 81px; margin-bottom: 0; }
  .wake-words { grid-column: 2; grid-row: 1; min-height: 0; max-height: 100%; align-self: stretch; }
  .wake-foot { grid-column: 1 / -1; grid-row: 2; flex-wrap: wrap; justify-content: center; }
}
