/* Sotto — shared design system.
   Elegant, simple, functional. One stylesheet, full light/dark theming via CSS
   custom properties. Theme is selected by `data-theme="light|dark"` on <html>
   (set by theme.js); when absent we honour the OS via prefers-color-scheme.

   Colour tokens are SEMANTIC, not literal, so the same component markup styles
   correctly in both themes. The advice-type accents (now/ambient/answer/note/
   action/summary/translation) mirror web/panel.html so the wire contract and the
   visual language stay in lockstep. */

/* ----------------------------------------------------------------- tokens */
:root {
  /* Geometry & type — shared across themes. */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --r-sm: 7px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 999px;
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 8px 24px -12px rgba(0, 0, 0, .25);
  --ring: 0 0 0 3px var(--accent-ring);
  --t-fast: .14s ease;
  --t-med: .22s ease;
}

/* Dark theme (default look — matches the existing panel). */
:root,
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0e1116;
  --bg-grad: radial-gradient(1200px 600px at 80% -10%, #141b26 0%, #0e1116 60%);
  --surface: #151a21;
  --surface-2: #12161c;
  --surface-3: #1b212a;
  --input-bg: #0b0e12;
  --border: #232a33;
  --border-strong: #2f3a47;
  --text: #e6e9ee;
  --text-dim: #aab3c0;
  --text-faint: #6b7686;
  --accent: #2563eb;
  --accent-hi: #3b82f6;
  --accent-contrast: #ffffff;
  --accent-ring: rgba(59, 130, 246, .35);
  --good: #4ade80;
  --bad: #f87171;

  --c-now: #f87171;        --c-now-bg: rgba(248, 113, 113, .10);
  --c-ambient: #3b82f6;    --c-ambient-bg: transparent;
  --c-answer: #a78bfa;     --c-answer-bg: rgba(167, 139, 250, .08);
  --c-note: #64748b;       --c-note-bg: rgba(100, 116, 139, .08);
  --c-action: #fbbf24;     --c-action-bg: rgba(251, 191, 36, .08);
  --c-summary: #34d399;    --c-summary-bg: rgba(52, 211, 153, .10);
  --c-translation: #22d3ee;--c-translation-bg: rgba(34, 211, 238, .08);
  --c-you: #4ade80;        --c-you-bg: rgba(74, 222, 128, .08);
}

/* Light theme. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f6f9;
  --bg-grad: radial-gradient(1200px 600px at 80% -10%, #eaf0fb 0%, #f4f6f9 60%);
  --surface: #ffffff;
  --surface-2: #f7f9fc;
  --surface-3: #eef1f6;
  --input-bg: #ffffff;
  --border: #e2e7ef;
  --border-strong: #d2d9e4;
  --text: #1a2230;
  --text-dim: #4a5567;
  --text-faint: #8a94a6;
  --accent: #2563eb;
  --accent-hi: #1d4ed8;
  --accent-contrast: #ffffff;
  --accent-ring: rgba(37, 99, 235, .22);
  --good: #16a34a;
  --bad: #dc2626;

  --c-now: #dc2626;        --c-now-bg: rgba(220, 38, 38, .07);
  --c-ambient: #2563eb;    --c-ambient-bg: transparent;
  --c-answer: #7c3aed;     --c-answer-bg: rgba(124, 58, 237, .06);
  --c-note: #64748b;       --c-note-bg: rgba(100, 116, 139, .06);
  --c-action: #d97706;     --c-action-bg: rgba(217, 119, 6, .07);
  --c-summary: #059669;    --c-summary-bg: rgba(5, 150, 105, .08);
  --c-translation: #0891b2;--c-translation-bg: rgba(8, 145, 178, .07);
  --c-you: #16a34a;        --c-you-bg: rgba(22, 163, 74, .06);
}

/* When no explicit theme is set, follow the OS. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;
    --bg: #f4f6f9;
    --bg-grad: radial-gradient(1200px 600px at 80% -10%, #eaf0fb 0%, #f4f6f9 60%);
    --surface: #ffffff;
    --surface-2: #f7f9fc;
    --surface-3: #eef1f6;
    --input-bg: #ffffff;
    --border: #e2e7ef;
    --border-strong: #d2d9e4;
    --text: #1a2230;
    --text-dim: #4a5567;
    --text-faint: #8a94a6;
    --accent: #2563eb;
    --accent-hi: #1d4ed8;
    --accent-ring: rgba(37, 99, 235, .22);
    --good: #16a34a;
    --bad: #dc2626;
    --c-now: #dc2626;        --c-now-bg: rgba(220, 38, 38, .07);
    --c-ambient: #2563eb;    --c-ambient-bg: transparent;
    --c-answer: #7c3aed;     --c-answer-bg: rgba(124, 58, 237, .06);
    --c-note: #64748b;       --c-note-bg: rgba(100, 116, 139, .06);
    --c-action: #d97706;     --c-action-bg: rgba(217, 119, 6, .07);
    --c-summary: #059669;    --c-summary-bg: rgba(5, 150, 105, .08);
    --c-translation: #0891b2;--c-translation-bg: rgba(8, 145, 178, .07);
    --c-you: #16a34a;        --c-you-bg: rgba(22, 163, 74, .06);
  }
}

/* ----------------------------------------------------------------- base */
* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font: 15px/1.5 var(--font);
  background: var(--bg);
  background-image: var(--bg-grad);
  background-attachment: fixed;
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  transition: background-color var(--t-med), color var(--t-med);
}

button, input, select, textarea { font: inherit; color: inherit; }

::selection { background: var(--accent-ring); }

/* Scrollbars — quiet in both themes. */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--r-pill); }
*::-webkit-scrollbar-track { background: transparent; }

/* ----------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: 9px 14px; border-radius: var(--r-sm);
  border: 1px solid var(--border-strong); background: var(--surface-3);
  color: var(--text); cursor: pointer; font-size: 14px; font-weight: 500;
  /* 23 of these are <a>, not <button> (`grep -c '<a[^>]*class="btn'` over
     web/*.html) — without this the UA stylesheet underlines the label inside
     a filled button. Seen on `#watchOpenBtn` ("Watch it") by the 2026-09-08
     two-engine drive; the same slip was on every anchor-button before it. */
  text-decoration: none;
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast), opacity var(--t-fast);
}
.btn:hover { border-color: var(--accent); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: none; box-shadow: var(--ring); }
.btn:disabled { opacity: .45; cursor: default; }
.btn:disabled:hover { border-color: var(--border-strong); }
/* `display: inline-flex` above out-specifies the UA stylesheet's `[hidden] {
   display: none }`, so `btn.hidden = true` in JS did nothing and the control
   stayed on screen. Caught by looking at a screenshot: the mailbox dialog's
   Disconnect was rendering with no mailbox connected to disconnect. */
.btn[hidden] { display: none; }

.btn-primary {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-contrast); font-weight: 600;
}
.btn-primary:hover { background: var(--accent-hi); border-color: var(--accent-hi); }

/* Compact variant. Defined inline in playbooks.html until 2026-08-18, when the
   products list needed the same hover-revealed controls. */
.btn-sm { font-size: 12px; padding: 4px 10px; }
.btn-ghost { background: transparent; border-color: transparent; }
.btn-ghost:hover { background: var(--surface-3); border-color: var(--border); }

.btn-icon {
  width: 38px; height: 38px; padding: 0; justify-content: center;
  border-radius: var(--r-sm);
}

/* Pill toggle (mic/cam) — active vs muted. */
.btn-toggle.off {
  background: var(--c-now-bg); border-color: var(--c-now); color: var(--c-now);
}

/* ----------------------------------------------------------------- inputs */
.input, select.input {
  width: 100%; padding: 10px 12px; border-radius: var(--r-sm);
  border: 1px solid var(--border-strong); background: var(--input-bg);
  color: var(--text); transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
/* PROSE FIELDS GROW; they do not truncate. The success drivers, the clause
   fallbacks and "What you're negotiating" are sentences — "below 14 weeks —
   walk (commissioning quality collapses)" — held in fixed-width grid columns,
   and as single-line inputs every one of them read as "below 14 weeks — walk
   (commissioning…" on the table a host scans in the ten minutes before a call
   (P-34). `field-sizing` grows the box natively where it is supported; the JS
   in the row editors does it everywhere else. `resize: none` because the height
   is derived from the content, and a manual handle fights it. */
.input.grow { resize: none; overflow: hidden; min-height: 0; field-sizing: content; }
.input:focus, select.input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.input::placeholder { color: var(--text-faint); }

/* ----------------------------------------------------------------- tags */
.tag {
  font-size: 10px; text-transform: uppercase; letter-spacing: .05em; font-weight: 600;
  padding: 2px 7px; border-radius: var(--r-pill);
  background: var(--surface-3); color: var(--text-faint); white-space: nowrap;
}
.tag.now { background: var(--c-now-bg); color: var(--c-now); }
/* The playbook a deal runs under. Not uppercased and not truncated: it is a
   name a human chose, and "MANDAT EXCLUSIV · V3" reads as a status code rather
   than as their document. Allowed to wrap on a narrow card. */
.tag.pb {
  text-transform: none; letter-spacing: 0; font-weight: 500;
  background: var(--c-ambient-bg); color: var(--c-ambient);
  border: 1px solid var(--c-ambient); white-space: normal; word-break: break-word;
}
/* A FACT CHIP CARRIES USER-AUTHORED TEXT, so it may not be `nowrap`. The
   client cards render `industry` and `size` as these, and a `size` field
   holding a whole sentence — "OWNER-OPERATED. MICHAEL SLOOPKA IS THE PRACTICE;
   NO EMPLOYEES ARE NAMED ANYWHERE ON THE SITE" — became one unbreakable chip
   that ran past its card, past the grid column and past the page content width
   in both themes (P-34, reproduced in rounds 1, 2 and 3). Same treatment and
   same reason as `.tag.pb` above: a value a person typed is allowed to wrap. */
.tag.fact { white-space: normal; word-break: break-word; }
/* A PILL RADIUS ON A BLOCK THAT WRAPS CUTS INTO ITS OWN TEXT. `--r-pill` is
   999px, which is exactly right for the single-line chip these started as and
   wrong the moment `.tag.fact` and `.tag.pb` were allowed to wrap: on a
   four-line `size` sentence the curve intended for one line eats the ends of the
   first and last (dark theme, where the chip background is visible).
   CAPPED, not replaced. These two classes also render short single-line chips —
   `web/teams.js` draws `1 PERSON` / `9 PEOPLE` as `.tag fact`, and
   `web/deals.js` draws the playbook name as `.tag pb` — and this QA round
   records those as CORRECT, so squaring them off would be a new defect. 11px is
   at or just above half the box height (10-11px font, 1.5 line-height, 2px
   padding => ~19-20.5px), so a single-line chip still renders as a pill while a
   wrapped one cannot curve into its text.
   ONE rule reaches every page: eleven pages redeclare `.tag` with `--r-pill`,
   but `.tag.fact` (0,2,0) outranks a bare `.tag` (0,1,0) wherever it is set, and
   no page sets a radius on `.tag.fact` or `.tag.pb` at all. */
.tag.fact, .tag.pb { border-radius: 11px; }
.tag.private { background: var(--c-answer-bg); color: var(--c-answer); }
.tag.refs { background: var(--c-ambient-bg); color: var(--c-ambient); border: 1px solid var(--c-ambient); }
.tag.action { background: var(--c-action-bg); color: var(--c-action); }
.tag.summary { background: var(--c-summary-bg); color: var(--c-summary); }
.tag.translation { background: var(--c-translation-bg); color: var(--c-translation); }
.tag.you { background: var(--c-you-bg); color: var(--c-you); }

/* ----------------------------------------------------------------- presence dot */
.dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint);
  flex: none; transition: background var(--t-fast);
}
.dot.live { background: var(--good); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 22%, transparent); }
.dot.warn { background: var(--c-action); }
.dot.error { background: var(--bad); }

/* ----------------------------------------------------------------- a11y */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ----------------------------------------------------------------- modal */
/* Promoted out of the page-local <style> blocks in clients.html, deals.html and
   playbooks.html, which each carried a byte-identical copy. settings.js mounts a
   dialog on NINE pages, and a page-local rule would leave it unstyled on eight
   of them — the same drift that left Sign out on 2 of 11 pages.

   client.html deliberately keeps its own `.modal` (560px and scrollable, for a
   two-column form) and clients.html keeps `.modal.wide`; both are real
   overrides and both still win, being later in the cascade. */
.modal-bg { position: fixed; inset: 0; background: color-mix(in srgb, var(--bg) 72%, transparent); display: grid; place-items: center; padding: var(--sp-4); z-index: 30; }
.modal-bg[hidden] { display: none; }
.modal { width: 100%; max-width: 420px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow); padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-4); }
.modal h2 { margin: 0; font-size: 17px; }
.modal .row { display: flex; gap: var(--sp-2); justify-content: flex-end; }
.modal-note { margin: 0; font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
.modal-note a { color: var(--accent); }

/* The destructive-action confirmation (web/confirm.js) reuses `.modal` whole;
   only its message needs a rule of its own. `pre-wrap` because that text is the
   exact string the browser's confirm() used to render, line breaks and all —
   and reading size rather than `.modal-note`'s 11.5px, because for the one
   message with no blank line it IS the whole warning, not a footnote to it. */
.confirm-msg { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--text-dim); white-space: pre-wrap; }
/* Lane 1-4: the typed confirmation on a delete that costs somebody else something. */
.confirm-typed { display: block; margin: 12px 0 4px; font-size: 12.5px; color: var(--text-dim); }
#confirmTyped { width: 100%; box-sizing: border-box; font: inherit; padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg); color: var(--text); }

/* ----------------------------------------------------------------- app bar */
/* Promoted out of ten page-local <style> blocks that each carried a
   byte-identical copy — the same reasoning as `.modal` above, and the same
   drift nav.js exists to end. The bar's CONTENTS are now built by nav.js and
   settings.js, so a page-local copy of these rules would style the bar on one
   page and leave it unstyled on the nine others.

   cockpit.html and grill-eval.html keep their own `header.bar` (a denser in-call
   bar, and a sticky one). Both live in page-local <style> blocks that load after
   this file, so both still win — those are real overrides, not drift. */
header.bar {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 12px var(--sp-5); border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.brand {
  display: flex; align-items: center; gap: 9px; font-weight: 700;
  text-decoration: none; color: inherit;
}
.brand .mark {
  width: 28px; height: 28px; border-radius: 8px;
  display: grid; place-items: center;
  background: linear-gradient(140deg, var(--accent), var(--c-translation));
  color: #fff; font-weight: 800;
}
.brand .sub { color: var(--text-faint); font-weight: 500; font-size: 12px; }
.bar-spacer { margin-left: auto; }
/* The breadcrumb a detail page puts left of the spacer (deal, playbook). Its
   CONTENT is page-specific; only the shape is shared. */
.crumb { color: var(--text-faint); font-size: 13px; }
.crumb a { color: inherit; }
/* Mounted by nav.js when ?demo=1 is in play, on every page at once. */
.demo-flag {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; padding: 3px 9px; border-radius: var(--r-pill);
  background: var(--c-action-bg); color: var(--c-action);
}

/* ----------------------------------------------------------------- identity */
/* The Settings control (settings.js), mounted into `.bar` on every
   signed-in page. Quiet by default: it is read a hundred times and changed
   once, so it must not compete with the page for attention. */
/* --text-dim, not --text-faint: this is a name, and --text-faint measures below
   the 4.5:1 contrast floor in both themes. */
.who-btn { display: inline-flex; align-items: center; gap: 7px; max-width: 210px; color: var(--text-dim); }
.who-btn .who-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-btn .who-dot {
  flex: none; width: 21px; height: 21px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface-3); color: var(--text-dim);
  font-size: 11px; font-weight: 700; line-height: 1;
}
.who-btn:hover .who-dot { background: var(--accent); color: var(--accent-contrast); }
.sig-preview {
  font-size: 12.5px; color: var(--text-dim); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 9px 11px; line-height: 1.5; margin: 0;
}
.sig-preview b { color: var(--text); font-weight: 650; }
.sig-err {
  font-size: 12.5px; color: var(--c-now); background: var(--c-now-bg);
  border: 1px solid var(--c-now); border-radius: var(--r-sm);
  padding: 8px 10px; line-height: 1.45; margin: 0;
}
.sig-err[hidden] { display: none; }

/* ----------------------------------------------------------------- settings menu */
.sx-wrap { position: relative; display: inline-flex; }
.sx-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 35;
  min-width: 186px; padding: 5px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: 1px;
}
.sx-menu[hidden] { display: none; }
.sx-item {
  appearance: none; border: none; background: transparent; cursor: pointer;
  text-align: left; font: inherit; font-size: 13px; color: var(--text);
  padding: 8px 10px; border-radius: var(--r-sm);
}
.sx-item:hover { background: var(--surface-2); color: var(--accent); }
.sx-item:focus-visible { outline: none; box-shadow: var(--ring); }
.sx-item[hidden] { display: none; }
.sx-sep { height: 1px; background: var(--border); margin: 4px 2px; }
/* Disconnect sits with Test on the left of the dialog's button row, far from
   Save: it is the one control there whose misclick turns off email for the
   entire organisation. */
.mbx-dialog .mbx-disc { color: var(--bad); }
.mbx-dialog .mbx-disc:hover { border-color: var(--bad); }

/* Advice audio in the cockpit (docs/plans/advice-tts.md Phase 6): the bar
   popover and the per-card play button. */
.audio-wrap { position: relative; display: inline-flex; }
/* The display rules above/below would defeat the `hidden` attribute (the UA's
   display:none loses to any class rule) — the recording tests pin this. */
.audio-wrap[hidden], .audio-pop[hidden] { display: none; }
.audio-pop {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 60;
  min-width: 220px; padding: 12px 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-sm); box-shadow: 0 8px 24px rgba(0, 0, 0, .25);
  display: flex; flex-direction: column; gap: 10px; text-align: left;
}
.audio-pop label { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; }
.audio-pop .ap-main { font-weight: 600; }
.audio-pop .ap-group {
  display: flex; flex-direction: column; gap: 6px;
  border-top: 1px solid var(--border); padding-top: 10px;
}
.audio-pop .ap-note { font-size: 12px; color: var(--text-dim); margin: 0; }
.card .card-play {
  border: 1px solid var(--border); background: none; color: var(--text-dim);
  border-radius: var(--r-sm); font-size: 10px; line-height: 1;
  padding: 3px 7px; cursor: pointer;
}
.card .card-play:hover { color: var(--text); border-color: var(--accent); }
.card .card-play.err { color: var(--bad); border-color: var(--bad); }
.card .card-play:disabled { opacity: .5; cursor: default; }

/* Advice voice dialog (docs/plans/advice-tts.md Phase 4): the sliders reuse
   the mailbox form's .fld shell; only the range row is new. */
.vx-fld { display: flex; align-items: center; gap: 10px; }
.vx-fld input[type="range"] { flex: 1; accent-color: var(--accent); }
.vx-val {
  min-width: 3em; text-align: right; font-size: 12px;
  color: var(--text-dim); font-variant-numeric: tabular-nums;
}

/* THE SHARED TOAST. `.toast` was a page-local element defined in eleven
   separate <style> blocks and MISSING from two — `product.html` and
   `products.html`, the pages the product library added. On those two the div
   existed, `notify()` filled it, and it rendered as unstyled text at the foot
   of the document: every success and every error was invisible, including
   "Could not draft: the model wrote 2322 characters…". The symptom is a button
   that does nothing.
   Found 2026-08-19 by pressing one. Defined here so the next page cannot repeat
   it; the eleven page-local copies still win where they exist, because a page's
   own <style> comes after this file. Same fix as 854b16b made for `.dropzone`,
   `.btn.danger` and `.btn-sm`, for the same reason.
   The `[hidden]` guard is not optional — this rule sets `position`, and a class
   with a layout property needs its own `[hidden]` or the attribute silently
   does nothing (the rule written at `.thinking[hidden]` below). */
.toast {
  position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%);
  background: var(--c-now-bg); color: var(--bad); border: 1px solid var(--bad);
  padding: 8px 14px; border-radius: var(--r-sm); font-size: 13px; z-index: 40;
  max-width: min(560px, 92vw); text-align: center;
}
.toast.ok { background: var(--c-summary-bg); color: var(--c-summary); border-color: var(--c-summary); }
/* A REFUSAL IS NOT AN ALARM. The base rule above is the error tone, so every
   `notify(msg, false)` in the app paints in `--c-now-bg` / `--bad` — the palette
   `.advice.now` uses, which noise discipline reserves for the rare interruption
   (CLAUDE.md). Spending it on "wait a second, this is already happening" is the
   dilution that rule exists to prevent, and the Practice screen is the surface
   an instructor demo is watched on.
   `--c-note` is the deliberate choice: it is already this design system's word
   for the quiet, accumulating, non-urgent thing, so a soft refusal reads as a
   note rather than as a fault.
   ALL THREE PROPERTIES ARE SET, because on practice.html `sim.css` loads AFTER
   this file and redefines `.toast` with its own background and border. Two
   classes beat one, so this wins there without sim.css needing to know about it
   — but only for the properties it names, and a half-set rule would leave red
   text on a neutral ground.
   No `[hidden]` guard needed and none wanted: this sets no `display`, so
   `.toast[hidden]` below still decides visibility. */
.toast.wait { background: var(--c-note-bg); color: var(--c-note); border-color: var(--c-note); }
.toast[hidden] { display: none; }

/* This module reports outcomes from all nine pages, and `.toast` is a
   page-local element on most of them — `.sx-toast` predates the shared rule
   above and is kept so the module does not depend on a page's markup. */
.sx-toast {
  position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%);
  background: var(--c-now-bg); color: var(--bad); border: 1px solid var(--bad);
  padding: 8px 14px; border-radius: var(--r-sm); font-size: 13px; z-index: 40;
}
.sx-toast.ok { background: var(--c-summary-bg); color: var(--c-summary); border-color: var(--c-summary); }
.sx-toast[hidden] { display: none; }

/* ------------------------------------------------- settings dialog internals */
/* Moved out of clients.html with the mailbox form, and SCOPED to `.mbx-dialog`
   rather than promoted as-is. `.hint` and `.fld` are generic enough that four
   other pages already use `class="hint"` for their own purposes — a bare
   `.hint` rule in the shared sheet would silently restyle deal.html,
   simulation.html and playbook.html, which is not a change anyone asked for. */
.mbx-dialog .mbx-form { display: flex; flex-direction: column; gap: var(--sp-3); }
/* --text-dim, NOT --text-faint: faint measures 3.8:1 on this surface in dark
   and 3.06:1 in light, and these are form labels and the instructions for a
   credential — they have to clear 4.5:1. */
.mbx-dialog .fld { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--text-dim); }
.mbx-dialog .lbl { display: flex; align-items: baseline; gap: 6px; }
.mbx-dialog .lbl em { font-style: normal; font-size: 11px; color: var(--text-faint); }
/* Standalone, not `.fld .hint` — the disclosure's own intro sits outside any
   field, and scoping this under .fld left it rendering at full body size. */
.mbx-dialog .hint { font-size: 11.5px; color: var(--text-dim); line-height: 1.45; font-weight: 400; }
/* The reveal toggle sits INSIDE the field: a button floating beside a password
   box reads as a separate action, and this one is part of the field. The shell
   carries the border so the two look like one control. */
.mbx-dialog .pw-shell { position: relative; display: flex; align-items: stretch; }
.mbx-dialog .pw-shell .input { padding-right: 40px; }
.mbx-dialog .pw-eye {
  position: absolute; right: 1px; top: 1px; bottom: 1px; width: 36px;
  display: grid; place-items: center;
  border: none; background: transparent; cursor: pointer;
  color: var(--text-faint); border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.mbx-dialog .pw-eye:hover { color: var(--accent); }
.mbx-dialog .pw-eye:focus-visible { outline: none; box-shadow: var(--ring); }

.mbx-dialog .fld-row { display: flex; gap: var(--sp-2); }
.mbx-dialog .fld-row .fld { flex: 1; min-width: 0; }
.mbx-dialog .fld-row .fld.port { flex: 0 0 84px; }

/* Hosts and ports behind a disclosure: the Zoho Canada defaults are right for
   everyone except another region, so the common path is two fields. */
.mbx-dialog .adv { border-top: 1px solid var(--border); padding-top: var(--sp-3); }
.mbx-dialog .adv > summary {
  cursor: pointer; font-size: 12px; color: var(--text-dim); list-style: none;
  display: inline-flex; align-items: center; gap: 6px; border-radius: var(--r-sm);
}
.mbx-dialog .adv > summary::-webkit-details-marker { display: none; }
.mbx-dialog .adv > summary:hover { color: var(--accent); }
.mbx-dialog .adv > summary:focus-visible { outline: none; box-shadow: var(--ring); }
.mbx-dialog .adv > summary svg { transition: transform var(--t-fast); }
.mbx-dialog .adv[open] > summary svg { transform: rotate(-90deg); }
.mbx-dialog .adv-body { display: flex; flex-direction: column; gap: var(--sp-3); padding-top: var(--sp-3); }

/* The dialog is bounded by the VIEWPORT and the form takes what is left, so
   the disclosure can open on a short laptop or a phone without the buttons
   sliding off-screen. A fixed max-height on the form cannot do that: it either
   clips on small screens or wastes space on large ones. `dvh` so the mobile URL
   bar collapsing does not crop the footer. */
/* 560px, not 520: the button row gained Disconnect when the form moved out of
   clients.html, and four controls overflowed 520 by a dozen pixels — enough to
   wrap "Save changes" onto its own line under Cancel, which reads as a mistake.
   client.html's form dialog already uses 560, so this is the existing width for
   a dialog with a real form in it rather than a number picked to fit. */
.modal.wide { max-width: 560px; max-height: calc(100vh - 2 * var(--sp-4)); }
@supports (height: 100dvh) {
  .modal.wide { max-height: calc(100dvh - 2 * var(--sp-4)); }
}
.modal.wide .mbx-form { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 2px; }
.modal.wide .row { flex: none; }

.mbx-dialog .mbx-err {
  font-size: 12.5px; color: var(--bad); background: var(--c-now-bg);
  border: 1px solid var(--bad); border-radius: var(--r-sm);
  padding: 8px 10px; line-height: 1.45; margin: 0;
}
.mbx-dialog .mbx-err[hidden] { display: none; }
/* Success has its own colour and its own element. Reusing the error box in
   green would mean one node whose meaning depends on a class — and the state
   that matters most here is "it signed in", which must not be able to inherit
   the styling of "it did not". */
.mbx-dialog .mbx-ok {
  font-size: 12.5px; color: var(--c-summary); background: var(--c-summary-bg);
  border: 1px solid var(--c-summary); border-radius: var(--r-sm);
  padding: 8px 10px; line-height: 1.45; margin: 0;
  display: flex; align-items: center; gap: 7px;
}
.mbx-dialog .mbx-ok[hidden] { display: none; }
/* Wraps rather than overflowing on a narrow phone, and the primary action
   stays last in the flow so it lands bottom-right wherever it ends up. */
.mbx-dialog .mbx-row { align-items: center; flex-wrap: wrap; }
.mbx-dialog .bar-spacer { margin-left: auto; }
/* The connect check is a REAL IMAP login and takes seconds. A button that just
   sits there is the defect this must not ship (a synchronous LLM pass with no
   feedback was read as a failure during live testing) — so the control names
   what is happening while it happens. */
.mbx-dialog .spin {
  width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor; animation: mbx-spin .6s linear infinite;
}
@keyframes mbx-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .mbx-dialog .spin { animation-duration: 2.4s; } }

/* ============================ Showcase (marketing replay) ============================ */
/* sim-showcase Phase 4: a static split-screen replay for the website. Scoped .sc-*
   classes; reuses the design tokens above so it tracks light/dark via theme.js. */
.sc-disclosure { font-size: 12px; font-weight: 600; color: var(--c-action);
  border: 1px solid var(--c-action); border-radius: var(--r-pill); padding: 3px 10px; letter-spacing: .02em; }
/* The public page's one call to action. `white-space: nowrap` because the bar
   is a flex row and "Ask for a trial" wrapping to two lines is what a narrow
   viewport does to a four-word button otherwise. */
.sc-trial { white-space: nowrap; }
.sc-noscript { background: var(--c-now-bg); color: var(--text); border: 1px solid var(--c-now);
  padding: var(--sp-3) var(--sp-4); margin: var(--sp-4); border-radius: var(--r-md); }
.sc-main { max-width: 1120px; margin: 0 auto; padding: var(--sp-5) var(--sp-4) 64px; }
.sc-intro h1 { font-size: 30px; line-height: 1.15; margin: 0 0 var(--sp-2); }
.sc-intro p { color: var(--text-dim); margin: 0 0 var(--sp-5); max-width: 62ch; }

.sc-banner { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-5); box-shadow: var(--shadow); }
.sc-banner-title { font-weight: 650; margin-bottom: var(--sp-4); }
.sc-cmp { display: flex; align-items: stretch; gap: var(--sp-4); }
.sc-cmp-col { flex: 1; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-4); }
.sc-cmp-col.good { border-color: var(--good); background: var(--c-you-bg); }
.sc-cmp-h { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); margin-bottom: var(--sp-2); }
.sc-cmp-big { font-size: 34px; font-weight: 750; line-height: 1; }
.sc-cmp-big span { font-size: 13px; font-weight: 500; color: var(--text-faint); margin-left: 4px; }
.sc-cmp-col.good .sc-cmp-big { color: var(--good); }
.sc-cmp-row { font-size: 13px; color: var(--text-dim); margin-top: var(--sp-2); }
.sc-cmp-row b { color: var(--text); }
.sc-cmp-vs { align-self: center; color: var(--text-faint); font-weight: 600; }

.sc-controls { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); flex-wrap: wrap; }
.sc-pick { max-width: 280px; }
.sc-speeds { display: inline-flex; gap: 2px; }
.sc-speed.active { background: var(--accent); color: var(--accent-contrast); border-color: var(--accent); }
.sc-clock { font-variant-numeric: tabular-nums; color: var(--text-faint); font-size: 13px; }

.sc-split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.sc-col { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-4); min-height: 420px; display: flex; flex-direction: column; }
.sc-col-sotto { background: var(--surface-2); }
.sc-col-head { display: flex; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.sc-col-head h2 { font-size: 15px; margin: 0; }
.sc-sub { font-size: 12px; color: var(--text-faint); }
.sc-silence { font-size: 12px; color: var(--text-faint); font-style: italic; margin-bottom: var(--sp-3); }
.sc-feed { flex: 1; overflow-y: auto; max-height: 62vh; display: flex; flex-direction: column; gap: var(--sp-3); scroll-behavior: smooth; }
.sc-empty { color: var(--text-faint); text-align: center; margin: auto; }

.sc-turn { max-width: 88%; }
.sc-turn.us { align-self: flex-end; text-align: right; }
.sc-who { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; margin: 0 6px 3px; }
.sc-coached { margin-left: 6px; color: var(--c-now); font-weight: 650; text-transform: none; letter-spacing: 0; }
.sc-bubble { padding: 9px 13px; border-radius: 14px; line-height: 1.45; display: inline-block; text-align: left; }
.sc-turn.them .sc-bubble { background: var(--surface-3); border-bottom-left-radius: 4px; }
.sc-turn.us .sc-bubble { background: var(--accent); color: var(--accent-contrast); border-bottom-right-radius: 4px; }

.sc-advice { border-left: 3px solid var(--c-ambient); background: var(--surface); border-radius: var(--r-md); padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4); }
.sc-advice.sc-now { border-left-color: var(--c-now); background: var(--c-now-bg); }
.sc-advice.sc-note { border-left-color: var(--c-note); }
.sc-advice.sc-action_item { border-left-color: var(--c-action); }
.sc-advice.sc-summary { border-left-color: var(--c-summary); background: var(--c-summary-bg); }
.sc-atag { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); margin-bottom: 3px; }
.sc-advice.sc-now .sc-atag { color: var(--c-now); }
.sc-ahead { font-weight: 650; }
.sc-adetail { font-size: 13px; color: var(--text-dim); margin-top: 2px; }
.sc-caveat { color: var(--text-faint); font-size: 12px; margin-top: var(--sp-4); font-style: italic; }

@media (max-width: 760px) {
  .sc-split { grid-template-columns: 1fr; }
  .sc-cmp { flex-direction: column; }
}

/* ---- Showcase: hero / primer / scenario / debrief (Phase 4+) ---- */
.sc-hero { margin-bottom: var(--sp-5); }
.sc-hero h1 { font-size: 32px; line-height: 1.12; margin: 0 0 var(--sp-3); max-width: 20ch; }
.sc-lede { color: var(--text-dim); margin: 0 0 var(--sp-2); max-width: 68ch; font-size: 15px; }
.sc-lede b { color: var(--text); }
.sc-lede-dim { color: var(--text-faint); }

.sc-primer { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); margin-bottom: var(--sp-5); }
.sc-step { display: flex; gap: var(--sp-3); align-items: flex-start; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: var(--sp-3) var(--sp-4); font-size: 13px; color: var(--text-dim); }
.sc-step b { color: var(--text); }
.sc-step-n { flex: none; width: 24px; height: 24px; border-radius: var(--r-pill); background: var(--accent); color: var(--accent-contrast);
  font-weight: 700; font-size: 13px; display: grid; place-items: center; }
.sc-lock { color: var(--c-summary); font-weight: 600; }

.sc-scenario { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-5); box-shadow: var(--shadow); }
.sc-scenario-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.sc-scenario-head h2 { font-size: 16px; margin: 0; }
.sc-case { color: var(--text-dim); font-size: 14px; }
.sc-brief-note { font-size: 12px; color: var(--text-faint); font-style: italic; margin-bottom: var(--sp-2); }
.sc-brief-row { display: flex; gap: var(--sp-3); padding: 5px 0; border-top: 1px solid var(--border); font-size: 13px; }
.sc-brief-row:first-of-type { border-top: none; }
.sc-brief-k { flex: none; width: 92px; color: var(--text-faint); text-transform: uppercase; font-size: 11px; letter-spacing: .04em; padding-top: 1px; }
.sc-brief-v { color: var(--text); }
.sc-brief-row.red .sc-brief-v { color: var(--c-now); }

.sc-debrief { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5) var(--sp-5); margin-top: var(--sp-5); box-shadow: var(--shadow); }
.sc-debrief h2 { font-size: 18px; margin: 0 0 var(--sp-3); }
.sc-outcome { font-weight: 650; font-size: 15px; margin-bottom: var(--sp-2); }
.sc-db-summary { color: var(--text-dim); margin: 0 0 var(--sp-4); max-width: 82ch; line-height: 1.55; }
.sc-tactics { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.sc-tactic-col h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint); margin: 0 0 var(--sp-2); }
.sc-tactic-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.sc-tactic-list li { background: var(--surface-2); border-radius: var(--r-sm); padding: 8px 11px; }
.sc-t-name { display: block; font-weight: 600; font-size: 13px; }
.sc-t-note { display: block; color: var(--text-dim); font-size: 12.5px; margin-top: 1px; }
.sc-t-none { color: var(--text-faint); font-style: italic; }
.sc-turning { margin-top: var(--sp-4); padding: var(--sp-3); background: var(--c-action-bg); border-left: 3px solid var(--c-action);
  border-radius: var(--r-sm); font-size: 13px; }
.sc-pick-label { font-size: 13px; color: var(--text-dim); display: inline-flex; align-items: center; gap: 6px; }

@media (max-width: 760px) {
  .sc-primer { grid-template-columns: 1fr; }
  .sc-tactics { grid-template-columns: 1fr; }
  .sc-hero h1 { font-size: 26px; }
}

.sc-tip { font-size: 12.5px; color: var(--text-faint); margin: -4px 0 var(--sp-3); }
.sc-tip b { color: var(--text-dim); }

.sc-debrief-locked { margin-top: var(--sp-5); padding: var(--sp-4) var(--sp-5); border: 1px dashed var(--border-strong);
  border-radius: var(--r-lg); background: var(--surface-2); color: var(--text-dim); font-size: 13.5px; text-align: center; }
.sc-debrief-locked b { color: var(--text); }

/* THE THIRD INSTANCE OF ONE TRAP, so the guard lives here where both pages get
   it. `.thinking` sets `display: inline-flex` in deal.html and `display: flex`
   in practice.css; both out-order the UA stylesheet's `[hidden] { display: none }`
   at equal specificity, so `hidden` did nothing and "Sotto is thinking …" sat on
   screen permanently — on the deal page at rest, and on the practice page even
   when `practice.js:351` had explicitly hidden it because it was the host's turn.
   Same shape as `.btn[hidden]` and `.dropzone[hidden]`: if you give a class a
   `display`, give it a `[hidden]` too. */
.thinking[hidden] { display: none; }

/* The destructive variant. Defined in practice.css and sim.css before this,
   which meant a page that loaded neither — the product library — had no way to
   mark Delete as different from the benign buttons beside it. */
.btn.danger { color: var(--bad); }
.btn.danger:hover:not(:disabled) { border-color: var(--bad); background: var(--surface-2); }

/* ── Dropzone ────────────────────────────────────────────────────────────────
   Shared by the deal page and the product page. Lived in deal.html's inline
   <style> until 2026-08-18, when the product library needed the same control
   and copying it would have made two definitions that drift. Both pages load
   app.css, so moving it here changes nothing about how either renders. */
.dropzone {
  border: 1.5px dashed var(--border-strong); border-radius: var(--r-md);
  padding: var(--sp-5) var(--sp-4); text-align: center; cursor: pointer;
  background: var(--surface-2); transition: border-color var(--t-fast), background var(--t-fast);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.dropzone:hover, .dropzone.drag { border-color: var(--accent); background: var(--surface-3); }
.dropzone.drag { box-shadow: var(--ring); }
/* `display: flex` above out-orders the UA stylesheet's `[hidden] { display: none }`
   — same specificity (0,1,0), and this rule comes later — so without this line
   setting `.hidden = true` on a dropzone does nothing at all. Same trap as
   `.btn[hidden]` above. */
.dropzone[hidden] { display: none; }
/* An archived product must not accept a drop. Greyed, and the JS refuses too —
   the class alone is presentation and would be four seconds of devtools away. */
.dropzone.off { opacity: .5; cursor: default; }
.dropzone.off:hover { border-color: var(--border-strong); background: var(--surface-2); }
.dz-ic { font-size: 26px; opacity: .85; }
.dz-main { font-size: 14px; font-weight: 500; }
.dz-link { color: var(--accent); font-weight: 600; }
.dz-sub { font-size: 12px; color: var(--text-faint); }
.dz-file { display: flex; align-items: center; gap: var(--sp-3); padding: 12px;
  border: 1px solid var(--border); border-radius: var(--r-md); margin-top: var(--sp-3); }
.dz-file[hidden] { display: none; }
.dz-file .ic { width: 36px; height: 36px; border-radius: 8px; display: grid;
  place-items: center; background: var(--surface-3); flex: none; }
.dz-file .name { font-weight: 500; word-break: break-word; }
.dz-file .sub { font-size: 12px; color: var(--text-faint); }
.dz-file .sp { margin-left: auto; display: flex; gap: 6px; flex: none; }

/* STATE row 89: case materials on the participant's cohort page. */
.materials-list { list-style: none; padding: 0; margin: 0; }
.materials-list li { margin: 0 0 var(--sp-2); }
.material-text { white-space: pre-wrap; font: inherit; font-size: 0.92em; line-height: 1.45; }
.material-reader { margin-top: var(--sp-4); }
