/* SURA/Amrut (2026-10-01): repainted from Marea's dark-teal palette to a
 * dark-amber palette matching Amrut's own brand amber (#c8883a / #e4ae62 /
 * #9a6828 — taken from the supplied index_1.html reference). Per this
 * file's own long-standing convention (see the next comment block): every
 * CUSTOM-PROPERTY VALUE below is repainted, but every NAME is left exactly
 * as Marea had it (--seafoam, --coral, --voice-teal, etc., are now amber
 * hues, not teal/cyan ones) — every one of this file's ~2000 lines of
 * component rules already reads these custom properties, so repainting
 * them in this one block re-themes the entire app without touching a
 * single consuming rule below. The few hardcoded rgba(...) literals that
 * mirrored the old teal values outside of var() (search this file for
 * "79,209,174", "124,138,166", "120,230,225", "140,240,235", "229,98,74"
 * to find them) are retinted alongside their token, same approach Marea's
 * own 2026-09-17 retint used (see below). --danger* keeps its original
 * red-orange — that token is domain-agnostic, not part of the brand hue.
 * 2026-10-04 follow-up: the split login page (.login-split-*, further down
 * this file) IS now rebranded too. It was originally left untouched because
 * sura-poc's .env sets REQUIRE_AUTH=false (see backend/config.py), making
 * /login unreachable in the normal flow — but the page is still served
 * directly at that URL, so it needed fixing regardless of reachability.
 * The Marea-branded photo asset it referenced (web/assets/login-ocean.jpg)
 * was never actually present in this app (404), and no Amrut-specific photo
 * replaces it — see .login-split-visual below for the CSS gradient used
 * instead. No backend/auth logic changed; this is visual/copy only.
 *
 * Marea Seafood Intelligence Workspace — visual redesign, 2026-09-13,
 * repainted to a premium black/white/blue palette, 2026-09-14 (per Vengat's
 * conference-floor feedback: the previous ocean teal/coral scheme read as
 * generic "seafood" branding that blends into a seafood-industry conference
 * environment instead of standing apart from it).
 *
 * Token NAMES are unchanged from the original teal/coral palette
 * (--seafoam*, --coral*) regardless of what hex value they hold at any
 * given point in this file's history — every component rule below already
 * reads these custom properties, so repainting the values in one place
 * re-themes the entire app without touching a single consuming rule.
 * --seafoam* = the single primary accent (active/engaged/interactive) and
 * --coral* = a muted slate secondary (processing/caution/lower emphasis).
 * As of 2026-09-17 (see the second dated note below) --seafoam* is teal —
 * the same teal as --voice-teal* — and --coral* is still the original
 * muted slate, so they're no longer the "same hue family" this paragraph
 * once described; see --coral's own comment for why that's fine.
 *
 * Contrast-checked (WCAG relative-luminance/contrast-ratio script): every
 * text-role token pairing below is >=4.5:1 against every background token
 * it's actually used on.
 *
 * 2026-09-17: this is now the ONLY theme — the app previously also shipped
 * a light theme (a `:root[data-theme="light"]` override block) selectable
 * from Settings > Appearance, which made the app visually inconsistent
 * (most of the Voice Assistant experience was already permanently dark
 * regardless of that setting — see the --voice-stage and --voice-teal
 * tokens and the .sidebar/.app-header/.voice-stage comments below). That
 * toggle, its "Appearance" Settings card, and the light-theme token block
 * have all been removed; every component still just reads these bare
 * :root custom properties, so removing the override was the entire change.
 *
 * 2026-09-17, later same day: the "premium black/blue" repaint described
 * above is itself superseded now. The --seafoam, --focus, --bg-, --border,
 * and --text- token groups below were repainted AGAIN, this time to the
 * same dark-teal identity the Voice Assistant stage already used
 * (--voice-teal, --voice-stage-bg), because having the app-wide palette be
 * blue while the stage was teal read as two different products glued
 * together (most visibly: the Conversations, Settings, Knowledge Base, and
 * Upload pages fell through to the plain near-black --bg-void with no
 * teal tint at all, while the voice stage next to them was clearly teal).
 * Token NAMES are unchanged again for the same reason as the paragraph
 * above, only the hex and rgba values changed, so this whole app-wide
 * re-theme is contained to this root block plus a handful of hardcoded
 * rgba(91,141,239,...) literals elsewhere in this file that mirrored the
 * old --seafoam blue outside of a var() reference (search for
 * "79,209,174" to find their retinted replacements). The --coral,
 * --tide-calm, and --danger token groups are unaffected, see their own
 * comments below for why.
 */
:root {
  /* 2026-09-17: repainted from the "premium black/blue" palette to the
     same dark-teal identity the Voice Assistant stage already used (see
     --voice-teal/--voice-stage-bg below) — a follow-up request to make
     every page read as one consistently-teal Marea instead of two
     different color families (this app-wide blue vs. that stage's teal).
     Token NAMES are unchanged, only the values below, so every existing
     component rule re-themes automatically without being touched. */
  --bg-void: #1C1410;
  --bg-deep: #241A13;
  --bg-surface: #2D2016;
  --bg-elevated: #3A2A1B;
  --bg-elevated-hover: #463322;

  --border-subtle: rgba(228,174,98,0.08);
  --border: rgba(228,174,98,0.14);
  --border-strong: rgba(228,174,98,0.26);

  --text-primary: #FBF3E9;
  --text-secondary: #C9B9A9;
  --text-tertiary: #948A7C;

  /* primary accent — Amrut's brand amber (same hue as the Voice Assistant
     stage's --voice-teal below, now also amber); kept as its own token name
     since most component rules already read --seafoam, not --voice-teal
     directly. */
  --seafoam: #C8883A;
  --seafoam-strong: #E4AE62;
  --seafoam-dim: rgba(200,136,58,0.16);

  /* secondary accent — muted warm tan, lower emphasis. Left as its own
     (no-longer-same-hue) family deliberately: it's used for a handful of
     specific secondary/caution states (SME captions, citations, the "your
     data" badge fallback), not a competing primary, so a quiet neutral
     still reads fine paired with the amber primary above. */
  --coral: #A6967C;
  --coral-strong: #C2B29A;
  --coral-dim: rgba(166,150,124,0.16);

  --danger: #E5624A;
  --danger-bg: rgba(229,98,74,0.12);
  --danger-text: #FFD7CF;

  --focus: #C8883A;

  /* Resting ("calm") color for the voice-tides visual — a paler, quieter
     tan than --seafoam. Illuminated tides (listening/answering) reuse
     --seafoam/--seafoam-strong directly rather than a separate token, so
     there's still only one accent hue in the whole app, just two
     intensities of it. */
  --tide-calm: #8A7257;

  /* Translucent "glass" surfaces (caption panel / evidence dock) — kept as
     their own tokens rather than derived from --bg-surface/--bg-deep at
     render time, since these are the literal historical rgba() values
     (bg-surface/bg-deep with an alpha channel) and giving them names makes
     that relationship explicit instead of accidental. (2026-09-17: RGB
     channels updated alongside --bg-surface/--bg-deep above so this stays
     true — alpha unchanged.) */
  --glass-panel: rgba(45,32,22,0.6);
  --glass-dock: rgba(36,26,19,0.66);

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 20px;

  --font-display: 'Fraunces', serif;
  --font-ui: 'Archivo', sans-serif;
  /* Used ONLY for the Voice Assistant's spoken-conversation text
     (.caption-text, 2026-09-15) — a request specifically scoped to that
     transcript, not a whole-app typography change. Everything else keeps
     --font-display/--font-ui exactly as before. */
  --font-transcript: 'Inter', var(--font-ui);

  /* Voice Assistant visualization palette (2026-09-15, per Vengat's Marea
     reference HTML) — originally kept deliberately separate from
     --seafoam/--coral above (this whole app had been repainted to
     blue/slate-blue at the time, see the file-header comment, and only the
     orb/waveform were meant to carry the reference's green/teal language).
     2026-09-17: --seafoam now uses this exact same teal (see above) as
     part of unifying the whole app's theme, so these two token families
     are intentionally the same color today, kept as separate names because
     most rules already read one or the other and didn't need to change.
     These --voice-teal* tokens exist to be read by .orb-wrap's glow and
     app.js's strand renderer specifically; --coral/--tide-calm are
     unaffected by this and keep their previous meaning.
     (Bug note: this comment used to reference --seafoam and --coral
     side by side with an asterisk-slash between them, which is the CSS
     comment-close sequence — writing it out even here would repeat the
     mistake, so it's just described. That accidental sequence silently
     truncated this comment and dropped the --voice-teal declaration below
     from ever parsing, so every var(--voice-teal) consumer was actually
     falling back to an inherited color this whole time. Fixed here
     alongside the app-wide retint; --voice-teal now actually applies its
     own value rather than borrowing a parent's.) */
  --voice-teal: #C8883A;
  --voice-teal-strong: #E4AE62;
  --voice-teal-dim: rgba(200,136,58,0.14);
  --voice-atmosphere: radial-gradient(ellipse farthest-side at center, rgba(200,136,58,0.16) 0%, rgba(200,136,58,0) 100%);

  /* Voice Assistant STAGE backdrop (2026-09-16, per an approved reference
     preview, marea_animation_preview_v2.html) — scoped to .voice-stage
     ONLY (confirmed with the requester: sidebar/header/every other view
     keep today's theme exactly, see the .voice-stage rule below). The
     stage's own background is deliberately theme-INDEPENDENT — always
     this dark teal/black, never the light theme's white/off-white — so
     these tokens live in bare :root only and are NOT redefined in the
     light-theme block below; both themes read the same values here. */
  --voice-stage-bg: #1c1410;
  --voice-stage-glow: radial-gradient(circle at 50% 42%, rgba(138,94,42,0.32), transparent 55%);
  --voice-stage-hint: rgba(255,232,196,0.62);
  --voice-stage-hint-dim: rgba(255,232,196,0.42);
  --voice-stage-danger: #FF8A76;
  --voice-stage-caution: #FFC98A;
  /* Distinct third accent for search_client_data evidence items (the
     user's own uploaded data) — deliberately not the same hue as
     --voice-teal (KB/web, now amber) or --voice-stage-caution (the
     ok_no_cite "caution" state, also warm), so "your data" is never
     visually confusable with either at a glance. SURA/Amrut (2026-10-01):
     Marea's own distinct accent here (#E0B34D, a warm gold) was too close
     to the new amber primary to still read as distinct, so this one uses
     a cool blue-grey instead — the inverse of Marea's teal-primary/
     gold-accent pairing. */
  --voice-stage-client-data: #6E9FBF;
  --voice-stage-client-data-dim: rgba(110,159,191,0.14);

  /* 2026-09-16, follow-up request: extend the same dark treatment to every
     other surface inside the Voice Assistant experience — the References
     panel (.evidence-dock) plus the floating cards inside .voice-stage
     itself (.caption-panel/.followup-chip/.error-banner) — so nothing
     reads as a disconnected light panel next to the dark orb. All scoped
     under #viewVoiceAssistant below; sidebar/header and the other views
     (Conversations/Knowledge Base/Settings) are untouched, same boundary
     confirmed for the stage background above. */
  --voice-stage-card-bg: rgba(255,255,255,0.045);
  --voice-stage-card-border: rgba(228,174,98,0.16);
  --voice-stage-card-border-strong: rgba(228,174,98,0.32);
  --voice-stage-text: #FBF3E9;
}

* { box-sizing: border-box; }

/* An element's `hidden` attribute must always actually hide it, even when
   a more specific class on the same element also sets `display` (sidebar
   view-panels, the voice-assistant main view) — without this, the cascade
   would let e.g. `.app-main{display:flex}` win over the UA default
   `[hidden]{display:none}` and the element would stay visible. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg-void);
  color: var(--text-primary);
  font-family: var(--font-ui);
  -webkit-font-smoothing: antialiased;
  height: 100%;
}

body { overflow: hidden; }

button {
  font-family: inherit;
  cursor: pointer;
}
button:disabled { cursor: not-allowed; }

/* ---- accessibility baseline ------------------------------------------- */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- app shell ---------------------------------------------------------- */
.app {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: row;
  position: relative;
  background:
    radial-gradient(120% 90% at 12% -10%, rgba(200,136,58,0.07), transparent 45%),
    radial-gradient(90% 70% at 100% 110%, rgba(200,136,58,0.04), transparent 50%),
    var(--bg-void);
  overflow: hidden;
}

/* ---- sidebar -------------------------------------------------------------
 * Collapsed to an icon-only rail by default (persisted via localStorage) so
 * the Voice Assistant keeps the lion's share of the screen — Conversations/
 * Knowledge Base/Settings are enhancements around it, not equal-billing
 * navigation. The hamburger in the header expands it to show labels too.
 */
.sidebar {
  flex: none;
  width: 72px;
  display: flex;
  flex-direction: column;
  /* 2026-09-16 follow-up: unified with the Voice Assistant stage's dark
     teal/black — same tokens as .voice-stage itself (bare :root) rather
     than --bg-deep, so the sidebar always matches it. (2026-09-17: the app
     is now single-themed anyway — see the file-header comment above — but
     this rule is kept as-is since it was already correct.) Width/padding/
     gap/nav structure below are all unchanged. */
  background: var(--voice-stage-bg);
  border-right: 1px solid var(--voice-stage-card-border);
  padding: 18px 0;
  gap: 2px;
  overflow: hidden;
  /* 0.4s everywhere in this rule (matches .app-header/.evidence-dock/the
     rest of the presentation-mode transition below) so the sidebar collapse,
     header collapse, and evidence-dock collapse all finish in the same
     beat — mismatched durations across the three is what made entering
     presentation mode read as several small jumps instead of one smooth
     transform, even though each piece was individually animated. */
  transition: width 0.4s ease, opacity 0.4s ease, padding 0.4s ease, border-color 0.4s ease;
  z-index: 6;
}
.sidebar.expanded { width: 216px; }

/* IdeaBoxAI brand mark — 2026-09-14: both PNGs below were regenerated from
 * the reference "IdeaBoxAI" logo image the user supplied (gradient
 * interlocking-hexagon icon + wordmark). The earlier two phases' assets were
 * cropped from a DIFFERENT, older source JPEG that only read "IdeaBox" (no
 * "AI", a plain solid-color icon) — that was the actual root cause of the
 * "logo looks wrong/cut off" reports, not a CSS clipping bug (the CSS here
 * was already correct, object-fit:contain, verified pixel-for-pixel complete
 * in a real browser in the prior round — it was rendering the wrong asset
 * completely). The reference image was a flat, opaque screenshot-style PNG
 * (light-lavender rounded-card background baked in, not a transparent
 * source), so both files here are re-extracted via chroma-key/alpha
 * un-blending against that background rather than a plain crop; the icon and
 * text regions were keyed separately because the icon's blue is much closer
 * to the lavender background in raw distance than the near-black text is, so
 * a single global threshold under-saturated the icon. See the "Known
 * limitation" note in the build summary re: the source image's native
 * resolution (145x40px) — these are upscaled 4x with Lanczos resampling
 * before being placed here, which smooths edges but cannot add detail a
 * higher-resolution source would have.
 * ideabox-logo-icon.png is just the hexagon mark, and ideabox-logo-mark.png
 * is the hexagon + "IdeaBoxAI" wordmark. We show the icon-only mark on the
 * collapsed rail and the full wordmark once expanded, rather than showing
 * one asset scaled to both sizes, because testing showed the full wordmark
 * scaled down to the ~40px collapsed rail becomes illegible at that width no
 * matter the resampling quality, while the icon alone reads cleanly small.
 * This is the same icon-vs-full-lockup split most product brands use for
 * compact UI chrome, not a crop that mutilates the supplied artwork.
 * The wordmark is dark-on-transparent with no dark-theme variant; rather
 * than recolor the asset itself — which the brief explicitly asks us not to
 * do — it sits on a small white "badge" plinth so it stays legible against
 * the dark theme's near-black sidebar; in the light theme the badge is
 * nearly invisible against the already-white sidebar, so it reads as just
 * the logo either way. */
.sidebar-brand {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 14px;
  padding: 0 10px;
}
.sidebar-brand-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 40px;
  min-width: 0; /* let a flex child image shrink/contain instead of forcing overflow */
  padding: 8px;
  border-radius: var(--radius-sm);
  background: #FFFFFF;
  border: 1px solid var(--border-subtle);
  overflow: hidden; /* belt-and-suspenders: never let the mark visually spill its badge */
  transition: max-width 0.2s ease, padding 0.2s ease;
}
/* object-fit: contain (with an explicit box on both axes) rather than the
 * previous width:100%/height:auto pairing — functionally equivalent for an
 * unconstrained aspect ratio, but robust against any future rule that ends
 * up constraining both dimensions at once (that's the one combination
 * where a plain height:auto stops protecting you and the image can be
 * stretched or cropped to fill the box; contain never crops or stretches). */
.sidebar-brand-logo {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  min-width: 0;
}
.sidebar-brand-logo-icon { width: auto; height: 26px; }
.sidebar-brand-logo-full { display: none; width: 100%; height: auto; }
.sidebar.expanded .sidebar-brand { justify-content: flex-start; padding: 0 22px; }
.sidebar.expanded .sidebar-brand-badge { max-width: 168px; padding: 8px 12px; }
.sidebar.expanded .sidebar-brand-logo-icon { display: none; }
.sidebar.expanded .sidebar-brand-logo-full { display: block; }

.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.sidebar-item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: none;
  border: none;
  border-right: 2px solid transparent;
  /* 2026-09-16 follow-up: swapped from --text-tertiary/--seafoam* (theme-
     dependent, tuned for a light sidebar) to the same voice-stage/teal
     tokens the rest of the always-dark Voice Assistant experience already
     uses (e.g. .followup-chip below) — keeps hover/active consistent with
     that existing theme rather than introducing a new palette. */
  color: var(--voice-stage-hint-dim);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 12px 0;
  white-space: nowrap;
  transition: color 0.15s ease, background 0.15s ease;
}
.sidebar.expanded .sidebar-item { justify-content: flex-start; padding: 12px 24px; }
.sidebar-item:hover { color: var(--voice-stage-text); background: var(--voice-teal-dim); }
.sidebar-item.active { color: var(--voice-teal-strong); background: var(--voice-teal-dim); border-right-color: var(--voice-teal); }
.sidebar-item:focus-visible { outline-color: var(--voice-teal); }
.sidebar-item-icon { width: 20px; height: 20px; flex: none; }
.sidebar-item-icon svg { width: 100%; height: 100%; }
.sidebar-item-label { display: none; }
.sidebar.expanded .sidebar-item-label { display: inline; }

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  z-index: 5;
}
.sidebar-backdrop.show { display: block; }

/* 2026-09-16: Sample Questions — a small, dismissible panel (per the
   request: NOT a large new panel, so it deliberately does not reuse the
   full-screen .workspace overlay treatment used by chart/report/about).
   Centered + backdrop is the simplest layout that works unchanged across
   the desktop rail, the mobile drawer, and every viewport width without a
   dedicated media query — width/max-height already clamp responsively via
   min()/vh. */
.sq-category { margin-bottom: 16px; }
.sq-category:last-child { margin-bottom: 0; }
.sq-category-title { font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--seafoam); margin-bottom: 8px; }
.sq-question-btn {
  display: block;
  width: 100%;
  text-align: left;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.4;
  padding: 10px 12px;
  margin-bottom: 6px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.sq-question-btn:last-child { margin-bottom: 0; }
.sq-question-btn:hover, .sq-question-btn:focus-visible { border-color: var(--seafoam); background: var(--seafoam-dim); color: var(--text-primary); }

.app-shell {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.app-header {
  flex: none;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 32px;
  /* 2026-09-17: unified with the always-dark Marea voice-stage theme — same
     tokens as .sidebar/.voice-stage (bare :root), so the header always
     matches them instead of showing through to .app's --bg-void background.
     Layout/dimensions/spacing below are all unchanged. */
  background: var(--voice-stage-bg);
  border-bottom: 1px solid var(--voice-stage-card-border);
  z-index: 3;
  max-height: 90px;
  overflow: hidden;
  transition: max-height 0.4s ease, padding 0.4s ease, opacity 0.4s ease, border-color 0.4s ease, background 0.4s ease;
}
.app-header .brand { margin-right: auto; }
.sidebar-toggle-btn { padding: 9px; flex: none; }

.brand { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.brand-mark { font-family: var(--font-display); font-size: 18px; font-weight: 600; color: var(--voice-stage-text); letter-spacing: -0.005em; white-space: nowrap; }
.brand-sub { font-size: 11.5px; color: var(--voice-stage-hint-dim); letter-spacing: 0.02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.header-controls { display: flex; align-items: center; gap: 10px; flex: none; }

.state-pill {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--voice-stage-hint-dim);
  border: 1px solid var(--voice-stage-card-border);
  border-radius: 100px;
  padding: 6px 14px;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: border-color 0.4s ease, color 0.4s ease;
  white-space: nowrap;
}
.state-pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--voice-stage-hint-dim); transition: background 0.4s ease, box-shadow 0.4s ease; }
.state-pill.listening { border-color: var(--voice-teal); color: var(--voice-teal-strong); }
.state-pill.listening .dot { background: var(--voice-teal-strong); box-shadow: 0 0 10px var(--voice-teal-strong); }
.state-pill.thinking { border-color: var(--voice-stage-caution); color: var(--voice-stage-caution); }
.state-pill.thinking .dot { background: var(--voice-stage-caution); }
.state-pill.answering { border-color: var(--voice-teal); color: var(--voice-teal-strong); }
.state-pill.answering .dot { background: var(--voice-teal-strong); box-shadow: 0 0 10px var(--voice-teal-strong); }
.state-pill.connecting { border-color: var(--voice-stage-card-border-strong); color: var(--voice-stage-hint); }
.state-pill.error { border-color: var(--voice-stage-danger); color: var(--voice-stage-danger); }
.state-pill.error .dot { background: var(--voice-stage-danger); }

.icon-btn {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-secondary);
  background: rgba(0,0,0,0.15);
  border: 1px solid var(--border);
  border-radius: 100px;
  padding: 7px 14px 7px 10px;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}
.icon-btn:hover { color: var(--text-primary); border-color: var(--seafoam); background: rgba(200,136,58,0.08); }
/* 2026-09-18: used by #muteBtn while there's no live session to mute (see
   app.js's 'connected'/'disconnected' handlers) — the button should look
   and behave as truly inert, not just visually dimmed, so it never again
   presents a clickable affordance that silently does nothing. */
.icon-btn:disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
.icon-btn-icon { display: inline-flex; width: 16px; height: 16px; }
.icon-btn-icon svg { width: 100%; height: 100%; }
.icon-mic-off { display: none; }
.icon-btn.muted { color: var(--coral-strong); border-color: rgba(166,150,124,0.5); }
.icon-btn.muted .icon-mic-on { display: none; }
.icon-btn.muted .icon-mic-off { display: inline-flex; }

/* 2026-09-16: Chat box visibility toggle — same icon-swap mechanism as
   muted/icon-mic-on/off just above, reused verbatim for #chatToggleBtn. */
.icon-chat-off { display: none; }
.icon-btn.chat-off { color: var(--text-tertiary); }
.icon-btn.chat-off .icon-chat-on { display: none; }
.icon-btn.chat-off .icon-chat-off { display: inline-flex; }

/* 2026-09-19: Sample Questions visibility toggle — identical mechanism,
   reused verbatim for #sqToggleBtn (see app.js). */
.icon-sq-off { display: none; }
.icon-btn.sq-off { color: var(--text-tertiary); }
.icon-btn.sq-off .icon-sq-on { display: none; }
.icon-btn.sq-off .icon-sq-off { display: inline-flex; }

/* 2026-09-17: header icon-buttons (mic/chat/present/about/sidebar-toggle)
   restyled for the always-dark header — scoped to `.app-header .icon-btn`
   rather than editing the base `.icon-btn` rules above, so that any future
   reuse of the bare `.icon-btn` class outside the header still follows the
   app's own light/dark toggle by default, rather than the always-dark
   voice-stage theme this scoped override applies. */
.app-header .icon-btn {
  color: var(--voice-stage-hint);
  background: var(--voice-stage-card-bg);
  border-color: var(--voice-stage-card-border);
}
.app-header .icon-btn:hover {
  color: var(--voice-stage-text);
  border-color: var(--voice-teal);
  background: var(--voice-teal-dim);
}
.app-header .icon-btn.muted { color: var(--voice-stage-caution); border-color: var(--voice-stage-card-border-strong); }
.app-header .icon-btn.chat-off { color: var(--voice-stage-hint-dim); }
.app-header .icon-btn.sq-off { color: var(--voice-stage-hint-dim); }

/* ---- main layout: voice stage + evidence dock -------------------------- */
.app-main {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: stretch;
}

.voice-stage {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* 2026-09-19 fix: plain `center` on a column flexbox has a well-known
     failure mode once children are taller than the box — the overflow is
     clipped symmetrically top+bottom, and with this box's default scroll
     position at top the FIRST child (.orb-wrap, the voice animation) is
     exactly the part that lands in the clipped-off region above the
     visible/scrollable area. That's what happened here: the 10-question,
     4-domain Sample Questions block (see .sq-inline) is taller than the
     old 4-category/8-question one, and once orb+hint+chat+follow-ups+
     sample-questions together exceeded .voice-stage's viewport-bounded
     height, the animation itself scrolled out of reach — not hidden by
     any display/visibility rule, just centered into the unreachable half
     of the overflow. `justify-content: safe center` is the standards
     answer to exactly this: it centers like `center` whenever everything
     fits, and falls back to start-alignment (so the orb anchors at the
     top, inside the visible/scrollable area, with the excess pushed down
     into .sq-inline instead) the moment it wouldn't otherwise fit. The
     plain `center` line above stays as a safe fallback for a browser that
     doesn't parse the `safe` keyword — such a browser ignores the second,
     invalid-to-it declaration and keeps this first one, which is exactly
     today's pre-fix behavior for that edge case, not a regression. */
  justify-content: center;
  justify-content: safe center;
  gap: 26px;
  padding: 24px 40px;
  /* Entering presentation mode changes this rule's own padding/gap (see
     .app.presentation-mode .voice-stage below) — without a transition here
     those snapped instantly even while the sidebar/header/dock around it
     were animating, which is part of what made the whole switch read as a
     jump rather than one smooth move. */
  transition: padding 0.4s ease, gap 0.4s ease;
  position: relative;
  overflow-y: auto;
  /* 2026-09-16: dark teal/black backdrop with a subtle radial glow behind
     the orb, per an approved reference preview (marea_animation_preview_v2
     .html) — scoped to THIS element only, confirmed with the requester:
     the sidebar, header, evidence dock (References panel), and every
     other view (Conversations/Knowledge Base/Settings) are siblings of
     this element or live elsewhere in the DOM and are completely
     unaffected. Always dark regardless of --bg-void's own theme value
     (see the token note above), so no light-theme variant is needed here. */
  background: var(--voice-stage-glow), var(--voice-stage-bg);
}

/* ---- orb ---------------------------------------------------------------- */
.orb-wrap {
  position: relative;
  /* 2026-09-15: widened from a 200x200 square into a wide strip so the tide
     lines drawn inside (see .voice-canvas / drawTides in app.js) actually
     reach toward both edges of the stage instead of being squeezed into a
     small square — same click/tap/keydown target as before, just a wider
     one, which if anything makes it an easier target, not a smaller one. */
  width: min(560px, 82vw);
  height: 170px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

/* Soft teal "atmosphere" glow behind the orb only (per Vengat's Marea
   reference HTML) — a small, contained styling addition directly tied to
   the Voice Assistant animation, not a background/layout change: it lives
   entirely inside .orb-wrap's own box, so the app shell/sidebar/evidence
   dock background is untouched. Brightens a little while Marea is
   answering (data-state, the same attribute app.js already sets — see
   setUiState) to echo the glow app.js draws on the strands themselves. */
.orb-wrap::before {
  content: '';
  position: absolute;
  inset: -22%;
  background: var(--voice-atmosphere);
  opacity: 0.7;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
.orb-wrap[data-state="answering"]::before { opacity: 1; }

/* ---- voice visualization: line-to-circle strands --------------------------
 * (2026-09-15, third pass — ported from Vengat's Marea reference HTML, see
 * app.js's drawWave/drawVoiceFrame) A small number of translucent "strands"
 * that morph between a thin flat line (idle/listening) and a full glowing
 * circle with faint ripple rings (answering), brightening with real audio
 * loudness rather than changing hue — the whole animation stays in this
 * reference's green/teal language (--voice-teal* above), deliberately kept
 * SEPARATE from the app's own blue --seafoam/--coral accent used everywhere
 * else (see the palette note near the top of this file).
 *
 * #orb / .orb-wrap keeps its original size, position, and tap-to-start
 * click/keydown handlers from app.js completely unchanged — only what's
 * drawn INSIDE it (on the <canvas>) and the glow behind it are new. app.js
 * redraws it every animation frame: a calm, thin, near-still line at idle,
 * the REAL microphone waveform (Web Audio AnalyserNode time-domain data)
 * shaping the strands while listening, a slower synthetic wave while
 * thinking as it starts curling toward a circle, and Marea's own REAL
 * output-audio amplitude shaping the strands while she's speaking — same
 * analyser technique as before, tapped off the existing mic stream / remote
 * <audio> element, no change to the WebRTC/session code that creates them.
 *
 * (2026-09-16, fifth pass) Purely visual tuning within this same canvas
 * drawing, per a reference mockup of all 5 states side by side: connecting
 * now gets its own faint pulse rings instead of reusing idle's target,
 * idle/connecting draw one small center glow dot, and thinking curls
 * further with one extra guide ring. No selector below changed — only
 * app.js's drawWave/waveTargetsFor did; see that file for the exact diff.
 */
.voice-line {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.voice-canvas { width: 100%; height: 100%; display: block; }

.orb-hint { font-size: 12.5px; color: var(--text-tertiary); text-align: center; letter-spacing: 0.01em; transition: font-size 0.4s ease; }

/* Live wake-word diagnostic line — deliberately visible on the main screen
 * (not just DevTools console) so "did it hear me, and as what?" is
 * answerable by looking at the page itself while testing, without needing
 * to open Settings or the console. Small and muted so it reads as a status
 * line, not a UI element competing with the hint above it. */
.wake-debug-line {
  margin-top: 4px;
  font-size: 11px;
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
  color: var(--text-tertiary);
  text-align: center;
  letter-spacing: 0;
  max-width: 90vw;
  overflow-wrap: break-word;
}
.wake-debug-line.is-match { color: var(--seafoam-strong, var(--seafoam)); font-weight: 600; }
.wake-debug-line.is-error { color: var(--danger); }

/* 2026-09-16: .orb-hint/.wake-debug-line sit directly on .voice-stage's
   background with no card/panel of their own (unlike .caption-panel/
   .followup-chip/.error-banner just below, which have their own opaque
   backgrounds and need no change here). Now that .voice-stage is always
   dark regardless of app theme (see above), the base rules' theme-
   dependent --text-tertiary/--seafoam-strong/--danger would still be
   legible in dark theme but wash out or clash in light theme, where those
   tokens are tuned for a white backdrop. These scoped overrides win by
   specificity and make both themes read identically here, matching the
   reference preview's own muted cyan hint/label color. */
.voice-stage .orb-hint { color: var(--voice-stage-hint-dim); }
.voice-stage .wake-debug-line { color: var(--voice-stage-hint-dim); }
.voice-stage .wake-debug-line.is-match { color: var(--voice-teal-strong); font-weight: 600; }
.voice-stage .wake-debug-line.is-error { color: var(--voice-stage-danger); }

/* ---- sample questions (inline, above the chat box) ----------------------
 * 2026-09-18: moved from a permanently-visible rail on the left of the
 * Voice Assistant view (.sample-questions-rail, removed) to a compact
 * block living directly inside .voice-stage itself, right above
 * .caption-panel — a child of .voice-stage now rather than a sibling of
 * it, so it's still shown/hidden with the rest of #viewVoiceAssistant
 * automatically, with no open/close state of its own. Removing the old
 * 250px-wide rail also gives .voice-stage (already flex:1) the extra
 * horizontal room the new layout asked for, with no width change needed
 * here. Same .sq-category/.sq-category-title/.sq-question-btn markup and
 * data-ask wiring as before (app.js's click listener is class-based and
 * untouched) — each of the 4 existing categories becomes one compact grid
 * cell, so they keep reading as distinct groups while the whole block
 * wraps into 2-4 columns depending on available width instead of a tall
 * scrolling list. Deliberately always dark (--voice-stage-* tokens) to
 * match the rest of this always-dark voice stage, same as the rail was. */
.sq-inline {
  width: min(760px, 100%);
  display: flex;
  flex-direction: column;
  gap: 8px;
  transition: width 0.4s ease;
}
.sq-inline-head { display: flex; align-items: center; justify-content: center; gap: 8px; }
.sq-inline-head-icon { width: 15px; height: 15px; color: var(--voice-teal); flex: none; }
.sq-inline-head-icon svg { width: 100%; height: 100%; }
.sq-inline-head h2 { margin: 0; font-family: var(--font-ui); font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--voice-stage-hint); }

/* auto-fit + minmax is what gives the "2-4 compact columns depending on
   available width" behavior for free — each category is one cell, wide
   screens fit several side by side, narrow ones collapse to a single
   column with no explicit media-query breakpoint needed for this block.
   2026-09-19: gap/padding/font-size below tightened (10px->7px grid gap,
   8-10px->6-9px button padding, 12px->11.5px button text) after the exact
   10 SME questions (longer wording, 4 domains, up to 3 questions in a
   domain) made this block noticeably taller than the old 4-category/
   8-question placeholder set — a pure density/spacing change, no content,
   category, or question wording touched. This is what requirement #7
   ("compact enough that it does not push the voice animation out of the
   visible viewport") asks for; the .voice-stage `safe center` fix above is
   what actually guarantees the orb stays reachable even if this block is
   still tall on a short viewport — the two changes are complementary, not
   alternatives. */
.sq-inline-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 7px;
}
.sq-inline-btns { display: flex; flex-direction: column; gap: 5px; }

/* The shared .sq-category/.sq-category-title/.sq-question-btn rules above
   were sized for the old popup/rail's roomier card — these overrides win
   by specificity and re-tune spacing/size for this tighter inline spot,
   plus the same always-dark recoloring the rail used to apply. */
.sq-inline .sq-category { margin-bottom: 0; }
.sq-inline .sq-category-title { margin-bottom: 4px; font-size: 10.5px; color: var(--voice-teal-strong); }
.sq-inline .sq-question-btn {
  margin-bottom: 0;
  padding: 6px 9px;
  font-size: 11.5px;
  line-height: 1.3;
  color: var(--voice-stage-hint);
  background: var(--voice-stage-card-bg);
  border-color: var(--voice-stage-card-border);
}
.sq-inline .sq-question-btn:hover,
.sq-inline .sq-question-btn:focus-visible {
  border-color: var(--voice-teal);
  background: var(--voice-teal-dim);
  color: var(--voice-teal-strong);
}

/* 2026-09-19: previously this unconditionally forced Sample Questions
   hidden in Presentation Mode (no way to show them there at all), same as
   when they lived in the now-removed rail. Per Venkat's feedback, the new
   #sqToggleBtn show/hide control must work in Presentation Mode too, so an
   unconditional force-hide here would make that toggle a dead control
   while presenting. Visibility is now driven ONLY by .sq-hidden (below),
   set by app.js's toggle, identically in and out of Presentation Mode —
   the same show/hide state simply carries over into that mode rather than
   being overridden. */
.sq-inline.sq-hidden {
  display: none;
}

/* 2026-09-16: the caption/transcript card, follow-up chips, and the
   in-stage error banner are the other elements floating directly on
   .voice-stage's dark backdrop. Scoped to #viewVoiceAssistant (not the
   bare class names) because .error-banner is reused, unrelated, by the
   report-export error banner elsewhere in the app — see index.html —
   which must keep its normal theme-dependent look. */
#viewVoiceAssistant .caption-panel {
  background: var(--voice-stage-card-bg);
  border-color: var(--voice-stage-card-border);
}
#viewVoiceAssistant .caption-role { color: var(--voice-stage-hint); }
#viewVoiceAssistant .caption-role.you { color: var(--voice-teal-strong); }
#viewVoiceAssistant .caption-role.sme { color: var(--voice-teal); }
#viewVoiceAssistant .caption-text { color: var(--voice-stage-text); }
#viewVoiceAssistant .caption-text .cite { color: var(--voice-teal-strong); }
#viewVoiceAssistant .caption-empty { color: var(--voice-stage-hint-dim); }

#viewVoiceAssistant .followup-chip {
  color: var(--voice-stage-text);
  background: var(--voice-stage-card-bg);
  border-color: var(--voice-stage-card-border);
}
#viewVoiceAssistant .followup-chip:hover {
  border-color: var(--voice-teal);
  background: var(--voice-teal-dim);
}

#viewVoiceAssistant .error-banner {
  background: rgba(255,138,118,0.10);
  border-color: rgba(255,138,118,0.32);
  color: #FFD9CE;
}
#viewVoiceAssistant .error-banner-icon { color: var(--voice-stage-danger); }

/* ---- transcript / captions ---------------------------------------------- */
.error-banner {
  width: min(680px, 100%);
  background: var(--danger-bg);
  border: 1px solid rgba(229,98,74,0.4);
  color: var(--danger-text);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  font-size: 12px;
  line-height: 1.5;
  display: none;
  align-items: flex-start;
  gap: 10px;
}
.error-banner.show { display: flex; }
.error-banner-icon { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--danger); }

.caption-panel {
  width: min(680px, 100%);
  min-height: 110px;
  /* 2026-09-19 fix: .voice-stage is a column flexbox, and every flex item
     defaults to flex-shrink:1 with an "automatic minimum size" that is
     smaller than its actual content height. Once the orb/hint/sample-
     questions/panel/follow-ups together got taller than .voice-stage's own
     available height (any desktop-width viewport with a long, multi-
     paragraph answer), the flex algorithm shrank THIS box down to fit —
     .caption-panel's rendered height dropped well below its content's real
     height (e.g. ~172px box for ~972px of real content) — and because
     overflow defaults to visible, the answer text kept rendering at its
     full height and spilled out past the card's own background/border
     instead of the card growing. flex-shrink:0 stops that: the card always
     renders at its natural content height (min-height above only raises
     the floor for short answers), and .voice-stage's own overflow-y:auto
     (already set, unchanged) scrolls the extra height instead — one
     scrollbar, on the page's existing scroll container, not a new one
     inside the card. This repo has no committed frontend test harness
     (no package.json/Playwright config) — verified instead with a one-off
     Playwright script driving the real index.html/style.css at 375/768/
     1024/1440px, before and after, injecting a long multi-paragraph/
     list/long-URL response into #captionText the same way mdRenderInto
     does; see the PR/session notes for the exact before/after numbers. */
  flex-shrink: 0;
  background: var(--glass-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px 24px;
  backdrop-filter: blur(6px);
  transition: border-color 0.4s ease, width 0.4s ease;
}
/* 2026-09-16: Chat box toggle — purely a display switch on the panel
   itself. app.js only ever adds/removes this class; it never touches the
   captionEmpty/captionContent inline-style logic that already decides
   whether a response has arrived, so the voice/response pipeline is
   completely unaffected by whether the box is shown or hidden. */
.caption-panel.chat-hidden { display: none; }
.caption-role { font-size: 10.5px; color: var(--text-tertiary); margin-bottom: 8px; letter-spacing: 0.04em; text-transform: uppercase; font-weight: 600; }
.caption-role.you { color: var(--seafoam); }
.caption-role.sme { color: var(--coral); }
/* 2026-09-15: switched from --font-display (Fraunces, a serif display face)
   to Inter at a smaller size, scoped to just this element per explicit
   request — the spoken-conversation text specifically read as too large/
   too "editorial" next to the rest of the UI's sans-serif chrome. */
/* overflow-wrap: anywhere (2026-09-19) — a long unbroken word or URL in a
   real answer has no natural wrap point at 15px in a 680px-wide card;
   without this it would push .caption-panel wider rather than wrapping,
   causing the exact horizontal-overflow failure mode this fix also covers.
   Normal text is completely unaffected — words only break when a line
   genuinely has nowhere else to wrap. */
.caption-text { font-family: var(--font-transcript); font-size: 15px; line-height: 1.6; color: var(--text-primary); font-weight: 400; letter-spacing: -0.001em; transition: font-size 0.4s ease; overflow-wrap: anywhere; }
.caption-text .cite { color: var(--coral); font-size: 12.5px; font-family: var(--font-ui); vertical-align: super; }
.caption-empty { color: var(--text-tertiary); font-family: var(--font-ui); font-size: 13px; }

/* 2026-09-16: structural elements the new safe Markdown renderer
   (mdRenderInto in app.js) can produce inside .caption-text — paragraphs,
   headings, lists, inline code, links. Kept minimal/typographic rather
   than "designed," since this is spoken conversation text, not a
   document: no card-within-a-card, no heavy rules, just correct spacing
   and hierarchy so structure reads clearly instead of as one paragraph. */
.caption-text .md-p { margin: 0 0 10px 0; }
.caption-text .md-p:last-child { margin-bottom: 0; }
.caption-text ul, .caption-text ol { margin: 2px 0 12px 0; padding-left: 22px; }
.caption-text ul:last-child, .caption-text ol:last-child { margin-bottom: 0; }
.caption-text li { margin: 3px 0; }
.caption-text li > ul, .caption-text li > ol { margin: 3px 0 3px 0; }
.caption-text .md-heading { font-weight: 700; margin: 2px 0 8px 0; line-height: 1.35; }
.caption-text .md-heading:first-child { margin-top: 0; }
.caption-text .md-heading-1, .caption-text .md-heading-2 { font-size: 1.14em; }
.caption-text .md-heading-3, .caption-text .md-heading-4 { font-size: 1.05em; }
.caption-text .md-heading-5, .caption-text .md-heading-6 { font-size: 1em; text-transform: uppercase; letter-spacing: 0.03em; }
.caption-text code {
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
  font-size: 0.92em;
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  padding: 1px 5px;
}
.caption-text a { color: var(--seafoam); text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 2px; }
.caption-text a:hover { text-decoration-color: var(--seafoam); }
/* Dark Voice Assistant experience (see the #viewVoiceAssistant block
   further down) — inline code/link colors tuned for the dark card
   instead of the light-theme defaults above. */
#viewVoiceAssistant .caption-text code {
  background: rgba(255,255,255,0.07);
  border-color: var(--voice-stage-card-border);
  color: var(--voice-stage-text);
}
#viewVoiceAssistant .caption-text a { color: var(--voice-teal-strong); text-decoration-color: var(--voice-stage-card-border-strong); }
#viewVoiceAssistant .caption-text a:hover { text-decoration-color: var(--voice-teal-strong); }

.followups { display: flex; width: min(680px, 100%); gap: 10px; flex-wrap: wrap; justify-content: center; opacity: 0; max-height: 0; overflow: hidden; transition: opacity 0.4s ease, width 0.4s ease; }
.followups.show { opacity: 1; max-height: 60px; }
.followup-chip { font-family: var(--font-ui); font-size: 12px; color: var(--text-primary); background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 100px; padding: 9px 18px; transition: border-color 0.2s ease, background 0.2s ease, transform 0.15s ease; }
.followup-chip:hover { border-color: var(--coral); background: var(--bg-elevated-hover); transform: translateY(-1px); }
.followup-chip:active { transform: translateY(0); }

/* ---- evidence dock ------------------------------------------------------- */
.evidence-dock {
  flex: none;
  width: 360px;
  border-left: 1px solid var(--border-subtle);
  background: var(--glass-dock);
  padding: 24px 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow-y: auto;
  transition: width 0.4s ease, padding 0.4s ease, opacity 0.4s ease, border-color 0.4s ease;
}
.evidence-head { display: flex; align-items: center; gap: 10px; }
.evidence-head-icon { width: 18px; height: 18px; color: var(--seafoam); flex: none; }
.evidence-head h2 { margin: 0; font-family: var(--font-ui); font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-secondary); }
.evidence-empty { margin: 0; font-size: 12.5px; line-height: 1.6; color: var(--text-tertiary); }

.evidence-list { display: flex; flex-direction: column; gap: 10px; }
.source-item {
  border: 1px solid var(--border);
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  display: flex;
  gap: 10px;
  align-items: flex-start;
  animation: source-in 0.3s ease both;
}
@keyframes source-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
.source-item-icon { width: 16px; height: 16px; flex: none; margin-top: 2px; }
.source-item.is-grounded .source-item-icon { color: var(--seafoam); }
.source-item.is-caution .source-item-icon { color: var(--coral); }
.source-item.is-unavailable .source-item-icon { color: var(--text-tertiary); }
.source-item-body { min-width: 0; }
/* Requirement #4 (Vengat feedback): the quoted phrase in the new
   research-paper-style reference — "<quote>" above the Author, Title
   (p. N) line rendered by .source-doc/.source-page below. */
.source-quote { display: block; font-size: 12.5px; font-style: italic; color: var(--text-primary); line-height: 1.5; margin-bottom: 4px; word-break: break-word; }
.source-doc { font-size: 12px; font-weight: 600; color: var(--text-primary); line-height: 1.4; word-break: break-word; }
.source-page { display: inline-block; margin-left: 6px; font-size: 10.5px; font-weight: 600; color: var(--seafoam); background: var(--seafoam-dim); border-radius: 100px; padding: 1px 8px; vertical-align: middle; }
.source-subject { font-size: 11px; color: var(--text-tertiary); margin-top: 4px; line-height: 1.5; }
.source-note { font-size: 11px; color: var(--text-tertiary); margin-top: 4px; line-height: 1.5; font-style: italic; }

/* Tavily web-search results (search_web tool) — additive to the evidence
   panel above, distinguishing a live-web source from a curated Knowledge
   Base citation without changing any existing KB rendering rule. Uses only
   the existing neutral text/border tokens, not --seafoam (KB's color) or
   --voice-teal (deliberately scoped to the Voice Assistant animation only
   — see the 2026-09-16 teal-repaint note above .voice-line/.voice-canvas). */
.source-item.is-web .source-item-icon { color: var(--text-secondary); }

/* search_client_data results (the user's own uploaded farm data) — its own
   accent so it reads as visibly distinct from both a curated-KB citation
   (--seafoam) and a web result (neutral text-secondary), per the "clearly
   distinguish client data / KB references / web references" requirement.
   (The Voice Assistant view's dark evidence panel gets its own override
   below, using --voice-stage-client-data instead — same reasoning as the
   is-web override just above it.) */
.source-item.is-client-data .source-item-icon { color: var(--coral); }
.source-item.is-client-data .source-badge { color: var(--coral); background: var(--coral-dim); }
.source-link { color: var(--text-primary); text-decoration: none; border-bottom: 1px solid var(--border-strong); }
.source-link:hover { color: var(--seafoam); border-bottom-color: var(--seafoam); }
.source-badge {
  display: inline-block;
  margin-left: 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-secondary);
  background: var(--border-subtle);
  border-radius: 100px;
  padding: 1px 8px;
  vertical-align: middle;
}

/* 2026-09-16: References panel dark treatment — "the References panel
   currently looks disconnected from the Marea voice experience." Scoped
   to #viewVoiceAssistant .evidence-dock (rather than the bare classes)
   purely for consistency/documentation with the rules above; these
   classes are not reused outside this view today, but scoping the same
   way keeps this whole block easy to find/maintain together. Position,
   width (360px), and every layout property are untouched — background/
   border/text colors only. */
#viewVoiceAssistant .evidence-dock {
  background: var(--voice-stage-bg);
  border-left-color: var(--voice-stage-card-border);
}
#viewVoiceAssistant .evidence-head-icon { color: var(--voice-teal); }
#viewVoiceAssistant .evidence-head h2 { color: var(--voice-stage-hint); }
#viewVoiceAssistant .evidence-empty { color: var(--voice-stage-hint-dim); }

#viewVoiceAssistant .source-item {
  background: var(--voice-stage-card-bg);
  border-color: var(--voice-stage-card-border);
}
#viewVoiceAssistant .source-item.is-grounded .source-item-icon { color: var(--voice-teal); }
#viewVoiceAssistant .source-item.is-caution .source-item-icon { color: var(--voice-stage-caution); }
#viewVoiceAssistant .source-item.is-unavailable .source-item-icon { color: var(--voice-stage-hint-dim); }
#viewVoiceAssistant .source-quote { color: var(--voice-stage-text); }
#viewVoiceAssistant .source-doc { color: var(--voice-stage-text); }
#viewVoiceAssistant .source-page { color: var(--voice-teal-strong); background: var(--voice-teal-dim); }
#viewVoiceAssistant .source-subject { color: var(--voice-stage-hint-dim); }
#viewVoiceAssistant .source-note { color: var(--voice-stage-hint-dim); }
#viewVoiceAssistant .source-item.is-web .source-item-icon { color: var(--voice-stage-hint); }
#viewVoiceAssistant .source-item.is-client-data .source-item-icon { color: var(--voice-stage-client-data); }
#viewVoiceAssistant .source-item.is-client-data .source-badge { color: var(--voice-stage-client-data); background: var(--voice-stage-client-data-dim); }
#viewVoiceAssistant .source-link { color: var(--voice-stage-text); border-bottom-color: var(--voice-stage-card-border-strong); }
#viewVoiceAssistant .source-link:hover { color: var(--voice-teal-strong); border-bottom-color: var(--voice-teal-strong); }
#viewVoiceAssistant .source-badge { color: var(--voice-stage-hint); background: rgba(255,255,255,0.08); }

/* ---- presentation mode --------------------------------------------------
 * A conference/keynote-demo mode: the left sidebar, the top header, and the
 * right evidence dock all collapse away (smoothly, not a hard cut) so the
 * voice tides become the entire viewport. Toggled by adding/removing the
 * `presentation-mode` class on #app (see app.js) — every rule below is
 * scoped under that one class so nothing here can leak into normal use, and
 * every element it touches already has its own transition (added above)
 * so this reads as a deliberate collapse/expand, not a flash.
 *
 * The voice states/visualization themselves are completely untouched here —
 * only their size and surrounding chrome change, per the brief's explicit
 * "preserve the existing voice visualization behavior."
 */
.app.presentation-mode .sidebar {
  width: 0;
  padding-left: 0;
  padding-right: 0;
  opacity: 0;
  border-color: transparent;
  pointer-events: none;
}
.app.presentation-mode .sidebar-backdrop { display: none; }
.app.presentation-mode .app-header {
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  opacity: 0;
  border-color: transparent;
  pointer-events: none;
}
.app.presentation-mode .evidence-dock {
  width: 0;
  padding-left: 0;
  padding-right: 0;
  opacity: 0;
  border-color: transparent;
  overflow: hidden;
  pointer-events: none;
}

/* The stage itself gets full-viewport breathing room and a larger, more
 * "keynote" scale — clamp()'d to vmin so it scales gracefully from a laptop
 * preview up to an actual conference-room projector resolution without
 * needing separate breakpoints. */
.app.presentation-mode .voice-stage { padding: 6vh 6vw; gap: 4vh; }
.app.presentation-mode .orb-wrap {
  width: clamp(340px, 62vmin, 820px);
  height: clamp(150px, 20vmin, 260px);
  transition: width 0.4s ease, height 0.4s ease;
}
.app.presentation-mode .orb-hint { font-size: clamp(14px, 1.3vmin, 18px); }
.app.presentation-mode .caption-panel { width: min(860px, 88vw); }
.app.presentation-mode .caption-text { font-size: clamp(19px, 2vmin, 26px); }
.app.presentation-mode .followups { width: min(860px, 88vw); }
/* 2026-09-19: the orb grows considerably in this mode (up to 260px tall,
   see .orb-wrap above) — if Sample Questions are also shown here (the
   #sqToggleBtn state carries over into Presentation Mode, per requirement
   #6/#13), a shorter presentation display (a laptop screen used to
   rehearse, not just a projector) has less spare height to work with than
   the base view. Tightening this block a little further than the already-
   compacted base .sq-inline rules is a pure spacing adjustment — same
   content, same 10 questions, same 4 domains — and works together with
   .voice-stage's `safe center` fix, which is what actually guarantees the
   orb itself never becomes unreachable even if this block still ends up
   tall on a very short viewport. */
.app.presentation-mode .sq-inline { width: min(860px, 88vw); gap: 6px; }
.app.presentation-mode .sq-inline-grid { gap: 6px; }
.app.presentation-mode .sq-inline .sq-question-btn { padding: 5px 8px; }

/* Exit control — a small, unobtrusive floating pill, top-right. Present in
 * the DOM at all times but only shown while presentation mode is active. */
.present-exit-btn {
  display: none;
  position: fixed;
  top: 22px;
  right: 22px;
  z-index: 50;
  align-items: center;
  gap: 8px;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 100px;
  padding: 9px 16px 9px 12px;
  opacity: 0.55;
  transition: opacity 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.present-exit-btn svg { width: 15px; height: 15px; flex: none; }
.present-exit-btn:hover,
.present-exit-btn:focus-visible { opacity: 1; border-color: var(--seafoam); color: var(--text-primary); }
.present-exit-kbd {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-tertiary);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 1px 6px;
}
.app.presentation-mode .present-exit-btn { display: flex; }

@media (max-width: 640px) {
  .present-exit-btn span:not(.present-exit-kbd) { display: none; }
  .present-exit-btn { padding: 9px; }
  .app.presentation-mode .voice-stage { padding: 8vh 5vw; }
}

/* ---- workspaces: analytics / document / about -------------------------- */
.workspace {
  position: absolute; inset: 0;
  /* Fully opaque, not translucent — these are dedicated full-screen
     workspace views, not a hint-of-background popup. A near-opaque
     (e.g. 0.98) background still lets bright content behind it ghost
     through faintly; opaque-stop gradient avoids that entirely while
     keeping the subtle depth. Built from theme tokens (both --bg-deep and
     --bg-void are solid hex in both themes, so this stays just as opaque
     as the literal hex pair it replaces) so it re-themes correctly in
     Light mode instead of always rendering the dark gradient. */
  background: linear-gradient(180deg, var(--bg-deep), var(--bg-void));
  display: flex; flex-direction: column;
  opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(10px);
  /* visibility flips to hidden only once the fade-out finishes, so a
     "closed" workspace is never composited (and can never ghost behind
     whichever workspace opens next) once at rest. */
  transition: opacity 0.35s ease, transform 0.35s ease, visibility 0s linear 0.35s;
  z-index: 5;
}
.workspace.show {
  opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(0);
  transition: opacity 0.35s ease, transform 0.35s ease, visibility 0s linear 0s;
}

.workspace-head {
  flex: none;
  display: flex; align-items: center; justify-content: space-between;
  padding: 22px 36px;
  border-bottom: 1px solid var(--border-subtle);
}
.workspace-head-title { display: flex; align-items: center; gap: 14px; min-width: 0; }
.workspace-head-icon {
  flex: none; width: 42px; height: 42px; border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1px solid var(--border);
  color: var(--seafoam);
}
.workspace-head-icon svg { width: 20px; height: 20px; }
.workspace-eyebrow { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 3px; }
.workspace-title { font-family: var(--font-display); font-size: 19px; font-weight: 500; margin: 0; color: var(--text-primary); letter-spacing: -0.008em; }
.workspace-close {
  display: flex; align-items: center; gap: 7px;
  font-size: 11.5px; font-weight: 500; color: var(--text-tertiary);
  background: none; border: 1px solid var(--border); border-radius: 100px;
  padding: 8px 16px;
}
.workspace-close svg { width: 14px; height: 14px; }
.workspace-close:hover { color: var(--text-primary); border-color: var(--coral); }

.workspace-body { flex: 1; min-height: 0; overflow-y: auto; padding: 28px 36px 36px; display: flex; flex-direction: column; align-items: center; }
.workspace-sub { font-size: 12.5px; color: var(--text-secondary); margin-bottom: 22px; line-height: 1.55; max-width: 640px; text-align: center; }

/* analytics workspace */
.chart-canvas {
  width: min(760px, 100%);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px;
  display: flex; align-items: center; justify-content: center;
  min-height: 340px;
  position: relative;
}
.chart-caption { text-align: center; font-size: 11.5px; color: var(--text-tertiary); margin-top: 14px; max-width: 640px; }

.chart-badge {
  position: absolute; top: 16px; left: 16px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-tertiary); background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 100px; padding: 4px 11px;
}

.chart-empty {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  color: var(--text-tertiary); font-size: 13px; max-width: 440px; text-align: center;
  padding: 20px;
}
.chart-empty svg { width: 30px; height: 30px; color: var(--text-tertiary); opacity: 0.8; }

/* document workspace */
.report-page {
  width: min(680px, 100%);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 34px 38px;
}
.report-kicker { font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--coral); margin-bottom: 12px; }
.report-title { font-family: var(--font-display); font-size: 20px; font-weight: 500; margin: 0 0 16px; color: var(--text-primary); letter-spacing: -0.008em; }
.report-body { font-size: 14px; line-height: 1.7; color: var(--text-secondary); margin-bottom: 20px; white-space: pre-wrap; }
.report-body .cite { color: var(--coral); font-weight: 600; }
.report-refs { border-top: 1px solid var(--border); padding-top: 16px; font-size: 12px; color: var(--text-tertiary); line-height: 1.8; }

.report-action-bar { width: min(680px, 100%); display: flex; gap: 12px; margin-top: 20px; }
.export-btn {
  flex: 1;
  display: flex; align-items: center; justify-content: center; gap: 9px;
  font-size: 12.5px; font-weight: 600; padding: 12px 18px;
  border-radius: var(--radius-md); border: 1px solid var(--border);
  background: var(--bg-elevated); color: var(--text-primary);
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.15s ease;
}
.export-btn .btn-icon { width: 17px; height: 17px; color: var(--seafoam); flex: none; }
.export-btn:hover:not(:disabled) { border-color: var(--seafoam); background: var(--bg-elevated-hover); transform: translateY(-1px); }
.export-btn:active:not(:disabled) { transform: translateY(0); }
.export-btn:disabled { color: var(--text-tertiary); background: var(--bg-surface); cursor: not-allowed; }
.export-btn:disabled .btn-icon { color: var(--text-tertiary); }

.report-action-bar + .error-banner { width: min(680px, 100%); margin-top: 14px; }

/* about */
.about-card { width: min(640px, 100%); }
.about-card p { font-size: 13px; line-height: 1.7; color: var(--text-secondary); margin: 0 0 14px; }
.about-card .about-heading { font-family: var(--font-display); font-size: 18px; color: var(--text-primary); margin-bottom: 16px; letter-spacing: -0.005em; }
.about-card .about-subhead { font-family: var(--font-ui); font-weight: 700; font-size: 12px; letter-spacing: 0.03em; text-transform: uppercase; color: var(--coral); margin-top: 22px; margin-bottom: 10px; }

/* ---- views: Conversations / Knowledge Base / Settings --------------------
 * Inline panels (not full-screen overlays like the chart/report/about
 * workspaces above) — they replace the voice-stage+evidence-dock content
 * area while a sidebar item other than Voice Assistant is active, switched
 * instantly via the `hidden` attribute (see app.js's switchView) rather
 * than the workspace overlays' fade, since these are peer navigation
 * destinations, not a modal takeover.
 */
.view-panel {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 32px 40px 40px;
  display: flex;
  flex-direction: column;
  gap: 26px;
}
.view-head { max-width: 720px; }
.view-eyebrow { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 7px; }
.view-title { font-family: var(--font-display); font-size: 22px; font-weight: 500; margin: 0 0 10px; color: var(--text-primary); letter-spacing: -0.01em; }
.view-desc { font-size: 13px; line-height: 1.6; color: var(--text-secondary); margin: 0; }
.view-empty { font-size: 12.5px; color: var(--text-tertiary); line-height: 1.6; }

/* Conversations */
.conversations-layout { flex: 1; min-height: 0; display: flex; gap: 20px; }
.conversations-list { flex: 1; min-width: 0; max-width: 520px; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; }
.conversation-row {
  display: flex; flex-direction: column; gap: 4px;
  text-align: left; width: 100%;
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 14px 16px;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.conversation-row:hover { border-color: var(--seafoam); background: var(--bg-elevated-hover); }
.conversation-row-title { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.conversation-row-meta { font-size: 11.5px; color: var(--text-tertiary); display: flex; gap: 10px; }
.conversation-row-delete {
  align-self: flex-end; margin-top: 2px;
  font-size: 11px; color: var(--text-tertiary); background: none; border: none; padding: 2px 4px;
}
.conversation-row-delete:hover { color: var(--danger); }

.conversation-detail { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.conversation-back { display: flex; align-items: center; gap: 8px; align-self: flex-start; font-size: 12px; color: var(--text-tertiary); background: none; border: 1px solid var(--border); border-radius: 100px; padding: 7px 14px; }
.conversation-back:hover { color: var(--text-primary); border-color: var(--seafoam); }
.conversation-back svg { width: 14px; height: 14px; }
.conversation-detail-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.conversation-detail-head h2 { font-family: var(--font-display); font-size: 17px; font-weight: 500; margin: 0; color: var(--text-primary); letter-spacing: -0.005em; }
.conversation-detail-date { font-size: 12px; color: var(--text-tertiary); }
.conversation-transcript { display: flex; flex-direction: column; gap: 12px; overflow-y: auto; }
.transcript-line { max-width: 640px; border-radius: var(--radius-md); padding: 12px 16px; font-size: 13px; line-height: 1.6; }
.transcript-line.role-user { align-self: flex-end; background: var(--seafoam-dim); color: var(--text-primary); }
.transcript-line.role-marea { align-self: flex-start; background: var(--bg-surface); border: 1px solid var(--border); color: var(--text-primary); }
.transcript-line-role { display: block; font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 4px; }

/* Knowledge Base */
.kb-card {
  display: flex; align-items: flex-start; gap: 16px;
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 20px 24px; max-width: 720px;
}
.kb-card-icon {
  flex: none; width: 40px; height: 40px; border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--seafoam);
}
.kb-card-icon svg { width: 19px; height: 19px; }
.kb-card-body { flex: 1; min-width: 0; }
.kb-card-title { font-size: 13.5px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
.kb-card-sub { font-size: 12px; color: var(--text-tertiary); line-height: 1.55; }
.kb-card-badge {
  flex: none; align-self: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  border-radius: 100px; padding: 5px 12px;
}
.kb-card-badge.is-grounded { color: var(--seafoam); background: var(--seafoam-dim); }

.kb-upload-form { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.kb-upload-form input[type="file"] { font-size: 12px; color: var(--text-secondary); max-width: 100%; }
.kb-upload-btn { flex: none; padding: 9px 18px; font-size: 12px; }
.kb-upload-status { margin-top: 10px; font-size: 12px; line-height: 1.5; }
.kb-upload-status.is-error { color: var(--danger); }
.kb-upload-status.is-success { color: var(--seafoam); }
.kb-upload-status.is-pending { color: var(--text-tertiary); }
/* 2026-09-19: multi-file upload — some files succeeded, some failed (see
   app.js's kbUploadForm submit handler). A distinct color from
   is-success/is-error so a mixed result reads as neither a clean success
   nor a total failure. */
.kb-upload-status.is-partial { color: var(--voice-stage-caution, #d9a441); }

/* ---- Client/farm data upload ("Upload your data") ------------------------
 * Reuses the .workspace/.kb-card/.kb-documents-list/.error-banner/.export-
 * btn mechanisms already defined above rather than inventing new modal or
 * list plumbing — only the pieces genuinely specific to this feature (the
 * dropzone, the selected-file row, staged-progress text, the success card)
 * get their own rules here.
 */
.cd-card {
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 24px; max-width: 860px; width: 100%;
}
.cd-dropzone {
  position: relative;
  border: 1.5px dashed var(--border-strong); border-radius: var(--radius-md);
  padding: 28px 20px; text-align: center; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.cd-dropzone:hover, .cd-dropzone:focus-visible { border-color: var(--seafoam); background: var(--bg-elevated); }
.cd-dropzone.is-dragover { border-color: var(--seafoam); background: var(--seafoam-dim); }
.cd-dropzone-icon { width: 26px; height: 26px; color: var(--seafoam); }
.cd-dropzone-icon svg { width: 100%; height: 100%; }
.cd-dropzone-text { font-size: 13px; color: var(--text-secondary); }
.cd-dropzone-link { color: var(--seafoam); font-weight: 600; text-decoration: underline; }
.cd-dropzone-hint { font-size: 11.5px; color: var(--text-tertiary); }
.cd-dropzone input[type="file"] {
  position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer;
}

.cd-selected-file {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 12px; padding: 9px 14px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 12.5px; color: var(--text-primary);
}
.cd-selected-file-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cd-selected-file-clear { flex: none; width: 20px; height: 20px; color: var(--text-tertiary); background: none; border: none; padding: 0; display: flex; align-items: center; justify-content: center; }
.cd-selected-file-clear:hover { color: var(--danger); }
.cd-selected-file-clear svg { width: 14px; height: 14px; }

.cd-submit-btn { margin-top: 14px; width: 100%; }

.cd-stage-status { margin-top: 12px; font-size: 12.5px; color: var(--text-secondary); display: flex; align-items: center; gap: 8px; }
.cd-stage-status::before {
  content: ''; width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 2px solid var(--border-strong); border-top-color: var(--seafoam);
  animation: cd-spin 0.8s linear infinite;
}
@keyframes cd-spin { to { transform: rotate(360deg); } }

.cd-card .error-banner { margin-top: 14px; width: 100%; }

.cd-success {
  margin-top: 14px; display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 16px; border-radius: var(--radius-md);
  background: var(--seafoam-dim); border: 1px solid rgba(200,136,58,0.35);
}
.cd-success-icon { flex: none; width: 20px; height: 20px; color: var(--seafoam); margin-top: 1px; }
.cd-success-icon svg { width: 100%; height: 100%; }
.cd-success-title { font-size: 13px; font-weight: 600; color: var(--text-primary); word-break: break-word; }
.cd-success-meta { font-size: 12px; color: var(--text-secondary); margin-top: 3px; }
.cd-success-note { font-size: 12px; color: var(--text-tertiary); margin-top: 6px; }

.cd-datasets { margin-top: 26px; }

/* ---- Client/farm data upload — "upload from your phone" QR option -------
 * Adds a second, side-by-side option to the existing single-column upload
 * card: the desktop dropzone/form (now wrapped as .cd-upload-col, otherwise
 * unchanged) on the left, an "OR" divider, and a new .cd-qr-col on the
 * right with a QR code that deep-links to this same upload page on a phone.
 */
.cd-upload-columns { display: flex; align-items: stretch; gap: 24px; }
.cd-upload-col { flex: 1 1 0; min-width: 0; }

.cd-divider {
  flex: none; display: flex; flex-direction: column; align-items: center; gap: 8px;
  color: var(--text-tertiary); font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
}
.cd-divider::before, .cd-divider::after { content: ''; flex: 1; width: 1px; background: var(--border); }

.cd-qr-col {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px;
  padding: 4px 6px;
}
.cd-qr-icon { width: 22px; height: 22px; color: var(--seafoam); }
.cd-qr-icon svg { width: 100%; height: 100%; }
.cd-qr-heading { font-size: 14px; font-weight: 600; color: var(--text-primary); margin: 2px 0 0; }
.cd-qr-sub { font-size: 12px; color: var(--text-secondary); max-width: 240px; }
.cd-qr-image-wrap {
  margin: 6px 0; padding: 10px; background: #fff; border: 1px solid var(--border); border-radius: var(--radius-md);
}
.cd-qr-image { display: block; width: 148px; height: 148px; }
.cd-qr-note {
  display: flex; align-items: flex-start; gap: 8px; margin-top: 8px;
  padding: 10px 12px; background: var(--seafoam-dim); border: 1px solid rgba(200,136,58,0.35);
  border-radius: var(--radius-sm); font-size: 11.5px; color: var(--text-secondary); text-align: left;
}
.cd-qr-note-icon { flex: none; width: 16px; height: 16px; color: var(--seafoam); margin-top: 1px; }
.cd-qr-note-icon svg { width: 100%; height: 100%; }

/* 2026-09-19: one small, quiet line under .cd-qr-note — deliberately NOT
   styled as another bordered/backgrounded box like .cd-qr-note/.cd-qr-
   warning, so it reads as a caption rather than another callout (per "do
   not make the UI noisy"). Text is set by refreshClientDataQr() in app.js. */
.cd-qr-expiry { margin-top: 4px; font-size: 10.5px; color: var(--text-tertiary); text-align: center; }

/* 2026-09-19: shown in place of .cd-qr-note (and the QR image is hidden)
   when there is no address a phone could actually reach — see
   resolveClientDataUploadUrl()/refreshClientDataQr() in app.js. Same shape
   as .cd-qr-note, styled with the existing --danger tokens (already used
   by .error-banner) rather than the informational seafoam ones, since this
   is telling the presenter something needs to be configured, not just
   describing normal behavior. */
.cd-qr-warning {
  display: flex; align-items: flex-start; gap: 8px; margin-top: 8px;
  padding: 10px 12px; background: var(--danger-bg); border: 1px solid rgba(229,98,74,0.35);
  border-radius: var(--radius-sm); font-size: 11.5px; color: var(--text-secondary); text-align: left;
}
.cd-qr-warning code { color: var(--text-primary); font-size: 11px; }
.cd-qr-warning-icon { flex: none; width: 16px; height: 16px; color: var(--danger); margin-top: 1px; }
.cd-qr-warning-icon svg { width: 100%; height: 100%; }

@media (max-width: 640px) {
  .cd-upload-columns { flex-direction: column; }
  .cd-divider { flex-direction: row; padding: 4px 0; }
  .cd-divider::before, .cd-divider::after { height: 1px; width: auto; }
}

.kb-documents { max-width: 720px; }
.kb-documents-head { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 10px; }
.kb-documents-list { display: flex; flex-direction: column; gap: 8px; }
.kb-document-row {
  display: flex; align-items: center; gap: 12px;
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 12px 16px;
}
.kb-document-info { flex: 1; min-width: 0; }
.kb-document-name { font-size: 12.5px; font-weight: 600; color: var(--text-primary); word-break: break-word; }
.kb-document-meta { font-size: 11.5px; color: var(--text-tertiary); margin-top: 2px; }
.kb-document-delete { flex: none; font-size: 11.5px; color: var(--text-tertiary); background: none; border: 1px solid var(--border); border-radius: 100px; padding: 6px 12px; }
.kb-document-delete:hover { color: var(--danger); border-color: var(--danger); }

/* Settings */
.settings-card {
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 24px 28px; max-width: 520px;
}
.settings-card-title { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 6px; }
.settings-card-sub { font-size: 12px; color: var(--text-tertiary); line-height: 1.55; margin-bottom: 16px; }
.voice-select {
  width: 100%; font-family: var(--font-ui); font-size: 12.5px; color: var(--text-primary);
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 12px;
}
.voice-select:disabled { color: var(--text-tertiary); cursor: not-allowed; }
.settings-note { margin-top: 10px; font-size: 12px; color: var(--seafoam); min-height: 1.4em; }
.settings-note.is-error { color: var(--danger); }

/* Wake-word toggle switch (Settings — Voice activation) */
.switch { position: relative; display: inline-flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  position: relative; flex: none; width: 40px; height: 22px; border-radius: 100px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  transition: background 0.2s ease, border-color 0.2s ease;
}
.switch-track::after {
  content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--text-tertiary); transition: transform 0.2s ease, background 0.2s ease;
}
.switch input:checked + .switch-track { background: var(--seafoam-dim); border-color: var(--seafoam); }
.switch input:checked + .switch-track::after { transform: translateX(18px); background: var(--seafoam); }
.switch input:disabled + .switch-track { opacity: 0.5; cursor: not-allowed; }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--focus); outline-offset: 3px; }
.switch-label { font-size: 12.5px; color: var(--text-primary); font-weight: 500; }

/* ---- responsive: tablet & mobile ---------------------------------------- */
@media (max-width: 1023px) {
  .app-main { flex-direction: column; overflow-y: auto; }
  .voice-stage { padding: 20px 24px; flex: none; }
  .evidence-dock { width: auto; border-left: none; border-top: 1px solid var(--border-subtle); }
  .app-shell { overflow-y: auto; }
  body { overflow: auto; }
  .conversations-layout { flex-direction: column; }
  .conversations-list { max-width: none; }
}

/* Below this width the sidebar becomes an off-canvas drawer (toggled fully
   open/closed by the hamburger) instead of a persistent icon rail — a
   72px-wide always-on rail eats too much of a phone screen's width from
   the Voice Assistant, which must stay the dominant experience. */
@media (max-width: 768px) {
  .sidebar {
    position: fixed; top: 0; left: 0; height: 100vh; height: 100dvh; width: 232px;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    box-shadow: 24px 0 48px rgba(0,0,0,0.35);
  }
  .sidebar.open { transform: translateX(0); }
  .sidebar .sidebar-item-label { display: inline; }
  .sidebar .sidebar-item { justify-content: flex-start; padding: 12px 22px; }
  /* The mobile drawer is always shown at its full 232px width — regardless
     of the persisted "expanded" preference — so the brand mark should match
     the expanded (left-aligned, larger, full-wordmark) sizing rather than
     the icon-only rail one. */
  .sidebar .sidebar-brand { justify-content: flex-start; padding: 0 22px; }
  .sidebar .sidebar-brand-badge { max-width: 168px; padding: 8px 12px; }
  .sidebar .sidebar-brand-logo-icon { display: none; }
  .sidebar .sidebar-brand-logo-full { display: block; }
}

@media (max-width: 640px) {
  .app-header { padding: 14px 16px; }
  .brand-sub { display: none; }
  .icon-btn-label { display: none; }
  .icon-btn { padding: 9px; }
  .header-controls { gap: 8px; }
  .orb-wrap { width: min(320px, 90vw); height: 130px; }
  .caption-text { font-size: 16.5px; }
  .workspace-head { padding: 16px 18px; }
  .workspace-body { padding: 20px 18px 28px; }
  .workspace-head-icon { width: 36px; height: 36px; }
  .workspace-title { font-size: 17px; }
  .report-page { padding: 24px 22px; }
  .chart-canvas { padding: 18px; min-height: 280px; }
  .report-action-bar { flex-direction: column; }
  .workspace-close span { display: none; }
  .view-panel { padding: 20px 18px 28px; }
  .view-title { font-size: 19px; }
  .kb-card { flex-direction: column; }
  .kb-card-badge { align-self: flex-start; }
  .settings-card { max-width: none; }
}

/* ========================================================================
   Sign-in page (web/login.html) — 2026-09-24. Reuses the app's existing
   warm dark-brown/amber token set (--bg-void/--bg-elevated/--seafoam/
   --border/etc.) exactly as-is, so this reads as the same product as the
   rest of the app rather than a bolted-on auth screen. Scoped entirely to
   .login-* classes — nothing here touches any existing rule.
   ======================================================================== */
.login-body {
  overflow: auto;
  background: var(--bg-void);
  display: flex;
  min-height: 100%;
}

.login-shell {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
}

.login-card {
  width: 100%;
  max-width: 380px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 40px 32px 32px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.35);
  text-align: center;
}

.login-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
}

.login-logo {
  width: 40px;
  height: 40px;
  object-fit: contain;
}

.login-brand-mark {
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 500;
  color: var(--text-primary);
  letter-spacing: 0.01em;
}

.login-subtitle {
  margin: 4px 0 28px;
  color: var(--text-secondary);
  font-size: 14.5px;
}

.login-error {
  background: var(--danger-bg);
  color: var(--danger-text);
  border: 1px solid rgba(229,98,74,0.3);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-size: 13.5px;
  margin-bottom: 18px;
  text-align: left;
}
.login-error[hidden] { display: none; }

.login-actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.login-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  padding: 12px 18px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: 14.5px;
  font-weight: 500;
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.login-btn:hover {
  border-color: var(--seafoam);
  background: var(--bg-elevated-hover);
}
.login-btn:active { transform: translateY(1px); }
.login-btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.login-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

.login-footnote {
  margin: 26px 0 0;
  color: var(--text-tertiary);
  font-size: 12px;
  line-height: 1.5;
}

@media (max-width: 480px) {
  .login-card { padding: 32px 22px 26px; }
  .login-brand-mark { font-size: 26px; }
}

/* ========================================================================
   Sign-in page — split composition (web/login.html), 2026-09-25, redesigned
   2026-09-28 against a supplied reference image (Marea: a real aquaculture/
   ocean photo filled the visual panel). SURA/Amrut (2026-10-04): that photo
   (web/assets/login-ocean.jpg) was Marea-specific artwork that never
   actually shipped in sura-poc (the <img> 404'd) and has no Amrut
   equivalent to drop in its place, so the visual panel now uses a CSS-only
   warm dark-amber gradient instead — no image asset required, so this can
   never 404. Paired with a light authentication panel, inside one large
   rounded frame that spans most of the viewport on the app's usual dark
   backdrop.

   Scoped entirely to .login-split-* classes (plus a couple of scoped
   overrides of the existing .login-btn/.login-error under
   .login-split-auth) — the admin login screen and anything else still
   using .login-shell/.login-card/.login-btn/.login-error directly is
   completely untouched; this is an additive, parallel structure, not a
   rework of those shared rules. The Google/Microsoft links, error banner,
   and footnote below are untouched markup wired to the exact same
   /auth/google, /auth/microsoft hrefs as before.
   ======================================================================== */
.login-split-body {
  align-items: center;
  justify-content: center;
}

.login-split-shell {
  width: 100%;
  min-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.login-split-frame {
  width: min(92vw, 1560px);
  height: min(86vh, 880px);
  min-height: 560px;
  display: flex;
  border-radius: 28px;
  overflow: hidden;
  box-shadow: 0 40px 100px rgba(0,0,0,0.5), 0 1px 0 rgba(255,255,255,0.04) inset;
  background: var(--bg-elevated);
}

/* -------------------------------------------------------------- visual -- */
/* Warm dark-amber gradient, same hue family as --seafoam/--bg-void (see
   the app-wide :root repaint at the top of this file) so this panel reads
   as the same brand as the rest of the app rather than a separate photo
   treatment. No <img>, so nothing here can 404. */
.login-split-visual {
  position: relative;
  flex: 1 1 56%;
  min-width: 0;
  overflow: hidden;
  background:
    radial-gradient(130% 100% at 15% -10%, rgba(228,174,98,0.22), transparent 55%),
    radial-gradient(90% 80% at 100% 115%, rgba(200,136,58,0.16), transparent 55%),
    linear-gradient(160deg, #2D2016 0%, #1C1410 60%, #130D09 100%);
}

.login-split-visual-content {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 40px 56px;
  box-sizing: border-box;
}

.login-visual-brand {
  display: flex;
  align-items: center;
  gap: 13px;
}
.login-visual-logo { width: 38px; height: 38px; object-fit: contain; }
.login-visual-wordmark {
  font-family: var(--font-display);
  font-size: 29px;
  font-weight: 500;
  color: var(--text-primary);
  letter-spacing: 0.01em;
}

.login-visual-bottom { max-width: 420px; }
.login-visual-eyebrow {
  margin: 0 0 10px;
  font-family: var(--font-ui);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--seafoam-strong);
}
.login-visual-tagline {
  margin: 0 0 18px;
  font-family: var(--font-display);
  font-size: clamp(24px, 2.9vw, 34px);
  font-weight: 500;
  line-height: 1.3;
  color: var(--text-primary);
  text-shadow: 0 2px 16px rgba(0,0,0,0.25);
}
.login-visual-accent {
  display: block;
  width: 78px;
  height: 3px;
  margin: 0 0 20px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--seafoam-strong), var(--seafoam));
}
.login-visual-powered {
  margin: 0;
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.7);
}
.login-visual-powered span { color: #FFFFFF; font-weight: 600; }

/* ---------------------------------------------------------------- auth -- */
.login-split-auth {
  flex: 1 1 44%;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #FBF6EC;
  padding: 40px;
  box-sizing: border-box;
}

.login-split-auth-inner {
  width: 100%;
  max-width: 380px;
}

.login-split-heading {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 29px;
  font-weight: 500;
  color: #17242A;
}
.login-split-subtitle {
  margin: 0 0 28px;
  font-size: 14.5px;
  color: #5B6B70;
}

/* Light-panel overrides of the shared .login-error/.login-btn rules —
   those are tuned for the dark theme (admin login, the original centered
   card above); this panel is deliberately light, so its buttons/error
   banner need their own contrast-correct colors without touching the
   shared rule everything else still relies on. */
.login-split-auth .login-error {
  background: #FDECEA;
  color: #A02E1E;
  border-color: rgba(160,46,30,0.18);
}
.login-split-auth .login-btn {
  min-height: 54px;
  background: #FFFFFF;
  border-color: #DDE3E1;
  color: #17242A;
  box-shadow: 0 1px 2px rgba(20,30,30,0.04);
}
.login-split-auth .login-btn:hover {
  border-color: var(--seafoam);
  background: #FBF1E2;
}
.login-split-auth .login-btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.login-split-auth .login-footnote { color: #8B9A98; }

@media (max-width: 900px) {
  .login-split-frame { width: 94vw; height: min(660px, 90vh); }
  .login-split-visual-content { padding: 32px 34px 28px; }
  .login-visual-tagline { font-size: clamp(19px, 3vw, 24px); }
}

@media (max-width: 720px) {
  .login-split-shell { padding: 0; align-items: stretch; }
  .login-split-frame {
    flex-direction: column;
    width: 100%;
    height: auto;
    min-height: 100%;
    border-radius: 0;
    box-shadow: none;
  }
  /* Shorter top visual section on mobile, per the reference redesign —
     the photo (and brand lockup) stay visible as a banner instead of
     disappearing; the tagline/accent/powered-by are hidden here (same as
     before this redesign) purely so their text never has to compete with
     the photo at a height too short to render them legibly. */
  .login-split-visual {
    flex: 0 0 auto;
    height: 168px;
    padding: 0;
  }
  .login-split-visual-content {
    padding: 20px 24px;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
  .login-visual-bottom { max-width: none; }
  .login-visual-tagline, .login-visual-accent, .login-visual-powered { display: none; }
  .login-split-auth {
    flex: 1 1 auto;
    padding: 36px 24px 40px;
  }
  .login-split-auth-inner { max-width: 400px; }
}

/* Header "signed in as X / Sign out" badge (2026-09-24) — see
   web/index.html's #headerUser and web/app.js's initHeaderAuthBadge(). */
.header-user {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 8px;
  margin-left: 4px;
  border-left: 1px solid var(--border);
}
.header-user-name {
  font-size: 12.5px;
  color: var(--text-secondary);
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 720px) {
  .header-user-name { display: none; }
}
