:root {
  --bg: #14161a;
  --panel: #1d2026;
  --line: #2c313a;
  --text: #e7e9ee;
  --dim: #949cab;
  --faint: #6b7382;
  --me: #4a9eff;
  --them: #ffb02e;
  --bad: #ff5b5b;
  --good: #3ddc84;
  /* A whole paragraph in the signal green is hard to read, so translated text
     gets a softened version of it: unmistakably green, still comfortable. */
  --translated: #8fe0b4;
  --tailored: #a78bfa;
}

* { box-sizing: border-box; }

/* Every scrollbar in the app — the sidebar, the conversation list, a tall
   modal, the transcript — in the page's own dark palette rather than
   whatever the OS theme happens to draw by default, which on most Windows
   setups is a pale grey slab that looks like it belongs to a different
   application. `scrollbar-color`/`scrollbar-width` is the standard property
   and covers Chrome 121+ on its own; the `::-webkit-scrollbar` rules beside
   it are what the capture APIs' Chrome/Edge requirement actually needs
   supported today. Applied on * rather than named per scrollable element, so
   a container that gains overflow later is never the one exception that
   still shows the OS default. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
*::-webkit-scrollbar { width: 11px; height: 11px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background-color: var(--line);
  border-radius: 20px;
  border: 3px solid var(--bg);
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--dim); }
/* Inside a modal the track is the panel colour, not the page's, or the
   3px border above would show as a thin ring of the wrong shade against it. */
.modal-card *::-webkit-scrollbar-thumb { border-color: var(--panel); }

body {
  margin: 0;
  padding: 14px 20px 20px;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
}

a { color: inherit; }
.hidden { display: none !important; }
.dim { color: var(--dim); font-size: 12px; }
.empty { color: var(--dim); }

/* ------------------------------------------------------------------ header */

.topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  max-width: 1320px;
  margin: 0 auto 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}

/* The Bamya logo box, the one in Leads and on the landing page, with the word
   after "bamya" changed: same border, same chip, same Arial Black. Drawn light
   on dark rather than dark on light, because the page it sits on is dark. */
.brand {
  flex: 0 0 auto;
  font-family: "Arial Black", "Helvetica Neue", sans-serif;
  font-size: 1.25rem;
  font-weight: 900;
  color: #fff;
  border: 1px solid #fff;
  border-radius: 4px;
  padding: 0.3em 0.5em;
  text-decoration: none;
  white-space: nowrap;
}

.brand:hover { color: #fff; }
.brand .logo-bamya { color: #fff; font-weight: 900; }

.brand .logo-co {
  background-color: #fff;
  color: #14161a;
  border-radius: 3px;
  padding: 0.1em 0.3em;
  margin-left: 0.1em;
}

/* The sign-in card is a flex column, so the <h1> carrying the mark would
   stretch its box the full width. Shrink it back to the mark. */
.gate-card .brand { align-self: flex-start; }

.title-input {
  flex: 1;
  min-width: 0;
  font-size: 21px;
  font-weight: 600;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 7px;
  padding: 5px 8px;
  color: var(--text);
}

/* The name as a heading. Same size and weight the input had, so moving it into
   the dialog changed where you edit it and not how the page reads. */
.title-text {
  flex: 1;
  min-width: 0;
  font-size: 21px;
  font-weight: 600;
  padding: 5px 8px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The dialog's fields stack; the title input inside it is an ordinary field
   rather than the borderless heading it used to be. */
#session-modal .title-input { border-color: var(--line); }
#session-modal .field { display: block; }
#session-modal .field > span { display: block; margin-bottom: 4px; color: var(--dim); font-size: 12px; }

.title-input:hover:not(:disabled) { border-color: var(--line); }
.title-input:focus { border-color: var(--me); outline: none; background: #12141a; }
.title-input:disabled { opacity: 1; color: var(--faint); }

.banner {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  border-radius: 9px;
  padding: 11px 14px;
  margin: 0 auto 14px;
  max-width: 1320px;
  font-size: 13.5px;
  border: 1px solid var(--bad);
  background: rgba(255, 91, 91, 0.12);
}

.banner-text { flex: 1; min-width: 0; }

.banner-close {
  flex: 0 0 auto;
  background: none;
  border: 0;
  color: inherit;
  opacity: 0.6;
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.banner-close:hover { opacity: 1; }

.banner-info { border-color: var(--me); background: rgba(74, 158, 255, 0.12); }

/* ------------------------------------------------------------------ layout */

.layout {
  display: grid;
  grid-template-columns: 270px minmax(0, 1fr);
  gap: 24px;
  align-items: start;
  max-width: 1320px;
  margin-inline: auto;
}

/* Collapsed, on a conversation: the history takes almost the whole page, but a
   thin rail stays so the toggle that reopens it has somewhere to live. The
   toggle sits INSIDE .sidebar now (the usual retract/expand icon, attached to
   the thing it controls), so the sidebar itself cannot go to display:none the
   way it used to — that would take the only way back to expanding it with it.
   #controls is what actually holds everything else, so hiding that alone
   leaves exactly the rail. */
.layout.sidebar-hidden { grid-template-columns: 34px minmax(0, 1fr); }
.layout.sidebar-hidden .sidebar #controls { display: none; }

/* The home screen, where there is no conversation to show controls for at all
   — not collapsed, not a thin rail, no sidebar. Written after sidebar-hidden
   above so it wins the cascade on the rare frame both classes are present
   (route() also takes care not to combine them). */
.layout.no-sidebar { grid-template-columns: minmax(0, 1fr); }
.layout.no-sidebar .sidebar { display: none; }

.icon-toggle {
  flex: 0 0 auto;
  padding: 6px 10px;
  font-size: 14px;
  line-height: 1;
  color: var(--dim);
}

.icon-toggle.on { color: var(--me); border-color: var(--me); }

.sidebar {
  position: sticky;
  top: 14px;
  max-height: calc(100vh - 28px);
  overflow-y: auto;
  font-size: 13px;
}

/* The retract/expand control, living on the sidebar itself rather than in the
   header. Full width of whatever the sidebar currently is, so it reads as a
   bar across the top when expanded and fills the 34px rail when collapsed —
   the one thing still visible there. */
.sidebar-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin-bottom: 8px;
  padding: 6px 0;
}
.sidebar-toggle svg { width: 17px; height: 17px; }
.gear-toggle svg { width: 17px; height: 17px; vertical-align: middle; }
/* The "reversed" icon for reopening — the same glyph mirrored, rather than a
   second one drawn by hand. .on is applied exactly when the sidebar is
   collapsed (see renderSidebar in app.js), which is exactly when the chevron
   should point the other way. */
.sidebar-toggle.on svg { transform: scaleX(-1); }

.side-block {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
}

/* ------------------------------------------------------------------ inputs */

input[type="text"], input[type="search"], textarea, select {
  background: #12141a;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  padding: 7px 9px;
  font: inherit;
  width: 100%;
}

textarea { resize: vertical; line-height: 1.45; font-size: 12.5px; }
select { cursor: pointer; }

input:disabled { opacity: 0.55; }

button {
  background: #23272f;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  padding: 7px 12px;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  white-space: nowrap;
}

button:hover:not(:disabled) { border-color: #4a5261; }
button:disabled { opacity: 0.4; cursor: default; }
button.primary { background: var(--me); border-color: var(--me); color: #06121f; font-weight: 600; }
button.danger { color: var(--bad); }

.icon-button {
  background: transparent;
  border: 0;
  font-size: 22px;
  line-height: 1;
  padding: 0 4px;
  color: var(--dim);
}

.pill {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 20px;
  border: 1px solid var(--line);
  color: var(--dim);
  white-space: nowrap;
}

/* Sign out is a POST with a CSRF token, so it is a form rather than a link.
   The wrapper must not disturb the row it sits in. */
.signout {
  display: inline-flex;
  margin: 0;
}

.signout .pill {
  background: none;
  cursor: pointer;
  font: inherit;
  font-size: 11px;
}


/* ------------------------------------------------------------------- modal */

.modal {
  position: fixed;
  inset: 0;
  background: rgba(8, 10, 13, 0.72);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 8vh 20px 20px;
  z-index: 20;
}

.modal-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 16px;
  width: 100%;
  max-width: 540px;
  max-height: 78vh;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
}

/* Double the ordinary dialog's width, for the one modal that browses many
   rows at once and wants room for a category chip and a capture-type icon
   beside each. */
/* Filling most of the viewport in both directions now, not just wider than an
   ordinary dialog: this is the one screen that browses everything you have
   recorded, and it wants the room in height as much as in width. vw/vh rather
   than a fixed px ceiling, so it scales with the window instead of stopping
   short on a large one or overflowing a small one. */
.modal-wide {
  max-width: 95vw;
  width: 1320px;
  max-height: 92vh;
}

/* #modal is the Conversations modal specifically, not the shared .modal
   class every dialog uses — it overlays the header too (.modal is already
   `position: fixed; inset: 0`, so it sits above everything on the page,
   header included) but the shared 8vh top padding left a gap above the card
   as if the header beneath still needed to show through. It does not: this
   is the one modal that reads as its own screen, so it starts at the top of
   the screen instead, with the same margin the other three sides keep. */
#modal { padding-top: 20px; }

/* No heading in this one modal — see the comment in index.html — so the close
   button is the only thing in its head row, and flex-end is what keeps it in
   its usual top-right corner rather than sliding to the left the way a single
   flex child does under .modal-head's ordinary space-between. */
/* Floated over the card's own corner rather than sitting in a reserved
   .modal-head row — see the comment in index.html. position: relative on the
   card is what makes this positioning context its own rather than the page's. */
.modal-wide { position: relative; }
.modal-close-float { position: absolute; top: 14px; right: 14px; z-index: 1; }

/* The mode and category filter rows can run to six or more pills; unlike the
   single-row tab strips elsewhere, these are allowed to wrap onto a second
   line rather than overflow the card. */
.modal-filter-tabs { flex-wrap: wrap; }

/* Lets a pill group's buttons act as direct children of #modal-filter-row —
   flex items of ITS row — rather than of a nested row of their own, so the
   square toggle button and whichever set of pills is active read as one
   line. `.hidden`'s `!important` still wins over this when a group is the
   inactive one, `display: contents` only applies to the visible group. */
.filter-pills { display: contents; }

/* One button, two icons: [data-filter-axis] on the button itself picks which
   shows, so the icon names the axis rather than a word beside it needing to.
   A square icon-toggle, not another pair of text pills, so switching axis
   costs no more room in the row than any other click there. */
.filter-axis-toggle svg { width: 17px; height: 17px; vertical-align: middle; }
.filter-axis-toggle .icon-category { display: none; }
.filter-axis-toggle[data-filter-axis="category"] .icon-mode { display: none; }
.filter-axis-toggle[data-filter-axis="category"] .icon-category { display: block; }

.modal-head { display: flex; align-items: center; justify-content: space-between; }
.modal-head h2 { margin: 0; font-size: 15px; }

/* A column now that this lives in its own narrow modal rather than inline in
   the wide Conversations one — the title, the picker and Create used to sit
   in a row because there was width to spare for it. */
.new-conversation { display: flex; flex-direction: column; gap: 10px; }

/* flex: 1 is what actually uses the taller modal-wide card: without it the
   list stays its own content height and the extra space below it sits empty
   rather than being filled with more rows before scrolling starts.
   min-height: 0 overrides a flex item's default of never shrinking below its
   content size, which is what lets overflow-y actually scroll inside a
   bounded card instead of pushing the card itself past max-height. */
.conversation-list { overflow-y: auto; display: flex; flex-direction: column; gap: 2px; flex: 1; min-height: 0; }
.conversation-list .empty { padding: 8px 2px; font-size: 13px; }

.conversation-row {
  display: flex;
  align-items: center;
  gap: 6px;
  border-radius: 8px;
  border-left: 2px solid transparent;
}

/* Title (with its badges), category, date and turns as four aligned columns
   across every row — no header row naming them, since the modal is wide
   enough now that what each column holds reads for itself at a glance
   without one. */
.conversation-link {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 150px 190px 150px;
  align-items: center;
  gap: 4px 16px;
  flex: 1;
  min-width: 0;
  padding: 9px 10px;
  text-decoration: none;
}

.conversation-row:hover { background: #23272f; }
.conversation-row.active { background: #23272f; border-left-color: var(--me); }

/* Both carry their own colour and an outer border at rest now, rather than
   only gaining one on hover — they still only APPEAR on hover (a list of
   conversations stays a list rather than a toolbar), but once shown they
   need to read as clearly as any other coloured control, not as two faint
   grey marks somebody has to be looking for. */
.row-delete {
  background: rgba(255, 91, 91, 0.1);
  border: 1px solid var(--bad);
  color: var(--bad);
  font-weight: 700;
  font-size: 17px;
  line-height: 1;
  padding: 4px 9px;
  border-radius: 6px;
  opacity: 0;
}

.row-duplicate {
  background: rgba(255, 176, 46, 0.12);
  border: 1px solid var(--them);
  color: var(--them);
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
  padding: 5px 7px;
  border-radius: 6px;
  opacity: 0;
}

.conversation-row:hover .row-delete,
.conversation-row:hover .row-duplicate { opacity: 1; }
.row-delete:hover { background: rgba(255, 91, 91, 0.22); }
.row-duplicate:hover { background: rgba(255, 176, 46, 0.24); }
.row-delete:focus-visible,
.row-duplicate:focus-visible { opacity: 1; }

/* Title, capture-type icon and mode badge on one line, rather than the icon
   and badge trailing at the far end of the whole row the way they used to. */
.conversation-title-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

/* Its own grid column — see buildConversationRows, which always creates this
   cell, empty for a row with no category, so date and turns land in the same
   column on every row rather than sliding left when there is nothing here. */
.conversation-category-col {
  display: flex;
  min-width: 0;
}

.conversation-title {
  display: block;
  flex: 0 1 auto;
  /* A flex item's default min-width is its content size, which defeats the
     ellipsis below — this is what actually lets the title truncate rather
     than pushing the icon and badges off the row. */
  min-width: 0;
  font-weight: 600;
  font-size: 13.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mode-badge {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: 20px;
  border: 1px solid var(--line);
  color: var(--dim);
  font-size: 11px;
  white-space: nowrap;
}

.conversation-row.active .conversation-title { color: #fff; }
.conversation-date,
.conversation-turns {
  display: block;
  color: var(--faint);
  font-size: 11.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Siblings of the link in the row's flex layout, not text inside it — see
   buildConversationRows. Both fade in the row's own colour, matching the
   plain dingbat style every other icon in this app uses. */
.row-capture-icon {
  flex: 0 0 auto;
  display: flex;
  color: var(--faint);
  opacity: 0.75;
}
.row-capture-icon svg { width: 14px; height: 14px; }

.category-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 120px;
  padding: 2px 8px;
  border-radius: 20px;
  border: 1px solid var(--line);
  color: var(--dim);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------------------------------------------------------------- controls */

.controls-block { display: flex; flex-direction: column; gap: 11px; }

/* Empty unless a flush failed, so it takes no room when all is well. */
.save-state:empty { display: none; }
.save-state { font-size: 11px; color: var(--bad); }

.release-button { align-self: flex-start; font-size: 11px; padding: 4px 9px; }

.meters { display: flex; flex-direction: column; gap: 7px; }
.meter-head { display: flex; justify-content: space-between; font-size: 11px; margin-bottom: 3px; }
.meter[data-lane="me"] .meter-head span:first-child { color: var(--me); font-weight: 600; }
.meter[data-lane="them"] .meter-head span:first-child { color: var(--them); font-weight: 600; }
.meter-source { color: var(--faint); }
.meter-track { height: 7px; background: #12141a; border-radius: 4px; overflow: hidden; }
.meter-fill { height: 100%; width: 0; border-radius: 4px; transition: width 60ms linear; }
.meter[data-lane="me"] .meter-fill { background: var(--me); }
.meter[data-lane="them"] .meter-fill { background: var(--them); }

.field { display: flex; flex-direction: column; gap: 4px; }
.field > span { font-size: 12px; font-weight: 600; }
.field small, .check small { color: var(--faint); font-size: 11px; line-height: 1.35; }

.check { display: grid; grid-template-columns: auto 1fr; gap: 1px 7px; align-items: center; font-size: 12.5px; }
.check small { grid-column: 2; }

.option-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.option-row .check { flex: 1; }
.option-select { width: auto; flex: 1; min-width: 0; font-size: 12.5px; padding: 5px 8px; }


/* --------------------------------------------------------- interview mode */

/* Same container as the interview block, but folded away: the microphone and
   its meters are set once and then only looked at when something is wrong. */
.panel-block {
  padding: 0 11px;
  margin: 2px -2px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.02);
}

.panel-block > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 0;
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
  list-style: none;
}

.panel-block > summary::-webkit-details-marker { display: none; }

/* A caret that turns, rather than the browser's default triangle. */
.panel-block > summary > span:first-child::before {
  content: '';
  display: inline-block;
  width: 0;
  height: 0;
  margin-right: 7px;
  vertical-align: 1px;
  border: 4px solid transparent;
  border-left-color: var(--dim);
  transition: transform 0.15s ease;
}

.panel-block[open] > summary > span:first-child::before {
  transform: rotate(90deg) translateX(-1px);
}

.panel-block > summary .dim { font-weight: 400; font-size: 11px; color: var(--faint); }
.panel-block > *:last-child { padding-bottom: 11px; }
.panel-block .field { margin-bottom: 10px; }

/* One container for every document row, interview or not, so they read as one
   list rather than as two groups that happen to look alike. It is also a
   <details> now, sharing .panel-block's summary/caret treatment below — hence
   the same box model .panel-block uses (horizontal padding only, so the
   summary sits flush against the top border the way Audio's and Actions' do)
   rather than the padding on every side a plain box would want.

   Audio and Actions share the tint: all three are the same accordion group,
   and the tint is what makes that group read as one thing in the sidebar
   rather than one distinct panel among plainer ones. ID selectors, rather
   than a shared class, are what keep this rule undisturbed by .panel-block's
   own border/background regardless of which is written first in the file. */
#docs-block,
#audio-block,
#options-block {
  border: 1px solid rgba(74, 158, 255, 0.35);
  background: rgba(74, 158, 255, 0.06);
}

#docs-block {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 11px;
  margin: 2px -2px;
}

.interview-rows { display: flex; flex-direction: column; gap: 10px; }

/* The one folded summary that speaks when there is nothing wrong to hide —
   every other one says nothing in that case. What the red rows exist to catch
   is exactly what folding the panel would otherwise hide, so this is the
   exception rather than the pattern. */
#docs-summary.bad { color: var(--bad); }

.doc-row { display: flex; align-items: center; gap: 8px; }
.doc-row button { flex: 1; text-align: left; }
.doc-row .dim { font-size: 11px; color: var(--faint); white-space: nowrap; }

/* A CV title can be long, and the button carries it. Truncate rather than let
   the sidebar widen or the label wrap onto three lines. */
.doc-button { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-button.cv-tailored { border-color: var(--tailored); color: var(--tailored); }

/* Red means the prompts would go out with this document too thin to be worth
   sending, which is the one thing worth noticing at a glance before an
   interview starts. */
.doc-button.thin {
  border-color: var(--bad);
  color: var(--bad);
  background: rgba(255, 91, 91, 0.08);
}

/* One dot, the same everywhere it appears: document rows and Diarize. */
.help-dot {
  flex: 0 0 auto;
  width: 19px;
  height: 19px;
  padding: 0;
  text-align: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--faint);
  font-size: 11px;
  line-height: 1;
  font-weight: 700;
}

/* `.doc-row button { flex: 1; text-align: left }` is more specific than the
   block above, so those two properties have to be won back by name. */
.doc-row .help-dot { flex: 0 0 auto; text-align: center; }

/* Auto answers is a real .doc-button now, same box and same white text as
   every other document row — clicking it toggles the switch beside it, since
   there is nothing here to open. flex/align-items is what .doc-button does
   not otherwise need: the bulb sits INSIDE this one, so its label and the
   bulb have to sit on one line together rather than the bulb dropping below
   or the label being pushed off by it. Same shape as .help-button, which
   solves the identical problem for Help's bulb. */
#btn-auto-answer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.help-dot:hover:not(:disabled) { color: var(--text); border-color: var(--dim); }

/* The switch sits at the end of the document's own row, so the button already
   names it and the switch carries no label of its own. */
.toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  flex: 0 0 auto;
  cursor: pointer;
  font-size: 12.5px;
}

.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }

.toggle-track {
  position: relative;
  flex: 0 0 auto;
  width: 32px;
  height: 18px;
  border-radius: 9px;
  background: #343a45;
  border: 1px solid var(--line);
  transition: background 0.15s ease;
}

.toggle-track::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--dim);
  transition: transform 0.15s ease, background 0.15s ease;
}

.toggle input:checked + .toggle-track { background: rgba(74, 158, 255, 0.55); border-color: var(--me); }
.toggle input:checked + .toggle-track::after { transform: translateX(14px); background: #fff; }
.toggle input:focus-visible + .toggle-track { outline: 2px solid var(--me); outline-offset: 2px; }
.toggle input:disabled + .toggle-track { opacity: 0.4; }

.brag-build-row { border-color: rgba(255, 176, 46, 0.4); background: rgba(255, 176, 46, 0.08); }
.brag-build-row button { flex: 0 0 auto; }
/* The tab strip is borrowed from the hints panel, where green means Help. Here
   it is just a tab, so it takes the neutral accent instead. */
/* The card already spaces its children, and .hints-tabs adds its own margin for
   the history panel, so it has to be beaten on specificity rather than order. */
.modal-card .brag-tabs { margin-bottom: 0; }
.brag-tabs .hints-tab.active { color: var(--me); border-color: rgba(74, 158, 255, 0.6); }

.brag-warning {
  margin: 0;
  padding: 9px 11px;
  border-radius: 8px;
  border: 1px solid rgba(255, 176, 46, 0.45);
  background: rgba(255, 176, 46, 0.09);
  color: var(--them);
  font-size: 12.5px;
  line-height: 1.45;
}

/* ------------------------------------------------- alerts and long waits */

/* Both open on top of another modal, so they sit above the rest of the stack
   rather than relying on document order. */
#busy-modal, #alert-modal { z-index: 30; }

.modal-narrow { max-width: 440px; }
.alert-body { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--dim); }
.modal-actions button.hidden { display: none !important; }

.busy { display: flex; align-items: flex-start; gap: 12px; }
.busy strong { display: block; font-size: 14px; }
.busy .dim { display: block; margin-top: 3px; font-size: 12px; line-height: 1.45; }
/* So the × sits at the right-hand edge rather than against the words. */
.busy-text { flex: 1; min-width: 0; }

/* Shown only while a call is being recorded, where a half-minute wait costs the
   thing on the screen behind it. Amber rather than red: it is the cost of what
   was just asked for, not a failure. */
/* No margin: `.modal-card` is a flex column with its own gap, and a margin here
   would be added to it rather than replacing it. */
.busy-live {
  margin: 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--them);
}

.spinner {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border-radius: 50%;
  border: 2px solid rgba(74, 158, 255, 0.25);
  border-top-color: var(--me);
  animation: spin 0.9s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* An indeterminate bar: the wait is known to be long but not how long, so it
   shows movement rather than a percentage it would have to invent. */
.busy-bar {
  height: 3px;
  border-radius: 2px;
  background: #2a2f39;
  overflow: hidden;
}

.busy-bar span {
  display: block;
  width: 34%;
  height: 100%;
  border-radius: 2px;
  background: var(--me);
  animation: sweep 1.5s ease-in-out infinite;
}

@keyframes sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(330%); }
}

.modal-tall { max-width: 820px; height: 80vh; }
.modal-tall textarea {
  flex: 1;
  min-height: 0;
  resize: none;
  font-size: 13.5px;
  line-height: 1.55;
}

.tailor-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px;
  border: 1px solid rgba(167, 139, 250, 0.4);
  background: rgba(167, 139, 250, 0.08);
  border-radius: 8px;
}

.tailor-row select { width: auto; flex: 1; min-width: 0; font-size: 12.5px; }
.preset-row { border-color: rgba(61, 220, 132, 0.4); background: rgba(61, 220, 132, 0.07); }
/* Each document's row is tinted by what it does: purple tailors, green starts
   from a preset, blue picks. */
.cv-row { border-color: rgba(74, 158, 255, 0.4); background: rgba(74, 158, 255, 0.07); }
.keyterms-row { border-color: rgba(255, 176, 46, 0.4); background: rgba(255, 176, 46, 0.08); }
.keyterms-row button { flex: 0 0 auto; }
.tailor-row .dim { font-size: 11px; white-space: nowrap; }

/* Ten preset colours for categories, held on a row as an INDEX rather than a
   stored hex — see categories.colour in app/db.py. This is the one place that
   index becomes a colour; a category's row elsewhere in the client (the
   picker here, the modal's rows) reads --category-N through this same list, so
   repainting the palette is a one-file change. Picked apart on the colour
   wheel and away from the app's own semantic colours (--bad, --good, --me,
   --them) so a category chip is never mistaken for a status. */
:root {
  --category-0: #ff6b6b;
  --category-1: #ffa94d;
  --category-2: #ffd43b;
  --category-3: #94d82d;
  --category-4: #37b24d;
  --category-5: #20c997;
  --category-6: #4dabf7;
  --category-7: #748ffc;
  --category-8: #b197fc;
  --category-9: #f783ac;
}

.category-dot {
  /* flex: 0 0 auto when it is a direct flex child (the picker row); the
     explicit display/vertical-align is what makes it render as a proper
     circle everywhere else — inside a pill button (the modal filter, a
     conversation row), which is not itself a flex container. */
  display: inline-block;
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--dim);
}
.category-dot[data-colour="0"] { background: var(--category-0); }
.category-dot[data-colour="1"] { background: var(--category-1); }
.category-dot[data-colour="2"] { background: var(--category-2); }
.category-dot[data-colour="3"] { background: var(--category-3); }
.category-dot[data-colour="4"] { background: var(--category-4); }
.category-dot[data-colour="5"] { background: var(--category-5); }
.category-dot[data-colour="6"] { background: var(--category-6); }
.category-dot[data-colour="7"] { background: var(--category-7); }
.category-dot[data-colour="8"] { background: var(--category-8); }
.category-dot[data-colour="9"] { background: var(--category-9); }

/* Sits before the label inside the category button and before each option's
   own title inside the menu below — .advice-menu button is a flex row with
   `align-items: baseline` for its text, which sets a 10px circle a little low;
   centering it here is the one adjustment a menu carrying a dot needs over
   the plain text ones. */
#category-button-dot { margin-right: 6px; }
.advice-menu button .category-dot {
  align-self: center;
  /* .advice-menu span (below) pushes a bare <span> — the secondary hint text
     like "Flash, ~2s" — to the right with margin-left: auto. The dot is a
     span too and was caught by the same rule, which is what pushed every
     named category to the right edge of the menu while "No category" (no
     dot, so no span to catch) stayed put on the left. Two classes beats one
     class + a type, so this wins regardless of which is written first. */
  margin-left: 0;
}

/* A session CV is not one of the library CVs, so it must not look like one.
   Only the closed control is tinted: options inherit the select's colour, so
   they have to be reset or the whole open dropdown turns purple. */
.cv-tailored {
  color: var(--tailored);
  border-color: var(--tailored);
  font-weight: 600;
}

.cv-tailored option {
  color: var(--text);
  font-weight: 400;
}

.option-tailored,
.cv-tailored option.option-tailored {
  color: var(--tailored);
  font-weight: 600;
}

.modal-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.modal-actions .dim { font-size: 11.5px; }
/* The hint spells out what the button is about to do, so it is the hint that
   wraps when it runs long, never the button that gets squeezed. */
.modal-actions button { padding: 8px 22px; flex: 0 0 auto; }

/* Lit = Flash, dark = Flash Lite. */
/* The bulb governs Help and nothing else, so it sits on the Help button rather
   than beside it. Small, because it is a secondary control on a primary one. */
.bulb {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-left: 8px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
  /* Against the blue Help button, not against the page. */
  color: rgba(6, 18, 31, 0.55);
  /* Unlit it is still a control, and without an edge it read as part of the
     Help label rather than as something separately pressable. */
  border: 1px solid var(--them);
  cursor: pointer;
  transition: color 120ms, background 120ms, box-shadow 120ms, border-color 120ms;
}

.bulb svg { width: 14px; height: 14px; fill: currentColor; }
.bulb:hover:not(.on) { color: rgba(6, 18, 31, 0.9); background: rgba(255, 176, 46, 0.25); }
.bulb:focus-visible { outline: 2px solid #06121f; outline-offset: 1px; }

.bulb.on {
  color: #1a1400;
  background: #ffd54a;
  border-color: #ffd54a;
  box-shadow: 0 0 9px rgba(255, 213, 74, 0.65);
}

/* inline-flex so the bulb sits on the text baseline row rather than below it */
.help-button {
  flex: 0 0 auto;
  padding: 7px 12px;
  font-size: 13px;
  display: inline-flex;
  align-items: center;
}


/* ----------------------------------------------------------------- history */

.hints {
  border: 1px solid rgba(61, 220, 132, 0.45);
  background: rgba(61, 220, 132, 0.08);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 14px;
}

.hints-error { border-color: var(--bad); background: rgba(255, 91, 91, 0.1); }

.hints-tabs { display: flex; align-items: center; gap: 6px; margin-bottom: 9px; }

.hints-tab {
  background: transparent;
  border: 1px solid transparent;
  color: var(--faint);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 10px;
  border-radius: 20px;
}

.hints-tab.has-result { color: var(--dim); }
.hints-tab.active { color: var(--good); border-color: rgba(61, 220, 132, 0.5); }
.hints[data-tab="advice"] .hints-tab.active { color: var(--me); border-color: rgba(74, 158, 255, 0.6); }

.hints-stamp {
  margin-left: auto;
  font-size: 11px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* How long ago, not when: green under a minute, amber through the second,
   red after — the same three colours the checklist marks coverage with, but
   never applied to the coverage ratio itself (see .stamp-age below), so the
   two scales are never asking to be read against each other. See ageClass in
   core.js for the boundaries.
   Answers/Advice colour the whole stamp, since it is only ever a status word
   or the age, never both at once. The side panel's stamp can hold both — a
   ratio AND an age — so there the colour lands on a nested .stamp-age span
   rather than on #side-stamp itself. */
.hints-stamp.fresh { color: var(--good); }
.hints-stamp.ageing { color: var(--them); }
.hints-stamp.stale { color: var(--bad); }
#side-stamp .stamp-age.fresh { color: var(--good); }
#side-stamp .stamp-age.ageing { color: var(--them); }
#side-stamp .stamp-age.stale { color: var(--bad); }

/* Advice is a different kind of answer, so the box says so. */
.hints[data-tab="advice"] { border-color: rgba(74, 158, 255, 0.45); background: rgba(74, 158, 255, 0.08); }
.hints-error, .hints[data-tab="advice"].hints-error { border-color: var(--bad); background: rgba(255, 91, 91, 0.1); }
.hints-message { margin: 0; font-size: 13px; color: var(--dim); }
.hints ul { margin: 0; padding-left: 18px; }
.hints li { font-size: 16.5px; line-height: 1.5; color: #fff; margin-bottom: 3px; }

/* Placeholders before Help has been pressed, so the box keeps its height. */
.hints-idle { border-color: var(--line); background: rgba(255, 255, 255, 0.02); }
.hint-empty::after {
  content: '';
  display: inline-block;
  width: 42%;
  height: 1px;
  vertical-align: middle;
  background: rgba(255, 255, 255, 0.09);
}

.hints li.hint-empty { color: var(--faint); }

.history { min-width: 0; }
.live { display: flex; flex-direction: column; }

.live-block { padding: 9px 0; }
.live-block:not(.active) { display: none; }
.live-block[data-lane="me"] .turn-who { color: var(--me); }
.live-block[data-lane="them"] .turn-who { color: var(--them); }

.live-block .turn-text {
  font-size: 21px;
  line-height: 1.45;
  color: #fff;
}

.live-block .turn-translation { font-size: 21px; }
/* The original drops back in the live block exactly as it does in the history. */
.live-block .turn-original { font-size: 13px; }

.live-block .turn-text::after,
.live-block .turn-translation::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 17px;
  margin-left: 4px;
  vertical-align: -2px;
  background: var(--me);
  animation: blink 1.1s steps(2, start) infinite;
}

/* Only the half being read carries the caret. */
.live-block .turn-original::after { content: none; }

@keyframes blink { to { visibility: hidden; } }

.transcript { display: flex; flex-direction: column; }

.turn { padding: 9px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
.turn-head { display: flex; align-items: baseline; gap: 9px; margin-bottom: 2px; }

.turn-who { font-size: 11.5px; font-weight: 700; letter-spacing: 0.02em; }
.turn[data-lane="me"] .turn-who { color: var(--me); }
.turn[data-lane="them"] .turn-who { color: var(--them); }

.turn-time { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }

.turn-delete {
  background: transparent;
  border: 0;
  color: var(--faint);
  font-size: 15px;
  line-height: 1;
  padding: 0 5px;
  border-radius: 4px;
  opacity: 0;
  transition: opacity 90ms;
}

.turn:hover .turn-delete { opacity: 1; }
.turn-delete:hover { color: var(--bad); background: rgba(255, 91, 91, 0.14); }
.turn-delete:focus-visible { opacity: 1; }

.turn-text {
  margin: 0;
  font-size: 15px;
  color: #b9c0cb;
  overflow-wrap: anywhere;
}

/* The newest portion is the one being read, so it gets the weight, unless a
   lane is mid-portion, in which case the live block is the current one. */
.turn-current .turn-text { font-size: 21px; line-height: 1.45; color: #fff; }
.turn-current .turn-who { font-size: 12.5px; }

.history.live-active .turn-current .turn-text { font-size: 15px; color: #b9c0cb; }
.history.live-active .turn-current .turn-who { font-size: 11.5px; }

.session-divider {
  color: var(--faint);
  font-size: 11px;
  text-align: center;
  margin: 12px 0;
  border-top: 1px dashed var(--line);
  padding-top: 8px;
}

.footer {
  color: var(--faint);
  font-size: 11.5px;
  margin-top: 22px;
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

@media (max-width: 860px) {
  .layout { grid-template-columns: 1fr; }
  .sidebar { position: static; max-height: none; }
  .title-input { font-size: 17px; }
  .live-block .turn-text, .turn-current .turn-text { font-size: 18px; }
  .live-block .turn-translation, .turn-current .turn-translation { font-size: 18px; }
  .live-block .turn-original, .turn-current .turn-original { font-size: 13px; }
}

/* Phone capture: one lane carrying both voices. */
.meter[data-lane="room"] .meter-head span:first-child { color: var(--tailored); font-weight: 600; }
.meter[data-lane="room"] .meter-fill { background: var(--tailored); }
.option-row.phone-capture { opacity: 0.45; }
.turn[data-lane="room"] .turn-who { color: var(--tailored); }
.live-block[data-lane="room"] .turn-who { color: var(--tailored); }

.advice-button { flex: 0 0 auto; padding: 7px 14px; font-size: 13px; border-color: rgba(74, 158, 255, 0.55); color: var(--me); }
.advice-button:hover:not(:disabled) { border-color: var(--me); }



/* Advice picks its model when pressed, so the button opens a menu. */
.advice-wrap { position: relative; flex: 0 0 auto; }
.advice-menu {
  position: absolute;
  top: calc(100% + 5px);
  left: 0;
  z-index: 30;
  min-width: 148px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.5);
}

.advice-menu button {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  background: transparent;
  border: 0;
  border-radius: 6px;
  padding: 7px 9px;
  text-align: left;
}

.advice-menu button:hover { background: #23272f; }
.advice-menu strong { font-size: 12.5px; font-weight: 600; }
.advice-menu span { margin-left: auto; font-size: 11px; color: var(--faint); }

/* ------------------------------------------------------------------- gate */

.gate {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 80vh;
  padding: 20px;
}

.gate-card {
  width: 100%;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  gap: 13px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 26px 24px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
}

.gate-card h1 { margin: 0; font-size: 19px; }
.gate-card .dim { margin: -8px 0 4px; }
.gate-card .primary { margin-top: 5px; padding: 9px; }

.gate-alt {
  margin: 14px 0 0;
  font-size: 13px;
  text-align: center;
}

.gate-alt .pill { margin-left: 6px; }

.gate-error {
  margin: 0;
  padding: 9px 11px;
  border-radius: 8px;
  border: 1px solid var(--bad);
  background: rgba(255, 91, 91, 0.12);
  font-size: 13px;
}

/* Turning translation on means the original is not the language being read, so
   the translation carries the weight and the original becomes the reference
   under it. Green rather than white keeps it distinguishable at a glance from
   a line that was never translated. */
.turn-translation {
  margin: 0;
  color: var(--translated);
  font-size: 15px;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.turn-original {
  margin: 3px 0 0;
  padding-left: 9px;
  border-left: 2px solid var(--line);
  color: var(--faint);
  font-size: 12.5px;
  line-height: 1.5;
}

/* The newest portion is read from across the room, so both halves grow. */
.turn-current .turn-translation { font-size: 21px; line-height: 1.45; }
/* The "newest portion" rules above turn .turn-text white and large, and they
   outrank .turn-original on specificity, so the original is put back by name. */
.turn-current .turn-original { font-size: 13px; color: var(--faint); }
.history.live-active .turn-current .turn-translation { font-size: 15px; }
.history.live-active .turn-current .turn-original { font-size: 12.5px; color: var(--faint); }


/* The context editor borrows the tab strip; green means Help in the history,
   so here it takes the neutral accent instead. */
.modal-card .context-tabs { margin-bottom: 0; }
.context-tabs .hints-tab.active { color: var(--me); border-color: rgba(74, 158, 255, 0.6); }

/* ------------------------------------------------------------- settings */

.settings { max-width: 640px; margin-inline: auto; }
.settings-title { flex: 1; font-size: 15px; font-weight: 600; }
.settings-heading {
  margin: 0 0 4px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.settings-form { display: flex; flex-direction: column; gap: 20px; }
.setting { display: flex; flex-direction: column; gap: 6px; }
.setting-help { margin: 0; line-height: 1.5; }
.settings-actions { display: flex; align-items: center; gap: 12px; }

/* One row per category, each carrying its own actions rather than one shared
   management row acting on whichever category a picker elsewhere happens to
   have selected — see categories-block in settings.html. */
.categories-list { display: flex; flex-direction: column; gap: 8px; list-style: none; margin: 0; padding: 0; }
.category-row-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
}
.category-row-item .category-row-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.category-row-item button { flex: 0 0 auto; }

/* ------------------------------------------------------------------- home */

/* With no conversation open, this is the page — the search results and the
   stats. Conversations, + New, Settings and search moved up into the header
   — see .topbar-home-controls — so there is no toolbar of its own here any
   more. */
/* Same width and centering as .layout — the conversation view's own container
   — rather than a narrower column of its own: this is the other page the app
   has, and the two should read as one interface rather than as a wide screen
   and a cramped one bolted together. */
.home { display: flex; flex-direction: column; gap: 14px; max-width: 1320px; margin-inline: auto; }

/* Renders as `display: contents` — its children (+New, Settings, search) are
   flex items of .topbar directly, not a row nested inside it, so they read as
   more buttons on the same header line rather than a second toolbar. Shown
   only with nothing open, in the room #detail-title-text sat empty in — see
   route() in app.js, which toggles both together. `.hidden`'s `!important`
   still wins over this while a conversation IS open. */
.topbar-home-controls { display: contents; }

/* button.primary is scoped to actual <button> elements on purpose elsewhere
   in the app, so an <a> — Settings is a link, not a form submit — needs its
   own rule to get the same plain look Conversations has, rather than
   rendering as plain text with no button chrome at all. */
.header-button-link {
  display: inline-flex;
  align-items: center;
  background: #23272f;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  padding: 7px 12px;
  font-size: 12.5px;
  text-decoration: none;
  white-space: nowrap;
}
.header-button-link:hover { border-color: #4a5261; }

/* + New is the one action in this group that makes something rather than
   taking you somewhere, so it keeps the app's "good" green rather than
   Conversations' and Settings' plain dark look — the same colour the
   checklist marks something actually done with. Otherwise plain #home-new:
   no size override any more, so it takes the same base button padding and
   font-size Conversations does, sitting beside it at the same size rather
   than as the larger, separately-styled pair the two of them used to be. */
#home-new { background: var(--good); border-color: var(--good); color: #062117; }
#home-new:hover:not(:disabled) { border-color: var(--good); filter: brightness(1.08); }

.topbar-search {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  flex: 0 1 320px;
  min-width: 0;
}

.topbar-search input { flex: 1; min-width: 0; }

/* A square icon button rather than a labelled "Search" — the box beside it
   already says what this is. Same family as the header's other icon-toggle
   buttons (the gear, the download arrow), just holding a magnifying glass. */
.topbar-search .icon-toggle {
  flex: 0 0 auto;
  width: 33px;
  height: 33px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.topbar-search .icon-toggle svg { width: 16px; height: 16px; }

/* A KPI row of stat tiles — a handful of headline numbers, not a chart. Value
   in the app's own semibold weight at a size that reads from across the room;
   label in sentence case underneath, in the app's muted ink. Proportional
   figures throughout: these are standalone values, not a column that needs to
   align, so tabular-nums would only make small numbers look loose. */
.home-stats {
  display: grid;
  /* 160px rather than the 120px a narrower container wanted: at the full
     .layout width there is room for larger, more legible tiles rather than
     five thin ones with padding to spare. */
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
}

.stat-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
}

.stat-value {
  font-size: 22px;
  font-weight: 600;
  font-variant-numeric: normal;
}

.stat-label { font-size: 12px; color: var(--dim); }

/* ------------------------------------------------------------ home charts */

/* A fixed two-column grid — five cards read as 2/2/1 rather than reflowing to
   three across at whatever width happens to fit a third 420px card, which is
   the one thing auto-fit cannot promise. */
.home-charts {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}

@media (max-width: 700px) {
  .home-charts { grid-template-columns: 1fr; }
}

.chart-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 14px;
}

.chart-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.chart-card-head h3 { margin: 0; font-size: 13px; font-weight: 600; color: var(--text); }

.chart-axis-toggle { display: flex; gap: 4px; }
.chart-axis-toggle .hints-tab { margin-bottom: 0; }

/* Chart.js reads its box's height in pixels, not its aspect ratio, once
   maintainAspectRatio is off — see statscharts.js, which turns that off
   specifically so a wide two-column layout does not stretch every chart
   into an letterbox. A fixed height here is what it reads instead. */
.chart-canvas-wrap { position: relative; height: 240px; }
.chart-canvas-wrap canvas { width: 100%; height: 100%; }

/* --------------------------------------------------------------- lane dots */

/* The proof both halves are live without unfolding the Audio panel. Grey until
   that lane has actually carried sound, so a flat lane is visible at a glance. */
.lane-dots {
  display: flex;
  gap: 7px;
  margin: 7px 0 0;
  align-items: center;
}

.lane-dot { display: flex; align-items: center; }

.lane-dot i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #444;
  display: inline-block;
  transition: background 120ms linear;
}

.lane-dot.live i { background: var(--good); }
/* Carried sound once, but nothing recently: the lane may have died. */
.lane-dot.stale i { background: #e0a33e; }

/* --------------------------------------------------------------- talk time */

.talk-time {
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--line);
  font-size: 11px;
}

.talk-bar {
  height: 4px;
  border-radius: 2px;
  background: #2a6df4;   /* the far end fills the remainder */
  overflow: hidden;
  margin-bottom: 4px;
}

/* Your share, drawn from the left. */
.talk-fill {
  height: 100%;
  background: var(--good);
  width: 0;
  transition: width 400ms linear;
}

.talk-time.talking-too-much .talk-fill { background: #e0a33e; }

/* ------------------------------------------------------------- write-ups */

.writeup-row { gap: 6px; }

.wide-card { max-width: 880px; }

/* One tab per section the mode splits out, plus the brief. Wraps rather than
   scrolls: these are four short labels, and a strip that scrolls sideways hides
   the one you wanted. Same chip as the Help/Advice tabs, so the two read as the
   same control. */
.writeup-tabs { flex-wrap: wrap; }
.writeup-tabs .hints-tab.active { color: var(--me); border-color: rgba(74, 158, 255, 0.6); }

.writeup-body {
  max-height: 60vh;
  overflow-y: auto;
  padding-right: 6px;
}

.writeup-section { margin-bottom: 18px; }

.writeup-section h3 {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--dim);
  margin: 0 0 6px;
}

.writeup-section ul { margin: 0; padding-left: 18px; }
.writeup-section li { margin-bottom: 5px; line-height: 1.5; }
.writeup-section p { margin: 0; line-height: 1.6; }

.writeup-empty { color: var(--dim); font-style: italic; }

/* ------------------------------------------------------------ pre-flight */

.preflight-lanes { display: flex; flex-direction: column; gap: 14px; margin: 4px 0 16px; }

.preflight-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  margin-bottom: 5px;
}

.preflight-tick { color: #444; font-size: 14px; }
.preflight-lane.ok .preflight-tick { color: var(--good); }
.preflight-lane.ok .preflight-head { color: var(--good); }

/* What the test tone proved about echo cancellation. Shown in the pre-flight
   only when it went wrong, and in the Audio panel either way. */
.echo-warning {
  font-size: 12px;
  line-height: 1.45;
  color: var(--bad);
  border-left: 2px solid var(--bad);
  padding-left: 9px;
  margin: 0 0 14px;
}

/* Hover for the working, click to copy it. */
.echo-status { font-size: 11px; margin-top: 8px; cursor: pointer; color: var(--dim); }
.echo-status.bad { color: var(--bad); }

/* The echo gate sits in the sidebar, directly under the capture button: it acts
   on the capture that is running, and it belongs beside the control that started
   it rather than floating over the page. Above the two folded panels, because a
   remedy you have to open a panel to find is one nobody finds mid-call. */
.echo-gate { margin-top: 10px; }

.echo-gate-button {
  display: block;
  width: 100%;
  border: 1px solid var(--bad);
  border-radius: 9px;
  background: var(--bad);
  color: #14161a;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  padding: 9px 14px;
  cursor: pointer;
}

/* Off is the loud state, because off is the state that needs pressing. Once it
   is on the button has done its job and should stop competing with the call. */
.echo-gate-button[aria-pressed="true"] {
  background: rgba(255, 91, 91, 0.14);
  color: var(--bad);
}

.echo-gate-count { font-weight: 400; opacity: 0.85; }

.echo-gate-note {
  margin: 6px 2px 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--dim);
}

/* ---------------------------------------------------------------- search */

.search-results { margin-bottom: 18px; }

.search-hit {
  display: block;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
}

.search-hit:hover { background: var(--panel); }

.search-hit-meta {
  display: block;
  font-size: 11px;
  color: var(--dim);
  margin-bottom: 3px;
}

.search-hit-text { display: block; line-height: 1.5; }
/* snippet() marks the matched terms with << >>, swapped for these on render. */
.search-hit-text mark { background: rgba(61, 220, 132, 0.22); color: inherit; border-radius: 2px; }

/* ------------------------------------------------------------------ flags */

/* A moment marked during the call. Deliberately loud in the history: the whole
   point is to find it again afterwards. */
.turn.flagged { border-left: 2px solid #e0a33e; padding-left: 8px; }
.turn-flag {
  background: none;
  border: none;
  color: #444;
  cursor: pointer;
  font-size: 12px;
  padding: 0 4px;
}
.turn.flagged .turn-flag { color: #e0a33e; }
.turn-flag:hover { color: #e0a33e; }

/* Open questions answering "nothing" is the good outcome, so it reads as a
   clear result rather than as a failure or an empty waiting state. */
.hints-clear { color: var(--good); }

/* The two folded panels sit together as a pair, so the second needs a hairline
   of its own rather than reading as part of the first. */
.panel-block + .panel-block { margin-top: 8px; }

/* Inside the panel the option rows no longer sit against the sidebar edge. */
#options-block .option-row:first-of-type { margin-top: 10px; }

/* --------------------------------------------------------------- mode menu */

/* What a conversation is decides everything under it, so it sits at the top of
   the sidebar. Same shape as the Advice menu, but full width, because a sidebar
   control has the room and a truncated "Mode: Assisted meeting" reads badly. */
.mode-wrap { width: 100%; margin-bottom: 2px; }

.mode-button {
  width: 100%;
  justify-content: space-between;
  font-weight: 600;
}

/* The caret is drawn rather than typed, so the label can be set as plain text
   from one place without carrying markup with it. */
.mode-button::after { content: '\25BE'; margin-left: 8px; color: var(--faint); }

.mode-wrap .advice-menu { width: 100%; min-width: 0; }
.mode-wrap .advice-menu span { color: var(--faint); }

/* Beside a help dot — in the sidebar for the capture type and the mode, or in
   the settings dialog for anything else shaped like it — the picker takes the
   room that is left instead of the whole width, exactly as the selects in the
   rows around it do. */
.option-row .mode-wrap { flex: 1; min-width: 0; margin-bottom: 0; }

/* The capture-type and mode rows sit directly in the sidebar rather than
   inside a folded panel, so they need their own spacing rather than borrowing
   a <details> block's. */
#controls > .option-row { margin-bottom: 8px; }

/* ---------------------------------------------------------- capture button */

/* One control for start, stop, what is happening and how long it has been
   happening. It changes colour rather than moving, so the thing you reach for
   mid-call is always in the same place. */
/* Full width on its own; inside .option-row, beside its `?`, it takes the
   room that is left instead, the same rule .auto-answer-toggle follows
   below. */
.option-row .capture-toggle { width: auto; flex: 1; }
.capture-toggle {
  width: 100%;
  padding: 9px 12px;
  font-size: 13.5px;
  font-weight: 600;
  /* The elapsed time is inside the label, so the digits must not reflow it. */
  font-variant-numeric: tabular-nums;
}

/* Recording. Amber rather than red: red now means something is wrong — it is
   the colour of the echo gate, which appears in this same column and says the
   far end is being written down as you. A capture running normally is not a
   problem, and two red controls a few pixels apart would make the one that is a
   problem read as ordinary. Filled amber against the outlined amber of
   `is-waiting` below: same hue for "the capture is doing something", filled for
   running and outlined for mid-flight. */
.capture-toggle.is-live {
  background: var(--them);
  border-color: var(--them);
  color: #2a1a00;
}

/* Connecting or finishing: mid-flight, and not pressable either way. */
.capture-toggle.is-waiting {
  background: transparent;
  border-color: var(--them);
  color: var(--them);
  opacity: 1;
}

/* The three write-up rows read as document rows: one ?, one full-width button.
   Minutes and the debrief are never both visible, so the pair reads as one
   row that changes name with the mode. */
#prebrief-row .doc-button,
#minutes-row .doc-button,
#debrief-row .doc-button { flex: 1; text-align: left; }

/* One control, three states, cycling on a press — off/fast/smart — with
   colour as the only thing that tells them apart: grey at rest, green for
   the cheap model, blue for the strong one. Sits in an .option-row beside its
   `?`, so it takes the room .mode-wrap does there. */
.option-row .auto-answer-toggle { flex: 1; text-align: center; font-weight: 600; }
.auto-answer-toggle {
  border: 1px solid var(--line);
  background: transparent;
  color: var(--faint);
}
.auto-answer-toggle:hover:not(:disabled) { border-color: var(--dim); color: var(--dim); }
.auto-answer-toggle[data-state="fast"] {
  background: rgba(61, 220, 132, 0.14);
  border-color: var(--good);
  color: var(--good);
}
.auto-answer-toggle[data-state="smart"] {
  background: rgba(74, 158, 255, 0.14);
  border-color: var(--me);
  color: var(--me);
}

/* Zero, one or two actions in the alert, so the container has to collapse. */
.alert-extra { display: contents; }

/* The two lists are a tab strip above the create form, not part of the card
   header, so they need a little air under them. */
.list-tabs { margin-bottom: 12px; }

/* Full width, the same as the sidebar's own mode button — the create form is
   a column now (see .new-conversation), so there is no row for the picker to
   size itself to fit inside any more. */
.new-conversation .capture-wrap .advice-menu { min-width: 210px; }

/* ------------------------------------------------- the panel beside the answers */

/* Two thirds the answers, one third the panel. A ratio rather than the fixed
   column it was, because what is in the panel is now sentences to read out
   loud rather than five short lines to glance at, and a fixed 236px wrapped
   every one of them onto four rows. */
/* --hints-split is the answers column's share, in flex-grow units that read
   directly as a percentage because both children share flex-basis 0 — see
   .hints-main/.side-panel below. 66.67 is the old fixed 2:1 ratio, kept as the
   default so a reader who has never dragged it sees exactly what they always
   saw. app.js clamps every value it ever writes here to [33.33, 66.67]: the
   two columns may never differ by more than a third, which also rules out a
   1:3 (25%) split some reader might otherwise drag to. */
.hints { display: flex; align-items: flex-start; --hints-split: 66.67; }
.hints-main { flex: var(--hints-split) 1 0; min-width: 0; }

/* The drag handle that sits between them, replacing the plain gap the layout
   used to have — its own width (16px) is what used to be .hints's `gap`, so
   the resting visual spacing is unchanged; only now it is grabbable, and the
   divider line sits centred inside it. Hidden together with .side-panel,
   since a handle with nothing on the other side of it is not a control. */
.hints-drag {
  flex: 0 0 auto;
  align-self: stretch;
  width: 16px;
  cursor: col-resize;
  position: relative;
}
.hints-drag::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: var(--line);
  transform: translateX(-50%);
  transition: background 120ms;
}
.hints-drag:hover::before,
.hints-drag.dragging::before,
.hints-drag:focus-visible::before { background: var(--me); }
.hints-drag:focus-visible { outline: none; }

/* Dragged well past where the two-sided range stops — not just to its edge,
   past it — reads as "get this out of my way" rather than "make it as small
   as the range allows", so the side is hidden outright instead of pinned at
   its narrowest legal width. flex: 0 0 0 plus the overflow/padding reset is
   what actually removes it from the row; --hints-split itself is untouched
   underneath, which is what dragging back from the edge resumes at. */
.hints.collapsed-main .hints-main,
.hints.collapsed-side .side-panel {
  flex: 0 0 0;
  min-width: 0;
  width: 0;
  padding: 0;
  margin: 0;
  border: 0;
  overflow: hidden;
}
.hints.collapsed-main .side-panel,
.hints.collapsed-side .hints-main { flex: 1 1 auto; }

/* While a side is hidden, the handle sits at the very edge doing nothing
   visible — so it pulses to say it is still a control, not furniture, and
   that dragging it back is how the hidden side returns. Stops the instant a
   fresh result reveals that side on its own; see revealHintsSplitIfCollapsed
   in app.js. */
@keyframes hints-drag-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
.hints-drag.collapsed { animation: hints-drag-pulse 1.8s ease-in-out infinite; }
.hints-drag.collapsed::before { background: var(--me); }

.side-panel {
  flex: calc(100 - var(--hints-split)) 1 0;
  min-width: 0;
  align-self: stretch;
}

/* The tab strip is the answer box's, at the size the panel's headings were:
   these are labels on a column, not a second set of controls competing with
   the ones above the hints. */
.side-tabs { margin-bottom: 8px; }
.side-tabs .hints-tab { font-size: 11px; padding: 3px 8px; }
.side-panel .hints-stamp,
#side-stamp { margin-left: auto; font-size: 11px; color: var(--dim); }

.side-panel ul { list-style: none; margin: 0; padding: 0; }

/* --------------------------------------------------------- auto answers */

/* One entry per question they have left hanging, newest first. The newest is
   the one being waited on, so it is the one that reads at full weight; the
   rest are what is still owed and sit under it, quieter. */
.auto-list { list-style: none; margin: 0; padding: 0; }

.auto-entry { margin-bottom: 11px; }
.auto-entry:last-child { margin-bottom: 0; }

.auto-question {
  margin: 0 0 2px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--dim);
}

.auto-answer {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--dim);
}

/* Bold and white, and only the top one. Something has to be the line you say
   now, and a panel where every entry shouts equally is one you have to read
   from the top every time it changes. */
.auto-entry.is-newest .auto-answer { font-weight: 600; color: #fff; font-size: 14px; }
.auto-entry.is-newest .auto-question { color: var(--text); }

.auto-body .hints-message { font-size: 12px; }

.checklist-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 12px;
  line-height: 1.4;
}

/* Red, amber, green. The dot carries the state and the text stays readable at
   every one of them, so the colour is a signal rather than a legibility cost. */
.checklist-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 4px;
  background: var(--bad);
}

.state-yellow .checklist-dot { background: var(--them); }
.state-green .checklist-dot { background: var(--good); }

.checklist-text { color: var(--dim); }
.state-yellow .checklist-text { color: var(--text); }
.state-green .checklist-text { color: var(--text); text-decoration: line-through; text-decoration-color: var(--good); }

/* --------------------------------------------------------------- bookmark */

/* Amber rather than blue: the three beside it each spend a model call, and this
   one only writes a flag. Same colour as the mark it leaves in the history. */
/* Square, and the glyph is the one it leaves on the line. Sized off the same
   7px vertical padding its neighbours use, so the row of buttons still lines
   up: a fixed height here would drift the moment the others change. */
.bookmark-button {
  flex: 0 0 auto;
  width: 30px;
  padding: 7px 0;
  font-size: 15px;
  line-height: 1;
  text-align: center;
  border-color: rgba(255, 176, 46, 0.55);
  color: var(--them);
}

.bookmark-button:hover:not(:disabled) { border-color: var(--them); }

/* ----------------------------------------------------------------- wizard */

/* One scaffold with its parts shown per step, so the styling is of the parts
   rather than of eleven panels. The card is the tall editor's, because it is
   the same kind of screen: a box you read and change, with a row of controls
   that act on it. */

/* Sized to the step rather than to the tallest step: two choice cards do not
   need eighty per cent of the window.

   A step with a box keeps the tall editor's fixed 80vh, and for the reason that
   number is 80 rather than 90 — `.modal` reserves 8vh above the card and 20px
   below it, so anything taller pushes its own footer off the bottom of the
   screen. The box is the flex child that absorbs the difference, and it has to
   be able to shrink to nothing to do that: a `min-height` on it stops the card
   clamping and the buttons walk off the page again. */
#wizard-modal .modal-card { height: auto; max-height: 80vh; }
#wizard-modal.wizard-tall .modal-card { height: 80vh; }

/* Which steps this mode has. A step already reached can be clicked back to;
   one not yet reached cannot, because its own answer often decides whether the
   steps after it exist at all. */
.wizard-progress {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 4px;
}

.wizard-pill {
  padding: 3px 9px;
  font-size: 11.5px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--faint);
}

.wizard-pill.done { color: var(--dim); border-color: rgba(74, 158, 255, 0.35); }
.wizard-pill.current {
  color: var(--text);
  border-color: var(--me);
  background: rgba(74, 158, 255, 0.14);
}
.wizard-pill:disabled { cursor: default; opacity: 0.55; }
.wizard-pill:not(:disabled):hover { color: var(--text); border-color: var(--dim); }

/* The reason the step exists, not a restatement of its title. Long on purpose:
   this is where the trade-off behind each choice is explained, and it is the
   only place most of it is ever read. */
.wizard-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--dim);
}

/* A card each, laid out side by side when there is room. The detail is what
   makes the choice answerable, so it is part of the control rather than a
   tooltip on it. */
.wizard-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  align-content: start;
  gap: 10px;
  overflow-y: auto;
}

.wizard-choice {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 13px;
  text-align: left;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: transparent;
  height: 100%;
  /* Buttons are nowrap everywhere else in this app, which is right for a label
     and wrong for a paragraph: without this the detail runs off the side and
     the dialog grows a horizontal scrollbar. */
  white-space: normal;
}

.wizard-choice strong { font-size: 14px; color: var(--text); }
.wizard-choice span { font-size: 12px; line-height: 1.5; color: var(--faint); }
.wizard-choice:hover { border-color: var(--dim); }
.wizard-choice.chosen {
  border-color: var(--me);
  background: rgba(74, 158, 255, 0.1);
}
.wizard-choice.chosen span { color: var(--dim); }

.wizard-switch { display: flex; align-items: center; gap: 9px; font-size: 12.5px; }

/* The name of the call. Only the leads run shows it — every other way in has
   already been given a title by the field that opened the wizard. */
.wizard-name-row { display: flex; align-items: center; gap: 10px; }
.wizard-name-row label { font-size: 12.5px; color: var(--dim); white-space: nowrap; }
.wizard-name-row input { flex: 1; min-width: 0; }

/* A standing warning about the box under it, and on the step that has one it is
   the ONLY prose above that box — it replaces the step's note rather than
   sitting under it, because the same point made twice at two lengths is how a
   dialog fills with text nobody reads.

   Orange rather than red: it is not reporting a failure, it is saying that a box
   which will accept anything deserves more than the least you can put in it. A
   literal orange rather than var(--them), which is the far end's lane colour and
   would drag this along with any change to that. */
.wizard-alert {
  margin: 0;
  padding: 9px 11px;
  font-size: 12.5px;
  line-height: 1.5;
  color: #ff9330;
  border: 1px solid rgba(255, 147, 48, 0.4);
  border-left-width: 3px;
  border-radius: 8px;
  background: rgba(255, 147, 48, 0.09);
}

/* The same block reporting a finished job rather than warning about an
   unfinished one. Green because it is the colour this app already uses for
   "this is done" — a covered checklist item, a live lane. */
.wizard-alert.done {
  color: var(--good);
  border-color: rgba(61, 220, 132, 0.4);
  background: rgba(61, 220, 132, 0.08);
}

/* Three controls rather than one, so this step has its own row. */
.wizard-language { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; }
.wizard-language .field { flex: 1; min-width: 170px; }

.wizard-state { margin: 0; font-size: 12px; line-height: 1.45; min-height: 16px; }

/* Cancel is the destructive one and sits apart from the three that navigate,
   so it cannot be hit while reaching for Back. */
.wizard-actions { align-items: center; }
.wizard-spacer { flex: 1; }
