:root {
  --bg: #0b0d11;
  --panel: #151a22;
  --panel-2: #1c222c;
  --line: #262d38;
  --text: #e9edf2;
  --muted: #8b95a5;
  --accent: #4da3ff;
  --green: #35c281;
  --red: #ff5a52;
  --sub-bg: rgba(0, 0, 0, 0.5);
  --sub-text: #ffffff;
  --sub-muted: #c9d1dc;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #eef0f3; --panel: #ffffff; --panel-2: #f0f2f5; --line: #dde2e8;
    --text: #131720; --muted: #667085;
  }
}

* { box-sizing: border-box; }

html, body {
  height: 100%; margin: 0;
  background: var(--bg); color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "SF Pro Text", Inter, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}
body { display: flex; flex-direction: column; }

/* ---- top bar ---------------------------------------------------------- */
/* Three columns like the footer: status on the left, settings on the right,
   and the record button centred on the bar between them. The outer columns are
   1fr each so the middle one stays centred whatever they contain. */
.bar {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 16px;
  /* Roomier at the bottom than the top: the input meter hangs below the
     record button, inside this padding. */
  padding: 8px 20px 19px; border-bottom: 1px solid var(--line);
  background: var(--panel); flex: 0 0 auto;
}
.bar > .brand     { justify-self: start; }
.bar > .rec-stack { justify-self: center; }
.bar > .bar-controls { justify-self: end; }
/* Speech and translation: everything that decides what the subtitles say. */
.bar-controls {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  justify-content: flex-end; min-width: 0;
}
.brand { cursor: default; }
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.status {
  color: var(--muted); font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  padding-left: 10px; border-left: 1px solid var(--line);
}
.status.err { color: var(--red); }
/* Running spend for the paid engines. An estimate, so it stays quiet -- it is
   there to be glanced at mid-talk, not to compete with the status. */
.cost {
  font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--muted); white-space: nowrap; cursor: default;
  padding: 2px 8px; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--line);
}
.cost[hidden] { display: none; }

/* The talk's title: reads like the cost chip next to it, but it is a button --
   clicking it reopens the title dialog to rename the talk in flight. */
.talk-title {
  font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--muted); cursor: pointer;
  /* One fixed width, so naming a talk never reflows the bar. A long title
     fades out at the edge instead of ending in an ellipsis: the chip is a
     label, and "..." reads like something is missing rather than trimmed.
     The full title is in the tooltip, and the dialog shows it in full. */
  width: 20ch; flex: 0 0 auto; position: relative;
  overflow: hidden; white-space: nowrap; text-align: left;
  padding: 2px 8px; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--line);
}
/* The fade itself: the chip's own background over the last few characters, so
   an overlong title dissolves instead of being cut mid-letter. A gradient over
   the text rather than a mask on the button, which would fade the pill's
   border with it. Short titles end before it and are untouched. */
.talk-title::after {
  content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 2.4em;
  background: linear-gradient(to right, transparent, var(--panel-2));
  border-radius: 0 999px 999px 0; pointer-events: none;
}
.talk-title:hover { color: var(--text); border-color: var(--accent); }
.talk-title[hidden] { display: none; }

.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: 0 0 auto; }
.dot.ready { background: var(--green); }
.dot.live  { background: var(--red); animation: pulse 1.4s ease-in-out infinite; }
/* Warming up: work is happening, but nothing can be recorded yet. */
.dot.loading { background: var(--accent); animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }

/* ---- stage + subtitles ------------------------------------------------- */
.stage {
  flex: 1 1 auto; position: relative; overflow: hidden; min-height: 0;
  background: var(--bg);
}
.stage.has-slides { background: #000; }
.stage:fullscreen { background: #000; }

/* The slide fills the stage, letterboxed; subtitles float over its bottom edge. */
.slide {
  /* Centred and letterboxed by JS, which sizes the backing store to the real
     pixels so text stays sharp on a projector. */
  position: absolute; inset: 0; margin: auto;
  max-width: 100%; max-height: 100%;
  user-select: none; -webkit-user-drag: none;
}
.slide[hidden] { display: none; }

/* Small enough to ignore while presenting, and it grows into a direction
   switch when the pointer (or the keyboard) actually comes for it -- in
   fullscreen there is no top bar to go back to. */
.live-badge {
  position: absolute; top: 12px; right: 14px; z-index: 2;
  display: none; align-items: center; gap: 6px;
  padding: 3px 9px; border-radius: 999px; background: rgba(0,0,0,.55);
  color: #fff; font: 600 11px ui-monospace, Menlo, monospace;
  transition: padding .12s ease-out, font-size .12s ease-out, background .12s ease-out;
}
.live-badge:hover, .live-badge:focus-within {
  padding: 6px 8px 6px 12px; font-size: 14px; background: rgba(0,0,0,.85);
}
.live-badge:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.live-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--red); animation: pulse 1.4s infinite;
}
/* The label is what the switch replaces: the pressed button says the same thing. */
.live-badge:hover #liveDir, .live-badge:focus-within #liveDir { display: none; }

.live-seg { display: none; gap: 4px; }
.live-badge:hover .live-seg, .live-badge:focus-within .live-seg { display: inline-flex; }
.live-seg button {
  font: inherit; padding: 3px 9px; border-radius: 999px; color: rgba(255,255,255,.72);
  border: 1px solid rgba(255,255,255,.28); background: transparent; white-space: nowrap;
}
.live-seg button:hover:not(:disabled) { background: rgba(255,255,255,.16); color: #fff; }
.live-seg button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.live-seg button[hidden] { display: none; }
.live-seg button:disabled { opacity: .4; }

/* Only needed when the top bar isn't visible. */
.stage:fullscreen .live-badge:not([hidden]) { display: flex; }
.hint {
  position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);
  margin: 0; width: min(560px, calc(100% - 32px));
  text-align: center; color: var(--muted); line-height: 1.8;
}
.hint.gone { display: none; }
/* Quieter and lower than the hint it follows: advice, not instructions. */
.tip {
  top: auto; bottom: 18%; transform: translateX(-50%);
  font-size: 13px; line-height: 1.7; opacity: 0.75;
}
kbd {
  font: 12px ui-monospace, Menlo, monospace; padding: 1px 6px;
  border: 1px solid var(--line); border-radius: 4px; background: var(--panel-2);
}

/* --subs-pos is the gap below the subtitles, in % of the stage height
   (set from app.js, adjustable with the ▲ ▼ buttons / Shift+↑↓). */
.subs {
  position: absolute; left: 50%; bottom: calc(var(--subs-pos, 4) * 1%);
  transform: translateX(-50%); z-index: 1;
  transition: bottom .15s ease-out, opacity .4s ease-out;
  width: min(1100px, calc(100% - 32px));
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  pointer-events: none;
}
.stage:fullscreen .subs { width: min(1400px, calc(100% - 64px)); }
.cue {
  max-width: 100%; text-align: center;
  background: var(--sub-bg); color: var(--sub-text);
  border-radius: 8px; padding: 6px 16px 8px;
  /* The lighter backdrop lets bright slides through; the shadow keeps text legible. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.cue.enter { animation: cue-in .18s ease-out; }
@keyframes cue-in { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } }

.cue .src {
  color: var(--sub-muted); font-size: clamp(12px, 1.3vw, 16px);
  margin-bottom: 2px;
}
.cue .src.live { font-style: italic; }
.cue .dst {
  font-size: clamp(18px, 2.5vw, 32px); font-weight: 600; line-height: 1.3;
  text-wrap: balance;
}
.cue .dst.pending { color: var(--sub-muted); font-weight: 400; }
.cue .dst.pending::after { content: "…"; animation: blink 1s steps(2) infinite; }
@keyframes blink { 50% { opacity: .2 } }
.cue .lat {
  margin-top: 4px; font-size: 11px; color: var(--sub-muted); opacity: .75;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.cue .tag {
  display: inline-block; margin-right: 6px; padding: 0 5px; border-radius: 3px;
  font-size: 10px; font-weight: 700; letter-spacing: .04em; vertical-align: 2px;
  background: rgba(255,255,255,.14); font-style: normal;
}

body.hide-src .cue .src { display: none; }
body.hide-lat .cue .lat { display: none; }

/* ---- controls ------------------------------------------------------------ */
/* Three regions: slides left, fullscreen centred, subtitles right. A grid
   rather than spacers so the centre stays centred on the screen even when the
   two sides are different widths (they always are -- the deck grows a nav). */
.controls {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 8px; padding: 8px 20px; border-top: 1px solid var(--line);
  background: var(--panel); flex: 0 0 auto;
}
.controls > .deck  { justify-self: start; }
.controls > .fs    { justify-self: center; }
.controls > .group { justify-self: end; }

button { font: inherit; cursor: pointer; border-radius: 8px; }
button:disabled { opacity: .45; cursor: not-allowed; }

/* The button and its level meter are one control. Stacked so the meter never
   competes for room in the bar, and the meter takes the button's width. */
.rec-stack { position: relative; display: flex; flex-direction: column; }
.rec {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  /* Wide enough for "Start listening" and "Stop listening" alike: the label
     changes on every press, and a button that resized would shift the whole
     bar under the pointer. Longer transient labels ("Loading models…") are
     allowed to grow it -- nothing is being aimed at while they show. */
  min-width: 190px; white-space: nowrap;
  padding: 9px 18px; border: none; color: #fff; background: var(--accent); font-weight: 600;
}
.rec.live { background: var(--red); }
.rec-icon { width: 10px; height: 10px; border-radius: 50%; background: #fff; }
.rec.live .rec-icon { border-radius: 2px; }

.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.seg button {
  border: none; border-radius: 0; padding: 7px 12px;
  background: var(--panel-2); color: var(--muted); font-weight: 600; font-size: 13px;
}
.seg button + button { border-left: 1px solid var(--line); }
.seg button[hidden] { display: none; }
.seg button[aria-pressed="true"] { background: var(--accent); color: #fff; }

/* How speech becomes the other language. A dropdown rather than a segment:
   it is set once at the start of a talk, and changing it reloads the models,
   so it should not sit under the thumb next to the direction switch. */
.mode {
  font: 600 13px -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  padding: 7px 10px; border-radius: 8px;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
}
.mode:disabled { opacity: 0.5; }

.ghost { padding: 7px 11px; background: var(--panel-2); border: 1px solid var(--line); color: var(--text); }
.ghost:hover:not(:disabled) { background: var(--line); }
.ghost.icon { padding: 5px 10px; font-size: 16px; line-height: 1; }

/* Presentation controls travel together: fullscreen, subtitle position, and
   how long each subtitle stays up. Boxed so they read as one cluster rather
   than four loose buttons in a long row. */
.group {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 6px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel-2);
}

.subpos { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); font-size: 13px; }
.subpos .ghost.icon { padding: 4px 8px; font-size: 12px; }
.subpos select {
  font: 12px -apple-system, BlinkMacSystemFont, system-ui, sans-serif; color: var(--text);
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 7px; padding: 4px 6px;
}
/* Auto-hide: the subtitle fades out, and reappears with the next sentence. */
.subs.faded { opacity: 0; }

/* Waiting for voice. Sits exactly where a subtitle would, so the eye doesn't
   move when speech arrives. */
.waiting {
  position: absolute; left: 50%; bottom: calc(var(--subs-pos, 4) * 1%);
  transform: translateX(-50%); z-index: 1; pointer-events: none;
  display: flex; align-items: center; gap: 9px;
  padding: 11px 15px; border-radius: 999px;
  background: var(--sub-bg);
}
.waiting[hidden] { display: none; }
.waiting span {
  width: 9px; height: 9px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.32);
  animation: waiting-dot 1.35s ease-in-out infinite;
}
.waiting span:nth-child(2) { animation-delay: .18s; }
.waiting span:nth-child(3) { animation-delay: .36s; }
/* Colour only: every dot stays at full size and opacity throughout, so the row
   never looks like it is losing a dot. */
@keyframes waiting-dot {
  0%, 55%, 100% { background: rgba(255, 255, 255, 0.32); }
  25%           { background: var(--sub-text); }
}
@media (prefers-reduced-motion: reduce) {
  .waiting span { animation: none; }
  .waiting span { background: rgba(255, 255, 255, 0.6); }
}

.deck, .deck-nav { display: inline-flex; align-items: center; gap: 6px; }
.deck-nav[hidden] { display: none; }
.page {
  min-width: 64px; text-align: center; color: var(--muted);
  font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
}

.toggle { color: var(--muted); font-size: 13px; display: flex; align-items: center; gap: 5px; cursor: pointer; white-space: nowrap; }
/* Absolute, so the pair is only as tall as the button: in flow the extra 14px
   would centre every other control 7px below the button it sits beside. The
   bar's bottom padding is sized to hold it. */
.meter {
  position: absolute; left: 0; right: 0; top: calc(100% + 5px); height: 8px;
  background: var(--panel-2); border-radius: 4px; overflow: hidden;
}
.meter-fill { height: 100%; width: 0; background: var(--muted); transition: width .07s linear; }
.meter-fill.speaking { background: var(--green); }
.meter-thresh { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--accent); opacity: .65; left: 0; }

@media (max-width: 700px) {
  /* No room to centre anything: the three regions wrap into rows, so the
     meter goes back into the flow -- hanging it under the button would lay it
     over whatever wrapped underneath. */
  .bar { padding: 8px 16px; display: flex; flex-wrap: wrap; }
  .rec-stack { gap: 5px; }
  .meter { position: static; }
  .controls { padding: 8px 16px; }
  /* No room to centre anything; let the three regions wrap into a row. */
  .controls { display: flex; flex-wrap: wrap; }
}

/* ---- engine chooser ------------------------------------------------------- */
dialog.engine {
  width: min(520px, calc(100% - 32px)); padding: 0;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--panel); color: var(--text);
  box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
dialog.engine::backdrop { background: rgba(0,0,0,.55); }
dialog.engine form { padding: 22px 24px 20px; display: flex; flex-direction: column; gap: 12px; }
dialog.engine h2 { margin: 0; font-size: 18px; }
dialog.engine .sub { margin: -6px 0 4px; color: var(--muted); font-size: 13px; }

.choice {
  display: flex; gap: 12px; align-items: flex-start; cursor: pointer;
  padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel-2);
}
.choice:has(input:checked) { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.choice input { margin-top: 3px; accent-color: var(--accent); }
.choice b { display: block; margin-bottom: 2px; }
.choice small { color: var(--muted); font-size: 12.5px; line-height: 1.45; display: block; }
.choice code { font-size: 11.5px; }
/* "Mac only" and friends: a platform caveat sitting inline with the choice title */
.choice .req {
  margin-left: 6px; padding: 1px 6px; border-radius: 999px; vertical-align: 1px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase;
  color: var(--muted); background: var(--bg); border: 1px solid var(--line);
}

/* Loading, reconnecting, warming up: small, in the corner, never modal. */
.work {
  position: absolute; top: 12px; right: 14px; z-index: 4;
  display: flex; align-items: center; gap: 7px;
  padding: 5px 11px 5px 9px; border-radius: 999px;
  background: rgba(0, 0, 0, .58); color: #fff;
  font: 500 11.5px ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: nowrap; pointer-events: none;
}
.work[hidden] { display: none; }
.spinner {
  width: 11px; height: 11px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .28); border-top-color: #fff;
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}

.openai-fields { display: flex; flex-direction: column; gap: 8px; padding: 2px 2px 0 38px; }
.openai-fields[hidden] { display: none; }
.openai-fields label[hidden] { display: none; }
.openai-fields label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.openai-fields input, .openai-fields select {
  font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text);
  background: var(--bg); border: 1px solid var(--line); border-radius: 7px; padding: 7px 9px;
}
.note { margin: 0; font-size: 12px; color: var(--muted); }
.error { margin: 0; font-size: 13px; color: var(--red); }
.error[hidden] { display: none; }
.actions { display: flex; justify-content: flex-end; margin-top: 4px; }

/* ---- context dialog ------------------------------------------------------- */
dialog .notes { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--muted); margin-top: 4px; }
dialog .notes input[type="text"], dialog .notes input[type="password"] {
  font: 14px -apple-system, BlinkMacSystemFont, system-ui, sans-serif; color: var(--text);
  background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 9px 11px;
}
dialog textarea {
  font: 13px/1.45 -apple-system, BlinkMacSystemFont, system-ui, sans-serif; color: var(--text);
  background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
  resize: vertical; min-height: 90px;
}
dialog .actions { gap: 8px; }
#contextBtn.active { border-color: var(--accent); color: var(--accent); }
