/* Every foreground token here clears WCAG AA (4.5:1) against every surface it
   is actually used on — the smallest text in this UI is 10px, so the bar is
   normal-text contrast, not large-text. Verify before changing a value.

   The ramp is one warm neutral plus one desaturated teal. There is exactly ONE
   accent hue and it means "active, primary, or system-authored" — never
   decoration. It replaced a five-hue pastel icon system, which was the single
   largest reason the empty state read as a consumer app rather than an
   analytical tool. Depth is carried by hairline borders and background tints;
   there is one shadow in the whole file, on the composer. */
:root {
  /* Surfaces */
  --bg: #FAF9F7;                  /* canvas: app background, centre column, top bar */
  --bg-elev: #F5F3EF;             /* sunken: sessions sidebar, "own data" row */
  --bg-panel: #FBFAF8;            /* evidence panel, context inputs */
  --bg-elev-2: #F2F0EB;           /* chip fill (column names) */
  --bg-hover: #EBE8E2;            /* session row hover */
  --border: #E3E1DC;              /* region borders, quiet borders */
  --border-strong: #DDDAD3;       /* card borders */
  --border-soft: #EFEDE8;         /* rows inside the evidence panel, context divider */
  --border-softest: #E9E7E2;      /* catalog row separators */
  --border-hover: #B9B5AC;        /* the only hover border */

  /* Text. Three tiers, worst-case surface --bg-elev-2:
       text 15.9:1 · text-soft 5.6:1 · text-dim 4.5:1
     There is no fourth tier: on a background this light, a gray dim enough to
     read as one either fails 4.5:1 or is indistinguishable from --text-dim.
     Below --text-dim, express hierarchy with size/weight — never opacity, which
     silently rescales contrast.
       DEVIATION FROM THE DESIGN SPEC, on purpose: the handoff specifies
     #8A8F96 for metadata and #A9ADB3 for timestamps/arrows/placeholders, which
     land at 3.1:1 and 2.1:1 — below AA for the 10-11px text they are used on.
     --text-dim is the same hue walked down to the first AA-passing value, and
     --text-faint aliases it rather than reintroducing the fourth tier. Flip
     both back to the literal hexes only if the AA bar above is dropped. */
  --text: #14161A;
  --text-soft: #5A6068;
  --text-dim: #696E75;
  --text-faint: var(--text-dim);

  /* Brand teal — one accent, one meaning */
  --accent: #1F4A52;              /* primary button, brand mark, selected state, links, indices */
  --accent-hover: #14383E;        /* filled-button hover */
  --accent-soft: #EEF2F1;         /* accent tint: evidence button fill, selected chip */
  --accent-border: #B9C7C7;       /* accent tint border */
  --on-accent: #ffffff;           /* text/icons on any filled --accent surface */
  --user-bubble: var(--accent);
  --user-bubble-text: var(--on-accent);
  --assistant-bubble: #ffffff;

  /* Plum — agent-derived tables, as opposed to source datasets. The one hue
     outside the neutral ramp and the accent, and it earns that by carrying a
     fact: this table was produced by the analysis, not loaded from a source. */
  --derived: #6b4e8a;
  --derived-soft: #ece5f4;

  /* Status */
  --success: #0f766e;             /* teal = verified/grounded, matches landing */
  --success-glow: rgba(15, 118, 110, 0.18);
  --warning: #8a6514;
  --warning-soft: #fbf4e4;
  --danger: #b3373a;

  /* Washes — ink-tinted rather than pure black, to sit on the warm neutral */
  --row-band: rgba(20, 22, 26, 0.025);
  --row-hover: rgba(31, 74, 82, 0.07);
  --focus-ring: rgba(31, 74, 82, 0.12);
  --overlay: rgba(20, 22, 26, 0.45);

  /* Elevation. The shell uses exactly one (--shadow-sm, on the composer); the
     rest belong to the conversation view and its modals. */
  --shadow-sm: 0 1px 2px rgba(20, 22, 26, 0.04);
  --shadow-md: 0 6px 18px rgba(20, 22, 26, 0.08);
  --shadow-lg: 0 10px 28px rgba(20, 22, 26, 0.10);
  --shadow-modal: 0 8px 32px rgba(20, 22, 26, 0.18);
  --shadow-input: 0 1px 2px rgba(20, 22, 26, 0.04);

  /* Fixed dimensions — the three-column frame */
  --rail-sessions: 252px;
  --rail-evidence: 336px;
  --bar-h: 56px;
  --col-max: 860px;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14.5px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Keyboard reachability is not optional on a screen this dense, and the
   prototype expressed no focus state at all. One ring, everywhere. */
:where(button, a, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Shared label types. The mono faces mark machine state — counts, vintages,
   parameters, dataset names — and are the reason the interface reads as an
   instrument rather than as prose. */
.mono-label,
.mono-label-sm {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  letter-spacing: 0.09em;
  color: var(--text-soft);
}
.mono-label { font-size: 11px; }
.mono-label-sm { font-size: 10px; letter-spacing: 0.1em; color: var(--text-dim); }

.app {
  display: flex;
  height: 100vh;
  height: 100dvh;   /* dynamic viewport unit so the mobile address bar can't push the composer off-screen */
  overflow: hidden;
}

/* ---- Region 1: sessions sidebar ----
   A fixed 252px column that is either present or absent. The previous version
   collapsed to a 64px rail so "New chat" stayed one click away; the rail is
   gone because the top bar's hamburger is always there and a permanent stub
   column costs width the analysis needs. Below the overlay breakpoint the same
   open/closed state drives an off-canvas drawer instead (see the narrow layer
   at the end of the file). */
.sidebar {
  width: var(--rail-sessions);
  flex: 0 0 var(--rail-sessions);
  background: var(--bg-elev);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
@media (min-width: 1101px) {
  .sidebar {
    transition: width 0.18s ease-out, flex-basis 0.18s ease-out;
  }
  .sidebar.collapsed {
    width: 0;
    flex-basis: 0;
    border-right: none;
  }
}
/* First paint only: chat.js restores the remembered state immediately after the
   markup renders, and that restore should snap rather than animate. The class
   is dropped on the next frame. Beats both media queries on specificity. */
.sidebar.sb-preload { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .sidebar, .sidebar.collapsed { transition: none; }
}

.sidebar-head {
  height: var(--bar-h);
  flex: 0 0 var(--bar-h);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 16px;
}
/* The compact Milou mark (chatbot/static/milou-mark-compact.svg), sized to
   its intended floor. No chip, container, or shadow behind it — it sits
   directly on the sidebar surface (--bg-elev), same as the wordmark. */
.brand-mark {
  width: 26px;
  height: 26px;
  flex: 0 0 26px;
  object-fit: contain;
}
.brand-word {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--text-soft);
  white-space: nowrap;
}

.new-analysis-wrap { padding: 6px 12px 14px; }
.new-chat {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: #ffffff;
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: 7px;
  padding: 9px 12px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition: border-color 0.12s;
}
.new-chat:hover { border-color: var(--accent); }
.nc-plus {
  color: var(--accent);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  line-height: 1;
}

.session-list {
  list-style: none;
  margin: 0;
  padding: 0 12px 12px;
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.session-group {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  padding: 12px 6px 6px;
}
.session-group:first-child { padding-top: 10px; }
.session-empty {
  color: var(--text-dim);
  font-size: 12.5px;
  padding: 10px 6px;
}
/* One line per analysis, title first. Ellipsis is the FALLBACK at narrow
   widths, not the default appearance — truncating at ~20 characters is what
   made two distinct analyses read as the same row. */
.session-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 8px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--text);
  font-size: 13px;
  transition: background 0.1s;
}
.session-item:hover { background: var(--bg-hover); }
.session-item.active { background: var(--accent-soft); }
.session-item .title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Fixed-width gutter shared by the stamp and the delete, so swapping them never
   reflows the title. Right-aligned so the column scans vertically like a log. */
.si-right {
  position: relative;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  width: 34px;
  height: 16px;
}
.si-time {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  color: var(--text-dim);
  white-space: nowrap;
  transition: opacity 0.1s;
}
.session-item.active .si-time { color: var(--accent); }
.session-item .delete {
  position: absolute;
  right: -2px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  background: none;
  border: none;
  border-radius: 5px;
  color: var(--text-soft);
  cursor: pointer;
  padding: 3px;
  transition: color 0.1s, background 0.1s, opacity 0.1s;
}
.session-item .delete svg { width: 13px; height: 13px; display: block; }
/* Hover OR keyboard focus swaps stamp for delete — :focus-within keeps the
   control reachable by tab, which display:none would have broken. */
.session-item:hover .si-time,
.session-item:focus-within .si-time { opacity: 0; }
.session-item:hover .delete,
.session-item:focus-within .delete { opacity: 1; }
.session-item .delete:hover { color: var(--danger); background: var(--bg-elev-2); }

.sidebar-footer {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  padding: 12px 18px;
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-soft);
  font-size: 12px;
}
.sidebar-status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}

/* ---- Region 2: the analysis column ---- */
.chat {
  flex: 1;
  display: flex;
  flex-direction: column;
  /* Required. Without it the flex child refuses to shrink and the two side
     panels overflow the viewport instead of the centre column giving way. */
  min-width: 0;
}
.chat-header {
  height: var(--bar-h);
  flex: 0 0 var(--bar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 20px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.session-info {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}
/* Three stacked bars, drawn in CSS. */
.sidebar-toggle {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 6px;
  background: none;
  border: 0;
  cursor: pointer;
  flex-shrink: 0;
}
.sidebar-toggle .sb-bar {
  display: block;
  width: 14px;
  height: 1.5px;
  background: var(--text);
}
.session-title {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.doc-status {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--text-dim);
  flex-shrink: 0;
}
.header-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* Save appears only once an analysis has finished, so it is never competing for
   attention — which is exactly why it carries the accent rather than the same
   grey outline as everything else. As a quiet white outline beside the tinted
   Evidence button it read as decoration and went unnoticed at the one moment it
   matters. It is the only header action, so it can afford the colour. */
.save-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent-border);
  border-radius: 6px;
  padding: 6px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.save-btn:hover:not(:disabled) {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}
.save-btn:disabled { background: none; color: var(--text-dim); border-color: var(--border); cursor: not-allowed; }
/* The explicit display above overrides the UA [hidden] rule, so a JS `hidden`
   toggle can't hide a .save-btn on its own. */
.save-btn[hidden] { display: none; }
/* Clicking the (soft-)disabled Audit button pulses Save to show where to go. */
.save-btn.nudge { animation: save-nudge 0.9s ease-out 2; }
@keyframes save-nudge {
  0%, 100% { border-color: var(--accent-border); }
  35% { border-color: var(--accent); background: var(--accent); color: #ffffff; }
}
@media (prefers-reduced-motion: reduce) { .save-btn.nudge { animation: none; } }

/* The one tinted control in the top bar, because the count on it is the
   screen's proof-of-work and should read before it is clicked. */
.evidence-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--accent-soft);
  color: var(--text);
  border: 1px solid var(--accent-border);
  border-radius: 6px;
  padding: 6px 12px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.12s;
}
.evidence-btn:hover { border-color: var(--accent); }
.evidence-btn .ev-count {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--accent);
}

/* ---- Messages ---- */
.messages {
  flex: 1;
  overflow-y: auto;
  /* `overflow-y: auto` alone coerces the other axis from `visible` to `auto`,
     so ANY child a pixel too wide puts a horizontal scrollbar under the whole
     conversation. `clip` opts out without creating a second scroll container:
     content that genuinely needs horizontal room — tables, code blocks — owns
     its own scroller (.table-scroll), which is where that scrolling belongs. */
  overflow-x: clip;
  padding: 16px 0;
}
/* The empty state is a document, not a chat log, so it gets the design's
   centre-column measure and rhythm; a running conversation keeps the wider
   column its tables need. */
.messages.is-empty { padding: 56px 40px 64px; }
.message {
  display: flex;
  flex-direction: column;
  margin: 0 auto 10px;
  max-width: 1180px;
  padding: 0 28px;
}
.message.user {
  align-items: flex-end;
}
.message .bubble {
  border-radius: 12px;
  padding: 10px 14px;
  word-wrap: break-word;
}
.message.user .bubble {
  max-width: 72%;
  background: var(--user-bubble);
  color: var(--user-bubble-text);
  white-space: pre-wrap;            /* preserve newlines in user text */
}
.message.assistant .bubble {
  max-width: 100%;                  /* fill the column for tables */
  background: var(--assistant-bubble);
  border: 1px solid var(--border);
  color: var(--text);
  overflow-x: auto;                 /* wide tables / code can scroll */
}

/* Markdown rhythm — tighten paragraph + list + heading spacing */
.message.assistant .bubble > *:first-child { margin-top: 0; }
.message.assistant .bubble > *:last-child { margin-bottom: 0; }
.message.assistant .bubble p { margin: 0 0 0.5em; }
.message.assistant .bubble ul,
.message.assistant .bubble ol { margin: 0.35em 0; padding-left: 22px; }
.message.assistant .bubble li { margin: 1px 0; }
.message.assistant .bubble li > p { margin: 0; }
.message.assistant .bubble h1,
.message.assistant .bubble h2,
.message.assistant .bubble h3,
.message.assistant .bubble h4 {
  margin: 0.7em 0 0.3em;
  font-weight: 600;
  line-height: 1.25;
}
.message.assistant .bubble h1 { font-size: 1.15em; }
.message.assistant .bubble h2 { font-size: 1.08em; }
.message.assistant .bubble h3 { font-size: 1em; }
.message.assistant .bubble h4 { font-size: 0.95em; color: var(--text-soft); }
.message.assistant .bubble hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 0.8em 0;
}
.message.assistant .bubble blockquote {
  margin: 0.4em 0;
  padding: 2px 12px;
  border-left: 3px solid var(--border);
  color: var(--text-soft);
}

.message.assistant .bubble code {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  background: var(--bg-elev-2);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 12.5px;
}
.message.assistant .bubble pre {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  margin: 6px 0;
  overflow-x: auto;
}
.message.assistant .bubble pre code {
  background: transparent;
  padding: 0;
}

/* Charts / images — never exceed the column; render as a framed card so the
   matplotlib PNGs sit on the cream background cleanly instead of floating. */
.message.assistant .bubble img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 10px 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fff;
  box-shadow: var(--shadow-sm);
}

/* Tables — denser, full-bleed within the bubble, with subtle banding.
   Columns take their natural content width (wide tables scroll inside
   .table-scroll rather than squeezing cells into multi-line wraps); only
   genuinely long prose cells wrap, at word boundaries. */
.message.assistant .bubble .table-scroll {
  overflow-x: auto;
  margin: 6px 0;
}
.message.assistant .bubble table {
  border-collapse: collapse;
  margin: 6px 0;
  font-size: 12.5px;
  width: max-content;
  min-width: 100%;
}
.message.assistant .bubble .table-scroll table { margin: 0; }
.message.assistant .bubble th,
.message.assistant .bubble td {
  border: 1px solid var(--border);
  padding: 4px 8px;
  text-align: left;
  vertical-align: top;
  white-space: normal;
  overflow-wrap: break-word;
  max-width: 42ch;
}
.message.assistant .bubble th {
  background: var(--bg-elev-2);
  font-weight: 600;
  position: sticky;
  top: 0;
}
.message.assistant .bubble tbody tr:nth-child(even) td {
  background: var(--row-band);
}
.message.assistant .bubble tbody tr:hover td {
  background: var(--row-hover);
}

/* Allow the assistant bubble to expand wider than user-text content when a
   wide table is present, by letting the bubble use the full column. */
.message.assistant { width: 100%; }
/* ---- Tool track (live status → collapsed disclosure) ---- */
.tool-track {
  margin-bottom: 4px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px;
  color: var(--text-dim);
  line-height: 1.4;
}
.tool-status {
  display: flex;
  align-items: center;
  gap: 6px;
}
.tool-status .spinner {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
  animation: pulse 1s ease-in-out infinite;
}
.tool-status strong {
  color: var(--text-soft);
  font-weight: 500;
}
.tool-status .args {
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 420px;
}
@keyframes pulse {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 1; }
}

/* Elapsed counter on the "Thinking" line — the moving number is what proves a
   long silent reasoning turn is alive. Tabular figures so the width doesn't
   jitter as the digits tick over. */
.tool-status.thinking .thinking-time {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.tool-disclosure {
  display: inline-block;
}
.tool-disclosure summary {
  list-style: none;
  cursor: pointer;
  color: var(--text-dim);
  padding: 2px 6px;
  border-radius: 4px;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: color 0.1s, background 0.1s;
}
.tool-disclosure summary::-webkit-details-marker { display: none; }
.tool-disclosure summary::before {
  content: "▸";
  display: inline-block;
  transition: transform 0.12s ease;
  font-size: 10px;
}
.tool-disclosure[open] summary::before {
  transform: rotate(90deg);
}
.tool-disclosure summary:hover {
  color: var(--accent);
  background: var(--bg-hover);
}
.tool-rows {
  margin: 6px 0 0 14px;
  padding-left: 8px;
  border-left: 1px solid var(--border);
}
.tool-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 2px 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tool-row .icon {
  color: var(--success);
  flex-shrink: 0;
  width: 12px;
}
.tool-row .name {
  color: var(--text-soft);
}
.tool-row .args {
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
}
.tool-row .error {
  color: var(--danger);
}
.tool-row.err .icon {
  color: var(--danger);
}

/* Three-step hierarchy without opacity: the old `opacity: .55` on 10.5px text
   rendered at 1.82:1. Rank now reads from weight and size, so every step stays
   legible. */
.tool-status .label,
.tool-row .label {
  color: var(--text-soft);
  font-weight: 500;
}
.tool-status .tool-name,
.tool-row .tool-name {
  color: var(--text-dim);
  font-size: 10.5px;
}
.tool-status .args,
.tool-row .args {
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 280px;
}
.tool-status .ellipsis {
  color: var(--text-dim);
  margin-left: 2px;
}

/* ---- Verification panel ---- */
.verify-track {
  margin-top: 6px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px;
  line-height: 1.4;
}
.verify-status {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
}
.verify-status.err { color: var(--danger); }
.verify-status .spinner {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 1s ease-in-out infinite;
}

.verify-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--bg-elev);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 10px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px;
  cursor: pointer;
  user-select: none;
  transition: color 0.1s, background 0.1s, border-color 0.1s;
}
.verify-btn:hover:not(:disabled) {
  color: var(--accent);
  background: var(--bg-hover);
  border-color: var(--accent);
}
.verify-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.verify-btn .verify-btn-icon {
  font-size: 10px;
}

.verify-disclosure {
  display: block;
}
.verify-disclosure summary {
  list-style: none;
  cursor: pointer;
  color: var(--text-dim);
  padding: 4px 8px;
  border-radius: 6px;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  transition: color 0.1s, background 0.1s;
}
.verify-disclosure summary::-webkit-details-marker { display: none; }
.verify-disclosure summary::before {
  content: "▸";
  display: inline-block;
  transition: transform 0.12s ease;
  font-size: 10px;
}
.verify-disclosure[open] summary::before {
  transform: rotate(90deg);
}
.verify-disclosure summary:hover {
  color: var(--accent);
  background: var(--bg-hover);
}
.verify-rows {
  margin: 8px 0 0 4px;
  padding-left: 12px;
  border-left: 2px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-family: "IBM Plex Sans", -apple-system, sans-serif;
  font-size: 13px;
}
.verify-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 0;
}
.verify-row .head {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.verify-row .icon {
  flex-shrink: 0;
  width: 14px;
  font-weight: 600;
}
.verify-row .claim {
  color: var(--text);
  font-style: italic;
}
.verify-row .note {
  color: var(--text-soft);
  margin-left: 20px;
  font-size: 12.5px;
}
.verify-row .src {
  color: var(--accent);
  margin-left: 20px;
  font-size: 11.5px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  word-break: break-all;
  text-decoration: none;
}
.verify-row .src:hover { text-decoration: underline; }

.verify-row.status-confirmed .icon { color: var(--success); }
.verify-row.status-validated .icon { color: var(--success); }
.verify-row.status-reproduced .icon { color: var(--accent); }
.verify-row.status-contradicted .icon { color: var(--danger); }
.verify-row.status-uncertain .icon { color: var(--warning); }
.verify-row.status-unverifiable .icon { color: var(--warning); }
.verify-row.status-source_changed .icon { color: var(--accent); }

/* ---- Audit report (claim-level verdicts + second-model review) ---- */
.audit-track { margin-top: 4px; }
.audit-disclosure .audit-lead {
  color: var(--text-soft);
  font-size: 12.5px;
  max-width: 640px;
}
.audit-badge {
  font-size: 10.5px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 7px;
  color: var(--text-dim);
  white-space: nowrap;
}
.audit-badge.ab-confirmed { color: var(--success); border-color: var(--success); }
.audit-badge.ab-validated { color: var(--success); border-color: var(--success); }
.audit-badge.ab-reproduced { color: var(--accent); border-color: var(--accent); }
.audit-badge.ab-contradicted { color: var(--danger); border-color: var(--danger); }
.audit-badge.ab-unverifiable { color: var(--warning); border-color: var(--warning); }
.audit-badge.ab-source_changed { color: var(--accent); border-color: var(--accent); }
.audit-review-row {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-size: 12.5px;
}
.audit-review-row .k {
  flex-shrink: 0;
  width: 110px;
  color: var(--text-dim);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
}
.audit-review-row .v { color: var(--text-soft); max-width: 560px; }
.audit-meta {
  color: var(--text-dim);
  font-size: 11px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  margin-top: 2px;
}
.audit-replay-warn {
  margin: 2px 0 6px;
  padding: 6px 9px;
  border-radius: 6px;
  border-left: 2px solid var(--warning);
  background: var(--warning-soft);
  color: var(--text-soft);
  font-size: 11.5px;
  line-height: 1.5;
}

.audit-evidence {
  margin-top: 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.audit-evidence > summary {
  padding: 9px 11px;
  cursor: pointer;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 600;
}

.audit-evidence-entry {
  padding: 10px 11px;
  border-top: 1px solid var(--border);
}

.audit-evidence-heading {
  font-size: 12px;
  font-weight: 650;
}

.audit-evidence-meta,
.audit-evidence-key {
  margin-top: 4px;
  color: var(--text-dim);
  font-size: 11px;
}

.audit-evidence-block pre {
  max-height: 220px;
  margin: 4px 0 0;
  padding: 8px;
  overflow: auto;
  border-radius: 6px;
  background: var(--bg-elev);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Request-correction row: the button reuses .msg-action, but here it sits at
   the LEFT of the report body, so its tooltip anchors left instead of right. */
.audit-actions { margin-top: 8px; }
.audit-fix[data-tip]::after { right: auto; left: 0; }

/* ---- Chart check (stale-chart flag + its one-click recheck) ----
   Deliberately one quiet line, not a panel: the flag is a maybe, not a verdict,
   so it should read as a footnote until the reader chooses to resolve it. */
.chart-check {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
}
.chart-check + .chart-check { margin-top: 4px; }
.chart-check .cc-icon {
  flex-shrink: 0;
  width: 12px;
  font-weight: 600;
}
.chart-check .verify-btn { margin-left: 2px; padding: 2px 8px; }
.chart-check--unchecked .cc-icon { color: var(--warning); }
.chart-check--match .cc-icon,
.chart-check--replaced .cc-icon { color: var(--success); }
.chart-check--error .cc-icon { color: var(--danger); }

/* ---- The empty state ----
   One screen, one job: state a decision. The hierarchy is deliberate and
   replaced nine equal-weight cards — one primary input, two recommended
   methods, then a quiet catalog of everything else. Left-aligned throughout:
   centred text reads as a marketing page, and this is a working surface. */
.empty-state {
  display: flex;
  flex-direction: column;
  gap: 40px;                       /* the section rhythm of the whole column */
  align-items: stretch;
  text-align: left;
  width: 100%;
  max-width: var(--col-max);
  margin: 0 auto;
  padding: 0;
  color: var(--text-soft);
}
/* Nothing in the empty state may be wider than the empty state. Every child is
   already width-capped, but the board's headings are model-written and one long
   word is all it takes. */
.empty-state > * { max-width: 100%; min-width: 0; }

.hero { display: flex; flex-direction: column; gap: 14px; }
.empty-state h1 {
  margin: 0;
  font-size: 40px;
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: -0.025em;
  color: var(--text);
}
.empty-state .hero p {
  margin: 0;
  max-width: 640px;
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--text-soft);
  text-wrap: pretty;
}

/* Where the composer sits while the analysis is empty. chat.js moves the real
   <footer class="composer"> in and out of this mount. */
.composer-mount:empty { display: none; }
.composer-note {
  margin: -30px 0 0;               /* rides with the composer, not the section */
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim);
}
.composer-note[hidden] { display: none; }

/* ---- Set your context ---- */
/* `display` here beats the UA sheet's `[hidden] { display: none }` — author
   rules always win over UA rules — so the hidden attribute has to be honored
   explicitly or the form is permanently open. */
.ctx-card[hidden] { display: none; }
.ctx-card {
  background: #ffffff;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.ctx-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
}
.ctx-head-text { display: flex; flex-direction: column; gap: 4px; }
.ctx-title { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; color: var(--text); }
.ctx-sub { font-size: 13px; color: var(--text-soft); }
.ctx-dismiss {
  background: none;
  border: 0;
  padding: 0 2px;
  cursor: pointer;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 13px;
  color: var(--text-dim);
  flex-shrink: 0;
}
.ctx-dismiss:hover { color: var(--text); }

.pf-q { display: flex; flex-direction: column; gap: 10px; }
.pf-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  letter-spacing: 0.09em;
  color: var(--text-soft);
}
.pf-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.pf-chip {
  border: 1px solid var(--border);
  background: var(--bg-panel);
  color: var(--text-soft);
  border-radius: 20px;
  padding: 6px 13px;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s;
}
.pf-chip:hover { border-color: var(--border-hover); color: var(--text); }
/* The default must be visibly preselected — in the previous version it was
   indistinguishable from the seven options nobody had chosen. */
.pf-chip[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text);
  font-weight: 500;
}
.ctx-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.ctx-fields .pf-q { gap: 8px; }
.pf-input {
  width: 100%;
  font: inherit;
  font-size: 13.5px;
  border: 1px solid var(--border-strong);
  border-radius: 7px;
  padding: 10px 12px;
  background: var(--bg-panel);
  color: var(--text);
  outline: none;
}
.pf-input::placeholder { color: var(--text-dim); }
.pf-input:focus { border-color: var(--accent); }
.pf-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  border-top: 1px solid var(--border-soft);
  padding-top: 16px;
}
.pf-go {
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: 7px;
  padding: 8px 16px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.12s;
}
.pf-go:hover { background: var(--accent-hover); }
.pf-go:disabled { background: var(--border-hover); cursor: default; }
.pf-clear[hidden] { display: none; }
.pf-clear {
  background: #ffffff;
  color: var(--text-soft);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 14px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: color 0.12s, border-color 0.12s;
}
.pf-clear:hover { color: var(--text); border-color: var(--border-hover); }
/* Load-bearing sentence: it explains the mechanism that justifies the
   recommendations. */
.pf-skip { font-size: 12.5px; color: var(--text-dim); }
.empty-state .pf-error {
  margin: 0;
  max-width: none;
  font-size: 13px;
  color: var(--danger);
}

/* The same slot, asking rather than complaining. A suspected typo in the market
   is not the visitor doing something wrong — it is us being unsure — so it
   loses the danger colour and gains two ways out. */
.empty-state .pf-error--ask {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  color: var(--text-soft);
}

.pf-fix {
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-panel);
  color: inherit;
  font: inherit;
  font-size: 12px;
  padding: 3px 9px;
  cursor: pointer;
}

.pf-fix:hover { border-color: var(--border-hover); }

/* Keeping what you typed must be as easy to hit as taking the suggestion —
   quieter, never harder. Colour rather than opacity, which rescales
   contrast silently (see the text-tier note at the top of this file). */
.pf-fix--keep { color: var(--text-dim); }

/* The collapsed entry point once context is applied or dismissed. A returning
   visitor gets this, never the full form unasked. */
.board-scope[hidden] { display: none; }
.board-scope {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 10px;
  background: #ffffff;
  border: 1px solid var(--border-strong);
  border-radius: 7px;
  padding: 7px 12px;
  font: inherit;
  font-size: 12.5px;
  color: var(--text-soft);
  cursor: pointer;
  transition: border-color 0.12s;
}
.board-scope:hover { border-color: var(--accent); }
.board-scope .bs-value {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--text);
}
.board-scope .bs-change { color: var(--accent); }
.board-scope .bs-spin {
  width: 11px;
  height: 11px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  border-top-color: var(--accent);
  animation: bs-spin 0.8s linear infinite;
}
@keyframes bs-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .board-scope .bs-spin { animation: none; } }

/* ---- Recommended for your context ---- */
.rec { display: flex; flex-direction: column; gap: 14px; }
.rec-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.mono-echo {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--text-dim);
  text-align: right;
}
.rec-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* A slot's card and its catalog rows are swapped in place as authoring lands,
   so each owns a wrapper that participates in the grid rather than boxing its
   children out of it. */
.rec-slot, .cat-slot { display: contents; }
.rec-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  background: #ffffff;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 18px;
  text-align: left;
  font: inherit;
  cursor: pointer;
  min-width: 0;
  transition: border-color 0.12s;
}
.rec-card:hover { border-color: var(--accent); }
.rec-card-head { display: flex; align-items: baseline; gap: 10px; }
/* A ranked index rather than a tinted icon chip: it conveys priority instead of
   decorative category colour, and needs no iconography. */
.rec-index {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px;
  color: var(--accent);
  flex-shrink: 0;
}
.rec-title {
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  overflow-wrap: anywhere;
}
.rec-desc {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-soft);
  /* The question itself, clamped. A reader should be able to judge the analysis
     before running it, and the prompt is the honest description of what will
     run — inventing a marketing blurb for it would not be. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.rec-sources {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--text-dim);
  padding-top: 2px;
  overflow-wrap: anywhere;
}

/* A card being rewritten, while it is written. The plan arrives before any
   model call returns (panel_gen._choose_slots), so this is on screen from the
   first moment — and only on the cards actually changing, so the ones staying
   put stay still. */
.rec-card--working, .rec-card--settling { position: relative; overflow: hidden; }
.rec-card--working { border-color: var(--accent-border); }
.rec-card--working::after,
.rec-card--settling::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 20%, var(--accent-soft) 50%, transparent 80%);
  opacity: 0.75;
  transform: translateX(-100%);
  animation: sg-working 1.6s ease-in-out infinite;
}
@keyframes sg-working { to { transform: translateX(100%); } }
/* How the shimmer ends on a slot whose card never arrived: it settles — a still
   wash fading out — instead of vanishing mid-sweep, so a promised card that was
   skipped reads as "finished", not "gave up". */
.rec-card--settling::after {
  animation: none;
  transform: none;
  opacity: 0;
  transition: opacity 0.4s ease-out;
}
.rec-card--fresh { animation: sg-fresh 1.4s ease-out; }
@keyframes sg-fresh {
  0%, 60% { border-color: var(--accent); }
  100% { border-color: var(--border-strong); }
}
.cat-row--working .cat-arrow { animation: cat-pulse 1.6s ease-in-out infinite; }
@keyframes cat-pulse { 0%, 100% { color: var(--text-dim); } 50% { color: var(--accent); } }
@media (prefers-reduced-motion: reduce) {
  .rec-card--fresh { animation: none; }
  /* Keep the state, drop the movement: a steady tint still says which cards are
     being worked on. */
  .rec-card--working::after { animation: none; opacity: 0.35; transform: none; }
  .cat-row--working .cat-arrow { animation: none; color: var(--accent); }
}

/* ---- Other analyses: the demoted catalog ---- */
.catalog { display: flex; flex-direction: column; gap: 12px; }
.catalog[hidden] { display: none; }
/* Note the zero ROW gap: rows are separated by their own bottom borders, which
   is what gives the list its table-like rhythm. */
.catalog-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 40px; }
.cat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 11px 0;
  border: 0;
  border-bottom: 1px solid var(--border-softest);
  background: none;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  min-width: 0;
  transition: border-color 0.12s;
}
.cat-row:hover { border-color: var(--border-hover); }
.cat-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-arrow {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--text-dim);
  flex-shrink: 0;
}

.empty-state .board-note {
  width: 100%;
  margin: -28px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim);
}
.empty-state .board-note[hidden] { display: none; }

/* Retained as a distinct full-width affordance, and a drop target. */
.bring-data {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  background: var(--bg-elev);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px 18px;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.bring-data:hover { border-color: var(--border-hover); }
.bring-data.dropping { border-color: var(--accent); background: var(--accent-soft); }
.bd-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.bd-title { font-size: 14px; font-weight: 600; }
.bd-sub { font-size: 13px; color: var(--text-soft); line-height: 1.5; }
.bd-cta {
  flex-shrink: 0;
  background: #ffffff;
  color: var(--text-soft);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  padding: 7px 14px;
  font-size: 13px;
  white-space: nowrap;
}

.access-cta { margin: 0; font-size: 13.5px; color: var(--text-dim); }

/* ---- Composer ----
   Promoted into the hero on the empty state (see .composer--hero) and pinned to
   the bottom of the column once there is a conversation to read. */
.composer {
  background: var(--bg);
  padding: 8px 40px 20px;
  width: 100%;
  max-width: calc(var(--col-max) + 80px);
  margin: 0 auto;
  position: relative;
}
.composer--hero { padding: 0; max-width: none; }
.composer-card {
  position: relative;
  background: #ffffff;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: var(--shadow-sm);    /* the only shadow in the shell */
  padding: 18px 18px 14px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  transition: border-color 0.12s;
}
.composer-card:focus-within { border-color: var(--accent); }
.composer textarea {
  width: 100%;
  resize: none;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  line-height: 1.5;
  min-height: 24px;
  max-height: 9em;                 /* ~6 lines, then it scrolls */
  padding: 0;
  overflow-y: auto;
}
.composer textarea::placeholder { color: var(--text-dim); }
.composer-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.cc-left { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.cc-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.cc-btn, .cc-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 10px;
  background: transparent;
  color: var(--text-soft);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s;
}
.cc-btn:hover, .cc-pill:hover { border-color: var(--border-hover); color: var(--text); }
.cc-glyph {
  width: 9px;
  height: 9px;
  border: 1.5px solid var(--text-dim);
  border-radius: 2px;
  flex-shrink: 0;
}
#composer-attach.has-file .cc-glyph { background: var(--accent); border-color: var(--accent); }
/* Monospace on purpose: these are analysis parameters, and mono marks them as
   machine state rather than prose. */
.cc-pill {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  white-space: nowrap;
}
/* A parameter moved off its default is state the reader must be able to see at
   a glance — it changes what gets asked. */
.cc-pill.set { border-color: var(--accent-border); background: var(--accent-soft); color: var(--text); }
/* The keyboard hint is the one place a glyph can go missing: IBM Plex Mono has
   no ⌘ (U+2318) or ⏎ (U+23CE), and a webfont that lacks a codepoint does NOT
   fall through to the next family for that character alone — it renders tofu.
   So the system UI faces, which do carry both, lead here. */
.cc-hint {
  font-family: -apple-system, "Segoe UI Symbol", "SF Mono", Menlo,
               "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--text-dim);
}
.cc-run {
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: 7px;
  padding: 8px 18px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s;
}
.cc-run:hover:not(:disabled) { background: var(--accent-hover); }
.cc-run:disabled { background: var(--border-hover); cursor: not-allowed; }
.cc-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  padding: 0;
  transition: border-color 0.12s, color 0.12s;
}
.cc-icon-btn:hover:not(:disabled) { border-color: var(--border-hover); color: var(--text); }
.cc-icon-btn[hidden] { display: none; }
.cc-icon-btn svg { width: 15px; height: 15px; display: block; }

/* Voice: recording and transcription get distinct shapes so the control is
   understandable without relying on colour alone. */
#composer-mic.is-recording { border-color: var(--danger); color: var(--danger); }
#composer-mic .voice-stop-icon,
#composer-mic .voice-spinner { display: none; }
#composer-mic.is-recording .voice-mic-icon { display: none; }
#composer-mic.is-recording .voice-stop-icon { display: block; }
#composer-mic.is-transcribing .voice-mic-icon { display: none; }
#composer-mic.is-transcribing .voice-spinner {
  display: block;
  width: 13px;
  height: 13px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: voice-spinner 0.7s linear infinite;
}
@keyframes voice-spinner { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  #composer-mic.is-transcribing .voice-spinner { animation-duration: 1.4s; }
}

.voice-status {
  margin: 0 2px 8px;
  color: var(--text-soft);
  font-size: 12.5px;
  line-height: 1.35;
}
.voice-status.recording { color: var(--danger); }
.voice-status.err { color: var(--danger); }
.voice-status.ok { color: var(--success); }
/* Announced, not shown: while recording, the visible state lives in the panel
   below, but the live region still has to say what changed. */
.voice-status.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Live recording panel. The point of this row is feedback: the meter is driven
   by the analyser node, so speaking visibly moves it and silence visibly does
   not — the previous UI could not tell those two apart. Colour follows the
   house rule: --accent means "active", --danger is spent only on the dot and
   on the last-seconds warning. */
.voice-panel {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 2px 8px;
  padding: 7px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--bg-panel);
  font-size: 12.5px;
  color: var(--text-soft);
}
.voice-panel[hidden] { display: none; }
.vp-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--danger);
  animation: voice-dot 1.6s ease-in-out infinite;
}
@keyframes voice-dot { 50% { opacity: 0.25; } }

/* 15 bars, each scaled independently so the meter reads as a waveform rolling
   right-to-left rather than one lump rising and falling. */
.vp-meter {
  flex: none;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 20px;
}
.vp-meter span {
  --v: 0;
  display: block;
  width: 3px;
  height: 20px;
  border-radius: 2px;
  background: var(--border-hover);
  transform: scaleY(calc(0.12 + 0.88 * var(--v)));
  transition: transform 0.08s linear, background-color 0.2s;
}
.voice-panel.is-live .vp-meter span { background: var(--accent); }
.vp-time {
  flex: none;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.voice-panel.is-ending .vp-time { color: var(--danger); }
.vp-hint {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.voice-panel.is-quiet .vp-hint { color: var(--warning); }
.vp-btn {
  flex: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 12px;
  padding: 4px 10px;
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s, background 0.12s;
}
.vp-btn:hover { border-color: var(--border-hover); color: var(--text); }
.vp-btn.vp-done {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.vp-btn.vp-done:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

/* The mic button itself breathes with the input level, so the control the user
   is looking at also confirms it is hearing them. */
#composer-mic.is-recording {
  box-shadow: 0 0 0 calc(3px * var(--voice-level, 0)) rgba(179, 55, 58, 0.18);
  transition: box-shadow 0.08s linear;
}

@media (prefers-reduced-motion: reduce) {
  .vp-dot { animation: none; }
  .vp-meter span { transition: none; }
  #composer-mic.is-recording { transition: none; }
}

@media (max-width: 640px) {
  .vp-meter { display: none; }
  .vp-hint { font-size: 12px; }
}

/* Parameter popovers. Anchored to the composer card rather than the pill so a
   narrow column can't push one off-screen. */
.cc-pop {
  position: absolute;
  z-index: 30;
  left: 0;
  bottom: calc(100% + 8px);
  min-width: 220px;
  background: #ffffff;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  box-shadow: var(--shadow-md);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.composer--hero .cc-pop { bottom: auto; top: calc(100% + 8px); }
.cc-pop[hidden] { display: none; }
.cc-pop-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  letter-spacing: 0.09em;
  color: var(--text-soft);
}
.cc-pop-opts { display: flex; flex-direction: column; gap: 2px; }
.cc-pop-opt {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  border: 0;
  background: none;
  border-radius: 6px;
  padding: 7px 8px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.cc-pop-opt:hover { background: var(--bg-hover); }
.cc-pop-opt[aria-pressed="true"] { background: var(--accent-soft); }
.cc-pop-opt .opt-note {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  color: var(--text-dim);
}
.cc-pop-range { display: flex; gap: 10px; }
.cc-pop-range label {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  letter-spacing: 0.09em;
  color: var(--text-soft);
}
.cc-pop-range input {
  width: 100%;
  font: inherit;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 13.5px;
  letter-spacing: normal;
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  padding: 7px 9px;
  background: var(--bg-panel);
  outline: none;
}
.cc-pop-range input:focus { border-color: var(--accent); }
.cc-pop-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.cc-pop-reset {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--text-soft);
  cursor: pointer;
}
.cc-pop-reset:hover { color: var(--text); }
.cc-pop-apply {
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: 6px;
  padding: 6px 14px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
}
.cc-pop-apply:hover { background: var(--accent-hover); }

/* Upload status line above the composer while/after a CSV is attached. */
.upload-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 2px 8px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px;
  color: var(--text-soft);
}
.upload-pill .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}
.upload-pill.uploading .dot { animation: pulse 1s ease-in-out infinite; }
.upload-pill.ok .dot { background: var(--success); }
.upload-pill.err { color: var(--danger); }
.upload-pill.err .dot { background: var(--danger); }

/* Post-upload summary card (rendered into the message stream) */
.upload-card {
  width: 100%;
  max-width: 1180px;
  margin: 4px auto 16px;
  padding: 0 28px;                 /* match the message gutter */
  box-sizing: border-box;
}
.uc-box {
  --sg-tint: var(--accent-soft);   /* the one accent tint; no per-card palette any more */
  --sg-ink: var(--accent);
  width: min(440px, 100%);
  background: var(--assistant-bubble);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 15px 15px 11px;
  box-shadow: var(--shadow-sm);
}
.uc-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 2px 10px;
}
.uc-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: var(--sg-tint);
  color: var(--sg-ink);
  flex-shrink: 0;
}
.uc-icon svg { width: 17px; height: 17px; }
.uc-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
  word-break: break-word;
}
.uc-facts {
  margin: 0 2px 10px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.uc-fact {
  font-size: 12.5px;
  color: var(--text-dim);
  line-height: 1.4;
}
.uc-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 11px;
  padding-top: 11px;
  border-top: 1px solid var(--border);
}
.uc-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--bg);
  color: var(--text-soft);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 12px 5px 10px;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
/* --sg-ink/--sg-tint come from the enclosing .uc-box; both fall back to the
   accent, so an unset token degrades to an accent chip rather than to
   white-on-white. */
.uc-chip:hover {
  border-color: var(--sg-ink, var(--accent));
  color: var(--text);
  background: var(--sg-tint, var(--accent-soft));
}
.uc-chip-ico { display: inline-flex; color: var(--sg-ink, var(--accent)); }
.uc-chip-ico svg { width: 14px; height: 14px; display: block; }
.uc-chip.primary {
  background: var(--sg-ink, var(--accent));
  border-color: var(--sg-ink, var(--accent));
  color: var(--on-accent);
}
.uc-chip.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.uc-chip.primary .uc-chip-ico { color: var(--on-accent); }

/* ---- "Datasets used" chip row (under each assistant answer) ---- */
/* ---- Meta-row under an answer: sources (left) + actions (right) on one line ---- */
.msg-meta {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-top: 8px;
}
.msg-meta .datasets-used {
  flex: 1 1 auto;
  min-width: 0;          /* allow the chip row to wrap instead of pushing actions off */
  margin-top: 0;
}

/* ---- Per-message actions (Export) — always visible, brightening on hover ---- */
.msg-actions {
  flex: 0 0 auto;
  display: flex;
  gap: 6px;
  margin-left: auto;     /* pin to the right even when there are no sources */
}
.msg-action {
  position: relative;    /* anchors the data-tip tooltip */
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 3px 9px;
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
/* Custom data-tip tooltip: the native title attribute waits on the OS hover
   delay (~1s, not controllable). Anchored to the button's right edge because
   the action bar is pinned to the right — the tip grows leftward, on-screen. */
.msg-action[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 7px);
  right: 0;
  width: max-content;
  max-width: 300px;
  background: var(--text);
  color: var(--bg);
  border-radius: 6px;
  padding: 6px 9px;
  font-size: 11px;
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: 40;
}
.msg-action[data-tip]:hover::after {
  opacity: 1;
  transition-delay: 0.15s;   /* just enough to not flash on pass-through */
}
.msg-action:hover:not(:disabled):not(.is-disabled) {
  background: var(--bg-hover);
  border-color: var(--accent);
  color: var(--accent);
}
/* .is-disabled instead of the disabled attribute where the tooltip must still
   show (Safari/Firefox drop hover events on real disabled controls) — e.g. the
   Audit button on an unsaved chat, whose hint points at Save.
   Unavailability is signalled by the dashed border and flat background, NOT by
   opacity: fading the whole button washed the label out against the light
   surface, and — because opacity makes a stacking context — dimmed the tooltip
   with it. Text stays at full --text-dim so it reads normally. */
.msg-action:disabled,
.msg-action.is-disabled {
  background: transparent;
  border-style: dashed;
  color: var(--text-dim);
  cursor: not-allowed;
}
.msg-action:disabled .ma-icon,
.msg-action.is-disabled .ma-icon { opacity: 0.7; }
.msg-action .ma-icon { font-size: 12px; line-height: 1; }

.datasets-used {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: 11.5px;
}
.datasets-used .du-label {
  color: var(--text-dim);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-right: 2px;
}
/* A chip is a quiet surface carrying exactly ONE saturated element: the footnote
   number. Everything else is line and text. --du-ink is the chip's single colour
   variable — navy for a real source table, plum for an agent-derived one — so the
   two variants cannot drift apart, and colour always *means* provenance rather
   than just filling space. */
.datasets-used .du-chip {
  --du-ink: var(--accent);
  --du-tint: var(--accent-soft);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--assistant-bubble);
  color: var(--text-soft);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 11px;
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.datasets-used .du-chip.derived {
  --du-ink: var(--derived);
  --du-tint: var(--derived-soft);
}
/* The tint is a hover reward, not a resting state — a row of filled pills was
   reading as noise. */
.datasets-used .du-chip:hover {
  border-color: var(--du-ink);
  background: var(--du-tint);
  color: var(--text);
}
.datasets-used .du-name {
  max-width: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Provenance label (CBP / CENSUS / DERIVED). Inked rather than filled: it still
   distinguishes source from derived at rest, without a second solid pill. */
.datasets-used .du-src {
  color: var(--du-ink);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
/* Footnote number on a chip that an inline [n] citation points at — the anchor
   for the [n] superscripts, and the only fill on the chip. */
.datasets-used .du-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  background: var(--du-ink);
  color: var(--on-accent);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
}
/* The badge supplies its own left inset; no border tint needed on top of it. */
.datasets-used .du-chip.cited { padding-left: 3px; }

/* ---- Inline citations: [n] superscripts grounding figures to their table ---- */
.bubble .cite {
  cursor: pointer;
  color: var(--accent);
  font-weight: 700;
  font-size: 0.72em;
  vertical-align: super;
  line-height: 0;
  margin-left: 1px;
  padding: 0 1px;
  border-radius: 3px;
  transition: background 0.12s, color 0.12s;
}
.bubble .cite:hover {
  background: var(--accent-soft);
}
/* Cited a table not loaded this session — shown, but never linked. */
.bubble .cite--unverified {
  cursor: help;
  color: var(--text-dim);
  font-weight: 600;
}
.bubble .cite--unverified:hover { background: transparent; }

/* ---- Dataset description tooltip (one shared element, chips only) ---- */
.ds-tooltip {
  position: fixed;
  z-index: 1200; /* above .dv-overlay (1000) and the confirm overlay (1100) */
  max-width: 340px;
  padding: 8px 11px;
  background: var(--assistant-bubble);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-modal);
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-soft);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s;
}
.ds-tooltip.visible { opacity: 1; }
.ds-tooltip .ds-tip-name {
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
}
.ds-tooltip .ds-tip-body {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Provenance strip at the tooltip foot: origin agency · data vintage (and the
   derivation chain for tables produced in the analysis). */
.ds-tooltip .ds-tip-prov {
  margin-top: 5px;
  padding-top: 5px;
  border-top: 1px solid var(--border);
  font-size: 11.5px;
  color: var(--text-dim);
  white-space: pre-line; /* derivation and origin·vintage on separate lines */
}

/* Amber vintage badge on a "Datasets used" chip whose data ends years back —
   the at-a-glance freshness signal (full detail in the tooltip/viewer). */
.datasets-used .du-vintage {
  font-size: 10px;
  font-weight: 700;
  color: #9a6700;
  background: rgba(210, 153, 34, 0.16);
  border-radius: 999px;
  padding: 0 6px;
  line-height: 16px;
}

/* Provenance card in the data viewer: the "check the source" surface — origin
   link, derivation, vintage, approximation notes. Sits under .dv-desc. */
.dv-prov {
  padding: 8px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elev);
  font-size: 12.5px;
  line-height: 1.55;
  flex-shrink: 0;
}
.dv-prov .dvp-row { display: flex; gap: 10px; }
.dv-prov .dvp-label {
  flex: 0 0 74px;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  line-height: inherit;
}
.dv-prov .dvp-value { color: var(--text-soft); }
.dv-prov .dvp-value a { color: var(--accent); }
.dv-prov .dvp-stale {
  margin-left: 6px;
  font-size: 10.5px;
  font-weight: 600;
  color: #9a6700;
  background: rgba(210, 153, 34, 0.16);
  border-radius: 999px;
  padding: 1px 7px;
}

/* ---- Ask-user clarifying-question panel ---- */
.ask-panel {
  margin-top: 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-elev);
  overflow: hidden;
  max-width: 720px;
}
.ask-tabs {
  display: flex;
  gap: 2px;
  padding: 6px 6px 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elev-2);
  flex-wrap: wrap;
}
.ask-tab {
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  color: var(--text-dim);
  font-size: 12.5px;
  font-weight: 500;
  padding: 7px 12px;
  border-radius: 8px 8px 0 0;
  cursor: pointer;
}
.ask-tab:hover { color: var(--text-soft); }
.ask-tab.active {
  background: var(--bg-elev);
  color: var(--accent);
  border-color: var(--border);
}
.ask-pane { padding: 14px 16px; }
.ask-question {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 10px;
}
.ask-options { display: flex; flex-direction: column; gap: 8px; }
.ask-option {
  text-align: left;
  background: var(--assistant-bubble);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 10px 12px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 2px;
  transition: border-color 0.1s, background 0.1s;
}
.ask-option:hover:not(:disabled) { border-color: var(--accent); background: var(--bg-hover); }
.ask-option.selected {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.ask-option:disabled { cursor: default; opacity: 0.85; }
.ask-opt-label { font-size: 13.5px; font-weight: 600; color: var(--text); }
.ask-opt-desc { font-size: 12px; color: var(--text-dim); }
.ask-option.selected .ask-opt-label::after {
  content: " ✓";
  color: var(--accent);
}
.ask-footer {
  display: flex;
  justify-content: flex-end;
  padding: 10px 14px;
  border-top: 1px solid var(--border);
  background: var(--bg-elev-2);
}
.ask-send {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 8px;
  padding: 7px 18px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.ask-send:hover:not(:disabled) { background: var(--accent-hover); }
.ask-send:disabled { background: var(--bg-hover); color: var(--text-dim); cursor: not-allowed; }
.ask-panel.answered { opacity: 0.7; }
.ask-panel.submitting { opacity: 0.82; }

/* ---- Partial-response notice (response interrupted, work persisted) ---- */
.partial-note {
  margin-top: 6px;
  padding: 8px 12px;
  border: 1px solid var(--warning);
  border-radius: 8px;
  background: var(--warning-soft);
  color: var(--warning);
  font-size: 13px;
  line-height: 1.4;
  max-width: 720px;
}

/* ---- Region 3: the evidence base ----
   The most important change in the redesign. The panel this replaces was
   titled "LOADED DATA" and held a single ambiguous line in an otherwise empty
   column; an executive's first silent question is "where is this from and how
   stale is it", and nothing answered it. Two ideas drive the structure: the
   user's own session tables and the system's standing sources answer different
   trust questions, so they are separated; and grain + vintage ride every row. */
.data-panel {
  width: var(--rail-evidence);
  flex: 0 0 var(--rail-evidence);
  border-left: 1px solid var(--border);
  background: var(--bg-panel);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: width 0.18s ease-out, flex-basis 0.18s ease-out;
}
.data-panel.collapsed {
  width: 0;
  flex-basis: 0;
  border-left: none;
}
/* First paint only, same contract as .sb-preload: the markup ships collapsed
   (the resting state for anyone who has not opened the panel), and a browser
   that remembers it open should snap there rather than slide in on load. */
.data-panel.dp-preload { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .data-panel, .data-panel.collapsed { transition: none; }
}
.dp-header {
  height: var(--bar-h);
  flex: 0 0 var(--bar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 18px;
  border-bottom: 1px solid var(--border);
}
.dp-title {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--text);
}
.dp-close {
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
}
.dp-close:hover { color: var(--text); }

.dp-body {
  flex: 1;
  overflow-y: auto;
  padding: 18px 18px 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.dp-section { display: flex; flex-direction: column; gap: 10px; }
/* The `display` above is an author rule and beats the UA sheet's
   `[hidden] { display: none }`, so without this a section hidden from JS keeps
   its heading on screen over an empty body — which is exactly how the
   connected-source switch first shipped. */
.dp-section[hidden] { display: none; }
.dp-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.dp-count {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  color: var(--text-dim);
}
.dp-empty {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim);
}

/* Section A — what this session is actually holding. */
.dp-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.dp-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: #ffffff;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  padding: 12px;
  cursor: pointer;
  transition: border-color 0.12s;
}
.dp-item:hover { border-color: var(--border-hover); }
.dp-item-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.dp-item-name {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dp-item-tag {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  color: var(--accent);
  white-space: nowrap;
  flex-shrink: 0;
}
.dp-item-tag.derived { color: var(--derived); }
.dp-item-tag.err { color: var(--danger); }
/* The shape line. The join key is stated because a silent join failure is the
   most likely way this product produces a confidently wrong answer. */
.dp-item-shape {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.45;
}
.dp-item.is-error { border-color: var(--danger); }
.dp-item-error { font-size: 12px; line-height: 1.45; color: var(--danger); }
/* Column chips let a reader confirm a file parsed as expected without leaving
   the screen. */
.dp-chips { display: flex; gap: 6px; flex-wrap: wrap; padding-top: 2px; }
.dp-chip {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  color: var(--text-soft);
  background: var(--bg-elev-2);
  border: 0;
  border-radius: 4px;
  padding: 3px 6px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
button.dp-chip { cursor: pointer; }
button.dp-chip:hover { color: var(--text); }

/* Section B — the standing catalog. Every row carries grain AND vintage; that
   pairing is what makes the panel auditable rather than reassuring. */
.dp-sources { display: flex; flex-direction: column; gap: 3px; }
.dp-source {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px 0 11px;
  border-bottom: 1px solid var(--border-soft);
  color: inherit;
  text-decoration: none;
}
a.dp-source:hover .dp-source-name { color: var(--accent); }
.dp-source-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  transition: color 0.12s;
}
.dp-source-meta {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  color: var(--text-dim);
}
/* Silent staleness is the failure mode this panel exists to prevent, so a
   source years behind says so here — before an answer leans on it, not after. */
.dp-source.stale .dp-source-meta { color: var(--warning); }
.dp-source.stale .dp-source-meta::after {
  content: " · STALE";
  font-weight: 500;
}
.dp-more {
  align-self: flex-start;
  background: none;
  border: 0;
  padding: 10px 0 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--accent);
  cursor: pointer;
}
.dp-more:hover { color: var(--text); }
.dp-more[hidden] { display: none; }

.dp-footer {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  padding: 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* An explicit stamp, not "2 hours ago": for audit purposes relative time is not
   a citable value. */
.dp-foot-line {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
  color: var(--text-soft);
}
.dp-foot-line.dim { color: var(--text-dim); }

/* ---- Data viewer modal ---- */
.dv-overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.dv-overlay.hidden { display: none; }
.dv-modal {
  background: var(--assistant-bubble);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-modal);
  width: 92vw;
  max-width: 1400px;
  height: 86vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.dv-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  background: var(--bg-elev);
}
.dv-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}
.dv-meta {
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
}
.dv-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
/* Catalog summary / lineage strip under the header; the `hidden` attribute
   hides it, so don't force a display value here. */
.dv-desc {
  padding: 8px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text-soft);
  font-size: 12.5px;
  line-height: 1.5;
  max-height: 96px;
  overflow-y: auto;
  flex-shrink: 0;
}
.dv-download {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 6px;
  padding: 5px 12px;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: opacity 0.15s;
}
.dv-download:hover { opacity: 0.85; }
.dv-close-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 3px 8px;
}
.dv-close-btn:hover { color: var(--text); background: var(--bg-hover); }
.dv-table-wrap {
  flex: 1;
  overflow: auto;
}
.dv-table {
  border-collapse: collapse;
  font-size: 12.5px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  width: 100%;
  min-width: max-content;
}
.dv-table th {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  padding: 5px 10px;
  font-weight: 600;
  text-align: left;
  position: sticky;
  top: 0;
  z-index: 1;
  white-space: nowrap;
}
.dv-table th .col-dtype {
  display: block;
  font-weight: 400;
  font-size: 10px;
  color: var(--text-dim);
  margin-top: 1px;
}
.dv-table td {
  border: 1px solid var(--border);
  padding: 4px 10px;
  vertical-align: top;
  max-width: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dv-table tbody tr:nth-child(even) td { background: var(--row-band); }
.dv-table tbody tr:hover td { background: var(--row-hover); }
.dv-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 10px 16px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
  background: var(--bg-elev);
}
.dv-prev, .dv-next {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-soft);
  font-size: 13px;
  padding: 4px 12px;
  cursor: pointer;
  transition: background 0.12s;
}
.dv-prev:hover, .dv-next:hover { background: var(--bg-hover); }
.dv-prev:disabled, .dv-next:disabled { opacity: 0.35; cursor: default; }
.dv-page-info { font-size: 12.5px; color: var(--text-dim); min-width: 160px; text-align: center; }

/* ---- Early-access lead capture (vertical front doors) ---- */
.access-cta { margin: 2px 0 0; font-size: 13.5px; color: var(--text-dim); }
.access-cta-btn {
  border: none; background: none; padding: 0;
  color: var(--accent); font: inherit; font-weight: 600;
  text-decoration: underline; cursor: pointer;
}
.access-cta a { color: var(--accent); }

.ra-overlay {
  position: fixed; inset: 0;
  background: var(--overlay);
  z-index: 1100;
  display: flex; align-items: center; justify-content: center;
}
.ra-overlay.hidden { display: none; }
.ra-modal {
  position: relative;
  background: var(--assistant-bubble);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-modal);
  width: min(92vw, 460px);
  max-height: 90vh;
  overflow-y: auto;
  padding: 26px 26px 22px;
}
.ra-modal h2 { margin: 0 0 6px; font-size: 19px; color: var(--text); }
.ra-sub { margin: 0 0 16px; font-size: 13.5px; line-height: 1.5; color: var(--text-dim); }
.ra-close {
  position: absolute; top: 10px; right: 12px;
  border: none; background: none; cursor: pointer;
  font-size: 20px; line-height: 1; color: var(--text-dim);
}
.ra-close:hover { color: var(--text); }
/* Honeypot: visually gone but present for bots that fill every field. */
.ra-hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.ra-label { display: block; margin: 0 0 12px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.ra-label input, .ra-label textarea {
  display: block; width: 100%; margin-top: 5px;
  padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text);
  font: inherit; font-size: 13.5px;
  box-sizing: border-box;
  resize: vertical;
}
.ra-label input:focus, .ra-label textarea:focus { outline: none; border-color: var(--accent); }
.ra-row { display: flex; gap: 12px; }
.ra-row .ra-label { flex: 1; min-width: 0; }
.ra-error { margin: 0 0 10px; font-size: 13px; color: var(--danger); }
.ra-submit {
  width: 100%;
  padding: 10px 14px;
  border: none; border-radius: 8px;
  background: var(--accent); color: var(--on-accent);
  font: inherit; font-size: 14px; font-weight: 600;
  cursor: pointer;
}
.ra-submit:hover { filter: brightness(1.06); }
.ra-submit:disabled { opacity: 0.6; cursor: default; }
.ra-book { margin: 8px 0 0; font-size: 13.5px; color: var(--text-dim); }
.ra-book a { color: var(--accent); }

/* ---- Sign-in modal ----
   Same shell as the request-access modal, but there is no form to fill in:
   one Google button, centered, with the terms line under it. */
.si-modal { text-align: center; }
.si-button {
  display: flex;
  justify-content: center;
  margin: 20px 0 14px;
  min-height: 44px;   /* holds the space while GIS loads, so nothing jumps */
}
.si-fine {
  margin: 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-soft);
}
.si-fine a { color: var(--text-dim); text-decoration: underline; }
.si-fine a:hover { color: var(--accent); }

/* ---- Account chip (sidebar) ----
   Identity and what's left of the free runs, directly above the footer. The
   runs counter goes amber on the last one — the only warning before the wall. */
.account-chip {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  padding: 10px 18px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  font-size: 12px;
}
/* The explicit display above beats the UA sheet's [hidden] rule, so a signed-out
   chip would otherwise render as an empty bordered strip. */
.account-chip[hidden] { display: none; }
.sidebar-footer[hidden] { display: none; }
.account-email {
  flex: 1 1 100%;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-runs { color: var(--text-soft); }
/* The signed-out prompt sits in the same slot as the runs counter and reads as
   the same class of information — a status, not a pitch. */
.account-prompt { color: var(--text-soft); }
.account-runs.low { color: var(--warning); font-weight: 500; }
.account-signout {
  margin-left: auto;
  background: none;
  border: 0;
  padding: 0;
  color: var(--text-soft);
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
}
.account-signout:hover { color: var(--accent); }

/* ---- The anonymous first run ----
   Sits under the answer a guest just received, where the offer is easiest to
   accept. Deliberately quieter than an alert: the analysis above it is the
   argument, this is only the next step. */
.guest-cta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin: 14px 0 2px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-hover);
  font-size: 13.5px;
  color: var(--text-dim);
}
.guest-cta-btn {
  margin-left: auto;
  padding: 7px 14px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: none;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.guest-cta-btn:hover { background: var(--accent); color: var(--bg); }

/* ============================================================
   Narrow-viewport layer
   The layout is a three-column flex (252px sessions + analysis column + 336px
   evidence). It gives way in three steps, each one buying width for the middle:
     < 1280px  the evidence panel is closed by default (chat.js)
     < 1100px  both side columns stop reserving width and become overlay
               drawers above the analysis column
     <  900px  the two-column grids collapse to one
   Rules live at the end of the file so these overrides win over the base
   declarations they replace.
   ============================================================ */

.sidebar-backdrop { display: none; }

/* Touch devices have no hover, so the delete control that normally only
   appears on :hover would be unreachable — keep it visible, drop the stamp. */
@media (hover: none) and (pointer: coarse) {
  .session-item .si-time { opacity: 0; }
  .session-item .delete { opacity: 1; }
}

@media (max-width: 1100px) {
  /* Sessions → left off-canvas drawer */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 82vw;
    max-width: 300px;
    flex-basis: auto;
    z-index: 60;
    transform: translateX(-100%);
    transition: transform 0.18s ease-out;
    box-shadow: var(--shadow-lg);
  }
  .sidebar.open { transform: translateX(0); }

  .sidebar-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: var(--overlay);
    z-index: 55;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease-out;
  }
  .sidebar-backdrop.visible { opacity: 1; pointer-events: auto; }

  /* Evidence → right off-canvas drawer (its own ✕ / the Evidence toggle close it) */
  .data-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: 86vw;
    max-width: var(--rail-evidence);
    flex-basis: auto;
    z-index: 60;
    box-shadow: var(--shadow-lg);
    transform: translateX(0);
    transition: transform 0.18s ease-out;
  }
  .data-panel.collapsed {
    width: 86vw;             /* keep the width so it slides out instead of snapping to 0 */
    max-width: var(--rail-evidence);
    border-left: 1px solid var(--border);
    transform: translateX(100%);
  }
  @media (prefers-reduced-motion: reduce) {
    .sidebar, .data-panel, .sidebar-backdrop { transition: none; }
  }
}

@media (max-width: 900px) {
  .rec-grid { grid-template-columns: 1fr; }
  .catalog-grid { grid-template-columns: 1fr; }
  .ctx-fields { grid-template-columns: 1fr; }
  .messages.is-empty { padding: 32px 20px 40px; }
  .empty-state { gap: 32px; }
  .empty-state h1 { font-size: 30px; }
  .empty-state .hero p { font-size: 14.5px; }
  .composer { padding: 8px 20px 16px; }
  .bring-data { flex-direction: column; align-items: flex-start; gap: 12px; }
}

@media (max-width: 768px) {
  /* Reclaim the horizontal gutters — 28px each side crushed the column */
  .chat-header { padding: 0 14px; }
  .message { padding: 0 14px; }
  .upload-card { padding: 0 14px; }
  .composer { padding: 8px 14px 12px; }
  .messages.is-empty { padding: 24px 16px 32px; }

  .empty-state h1 { font-size: 26px; }
  .header-controls { gap: 6px; }
  .session-title { max-width: 34vw; }
  .message.user .bubble { max-width: 88%; }
  /* The composer control row is the first thing to run out of room; the two
     parameter pills are readouts and can wrap under the buttons. */
  .composer-controls { flex-wrap: wrap; }
  .cc-right { margin-left: auto; }
  .cc-hint { display: none; }
}

/* Context remains readable on narrow screens and with a long decision. */
.board-scope { max-width: 100%; text-align: left; }
.bs-summary { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.bs-value, .bs-decision { overflow-wrap: anywhere; }
.bs-decision { font-size: 12px; line-height: 1.5; color: var(--text-soft); }
.decision-shortcuts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.rec-kind { display: block; font-size: 11px; color: var(--text-soft); margin-bottom: 10px; }
.catalog > summary { cursor: pointer; padding: 8px 0; }
.catalog[open] > summary { margin-bottom: 12px; }
.board-retry { align-self: flex-start; margin-top: 6px; }
.board-retry[hidden] { display: none; }
.cat-kind { font-size: 10px; color: var(--text-soft); white-space: nowrap; margin-left: auto; }
.rec-kind { margin: 8px 0; }
