:root {
  --bg: #0e1116;
  --panel: #161b22;
  --panel-2: #1c222b;
  --border: #30363d;
  --text: #e6edf3;
  --muted: #8b949e;
  --accent: #7aa2f7;
  --accent-2: #bb9af7;
  --ok: #9ece6a;
  --err: #f7768e;
  --warn: #e0af68;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif; height: 100%; }
/* 100dvh (dynamic viewport height) so the app fits the VISIBLE area on mobile
   browsers with a collapsing toolbar. Plain 100vh is the LARGE viewport on iOS
   Safari, which pushed the chat composer behind the bottom toolbar and let the
   oversized shell scroll — clipping either the header or the input. vh first as
   a fallback for browsers without dvh. */
#app { display: grid; grid-template-rows: auto 1fr; height: 100vh; height: 100dvh; }
/* Header spans the full width; the nav rail is a fixed overlay (see below), not a grid column. */
#app > header { grid-column: 1; }
/* All full-page views + the chat/content wrapper fill the row below the header, padded to
   clear the fixed nav rail's collapsed width so the rail overlays rather than pushes them. */
#app > *:not(header):not(#nav-rail) { grid-column: 1; grid-row: 2; padding-left: 52px; }
header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 16px; border-bottom: 1px solid var(--border); background: var(--panel);
}
/* The ☰ header toggle is mobile-only (shown in the ≤767px block). !important to
   beat the `.btn.icon` display rule. */
.header-menu-toggle { display: none !important; }
/* Top-bar ONLY (the direct child of #app — NOT the per-card <header>s inside
   dashboards, nor the .fullview-toolbar headers inside <main>s): own stacking
   context above the content layer so the notification panel — which drops down
   out of the top bar — floats over project tiles (.home-tile:hover uses
   transform → its own stacking context) and dashboard chart titles. Kept below
   modal/overlay z-indexes (100+) so dialogs still cover the bar. */
#app > header { position: relative; z-index: 60; }
.logo { color: var(--accent); }
.muted { color: var(--muted); font-size: 12px; }
.left { display: flex; gap: 10px; align-items: baseline; }
.right { display: flex; gap: 8px; align-items: center; }

/* Desktop only (nav rail is hidden below 768px, and mobile keeps its own
   tuned scroll/keyboard-avoidance behavior — see the 100dvh note above).
   A subpage taller than the viewport can grow #app past 100vh (the same
   "1fr" grid-row-growth quirk the nav rail works around below), which
   otherwise drags the in-flow header up and off-screen with the rest of
   the page. Pin it like the nav rail so it's always visible, and give the
   content row matching top padding so it doesn't render underneath it. */
@media (min-width: 768px) {
  #app > header { position: fixed; top: 0; left: 0; right: 0; }
  #app > *:not(header):not(#nav-rail) { padding-top: var(--header-h, 72px); }
}

/* ── Left nav rail ─────────────────────────────────────────────────────────── */
/* Fixed overlay (top cleared to the header, bottom pinned to the viewport) so
   it never scrolls away with the page and is never resized by a subpage's
   own height quirks. */
#nav-rail {
  /* --header-h is measured live in app.js (the header's height isn't a fixed
     value — logo/badges/spend-bar content changes it) so the rail starts
     right below the header instead of underneath it. The 72px fallback only
     covers the instant before that first measurement lands (hidden behind
     the splash screen in practice). */
  position: fixed; top: var(--header-h, 72px); left: 0; bottom: 0;
  width: 52px; z-index: 55;
  transition: width 0.22s ease;
}
#nav-rail.expanded { width: max-content; }

#nav-rail-inner {
  height: 100%;
  width: 100%;
  background: var(--panel);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  padding: 6px 0 12px;
  gap: 2px;
  transition: box-shadow 0.22s ease;
  /* Short viewports (laptop windows, browser resized down) can't fit every
     nav item — scroll the rail internally instead of clipping the bottom
     entries. */
  overflow-y: auto; overflow-x: hidden;
}
#nav-rail.expanded #nav-rail-inner {
  box-shadow: 4px 0 24px rgba(60,40,15,.10);
}

/* Toggle button */
.nav-toggle-btn {
  width: 100%; min-height: 40px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--muted);
  cursor: pointer; flex-shrink: 0;
  transition: color .15s, background .15s;
}
.nav-toggle-btn:hover { color: var(--koro-black); background: var(--panel-2); }
.nav-toggle-btn .ic { width: 17px; height: 17px; flex-shrink: 0; }
.nav-toggle-collapsed-content { display: flex; align-items: center; }
.nav-toggle-expanded-content {
  display: none; align-items: center;
  font-size: 13px; font-family: var(--sans); white-space: nowrap;
}
.nav-toggle-expanded-content span { padding-left: 9px; }
#nav-rail.expanded .nav-toggle-collapsed-content { display: none; }
#nav-rail.expanded .nav-toggle-expanded-content { display: flex; width: 100%; justify-content: flex-end; padding: 0 14px 0 16px; }
#nav-rail.expanded .nav-toggle-btn { justify-content: flex-start; }
#nav-rail.expanded .nav-item { padding-right: 20px; }

.nav-rail-divider { height: 1px; background: var(--border); margin: 4px 10px 6px; flex-shrink: 0; }

/* Nav items — icon left-padded to visually center in 52px; label animates in */
.nav-item {
  display: flex; align-items: center;
  width: 100%; min-height: 38px;
  padding: 9px 0 9px 16px;
  background: transparent; border: none;
  color: var(--koro-black); cursor: pointer;
  text-align: left; white-space: nowrap;
  position: relative; /* for lock badge */
  transition: background .15s;
  flex-shrink: 0;
}
.nav-item:hover:not(:disabled) { background: var(--panel-2); }
.nav-item > .ic { width: 18px; height: 18px; flex-shrink: 0; }

/* Active item — the nav entry matching the current view/route. Uses a
   dedicated warm brown rather than --accent (blue) — the KoRo beige shell
   makes blue read as out of place here. */
.nav-item.active { background: var(--panel-2); color: #8b5e3c; box-shadow: inset 3px 0 0 #8b5e3c; }
.nav-item.active:hover:not(:disabled) { background: var(--panel-2); }

.nav-label {
  font-size: 13px; font-family: var(--sans);
  max-width: 0; overflow: hidden; opacity: 0;
  padding-left: 0;
  transition: max-width 0.22s ease, opacity 0.15s ease, padding-left 0.22s ease;
}
#nav-rail.expanded .nav-label { max-width: 160px; opacity: 1; padding-left: 11px; }

/* Tooltip — appears to the right in collapsed mode; hidden when expanded */
.nav-item[data-tip] { position: relative; }
.nav-item[data-tip]::after {
  content: attr(data-tip);
  position: absolute; left: calc(100% + 8px); top: 50%; transform: translateY(-50%);
  white-space: nowrap;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); font-size: 11.5px; line-height: 1.4;
  padding: 3px 7px; border-radius: 6px;
  pointer-events: none; z-index: 55;
  opacity: 0; transition: opacity 0.12s ease 0s;
}
.nav-item[data-tip]:hover::after { opacity: 1; transition-delay: 0.4s; }
#nav-rail.expanded .nav-item[data-tip]::after { display: none; }

/* Locked state in nav rail */
.nav-item.nav-locked { cursor: not-allowed; color: var(--muted); opacity: 0.45; }
.nav-item.nav-locked:hover { background: transparent; }
/* Collapsed: hide the lock badge entirely */
#nav-rail:not(.expanded) .nav-lock-badge { display: none; }
/* Expanded: inline in the flex row, same gap as icon → label */
#nav-rail.expanded .nav-lock-badge { position: static; margin-left: 11px; width: auto; height: auto; }
#nav-rail.expanded .nav-lock-badge .ic { width: 14px; height: 14px; }

/* Running app version, pinned to the foot of the rail (margin-top:auto in the
   #nav-rail-inner flex column). renderAppVersion() (app.js) sets the text and
   clears [hidden] once /api/me resolves, and re-sets [hidden] for a missing or
   untracked "0.0.0" version. Shown ONLY when the rail is expanded — collapsed
   shows nothing. The :not([hidden]) guard is required: a bare
   `#nav-rail.expanded #app-version { display:block }` would override the
   [hidden] attribute's display:none and reveal the empty slot for the 0.0.0
   case. Muted/small, matching the rail's label treatment.
   Both selectors are scoped to `#nav-rail` on purpose: an unscoped `#app-version`
   rule out-specifies any class-based rule on an element that happens to share the
   id elsewhere in the document (1-0-0 vs 0-1-0), which is how a second slot gets
   silently killed. The rail owns this id; the mobile slot uses its own
   (#app-version-mobile, revealed in the <768px block). */
#nav-rail #app-version {
  display: none;
  margin-top: auto;
  padding: 10px 16px 0;
  font-size: 11px; color: var(--muted);
  white-space: nowrap; flex-shrink: 0;
}
#nav-rail.expanded #app-version:not([hidden]) { display: block; }

/* Mobile app-version slot — hidden by default; the <768px block below reveals
   it. Desktop shows the version in the rail instead, so exactly one of the two
   slots is ever visible. */
#app-version-mobile { display: none; }
/* ── End nav rail ──────────────────────────────────────────────────────────── */

/* Daily spend indicator — compact text + bar pair. The fill color reflects
   user spend ratio (green → yellow → red). Workspace pressure surfaces via
   the title tooltip rather than a second bar to keep the header tight. */
.spend-indicator {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px; border-radius: 6px;
  background: var(--panel-2); border: 1px solid var(--border);
  font-size: 11px; line-height: 1;
  font-family: var(--mono);
  cursor: default;
}
.spend-indicator[hidden] { display: none; }
.spend-indicator .spend-text { color: var(--text); white-space: nowrap; }
.spend-indicator .spend-bar {
  position: relative;
  display: inline-block;
  width: 64px; height: 6px;
  background: var(--bg); border-radius: 3px; overflow: hidden;
  border: 1px solid var(--border);
}
.spend-indicator .spend-bar-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 0%;
  background: var(--ok);
  transition: width 240ms ease, background-color 240ms ease;
}
/* Tiered color states — kept on the parent so the text matches the fill. */
.spend-indicator.medium .spend-bar-fill { background: var(--warn); }
.spend-indicator.medium .spend-text { color: var(--warn); }
.spend-indicator.high .spend-bar-fill { background: var(--err); }
.spend-indicator.high .spend-text { color: var(--err); }
/* Workspace-cap pressure: subtle red border on the whole pill — independent
   of user-level state, since a workspace block can hit even when the
   user's own bar is empty. */
.spend-indicator.workspace-warn { border-color: var(--err); }

.content { display: grid; grid-template-columns: 260px 1fr; min-height: 0; position: relative; }
/* Collapsed project sidebar (dashboard view) — the dashboard header toggle and
   deeplinks set this; restored when leaving the dashboard view. */
/* Single column when collapsed — the sidebar is removed from the grid
   (display:none), so .main-col must be the ONLY column or it lands in a
   leftover 0-width track and the dashboard renders squished. */
.content.sidebar-collapsed { grid-template-columns: 1fr; }
.content.sidebar-collapsed > #sidebar { display: none; }
/* In-sidebar "hide" button (top-right) — collapses the sidebar from any view. */
#sidebar { position: relative; }
/* Round, like the Data & context column's hide button on the other side. */
#sidebar-collapse-btn {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 32px; height: 32px; display: grid; place-items: center; padding: 0;
  border-radius: 99px; border: 1px solid var(--line-soft, var(--border)); background: var(--card-surface, var(--panel));
  color: var(--text); cursor: pointer; transition: background .15s, color .15s, border-color .15s;
}
#sidebar-collapse-btn:hover { background: #fff; color: var(--koro-black, #1a150d); border-color: #cdb79b; }
#sidebar-collapse-btn:focus-visible, #sidebar-extender:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Left-edge extender — a tab that pokes out from the screen edge when the
   sidebar is collapsed, with a "›" chevron, to bring the sidebar back. */
#sidebar-extender {
  /* left: past the nav rail — .content's padding-left (52px, line 27) sits under
     the rail, which covered a tab at left: 0 and made the sidebar unrecoverable. */
  display: none; position: absolute; left: 52px; top: 50%; transform: translateY(-50%); z-index: 6;
  width: 30px; height: 52px; padding: 0; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-left: none; background: var(--panel);
  color: var(--koro-black, #1a150d); cursor: pointer;
  border-radius: 0 14px 14px 0; box-shadow: 2px 2px 10px rgba(60,40,15,.14);
  transition: transform .15s, background .15s;
}
#sidebar-extender:hover { background: #fff; transform: translateY(-50%) translateX(3px); }
.content.sidebar-collapsed #sidebar-extender { display: flex; }

/* Desktop default: hide the mobile back bar entirely. */
.mobile-back-bar { display: none; }

/* ======= Mobile layout (< 768px) ======= */
@media (max-width: 767px) {
  /* Hard guard against ANY horizontal page overflow. If any deeply-nested
     element ends up wider than the viewport (an inline-styled input, a
     pre/code block, a Chart.js canvas that ignores responsive sizing, a
     legacy fixed-width image, etc.), the page would otherwise become
     horizontally scrollable and visually clip content. Clipping at
     html/body kills the scrollbar; the offending element still renders
     inside that bound (its own `overflow-x: auto` if any will scroll
     internally instead of dragging the whole page). Apply with !important
     to win over any sibling rule that might set overflow on these. */
  html, body { max-width: 100vw; overflow-x: hidden !important; }
  #app { max-width: 100vw; overflow-x: hidden; }
  #nav-rail { display: none !important; }
  /* The rail (and with it the desktop app-version display) is gone here, and
     mobile has no in-project sidebar either — so surface the version at the
     foot of the home screen instead. :not([hidden]) keeps the empty slot hidden
     for a missing / untracked "0.0.0" version, exactly like the rail's rule. */
  #app-version-mobile:not([hidden]) {
    display: block;
    padding: 18px 2px 4px;
    font-size: 11px; color: var(--muted);
    letter-spacing: 0.04em; user-select: text;
  }
  #app > *:not(header):not(#nav-rail) { grid-column: 1; padding-left: 0; }
  /* Same guard one level deeper for every full-page <main> view. Caps the
     content area at viewport so a wide child can't push the parent wider. */
  #home-view, #schema-view, #schema-edit-view,
  #memory-view, #memory-edit-view, #settings-view,
  #dashboard-view, #chat, .content {
    max-width: 100vw; overflow-x: hidden;
  }

  /* Mobile has NO in-project sidebar. Navigation happens on the home screen
     (My chats / dashboards / reports + their "New" buttons) and the header ←
     (home) button returns there — so the chat/report/dashboard LIST sidebar is
     always hidden and the main panel (chat or dashboard) always fills the area.
     The desktop sidebar-collapse + left-edge extender don't apply here either. */
  .content { display: block; height: 100%; }
  aside#sidebar { display: none !important; }
  #sidebar-extender { display: none !important; }
  .main-col {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr auto;
    min-width: 0; max-width: 100vw; height: 100%;
  }
  /* Belt-and-suspenders: every direct child of .main-col must respect the
     viewport width so a wide bubble / pre / table can't push the column
     wider via min-content. */
  .main-col > * { min-width: 0; max-width: 100%; }
  /* Long unbreakable strings in user bubbles (SQL identifiers, URLs, hashes)
     must wrap rather than widen the bubble. */
  .turn.user .bubble,
  .turn.assistant .bubble,
  .turn.assistant .md,
  .turn.assistant .text { overflow-wrap: anywhere; word-break: break-word; min-width: 0; }
  /* Pre/code blocks inside markdown should scroll horizontally inside the
     bubble — never push the bubble wider. */
  .md pre { overflow-x: auto; max-width: 100%; min-width: 0; }
  .md pre code { white-space: pre; }

  /* Mobile back bar inside main-col. Fixed height (48px ≈ iOS native nav bar)
     so the grid `auto` track can't grow with button padding / title wrapping.
     Without this, the back button's 40px min-height + 16px vertical padding +
     potential title wrap pushed the bar to 60–80px, which on an empty chat
     made the bar dominate the upper viewport. The internal back button gets
     its own scoped min-height so the global 40px touch-target rule from
     `.btn` below doesn't fight this. */
  /* Mobile title bar at the top of the chat/dashboard panel — shows the current
     chat (or dashboard) NAME. No "← Back" button: there's no sidebar to return
     to; the header ← (home) is the way back. Fixed 48px height so the grid
     `auto` row can't grow with padding / a wrapping title. */
  .mobile-back-bar {
    display: flex; align-items: center; gap: 10px;
    height: 48px; min-height: 48px; max-height: 48px;
    padding: 0 14px;
    border-bottom: 1px solid var(--border);
    background: var(--panel);
    box-sizing: border-box;
    flex-shrink: 0;
  }
  #mobile-back-btn { display: none; }
  .mobile-back-bar .btn { min-height: 32px; padding: 4px 10px; font-size: 13px; }
  .mobile-back-bar .mobile-main-title {
    flex: 1; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 14px;
  }

  /* Trim header + dashboard toolbar so they wrap nicely on narrow screens. */
  header { padding: 8px 12px; flex-wrap: wrap; gap: 8px; }
  /* Phone: drop the bar, keep the dollar text — the pill alone communicates
     the headroom roughly enough. Saves ~70px on a 393px viewport. */
  .spend-indicator .spend-bar { display: none; }
  .spend-indicator { padding: 2px 6px; font-size: 10.5px; }
  header .left, header .right { gap: 6px; }
  /* Collapsed header by default: just the logo lockup + back button + a ☰ toggle.
     The decorative tagline is dropped to keep it logo-clean. */
  .header-menu-toggle { display: inline-flex !important; align-items: center; justify-content: center; line-height: 1; font-size: 18px; padding: 4px; }
  header .tagline { display: none; }
  /* The spend bar + ~10 nav icons + sign-out (.right) are hidden until the user
     taps ☰ — then they drop onto their own wrapped row below the logo. EXCEPTION:
     the notification bell (#notif-wrap) stays visible in the collapsed bar so
     alerts are never buried in the menu. `display:none` on .right would hide the
     bell too (a child of a display:none parent can't be revealed), so instead
     keep .right itself displayed and hide only its OTHER children when collapsed.
     In the collapsed bar margin-left:auto pushes the bell to the right, next to ☰. */
  header .right { display: flex; }
  header:not(.nav-open) .right { margin-left: auto; }
  header:not(.nav-open) .right > *:not(#notif-wrap) { display: none !important; }
  header.nav-open .right {
    display: flex; flex-wrap: wrap; justify-content: flex-end;
    row-gap: 6px; flex-basis: 100%; order: 3;
  }
  header .muted { display: none; }
  /* Admin badge is decorative — recoverable from the ⚙ Settings view if needed. */
  header #admin-badge { display: none; }
  /* The "Become first admin" bootstrap button is rare + edge-case — hide its
     text on phones to save space (icon-free button shows as "BFA" if visible). */
  header #admin-bootstrap { font-size: 11px; padding: 4px 8px; }

  /* iOS auto-zooms when an <input> with font-size < 16px is focused.
     16px on every focusable form control prevents that jarring zoom flip. */
  input[type="text"], input[type="search"], input[type="email"],
  input[type="url"], input[type="number"], input[type="password"],
  input[type="tel"], input[type="date"], textarea, select {
    font-size: 16px;
  }
  /* Inputs default to full-width on phone — no narrow fixed mins. */
  #schema-search, #memory-filter { min-width: 0 !important; width: 100%; flex: 1 1 100%; }

  /* Touch targets: Apple HIG recommends 44x44, we aim for 40x40 minimum so
     the layout stays compact. */
  .btn { min-height: 40px; }
  .btn.small { min-height: 32px; }
  .btn.icon { min-width: 40px; min-height: 40px; padding: 6px 10px; }

  /* Dashboard header: stack title + actions vertically so buttons get room.
     Title h2 is hidden on mobile because the mobile-back-bar already shows
     the same name pinned at the top — repeating it just steals screen real
     estate. The description (when present) still renders below. */
  .dashboard-header { flex-direction: column; align-items: stretch; gap: 10px; padding-bottom: 10px; margin-bottom: 12px; }
  .dashboard-header-left h2#dashboard-title { display: none; }
  .dashboard-header-right { flex-wrap: wrap; gap: 6px; }
  .dashboard-header-right .btn { font-size: 12px; padding: 6px 10px; min-height: 36px; }
  /* Single-column card grid on narrow screens — cards lose their 440px min.
     `minmax(0, 1fr)` (not bare `1fr`) — a Chart.js canvas + 4 action icons +
     long card title easily push the card's min-content past viewport width.
     Bare `1fr` resolves to `minmax(auto, 1fr)` whose auto-minimum is the
     child min-content, so the column track expands past the viewport →
     action icons get clipped at the right edge. */
  .dashboard-grid { --dash-cols: 1; grid-template-columns: minmax(0, 1fr); gap: 10px; }
  /* A card may carry an inline `grid-column: span N` (app.js) on wider screens.
     On mobile _dashApplyCardGeometry clears that inline span, but keep the
     hard override too as a belt-and-braces guard: a stray span in the single
     explicit-column mobile grid would make CSS Grid auto-create implicit extra
     columns → a cramped 2-up layout with a 0px column that squishes content to
     vertical text. Force every card to the one column. (#311) */
  .dashboard-grid > .dashboard-card { grid-column: 1 / -1 !important; }
  .dashboard-card { min-width: 0; max-width: 100%; overflow: hidden; }
  .dashboard-card header { min-width: 0; }
  .dashboard-card header .title { min-width: 0; overflow-wrap: anywhere; }
  .dashboard-card header .actions { flex-shrink: 0; }
  .dashboard-card .chart-wrap { width: 100%; max-width: 100%; box-sizing: border-box; }
  .dashboard-card .chart-wrap canvas { max-width: 100% !important; }
  #dashboard-view { padding: 14px 12px; min-width: 0; max-width: 100vw; }
  .dashboard-card .chart-wrap { height: 240px; }

  /* Home tiles: single column on phones. Same minmax(0, …) discipline so a
     long project name + emoji can't push the tile wider than viewport. */
  /* .home-inner is a flex item of its (flex) <main> shell — default min-width:auto
     lets it grow to content intrinsic size and overflow the viewport (e.g. the
     Memory list rows ran off-screen so titles never reached their ellipsis).
     min-width:0 + max-width:100% pins it to the viewport so children truncate. */
  .home-inner { padding: 20px 14px 40px; gap: 24px; min-width: 0; max-width: 100%; }
  .home-projects { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .home-section-head { flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
  .home-section-head h1 { font-size: 20px; }
  .home-section-head h2 { font-size: 15px; }

  /* Schema view: search + buttons row should wrap below the title. */
  #schema-view .home-section-head > div { width: 100%; }
  /* Schema table rows: stack desc + meta below name. minmax(0,1fr) lets the
     name column shrink below its content so a long table FQN can wrap instead
     of forcing the card wider than the viewport. */
  .schema-table-row {
    grid-template-columns: minmax(0, 1fr) auto; gap: 6px; padding: 10px 4px;
  }
  .schema-table-row .desc { grid-column: 1 / -1; white-space: normal; font-size: 11.5px; }
  .schema-table-row .meta { grid-column: 1 / -1; flex-wrap: wrap; }
  /* Long mono identifiers (table FQNs / field names / inline code) wrap rather
     than overflow. `anywhere` is safe here — single-element labels in the
     VANILLA schema view, not multi-column React tables (see mobile-react-overflow). */
  .schema-table-row .name,
  .schema-dataset-head h3,
  .schema-field-row .name,
  #schema-view code { overflow-wrap: anywhere; word-break: break-word; white-space: normal; min-width: 0; }
  .schema-dataset-group { padding: 12px 14px; }

  /* Schema-edit / memory-edit / any full-view toolbar: wrap actions so 4
     buttons + title fit a 360px viewport. */
  .fullview-toolbar { flex-wrap: wrap; gap: 8px; }
  .fullview-toolbar-left,
  .fullview-toolbar-right { flex-wrap: wrap; gap: 6px; }
  .fullview-toolbar-right .btn { font-size: 12px; padding: 6px 10px; }

  /* Multi-column grids inside forms — collapse to single column on phones. */
  .schedule-row { grid-template-columns: 1fr; gap: 8px; }
  .schema-field-row { grid-template-columns: 1fr; gap: 8px; }
  .schema-field-row .row-extra { grid-template-columns: 1fr; }
  /* Project-edit "emoji + name" grid → stack. */
  #project-form > div[style*="grid-template-columns: 80px 1fr"] {
    grid-template-columns: 1fr !important;
  }

  /* Chat: smaller padding so the canvas isn't choked by 16px of margin. */
  main#chat { padding: 12px; gap: 12px; }
  .turn.user .bubble { max-width: 88%; }

  /* Markdown blocks inside assistant turns: horizontal scroll, not overflow. */
  .md pre, .md table { max-width: 100%; }
  .md table { display: block; overflow-x: auto; }

  /* Tool blocks: SQL pre should never blow out the viewport. */
  .tool pre.sql { font-size: 11.5px; max-width: 100%; overflow-x: auto; }
  .tool-body { max-height: 320px; }
  .tool-body table { font-size: 11px; min-width: 0; }
  .tool-body th, .tool-body td { padding: 5px 7px; }

  /* Dashboard tables: horizontal scroll already enabled, but tighten cells. */
  .table-card-wrap { max-height: 320px; }
  .dashboard-table th, .dashboard-table td { padding: 8px 10px; max-width: 200px; }

  /* Charts: full container, capped Chart.js canvas. */
  .chart-wrap canvas { max-width: 100%; }
  .tool-body .chart-wrap { height: 260px; }

  /* Composer: keep prompt + Send fitting; hint shrinks. */
  footer#composer-wrap { padding: 8px 10px 10px; }
  #composer { gap: 6px; }
  #composer #attach-btn { padding: 6px 8px; }
  #prompt { font-size: 16px; min-height: 44px; }
  .hint { font-size: 11px; }
  /* The admin auto-continue toggle has a long label — let it wrap below
     the hint copy rather than push the row sideways. */
  .composer-toggle { margin-left: 0; padding-left: 0; border-left: none; display: flex; margin-top: 4px; }

  /* Sidebar block click targets: chevron summary needs enough vertical room. */
  .block-header { padding: 10px 8px; min-height: 44px; }
  .block-header .btn.primary { min-height: 32px; }
  ul#chat-list .chat-item,
  #reports-list .report-item,
  #dashboard-list .dashboard-item { min-height: 40px; padding: 8px 10px; }
  /* Edit/delete buttons must always be visible (no hover on touch). */
  ul#chat-list .chat-item .del,
  ul#chat-list .chat-item .edit,
  ul#chat-list .chat-item .share,
  #dashboard-list .dashboard-item .share { opacity: 1; }

  /* Modals: take the full viewport, no rounded corners. */
  #modal-backdrop, #report-modal-backdrop, #run-modal-backdrop,
  #share-modal-backdrop, #share-report-modal-backdrop, #dashboard-modal-backdrop,
  #save-chart-modal-backdrop, #save-table-modal-backdrop,
  #build-chart-modal-backdrop, #build-table-modal-backdrop,
  #adapt-chart-modal-backdrop,
  #narrative-card-modal-backdrop,
  #scorecard-modal-backdrop,
  #notes-modal-backdrop,
  #copy-card-modal-backdrop,
  .modal-backdrop {
    padding: 0; align-items: stretch;
  }
  .modal, .modal.modal-wide {
    width: 100%; max-width: 100%; max-height: 100vh; height: 100vh;
    max-height: 100dvh; height: 100dvh; /* fit visible area on mobile (toolbar) */
    border-radius: 0; border-left: none; border-right: none;
  }
  .modal-header { padding: 10px 14px; }
  .modal-header h2 { font-size: 15px; }
  .modal form,
  .modal-body { padding: 12px 14px; gap: 10px; }
  .modal-footer { padding: 10px 14px; flex-wrap: wrap; margin: 14px -14px -12px; gap: 6px; }
  .modal-footer .btn { flex: 1 1 auto; min-width: 0; }
  .modal-footer .spacer { display: none; }

  /* Usage section in Settings: the JS-built KPI cards have an inline
     `min-width: 120px` and gap: 16px. Five cards × 120 + 4 × 16 = 664px
     which sprengs every phone viewport, and the parent flex container
     has no explicit width so cards expand into overflow rather than
     wrapping. Override the inline min-width + force the parent to
     respect viewport. Also size the chart container so Chart.js's
     responsive sizing has a constrained box to fit to. */
  #usage-summary {
    width: 100%;
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px !important;
  }
  #usage-summary > * {
    min-width: 0 !important;
    padding: 6px 10px !important;
  }
  #usage-summary > * div:last-child {
    font-size: 15px !important;
  }
  #usage-chart {
    width: 100%; max-width: 100%; height: 180px !important;
    overflow: hidden;
    position: relative;  /* contain absolutely-positioned canvas if any */
  }
  #usage-chart canvas {
    max-width: 100% !important; width: 100% !important;
    height: 180px !important;
  }
  /* Absolute belt-and-suspenders for the settings-pane: nothing inside
     may project past its own width. Stops the chart canvas and the
     KPI-tiles grid from being individually responsible for overflow. */
  .settings-pane > * { max-width: 100%; }
  .settings-pane * { max-width: 100%; }
  /* User-table (also in Settings → Users): keep horizontal scroll for
     wide content but cap its width so the page doesn't overflow. */
  .user-table-wrap { width: 100%; overflow-x: auto; }

  /* Settings: horizontal scrollable nav (existing rule extended).
     IMPORTANT: minmax(0, 1fr) — bare `1fr` is `minmax(auto, 1fr)` where
     `auto` is the grid item's min-content. The nav has white-space:nowrap
     items, so its min-content is wider than the phone viewport, which
     expands the grid track past 100vw and sprengs the layout. minmax(0, …)
     lets the track shrink below intrinsic content width so the inner
     overflow-x: auto on .settings-nav actually does the scrolling. */
  .settings-layout { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
  .settings-sidebar { min-width: 0; max-width: 100vw; }
  .settings-nav { min-width: 0; max-width: 100vw; }
  .settings-sidebar { border-right: none; border-bottom: 1px solid var(--border); }
  .settings-sidebar-head { padding: 10px 12px 6px; flex-direction: row; align-items: center; justify-content: space-between; }
  .settings-sidebar-head h1 { font-size: 16px; }
  .settings-nav {
    flex-direction: row; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    padding: 4px 8px 8px; gap: 4px;
  }
  .settings-nav-item { white-space: nowrap; padding: 8px 10px; font-size: 12.5px; min-height: 36px; }
  .settings-pane { padding: 16px 14px 40px; }

  /* User table: horizontal scroll wrapper still works, but cells tighten. */
  .user-table th, .user-table td { padding: 6px 8px; font-size: 11.5px; }
  .user-table .col-projects { min-width: 200px; }

  /* Member toolbar (settings, share, reports — universal "input + button" row).
     Stack so the input gets full width. */
  .member-toolbar { flex-wrap: wrap; }
  .member-toolbar input,
  .member-toolbar select { flex: 1 1 100%; min-width: 0; }
  .member-toolbar .btn { flex-shrink: 0; }

  /* Memory list: keep tappable, hide narrow metadata pill if cramped. */
  ul.memory-list-full .memory-item { padding: 12px 14px; }
}

/* Phone-only overrides (≤480px) — fires AFTER the tablet+phone block above.
   These apply only when there's truly no horizontal room left. */
@media (max-width: 480px) {
  /* Hide the "bq-chat" wordmark on phones; the ◆ logo + page context is enough. */
  header .left strong { display: none; }
  /* Sign in / out buttons: hide the verbose text, the icon font in the parent
     is gone so we'll keep the text but shrink. */
  header #signin-btn, header #signout-btn { font-size: 11px; padding: 6px 8px; }
  header { padding: 6px 8px; gap: 6px; }
  header .btn.icon { min-width: 36px; min-height: 36px; padding: 4px 8px; font-size: 13px; }

  /* Home + section heads: smaller fonts so they don't wrap into 3 lines. */
  .home-section-head h1 { font-size: 18px; }
  .home-section-head h2 { font-size: 14px; }
  .home-inner { padding: 14px 12px 32px; gap: 18px; }

  /* Schema view header: drop the muted tagline to a smaller font. */
  #schema-view .home-section-head h1,
  #memory-view .home-section-head h1 { font-size: 17px; }
  .schema-dataset-head h3 { font-size: 13px; }

  /* Schema-edit / memory-edit toolbar: the page title goes on its own line
     so all 4 action buttons remain reachable above the form. */
  .fullview-toolbar { padding: 6px 10px; gap: 6px; }
  .fullview-toolbar h1 { width: 100%; order: 99; font-size: 13px; max-width: 100%; }
  /* "Cancel" duplicates the "← Back" button — hide on phones. */
  #sc-cancel, #mf-cancel { display: none; }
  .fullview-body { padding: 12px 10px 32px; gap: 12px; }
  .fullview-body .settings-section h3 { font-size: 13px; }

  /* Chat header buttons inside the dashboard view are particularly cramped. */
  .dashboard-header-right .btn { font-size: 11.5px; padding: 5px 8px; }
  .dashboard-header-left h2 { font-size: 16px; }

  /* Dashboard card heights: smaller charts so two are visible without
     scrolling on a 700-tall viewport. */
  .dashboard-card .chart-wrap { height: 200px; }
  .dashboard-card { padding: 10px; }
  .dashboard-card header .title { font-size: 12.5px; }

  /* Chat message bubbles: more aggressive max-width so user-side bubbles
     don't crowd the viewport edge. */
  .turn.user .bubble { max-width: 92%; padding: 8px 12px; font-size: 13.5px; }

  /* Composer: hide the long auto-continue label entirely on tiny screens;
     admins can still toggle from tablet+. */
  .composer-toggle { display: none !important; }
  /* Attach button: icon-only, no extra padding. */
  #composer #attach-btn { min-width: 36px; }
  #send-btn { padding: 8px 12px; }

  /* Sidebar projects header (project block). */
  .sidebar-project-name { font-size: 15px; }
  .project-block { padding: 10px; gap: 6px; }

  /* Pills (sidebar project-meta, schema meta) — keep readable. */
  .pill, .project-meta .pill, .schema-table-row .meta .pill { font-size: 10px; padding: 1px 6px; }

  /* Modal footers: stack buttons vertically so each is a full tap-target row.
     `flex-wrap` and `flex` both have to be reset here, or the stack lays itself
     out SIDEWAYS and leaves the primary action off-dialog:
       - `flex-wrap: wrap` is inherited from the max-width:767px block above,
         where the footer is still a ROW and wrapping is what you want. Once
         `flex-direction` is `column`, wrapping means wrapping into extra
         COLUMNS, to the right — and `.modal` is `overflow: hidden`, so they are
         clipped rather than scrollable.
       - `flex: 1 1 100%` sets flex-BASIS along the main axis, which in a column
         is the HEIGHT: every button asked for 100% of the footer's height, so
         two of them could never share one column and the second wrapped out of
         the dialog. `#filter-save` sat ~225px past its right edge, i.e. the
         Filters modal could not be saved at all below 480px.
     `flex: 0 0 auto` + `width: 100%` is the full-width-row shape actually
     wanted; `nowrap` keeps the stack in one column. */
  .modal-footer { flex-direction: column; align-items: stretch; gap: 8px; flex-wrap: nowrap; }
  .modal-footer .btn { width: 100%; flex: 0 0 auto; }
  .modal-footer > .spacer { display: none; }

  /* Settings nav items: hide the emoji icon if rendered as a separate span. */
  .settings-nav-item { padding: 6px 10px; font-size: 12px; }
  .settings-nav-icon { font-size: 14px; width: 16px; }
  .settings-pane { padding: 14px 10px 32px; }
  .settings-section h3 { font-size: 13.5px; }

  /* Member list / runs list: less vertical padding, smaller font. */
  .member-list li { padding: 8px 10px; font-size: 12px; }
  .runs-list li { padding: 8px; font-size: 11.5px; }

  /* User table: horizontal scroll is fine, but tighten further. */
  .user-table th, .user-table td { padding: 5px 6px; font-size: 11px; }
  .user-table .col-email, .user-table .col-projects { min-width: 160px; }

  /* Tool blocks: narrower SQL preview. */
  .tool pre.sql { font-size: 11px; padding: 6px 8px; }
  .tool { padding: 6px 8px; }
  .tool summary { gap: 6px; flex-wrap: wrap; }


  /* Schema-proposals diff grid → single column on phones (the side-by-side
     before/after isn't useful at 320px). */
  .proposal-card .diff { grid-template-columns: 1fr; }
  .proposal-card .diff > div { max-height: 180px; }
}

aside#sidebar {
  background: var(--panel); border-right: 1px solid var(--border);
  display: flex; flex-direction: column; min-height: 0;
}
.project-block {
  padding: 12px; border-bottom: 2px solid var(--border);
  display: flex; flex-direction: column; gap: 8px;
  background: linear-gradient(to bottom, rgba(0, 150, 191, 0.04), transparent);
  flex: 0 0 auto;       /* pinned at top — never shrinks or grows */
}
/* Project name as the prominent sidebar header. */
.sidebar-project-name {
  margin: 0; font-size: 16px; font-weight: 600; color: var(--text);
  line-height: 1.2; word-break: break-word;
}
/* All collapsible sections live inside this scrollable wrapper so the sidebar
   never overflows the viewport. Individual lists no longer need their own
   max-heights — the wrapper handles total overflow. */
.sidebar-scroll {
  flex: 1 1 0; min-height: 0; overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column;
}
aside#sidebar { overflow-x: hidden; }
.block-label {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--muted);
}
.project-row { display: flex; gap: 6px; }
.project-row select {
  flex: 1; background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 6px 8px; font: inherit; font-weight: 500;
}
.project-row select:focus { outline: none; border-color: var(--accent); }

.project-meta {
  font-size: 11px; color: var(--muted);
  display: flex; flex-wrap: wrap; gap: 6px;
  min-height: 18px;
}
.project-meta .pill {
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 8px; font-size: 10.5px;
}
.project-meta .pill.accent { border-color: var(--accent); color: var(--accent); }

.btn.ghost {
  background: transparent; border-style: dashed; color: var(--muted);
  font-size: 12px; padding: 6px 8px;
}
.btn.ghost:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); background: var(--panel-2); }

.collapsible {
  display: flex; flex-direction: column; min-height: 0;
  padding-left: 4px;
  border-left: 3px solid var(--accent);
  margin: 8px 0 0 8px;
  overflow: hidden;
}
/* `.collapsible` sets display:flex, which beats the default [hidden] rule — so
   gating a sidebar section via the `hidden` attribute needs this override. */
.collapsible[hidden] { display: none; }
.collapsible > summary {
  list-style: none; cursor: pointer; outline: none; user-select: none;
}
.collapsible > summary::-webkit-details-marker { display: none; }
.collapsible .chevron {
  display: inline-block; width: 10px; height: 10px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg); transition: transform 0.15s; margin-right: 6px;
  flex-shrink: 0;
}
.collapsible[open] > summary .chevron { transform: rotate(45deg); }
/* All sidebar sections size to their content; the .sidebar-scroll wrapper
   handles overflow when the combined height exceeds the viewport. */
.chats-block[open] { flex: 0 0 auto; }
.reports-block { flex: 0 0 auto; margin-top: 12px; border-left-color: var(--accent-2); }
.reports-block[open] { flex: 0 0 auto; }

/* ---- Memory full-page list ---- */
#memory-view {
  display: flex; flex-direction: column; min-height: 100%;
  background: var(--bg);
}
#memory-view[hidden] { display: none; }
/* Desktop-only generous padding for the memory view. The previous rule had
   no media-query wrapper so it bled onto phones, where high specificity
   (1,0,1) silently beat the mobile .home-inner override (0,1,0) and forced
   32px gutters on a 375px viewport. */
@media (min-width: 768px) {
  #memory-view .home-inner { padding: 32px 32px 48px; }
}

ul.memory-list-full {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
ul.memory-list-full .memory-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-radius: 8px;
  cursor: pointer; font-size: 13.5px;
  border: 1px solid var(--border); background: var(--panel);
}
ul.memory-list-full .memory-item:hover {
  background: var(--panel-2); border-color: var(--accent);
}
ul.memory-list-full .memory-item .title {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.memory-list-full .memory-item .type {
  font-size: 10px; padding: 2px 7px; border-radius: 4px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em;
  flex-shrink: 0;
}
ul.memory-list-full .memory-item .type.finding { background: rgba(80, 161, 67, 0.15); color: var(--ok); }
ul.memory-list-full .memory-item .type.hypothesis { background: rgba(237, 149, 0, 0.15); color: var(--warn); }
ul.memory-list-full .memory-item .type.decision { background: rgba(0, 150, 191, 0.15); color: var(--accent); }
ul.memory-list-full .memory-item .type.reference { background: rgba(150, 120, 211, 0.15); color: var(--accent-2); }
ul.memory-list-full .memory-item.archived { opacity: 0.5; }
ul.memory-list-full .memory-item.archived .title::before { content: "🗄 "; }
ul.memory-list-full .memory-item .memory-owner {
  font-size: 11.5px; color: var(--muted); font-family: var(--mono);
  flex-shrink: 0;
}
/* Governed-memory pill — blessed institutional context, visible in governed chats. */
.gov-badge {
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 1px 6px; border-radius: 3px; flex-shrink: 0;
  background: rgba(80, 161, 67, 0.15); color: var(--ok);
}
ul.memory-list-full .memory-item.shared-in .title { font-style: italic; }
ul.memory-list-full .empty {
  color: var(--muted); font-size: 13px; padding: 32px; text-align: center;
  border: 1px dashed var(--border); border-radius: 8px;
}

/* ---- Inline memory results in list_memories tool blocks ----
   Renders inside `.tool-body` of a list_memories <details>. Each row is
   marked "used" via JS when a subsequent get_memory in the same turn
   fetches the matching id; the marker + green-tinted border make it
   visually obvious which entries Claude actually read in full versus
   merely scanned the title list. */
ul.memory-results-inline {
  list-style: none; margin: 4px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
ul.memory-results-inline .memory-result-item {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 6px 8px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--panel-2);
  font-size: 12px;
}
ul.memory-results-inline .memory-result-item.used {
  border-color: var(--ok);
  background: rgba(80, 161, 67, 0.06);
}
ul.memory-results-inline .memory-result-item .type {
  font-size: 9px; padding: 1px 6px; border-radius: 3px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em;
  flex-shrink: 0; margin-top: 1px;
}
ul.memory-results-inline .memory-result-item .type.finding { background: rgba(80, 161, 67, 0.15); color: var(--ok); }
ul.memory-results-inline .memory-result-item .type.hypothesis { background: rgba(237, 149, 0, 0.15); color: var(--warn); }
ul.memory-results-inline .memory-result-item .type.decision { background: rgba(0, 150, 191, 0.15); color: var(--accent); }
ul.memory-results-inline .memory-result-item .type.reference { background: rgba(150, 120, 211, 0.15); color: var(--accent-2); }
ul.memory-results-inline .memory-result-main {
  flex: 1; min-width: 0;
}
ul.memory-results-inline .memory-result-title {
  font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.memory-results-inline .memory-result-preview {
  font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.memory-results-inline .memory-used-marker {
  font-size: 13px; color: var(--ok); font-weight: 700;
  flex-shrink: 0; min-width: 16px; text-align: right;
  /* Empty by default — populated to "✓" by markMemoryUsedFromBlock(). */
}

/* ---- Schema discovery: find_relevant_tables + get_table_schema ----
   Mirrors the inline-memory-results pattern so the schema side of the chat
   reads at a glance: which tables Claude considered, and which it actually
   fetched the full column schema for. */
ul.schema-results-inline {
  list-style: none; margin: 4px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
ul.schema-results-inline .schema-result-item {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 6px 8px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--panel-2);
  font-size: 12px;
}
ul.schema-results-inline .schema-result-item.used {
  border-color: var(--ok);
  background: rgba(80, 161, 67, 0.06);
}
ul.schema-results-inline .schema-result-item .type.schema-icon {
  font-size: 9px; padding: 1px 6px; border-radius: 3px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em; flex-shrink: 0;
  margin-top: 1px;
  background: rgba(0, 150, 191, 0.15); color: var(--accent);
}
ul.schema-results-inline .schema-result-main { flex: 1; min-width: 0; }
ul.schema-results-inline .schema-result-title {
  font-weight: 600; color: var(--text); font-family: var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.schema-results-inline .schema-result-meta {
  font-size: 11px; font-family: var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.schema-results-inline .schema-result-desc {
  font-size: 11px; margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
ul.schema-results-inline .schema-used-marker {
  font-size: 13px; color: var(--ok); font-weight: 700;
  flex-shrink: 0; min-width: 16px; text-align: right;
}

/* ---- Governed metric read-tool summaries ----
   Renders inside `.tool-body` for get_metric / query_metric / metric_info /
   metric_dimension_values / resolve_from_graph. These tools return structured
   objects (not BigQuery rows), so instead of an empty rows table we show a
   compact key/value summary + chip lists so an expanded call is self-reading:
   what metric, what came back, what the graph resolver planned. */
dl.tool-kv {
  display: grid; grid-template-columns: max-content 1fr;
  gap: 2px 12px; margin: 6px 0 0; font-size: 12px; align-items: baseline;
}
dl.tool-kv dt { color: var(--muted); white-space: nowrap; }
dl.tool-kv dd {
  margin: 0; color: var(--text); min-width: 0;
  overflow-wrap: anywhere; word-break: break-word;
}
.tool-kv-sub {
  font-size: 10px; font-weight: 600; color: var(--text);
  text-transform: uppercase; letter-spacing: 0.05em;
  margin: 10px 0 4px;
}
.tool-kv-sub.muted { color: var(--muted); }
.tool-chips { display: flex; flex-wrap: wrap; gap: 4px; margin: 2px 0 0; }
.tool-chip {
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 1px 8px; font-size: 11px; font-family: var(--mono); color: var(--text);
}
.tool-dyn-note {
  font-size: 11px; margin: 6px 0; padding: 5px 8px;
  border-radius: 6px; color: var(--warn);
  background: rgba(224, 175, 104, 0.08); border: 1px solid rgba(224, 175, 104, 0.25);
}
ul.tool-gaps {
  list-style: none; margin: 2px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 3px; font-size: 11.5px;
}
ul.tool-gaps li {
  color: var(--text); padding-left: 12px; position: relative;
  overflow-wrap: anywhere;
}
ul.tool-gaps li::before {
  content: "•"; position: absolute; left: 0; color: var(--muted);
}
/* find_relevant_metrics: ranked candidate list (key + name, description, dims). */
ul.metric-hits {
  list-style: none; margin: 6px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
ul.metric-hits li {
  padding: 6px 8px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--panel-2);
}
.metric-hit-title { font-weight: 600; color: var(--text); font-family: var(--mono); font-size: 12px; }
.metric-hit-desc { font-size: 11.5px; margin-top: 1px; }
.metric-hit-dims { font-size: 11px; margin-top: 2px; font-family: var(--mono); overflow-wrap: anywhere; }

/* get_table_schema body — the fetched schema markdown is verbose; render in
   a scroll-capped <pre> so it doesn't dominate the chat scroll position. */
pre.schema-fetched-md {
  margin: 4px 0 0; padding: 8px 10px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
  font-family: var(--mono); font-size: 11px; line-height: 1.45;
  white-space: pre-wrap; word-break: break-word;
  max-height: 320px; overflow-y: auto;
}

/* ---- Reports library (📄 header view) ----
   Reuses ul.memory-list-full row scaffold; adds report-specific badges and
   action-button cluster on the right. Source-type pill mirrors the memory
   `.type` colour-coded approach for at-a-glance scanability. */
ul.memory-list-full .report-item .type.report-scheduled {
  background: rgba(0, 150, 191, 0.15); color: var(--accent);
}
ul.memory-list-full .report-item .type.report-chat {
  background: rgba(150, 120, 211, 0.15); color: var(--accent-2);
}
/* Title + source subtitle stacked. The wrapper takes the flex:1 slot that
   the bare .title used to own; the title still ellipsis-clips, and the
   muted subline shows project + owner attribution. */
ul.memory-list-full .report-item .report-title-wrap {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
ul.memory-list-full .report-item .report-title-wrap .title {
  flex: 0 0 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.memory-list-full .report-item .report-source-sub {
  font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
ul.memory-list-full .report-item .report-share-pill {
  font-size: 10.5px; padding: 2px 7px; flex-shrink: 0;
  background: var(--panel-2); color: var(--muted);
  border: 1px solid var(--border); border-radius: 4px;
}
/* ============================================================
   Dashboard country filter — settings pane (inside the filter overlay, #282)
   ============================================================ */
.filter-row {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 13px;
}
.filter-row input[type="checkbox"] { margin-top: 3px; flex-shrink: 0; }

/* Checkboxes/radios must never stretch to a form-control's full width and push
   their label onto a new line — keep them intrinsic-sized everywhere. */
input[type="checkbox"], input[type="radio"] { width: auto; flex: 0 0 auto; }

/* Universal safety net: ANY <label> that directly wraps a checkbox/radio lays
   out as an inline row (box on the left, text/description on the right) so a
   toggle and its label/description never stack vertically. Zero specificity via
   :where() → explicit component classes (.filter-row, .check-inline, …) still
   win, and browsers without :has() simply fall back to those classes. */
:where(label:has(> input[type="checkbox"]), label:has(> input[type="radio"])) {
  display: flex;
  /* Explicit: a column-direction rule elsewhere is exactly what broke this. */
  flex-direction: row;
  align-items: flex-start;
  gap: 8px;
}

/* Shared "checkbox + label (with optional sub-description) on one row" layout —
   used by card-edit + the global filter dialog so toggles render consistently. */
.check-inline {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 12px; cursor: pointer;
}
.check-inline > input[type="checkbox"] { margin: 2px 0 0; }
.check-inline > span { line-height: 1.35; }
.check-inline > span > small { display: block; font-size: 11px; margin-top: 1px; }
.filter-options-wrap {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  background: var(--panel-2);
  display: flex; flex-direction: column; gap: 8px;
  transition: opacity 100ms ease;
}
.filter-options-header {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 11.5px; font-weight: 600; color: var(--muted);
  letter-spacing: 0.04em; text-transform: uppercase;
}
/* Country-option SELECTION MODE (#271): explicit list / all markets in the data
   / named market group, with the per-mode explanation under each radio. */
.filter-mode-fieldset {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px 6px;
  margin: 0;
  display: flex; flex-direction: column; gap: 6px;
  background: var(--bg);
  /* A `fieldset` carries `min-width: min-content` from the UA stylesheet, which
     floors it at the widest thing inside — here the mode descriptions and the
     group picker — so it grew WIDER than the dialog and `.modal`'s
     `overflow: hidden` clipped the excess: at 380-400px the group <select> and
     the form's Create/Override buttons were cut off with nothing to scroll.
     `min-width: 0` is the fieldset-specific case of the rule in CLAUDE.md
     ("Don't let a control overflow its container"); the column-flex children
     then wrap instead of setting a floor. */
  min-width: 0;
}
.filter-mode-fieldset > legend {
  padding: 0 4px;
  font-size: 11.5px; font-weight: 600; color: var(--muted);
  letter-spacing: 0.04em; text-transform: uppercase;
}
.filter-mode-row {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 12px; cursor: pointer;
}
.filter-mode-row > input[type="radio"] { margin: 2px 0 0; }
.filter-mode-row > span { line-height: 1.35; }
.filter-mode-row > span > small { display: block; font-size: 11px; margin-top: 1px; }
/* Where market groups come from + the inline "define a group" form (#271). The
   mode is only usable if a group can actually be created from here. */
.filter-group-note {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  font-size: 11px; line-height: 1.4;
  margin: 4px 0 0 22px;
}
.filter-group-form {
  display: flex; flex-direction: column; gap: 6px;
  width: 100%;
  border: 1px solid var(--border); border-radius: 6px;
  padding: 8px; background: var(--bg);
}
.filter-group-form input {
  width: 100%; box-sizing: border-box; min-width: 0;
  font-size: 13px; padding: 5px 8px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
}
.filter-group-form-actions { display: flex; gap: 6px; justify-content: flex-end; }
/* Create / Edit / Delete affordances under the picker (#417). Its own flex row
   (inside the wrapping .filter-group-note) so the buttons stay on one line and
   wrap together, not interleaved with the description text. */
.filter-group-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* What the chosen dynamic mode currently resolves to (read-only preview). */
.filter-mode-resolved {
  font-size: 11.5px; line-height: 1.4;
  border-left: 2px solid var(--accent, #1f6feb);
  padding: 2px 0 2px 8px;
}
/* "The data has markets this dashboard doesn't" — non-blocking, one click adds
   them as option rows (the user still has to Save). */
.filter-new-values {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 11.5px; line-height: 1.4;
  color: var(--warn, #ed9500);
  background: rgba(237, 149, 0, 0.12);
  border: 1px solid rgba(237, 149, 0, 0.35);
  border-radius: 6px;
  padding: 6px 8px;
}
.filter-options-list { display: flex; flex-direction: column; gap: 6px; }
.filter-option-row {
  display: grid;
  grid-template-columns: 1fr 2fr auto;
  gap: 6px;
  align-items: center;
}
.filter-option-row input {
  font-size: 13px; padding: 5px 8px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  /* A grid/flex item's default `min-width: auto` floors it at its MIN-CONTENT
     width — and an <input>'s min-content is its intrinsic ~20-character width,
     which no `1fr`/`2fr` can shrink below. Two inputs plus the remove button
     therefore stayed ~400px wide inside a narrower modal and spilled out of
     `.modal { overflow: hidden }`, clipping the values field and the × button
     entirely on a narrow window. min-width: 0 is what lets the fr units win. */
  min-width: 0;
}
.filter-option-remove {
  padding: 2px 8px;
  background: transparent;
  color: var(--muted);
}
.filter-option-remove:hover { color: #ff7b72; background: rgba(199, 84, 80, 0.10); }
.filter-howto-details summary {
  font-size: 12px; color: var(--muted); cursor: pointer;
  padding: 2px 0; user-select: none;
}
.filter-howto-pre {
  font-family: var(--mono, monospace);
  font-size: 11.5px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 8px 10px;
  overflow: auto;
}

/* Country pills. Since #282 the market options are radio rows inside the
   filter overlay (.dashboard-filter-panel, styled near .dashboard-body), which
   restyles `.country-pill` there; these base styles + the scope note remain. */
.country-pill {
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 11px;
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.1s ease;
  font-family: inherit;
  white-space: nowrap;
}
.country-pill:hover:not(:disabled) {
  background: var(--panel);
  color: var(--text);
  border-color: var(--muted);
}
.country-pill.active {
  background: var(--accent, #1f6feb);
  color: white;
  border-color: var(--accent, #1f6feb);
}
.country-pill:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
/* "All" is a scoped subset of the data — a non-blocking disclosure next to the
   pills (#271). Amber, not red: the numbers are correct for the configured
   scope, they're just narrower than the warehouse. Click opens the country
   settings in the filter overlay (editors only). */
.country-scope-note {
  color: var(--warn, #ed9500);
  background: rgba(237, 149, 0, 0.14);
  border: 1px solid rgba(237, 149, 0, 0.40);
  border-radius: 999px;
  padding: 3px 10px;
  margin-left: 4px;
  font-size: 11px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.country-scope-note:disabled { cursor: default; }
.country-scope-note:hover:not(:disabled) { filter: brightness(0.97); }

.dashboard-time-range-select {
  background: var(--panel-2, #f2f2f2);
  color: var(--text, #111);
  border: 1px solid var(--border, #ddd);
  border-radius: 999px;
  padding: 4px 10px;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.dashboard-time-range-select:disabled { opacity: 0.6; cursor: default; }

/* ============================================================
   Dashboard scheduled refresh + version picker
   ============================================================ */
/* Scheduled refresh — a section of the ⚙ Settings modal (was its own modal). */
.df-schedule {
  /* A fieldset's UA `min-width: min-content` would refuse to shrink and get
     clipped by `.modal { overflow: hidden }` (CLAUDE.md, #466). */
  min-width: 0; margin: 6px 0 0; padding: 10px 0 0;
  border: 0; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 8px;
}
.df-schedule[hidden],
/* `.modal form label { display: flex }` outranks the UA [hidden] rule, so the
   per-frequency Day-of-week / Day-of-month fields need it spelled out. */
.df-schedule label[hidden] { display: none; }
.df-schedule > legend { padding: 0; margin-bottom: 4px; }
.df-schedule .schedule-fieldset { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.df-schedule .schedule-fieldset input,
.df-schedule .schedule-fieldset select { width: 100%; min-width: 0; box-sizing: border-box; }
.df-schedule .schedule-status { padding: 0; }
.df-schedule-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.df-schedule-actions .error-text:empty { display: none; }
.schedule-row {
  display: flex; align-items: center; gap: 10px;
  font-size: 13px;
  margin-bottom: 4px;
}
.schedule-fieldset {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px 14px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  transition: opacity 100ms ease;
}
.schedule-fieldset label {
  display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--muted);
}
.schedule-fieldset input,
.schedule-fieldset select {
  font-size: 13px; padding: 6px 10px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
}
.schedule-status {
  font-size: 11.5px;
  padding: 6px 0;
  /* Wrap nicely for long "next run / last run / error" strings. */
  word-break: break-word;
}

/* Data-version button (#282): the same compact button as the rest of the header
   and the tab row; the versions are picked in #dashboard-version-panel. */
.dashboard-version-btn { max-width: 260px; }
.dashboard-version-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dashboard-version-icon { flex: 0 0 auto; }
/* The popover borrows the filter overlay's chrome (.dashboard-filter-panel). */
.dashboard-version-panel .dvp-group { font-size: 12px; padding: 10px 2px 2px; }
.dashboard-version-panel .dvp-opt.active { font-weight: 500; }
.dashboard-version-panel .dvp-opt small { margin-left: 4px; font-size: 11.5px; }

/* Snapshot-view banner — sits above the tab strip + grid. Loud-but-
   not-alarming purple/blue accent that signals "you're not on live". */
.dashboard-snapshot-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 0 12px;
  padding: 8px 14px;
  background: rgba(91, 141, 239, 0.14);
  border: 1px solid rgba(91, 141, 239, 0.40);
  border-radius: 6px;
  font-size: 12.5px;
  color: var(--text);
}
.dashboard-snapshot-banner strong { color: #7aa8ff; }

/* ============================================================
   Dashboard tab strip (S2 — separate card-sets per tab)
   ============================================================ */
/* Real tabs (#282 follow-up): the strip sits on the beige page, tabs are
   bottom-aligned and touch the tab-content container (.dashboard-body) below.
   Inactive tabs are quiet text; the active one takes the container's light
   colour and merges with it, with concave fillets (::before/::after) where it
   meets the container's top edge. */
/* One row, never wrapped (#282): the tabs scroll horizontally in their own track
   (.dashboard-tabs-scroll, scrollbar hidden; the wheel scrolls it too) and the
   tab-level actions keep their place at the right end. A gradient fades the
   tabs out where they run under the edge (.fade-right / .fade-left, set by
   _wireTabScroller). */
.dashboard-tabs {
  display: flex; flex-wrap: nowrap; gap: 12px;
  padding: 0; margin: 0;
  align-items: flex-end;
}
.dashboard-tabs-viewport { position: relative; display: flex; flex: 1 1 auto; min-width: 0; }
.dashboard-tabs-scroll {
  display: flex; gap: 2px; align-items: flex-end; flex: 1 1 auto; min-width: 0;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none; overscroll-behavior-x: contain;
}
.dashboard-tabs-scroll::-webkit-scrollbar { display: none; }
.dashboard-tabs-scroll > .dashboard-tab { flex: 0 0 auto; }
.dashboard-tabs-viewport::before,
.dashboard-tabs-viewport::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 56px; z-index: 1;
  pointer-events: none; opacity: 0; transition: opacity 120ms ease;
}
.dashboard-tabs-viewport::before { left: 0; background: linear-gradient(to left, transparent, var(--bg)); }
.dashboard-tabs-viewport::after { right: 0; background: linear-gradient(to right, transparent, var(--bg)); }
.dashboard-tabs-viewport.fade-left::before,
.dashboard-tabs-viewport.fade-right::after { opacity: 1; }
.dashboard-tab {
  position: relative;
  background: transparent;
  color: var(--muted);
  border: 0;
  border-radius: 10px 10px 0 0;
  padding: 10px 18px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.1s ease, color 0.1s ease;
  white-space: nowrap;
  touch-action: manipulation; /* no double-tap-zoom delay on a tab tap */
}
.dashboard-tab:hover { background: var(--panel-2); color: var(--text); }
.dashboard-tab.active,
.dashboard-tab.active:hover {
  background: var(--panel);
  color: var(--text);
  font-weight: 600;
}
.dashboard-tab.active::before,
.dashboard-tab.active::after {
  content: ""; position: absolute; bottom: 0; width: 10px; height: 10px; pointer-events: none;
}
.dashboard-tab.active::before {
  left: -10px; background: radial-gradient(circle at 0 0, transparent 9.5px, var(--panel) 10px);
}
.dashboard-tab.active::after {
  right: -10px; background: radial-gradient(circle at 100% 0, transparent 9.5px, var(--panel) 10px);
}
/* First tab: flush with the container's square corner, no left fillet. */
.dashboard-tab.active:first-child::before { display: none; }
/* "+ Tab" is a ghost tab: the same box as a tab (padding, font, top-rounded
   corners, bottom-aligned), drawn as a dashed outline with no bottom edge. */
.dashboard-tab-add {
  background: transparent;
  color: var(--muted);
  border: 1px dashed var(--border);
  border-bottom: 0;
  margin-left: 4px;
}
.dashboard-tab-add:hover {
  background: transparent;
  color: var(--accent, #1f6feb);
  border-color: var(--accent, #1f6feb);
  border-style: solid;
}
/* Drop-target highlight while a card is being dragged over a tab. */
.dashboard-tab.drop-target {
  background: var(--accent, #1f6feb) !important;
  color: white !important;
  border-color: var(--accent, #1f6feb) !important;
  border-style: solid !important;
  transform: scale(1.05);
  box-shadow: 0 0 0 3px rgba(31, 111, 235, 0.25);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}

/* ===== Tab DnD reorder + kebab menu (owner-only affordances) ===== */
/* The strip is a positioning context for the absolutely-positioned
   menu popover that the kebab button opens. */
.dashboard-tabs { position: relative; }

/* Layout the label + kebab inside each tab button. Keep the existing
   padding values from .dashboard-tab; the kebab adds 18px of width on
   hover. */
.dashboard-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.dashboard-tab-label {
  /* Label takes natural width; tab as a whole stays content-sized. */
  pointer-events: none;
}
.dashboard-tab-kebab {
  /* Hidden by default; revealed on tab hover. Owner-only — non-owners
     never get a kebab appended, so the selector is fine targeting all
     instances. */
  opacity: 0;
  font-size: 14px;
  line-height: 1;
  letter-spacing: -1px;
  color: inherit;
  padding: 0 2px;
  margin: 0 -4px 0 2px;
  border-radius: 3px;
  user-select: none;
  cursor: pointer;
  transition: opacity 100ms ease, background 100ms ease;
}
.dashboard-tab:hover .dashboard-tab-kebab,
.dashboard-tab.active .dashboard-tab-kebab {
  opacity: 0.7;
}
.dashboard-tab-kebab:hover {
  opacity: 1 !important;
  background: rgba(27, 22, 14, 0.08);
}

/* Drag state for tab reorder. opacity-only — no layout impact. */
.dashboard-tab.dragging { opacity: 0.4; cursor: grabbing; }

/* Drop indicators for tab-on-tab reorder. Box-shadow on the left or
   right edge of the target tab. Doesn't trigger layout — same approach
   as the scorecard-row drop indicators. */
.dashboard-tab.drop-tab-before {
  box-shadow: -3px 0 0 -1px var(--accent), -3px 0 8px -2px var(--accent);
}
.dashboard-tab.drop-tab-after {
  box-shadow: 3px 0 0 -1px var(--accent), 3px 0 8px -2px var(--accent);
}

/* Kebab popover menu — appears below the tab on click. */
.dashboard-tab-menu {
  position: absolute;
  z-index: 50;
  min-width: 140px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.35);
  display: flex; flex-direction: column;
}
.dashboard-tab-menu-item {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px;
  background: transparent;
  color: var(--text);
  border: none;
  text-align: left;
  cursor: pointer;
  border-radius: 4px;
  font-size: 13px;
}
.dashboard-tab-menu-item:hover { background: var(--panel-2); }
.dashboard-tab-menu-item.dtm-danger { color: #ff7b72; }
.dashboard-tab-menu-item.dtm-danger:hover { background: rgba(199, 84, 80, 0.18); }
.dashboard-tab-menu-item .dtm-icon { font-size: 12px; opacity: 0.85; }

/* ============================================================
   Dashboard "+ New" menu (consolidated add-card popup)
   ============================================================ */
.dashboard-new-wrap { position: relative; display: inline-block; }
/* The "+ Component" menu (#282): one compact line per component — icon centred
   against a title and a one-line subtitle — in the same card chrome as the
   dashboard's other overlays. */
.dashboard-new-menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  z-index: 50;
  background: var(--panel, #fff);
  border: 1px solid var(--border, #e6e6e6);
  border-radius: 14px;
  box-shadow: 0 16px 40px rgba(27, 22, 14, 0.16), 0 2px 6px rgba(27, 22, 14, 0.06);
  width: 480px; max-width: calc(100vw - 24px); box-sizing: border-box;
  padding: 6px;
  display: flex; flex-direction: column;
}
.dashboard-new-menu[hidden] { display: none; }
.dashboard-new-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  padding: 6px 10px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text);
  transition: background 0.1s ease;
}
.dashboard-new-item:hover:not([disabled]) { background: var(--panel-2, #f7f7f9); }
.dashboard-new-item[disabled] { cursor: not-allowed; opacity: 0.55; }
.dni-icon { font-size: 18px; line-height: 1; flex: 0 0 22px; text-align: center; }
.dni-text { display: flex; flex-direction: column; gap: 0; min-width: 0; flex: 1 1 auto; }
.dni-title { font-size: 13px; font-weight: 500; line-height: 1.35; }
.dni-sub {
  font-size: 11.5px; line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dni-soon {
  font-size: 9.5px;
  padding: 1px 6px;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-left: 6px;
  vertical-align: middle;
  font-weight: normal;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.dashboard-new-divider {
  height: 1px;
  background: var(--border, #e6e6e6);
  margin: 3px 8px;
}

/* ============================================================
   KPI hero card (dashboard card type="kpi")
   ============================================================ */
.kpi-card-wrap {
  display: flex; flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  padding: 14px 4px 8px;
  min-height: 72px;
}
.kpi-value {
  font-size: 30px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--text);
  font-feature-settings: "tnum" 1;
  letter-spacing: -0.01em;
}
.kpi-delta {
  font-size: 12.5px;
  margin-top: 6px;
  font-weight: 500;
  font-feature-settings: "tnum" 1;
}
.kpi-delta-good { color: #2ea043; }
.kpi-delta-bad { color: #c75450; }
.kpi-delta-flat { color: var(--muted); }
.kpi-delta-muted { color: var(--muted); font-weight: normal; font-size: 11.5px; }

/* ============================================================
   Narrative & Action-Items cards (S3 interactive dashboards)
   ============================================================ */

/* Wrap around the rendered markdown body. Two flavors share most of the
   styling — the only divergence is in spacing because <ul> lists need
   different rhythm than prose paragraphs. */
.narrative-card-body {
  padding: 6px 4px 4px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text);
}
.narrative-card-body p {
  margin: 0 0 10px;
}
.narrative-card-body p:last-child {
  margin-bottom: 0;
}
.narrative-card-body ul,
.narrative-card-body ol {
  margin: 0; padding-left: 20px;
  display: flex; flex-direction: column; gap: 8px;
}
.narrative-card-body li {
  padding-left: 2px;
}
.narrative-card-body li::marker {
  color: var(--muted);
}
.narrative-card-body strong { font-weight: 600; }
.narrative-card-body code {
  font-size: 12px;
  background: var(--code-bg, rgba(127,127,127,0.12));
  padding: 1px 5px; border-radius: 4px;
}

/* Highlight tag spans — wrapped around analytically-loaded phrases by the
   model. Keep these subtle but visually-distinct: the mockup uses tinted
   pills, not loud blocks. */
.hl {
  font-weight: 600;
  border-radius: 4px;
  padding: 0 4px;
  /* tighten line-height so the pills don't visually disrupt prose flow */
  line-height: 1.4;
}
.hl-good {
  color: #56d364;
  background: rgba(46, 160, 67, 0.18);
}
.hl-bad {
  color: #ff7b72;
  background: rgba(199, 84, 80, 0.18);
}
.hl-warn {
  color: #f0b03e;
  background: rgba(212, 152, 38, 0.18);
}
.hl-num {
  /* Numbers — bold + tabular figures, no background tint, so they read
     as emphasized values rather than tagged spans. color:inherit so a
     <num> nested inside a <good>/<bad>/<warn> picks up the parent's
     status color (the renderer now handles nested tags, which the model
     occasionally produces despite the system-prompt ban). */
  color: inherit;
  background: transparent;
  font-feature-settings: "tnum" 1;
  padding: 0;
}

/* Inline "💬 Explore in chat" button — appears at the end of each
   action item whose next step is a data investigation. Server (Claude)
   marks these items by wrapping a seed prompt in <explore>...</explore>;
   the renderer replaces the tag with this button and stashes the prompt
   in data-prompt. Click → spawn a new chat pre-filled with that prompt. */
.action-explore-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 8px;
  padding: 2px 9px;
  font-size: 11px;
  font-weight: 600;
  background: rgba(91, 141, 239, 0.14);
  color: #7aa8ff;
  border: 1px solid rgba(91, 141, 239, 0.30);
  border-radius: 999px;
  cursor: pointer;
  vertical-align: baseline;
  transition: background 100ms ease, border-color 100ms ease, transform 80ms ease;
  /* Stop the button from breaking awkwardly across lines mid-emoji. */
  white-space: nowrap;
  user-select: none;
}
.action-explore-btn:hover {
  background: rgba(91, 141, 239, 0.24);
  border-color: rgba(91, 141, 239, 0.55);
}
.action-explore-btn:active { transform: scale(0.97); }
.action-explore-btn .ic-chat { width: 12px; height: 12px; flex: 0 0 auto; }

/* Footer line beneath the rendered body: shows the prompt that produced
   this content so the reader / editor can sanity-check the framing. */
.narrative-card-meta {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px dashed var(--border);
  font-size: 11.5px;
  font-style: italic;
  color: var(--muted);
  /* Single-line clip with ellipsis. The full prompt is in the title
     attribute (tooltip) so hover-to-read still works. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Narrative-card modal — a textarea-heavy form, no scrolling tables. */
#narrative-card-modal-backdrop .modal {
  width: min(560px, 100%);
}
#narrative-card-modal-backdrop textarea {
  width: 100%;
  min-height: 110px;
  resize: vertical;
  font-family: inherit;
  font-size: 13.5px;
  line-height: 1.45;
}

/* ============================================================
   Notes / decisions card — user-authored markdown + @mentions
   ============================================================ */

/* Modal */
#notes-modal-backdrop .modal { width: min(620px, 100%); }
#notes-modal-backdrop label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11.5px; color: var(--muted);
}
#notes-modal-backdrop input,
#notes-modal-backdrop textarea {
  font-family: inherit;
  font-size: 13.5px;
  padding: 8px 10px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  line-height: 1.5;
}
#notes-modal-backdrop textarea {
  min-height: 200px;
  resize: vertical;
}

/* Toolbar above the notes content textarea — markdown shortcuts. */
.notes-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 4px 4px 0 0;
  margin-bottom: -1px; /* overlap textarea's top border for a unified strip */
}
.notes-toolbar + textarea {
  border-top-left-radius: 0 !important;
  border-top-right-radius: 0 !important;
}
.notes-tool {
  background: transparent;
  border: none;
  color: var(--muted);
  padding: 3px 8px;
  font-size: 12.5px;
  border-radius: 3px;
  cursor: pointer;
  font-family: inherit;
  line-height: 1.3;
  min-width: 26px;
}
.notes-tool:hover {
  background: rgba(255,255,255,0.08);
  color: var(--text);
}
.notes-tool:active { transform: translateY(1px); }
.notes-tool b, .notes-tool i { font-style: normal; font-weight: 600; }
.notes-tool i { font-style: italic; }
.notes-tool-sep {
  width: 1px;
  height: 18px;
  background: var(--border);
  margin: 0 4px;
  flex-shrink: 0;
}

/* Body rendering of the notes card on the dashboard. */
.notes-card-body {
  padding: 4px 4px 0;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text);
}
.notes-card-body p { margin: 0 0 10px; }
.notes-card-body p:last-child { margin-bottom: 0; }
.notes-card-body ul, .notes-card-body ol {
  margin: 6px 0; padding-left: 20px;
  display: flex; flex-direction: column; gap: 4px;
}
.notes-card-body strong { font-weight: 600; }
.notes-card-body code {
  font-size: 12px;
  background: var(--code-bg, rgba(127,127,127,0.12));
  padding: 1px 5px; border-radius: 4px;
}
.notes-card-body blockquote {
  margin: 6px 0;
  padding: 4px 12px;
  border-left: 3px solid var(--border);
  color: var(--muted);
  font-style: italic;
}
.notes-card-meta {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px dashed var(--border);
  font-size: 11px;
  font-style: italic;
}

/* @mention chip — same shape as the highlight pills used elsewhere
   so the visual vocabulary stays coherent. Blue (accent) because
   mentions are people, not status. */
.mention-chip {
  display: inline-block;
  font-weight: 600;
  color: #7aa8ff;
  background: rgba(91, 141, 239, 0.16);
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 0.92em;
  cursor: default;
  user-select: none;
}

/* @-mention autocomplete dropdown. Positioned absolute relative to the
   viewport because the body element it's anchored under sits inside a
   modal-body container that's overflow:auto — using fixed positioning
   avoids any scroll-container clipping. z-index above the modal. */
.mention-dropdown {
  position: fixed;
  z-index: 150;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.40);
  max-height: 220px;
  overflow-y: auto;
  display: flex; flex-direction: column;
  padding: 4px;
}
.mention-dropdown[hidden] { display: none; }
.mention-item {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 10px;
  background: transparent;
  color: var(--text);
  border: none;
  text-align: left;
  cursor: pointer;
  border-radius: 4px;
  font-size: 13px;
}
.mention-item:hover, .mention-item.active {
  background: var(--panel-2);
}
.mention-name { font-weight: 600; }
.mention-email { font-size: 11px; }

/* ============================================================
   KPI Scorecard card — daily-engine-check multi-row table
   ============================================================ */

/* Description line + scope chip row above the table. Renders only when
   the card has meta to show; new cards skip it entirely. */
.scorecard-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.scorecard-meta {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;
  font-size: 11.5px;
  padding-bottom: 2px;
}
.scorecard-desc { flex: 1 1 auto; min-width: 0; }
.scorecard-scope-chip {
  display: inline-flex; align-items: center;
  padding: 2px 10px;
  background: rgba(91, 141, 239, 0.18);
  color: #7aa8ff;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

/* The scorecard table itself. Sticky header rows + zebra body so 10+
   rows stay readable. Inside a resized card the body becomes the
   scroll container, so overflow handling on .dashboard-card-body
   (already set elsewhere) takes care of long rows. */
.scorecard-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 12.5px;
  font-feature-settings: "tnum" 1;
}
.scorecard-table thead th {
  position: sticky;
  top: 0;
  background: var(--panel-2);
  color: var(--muted);
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 8px 10px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  z-index: 1;
}
.scorecard-table tbody td {
  padding: 10px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.scorecard-table tbody tr:last-child td { border-bottom: none; }
.scorecard-table tbody tr:hover td { background: rgba(255,255,255,0.025); }
.scorecard-table .col-num { text-align: right; white-space: nowrap; color: var(--muted); }
.scorecard-table .col-trend { width: 110px; text-align: center; }
.scorecard-table .col-status { width: 110px; text-align: center; }
.scorecard-table .col-kpi { min-width: 160px; }
.scorecard-table .scorecard-today { color: var(--text); font-weight: 600; font-size: 13.5px; }

/* ===== Row drag-and-drop reorder (owners only) =====
   Grip column is a 18px-wide drag handle on the left of each row.
   The row becomes draggable only when mousedown lands on the grip
   (set in JS) — clicks elsewhere still select text / fire normally.
   Drop indicators are top/bottom 2px accent bars rendered via
   pseudo-elements on the target <tr>, so the table layout never
   shifts during the drag. */
.scorecard-table .col-grip {
  width: 18px;
  padding: 8px 4px;
  color: var(--muted);
  cursor: grab;
  user-select: none;
  text-align: center;
  font-size: 12px;
  letter-spacing: -2px;
  /* Hidden until row hover so the grip column doesn't add visual noise
     for the 99% of the time the user isn't reordering. */
  opacity: 0;
  transition: opacity 100ms ease;
}
.scorecard-table.scorecard-reorderable tbody tr:hover .col-grip,
.scorecard-table.scorecard-reorderable tbody tr.dragging .col-grip {
  opacity: 0.7;
}
.scorecard-table .col-grip:active { cursor: grabbing; }
.scorecard-table tbody tr.dragging { opacity: 0.4; }
/* Drop indicator — box-shadow on the row instead of an absolute-positioned
   pseudo-element. Earlier version toggled `position: relative` on the
   target <tr> mid-drag, which triggered a table-layout recalc on every
   `dragover` event and produced visible "jumping" as rows shifted by a
   subpixel. box-shadow is a paint-only property: it doesn't affect
   layout, so the drag is now stable.
   The 0 / -3px / 0 / -1px spread keeps the bar visually 2px tall with a
   1px inset so it doesn't overflow the cell border. */
.scorecard-table tbody tr.drop-target-before {
  box-shadow: 0 -3px 0 -1px var(--accent), 0 -3px 8px -2px var(--accent);
}
.scorecard-table tbody tr.drop-target-after {
  box-shadow: 0 3px 0 -1px var(--accent), 0 3px 8px -2px var(--accent);
}

/* YoY column — two-line cell: percent delta (colored) above raw value
   (muted). Compact so it doesn't push the table to scroll horizontally
   on standard card widths. */
.scorecard-yoy-cell { line-height: 1.25; }
.scorecard-yoy-delta { font-weight: 600; font-size: 12.5px; }
.scorecard-yoy-value { font-size: 11px; margin-top: 1px; }
.scorecard-yoy-hint  { font-size: 10px; margin-top: 1px; font-style: italic; }

/* The yoy_enabled / future-toggles row in the modal meta — checkbox + label */
.scorecard-checkbox-row {
  flex-direction: row !important;
  align-items: flex-start;
  gap: 10px;
}
.scorecard-checkbox-row input[type="checkbox"] {
  margin-top: 3px;
  flex-shrink: 0;
}

.scorecard-kpi-name { font-weight: 600; color: var(--text); }

/* Per-row country override chip — small accent pill next to the KPI
   name. Indicates the row is locked to a specific market regardless
   of the dashboard's pill bar. */
.scorecard-country-chip {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #7aa8ff;
  background: rgba(91, 141, 239, 0.16);
  border-radius: 999px;
  vertical-align: middle;
  cursor: default;
}
/* "Inherit-from-dashboard" variant — slightly muted to distinguish
   from an explicit lock chip. Same color family so the visual
   vocabulary stays coherent. */
.scorecard-country-chip-inherit {
  color: var(--muted);
  background: rgba(255, 255, 255, 0.05);
  border: 1px dashed rgba(255, 255, 255, 0.10);
  padding: 0 6px;
  font-weight: 500;
}

/* Expand/collapse toggle for scorecard rows that have per-country
   breakdowns. Sits left of the KPI name. */
.scorecard-row-toggle {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 11px;
  padding: 0 4px 0 0;
  margin-right: 2px;
  line-height: 1;
  user-select: none;
  transition: color 100ms ease;
}
.scorecard-row-toggle:hover { color: var(--text); }
.scorecard-row-toggle.expanded { color: var(--text); }

/* Per-country child row inside a scorecard table. Indented + lighter
   so it visually nests under the parent row. */
.scorecard-row-child {
  background: rgba(255, 255, 255, 0.02);
}
.scorecard-row-child .col-kpi { padding-left: 24px; }
.scorecard-row-child-name {
  font-size: 12px;
}
.scorecard-row-child-indent {
  color: var(--muted);
  margin-right: 6px;
  font-family: var(--mono, monospace);
}
.scorecard-row-child .col-num,
.scorecard-row-child .scorecard-today {
  font-size: 12px;
  font-weight: 500;
}

/* Card-header scope chip — appears top-right of a card's header when
   the card is country-aware (its SQL contains {{country_codes}}).
   Mirrors the row-level chip's visual vocabulary so the scope signal
   is consistent across card-level and row-level affordances. */
.card-scope-chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  font-size: 10px;
  font-weight: 600;
  border-radius: 999px;
  white-space: nowrap;
  user-select: none;
  cursor: default;
}
.card-scope-chip-filtered {
  color: #7aa8ff;
  background: rgba(91, 141, 239, 0.16);
}
.card-scope-chip-global {
  color: var(--muted);
  background: rgba(255, 255, 255, 0.05);
  border: 1px dashed rgba(255, 255, 255, 0.10);
  padding: 0 7px;
  font-weight: 500;
}
.card-scope-chip-stale {
  color: var(--warn, #ed9500);
  background: rgba(237, 149, 0, 0.14);
  border: 1px solid rgba(237, 149, 0, 0.40);
  cursor: help;
}
/* A card whose governed metric is degraded (stale feed / pending re-validation).
   Deliberately subtle — the number IS valid, it's computed from older data, so
   this must read as a caveat rather than as an error state. The chip in the
   header carries the detail; this only tints the border so the card is
   identifiable when scanning a full dashboard. */
.card-metric-degraded {
  border-color: rgba(237, 149, 0, 0.35) !important;
}

.card-scope-chip-na {
  color: var(--muted);
  background: rgba(127, 127, 127, 0.10);
  border: 1px dashed rgba(127, 127, 127, 0.30);
  font-weight: 500;
  cursor: help;
}
/* Dimension-filter popover (advanced operators + ad-hoc add), shared by the
   side panel's "Advanced…" / "＋ Add filter" affordances (#282). */
.dim-filter-popover {
  background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 12px 32px rgba(27, 22, 14, 0.22); padding: 12px; font-size: 12px;
  display: flex; flex-direction: column; gap: 10px;
}
.dim-filter-pop-row { display: flex; flex-direction: column; gap: 4px; }
.dim-filter-popover select, .dim-filter-popover .dim-pop-search, .dim-filter-popover .dim-pop-scalar {
  width: 100%; box-sizing: border-box; padding: 6px 8px; font: inherit; font-size: 12px;
  background: var(--panel-2, #f2f2f2); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
}
.dim-filter-popover select.dim-pop-op { width: auto; }
.dim-pop-values { max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 6px; padding: 4px; }
.dim-pop-val { display: flex; align-items: center; gap: 6px; padding: 3px 6px; cursor: pointer; border-radius: 4px; }
.dim-pop-val:hover { background: var(--panel-2, rgba(127,127,127,0.08)); }
.dim-pop-loading { padding: 8px; color: var(--muted); font-size: 11.5px; }
.dim-filter-pop-actions { display: flex; justify-content: flex-end; gap: 8px; }
.scorecard-kpi-category {
  font-size: 11px;
  text-transform: lowercase;
  margin-top: 1px;
}
.scorecard-row-errored td { background: rgba(199, 84, 80, 0.04); }
.scorecard-row-error {
  font-size: 10.5px;
  color: #ff7b72;
  margin-top: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 220px;
}

/* Sparkline SVG bars — colored by status. The gradient (older = fainter)
   is applied via `opacity` per <rect> in JS so all bars share one color
   here. */
.sparkline { display: inline-block; vertical-align: middle; }
.sparkline-bar { fill: #5b8def; }            /* neutral default */
.sparkline-good .sparkline-bar { fill: #2ea043; }
.sparkline-warn .sparkline-bar { fill: #d49826; }
.sparkline-bad  .sparkline-bar { fill: #c75450; }
.sparkline-unknown .sparkline-bar { fill: var(--muted); }

/* Status pill — pill-shaped indicator, mirrors the highlight colors
   from narrative cards so the visual vocabulary is consistent. */
.status-pill {
  display: inline-flex; align-items: center;
  padding: 3px 10px;
  font-size: 11.5px;
  font-weight: 600;
  border-radius: 999px;
  white-space: nowrap;
}
.status-pill-good {
  color: #56d364;
  background: rgba(46, 160, 67, 0.18);
}
.status-pill-warn {
  color: #f0b03e;
  background: rgba(212, 152, 38, 0.22);
}
.status-pill-bad {
  color: #ff7b72;
  background: rgba(199, 84, 80, 0.20);
}
.status-pill-unknown {
  color: var(--muted);
  background: transparent;
  font-weight: normal;
}

/* ===== Scorecard modal — row editor list ===== */
#scorecard-modal-backdrop .modal {
  width: min(720px, 100%);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}
#scorecard-modal-backdrop .modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.scorecard-form-meta { display: flex; flex-direction: column; gap: 8px; }
.scorecard-form-meta label { display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--muted); }
.scorecard-form-meta input { font-size: 13px; padding: 6px 10px; }
.scorecard-rows-section { display: flex; flex-direction: column; gap: 8px; }
.scorecard-rows-header {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--muted);
  padding-top: 6px;
  border-top: 1px solid var(--border);
}
.scorecard-rows-list { display: flex; flex-direction: column; gap: 6px; }
.scorecard-row-editor {
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel-2);
  overflow: hidden;
}
.scorecard-row-editor > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 600;
  user-select: none;
  list-style: none;
}
.scorecard-row-editor > summary::-webkit-details-marker { display: none; }
.scorecard-row-editor > summary::before {
  content: "▸";
  color: var(--muted);
  display: inline-block;
  width: 12px;
  transition: transform 120ms ease;
}
.scorecard-row-editor[open] > summary::before { transform: rotate(90deg); }
.scorecard-row-summary-name { color: var(--text); }
.scorecard-row-form {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px;
  border-top: 1px solid var(--border);
}
.scorecard-row-form label { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--muted); }
.scorecard-row-form input,
.scorecard-row-form select,
.scorecard-row-form textarea {
  font-size: 13px;
  padding: 6px 10px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-family: inherit;
}
.scorecard-row-form textarea {
  font-family: var(--mono, monospace);
  font-size: 12px;
  line-height: 1.5;
  resize: vertical;
}
.scorecard-row-form code {
  font-size: 11px;
  background: rgba(127,127,127,0.1);
  padding: 0 4px;
  border-radius: 3px;
}

/* ===== Generate-from-prompt block at top of each row editor ===== */
.scorecard-row-generate {
  background: rgba(91, 141, 239, 0.06);
  border: 1px dashed rgba(91, 141, 239, 0.35);
  border-radius: 6px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 4px;
}
.scorecard-row-generate label {
  font-size: 11px;
  color: var(--muted);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.scorecard-row-generate textarea {
  font-family: inherit;
  font-size: 13px;
  resize: vertical;
  padding: 6px 10px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  line-height: 1.45;
}
.scorecard-row-generate-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.scorecard-row-generate-hint {
  font-size: 11px;
  line-height: 1.4;
}
.scorecard-row-generate-status {
  flex: 1 1 auto;
  font-size: 11.5px;
  min-width: 0;
  /* Allow wrapping — Claude's notes can be 1-2 sentences. */
  word-break: break-word;
}

/* ===== Lint result card under the Series SQL textarea ===== */
.scorecard-row-lint {
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.scorecard-lint-dryrun {
  font-size: 11px;
  padding: 4px 10px;
  background: rgba(46, 160, 67, 0.08);
  border-left: 3px solid #2ea043;
  border-radius: 3px;
}
.scorecard-lint-summary {
  font-size: 12.5px;
  padding: 10px 12px;
  border-radius: 6px;
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.scorecard-lint-ok {
  background: rgba(46, 160, 67, 0.08);
  border-color: rgba(46, 160, 67, 0.35);
}
.scorecard-lint-concerns {
  background: rgba(212, 152, 38, 0.10);
  border-color: rgba(212, 152, 38, 0.40);
}
.scorecard-lint-critical {
  background: rgba(199, 84, 80, 0.10);
  border-color: rgba(199, 84, 80, 0.40);
}
.scorecard-lint-error {
  background: rgba(199, 84, 80, 0.10);
  border-color: rgba(199, 84, 80, 0.40);
  color: #ff7b72;
}
.scorecard-lint-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.scorecard-lint-badge {
  display: inline-flex; align-items: center;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.scorecard-lint-badge-ok       { background: rgba(46, 160, 67, 0.18); color: #56d364; }
.scorecard-lint-badge-concerns { background: rgba(212, 152, 38, 0.22); color: #f0b03e; }
.scorecard-lint-badge-critical { background: rgba(199, 84, 80, 0.20); color: #ff7b72; }
.scorecard-lint-text { flex: 1 1 auto; color: var(--text); min-width: 0; }
.scorecard-lint-issues {
  margin: 0; padding: 0 0 0 18px;
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12px;
}
.scorecard-lint-issue-major strong:first-child { color: #ff7b72; }
.scorecard-lint-issue-minor strong:first-child { color: var(--muted); }
.scorecard-lint-actions {
  display: flex; gap: 8px;
  padding-top: 4px;
  border-top: 1px dashed var(--border);
}

/* ============================================================
   Reports library — grid + chips + collapsible sections
   ============================================================ */

/* Filter chips bar above the list. Each group is one row of chips
   labeled with what it filters on. */
.reports-chips-bar {
  display: flex; flex-wrap: wrap; gap: 16px 24px;
  padding: 8px 0 16px;
  border-bottom: 1px solid var(--border, #e6e6e6);
  margin-bottom: 12px;
}
.reports-chip-group {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.reports-chip-label {
  font-size: 11.5px; font-weight: 500;
  margin-right: 2px;
}
.reports-chips-bar button.chip {
  background: var(--panel-2, #f7f7f9);
  color: var(--text);
  border: 1px solid var(--border, #e6e6e6);
  border-radius: 14px;
  padding: 3px 12px;
  font-size: 11.5px;
  cursor: pointer;
  transition: all 0.1s ease;
  line-height: 1.3;
}
.reports-chips-bar button.chip:hover {
  background: var(--panel, #fff);
  border-color: var(--muted, #999);
}
.reports-chips-bar button.chip.active {
  background: var(--accent, #1f6feb);
  color: white;
  border-color: var(--accent, #1f6feb);
  font-weight: 500;
}

/* The library list itself in grid mode — UL is just a container of
   project sections (each one is a single <li> with the header + grid
   nested inside). Remove default list styling. */
ul.reports-grid-mode {
  list-style: none;
  padding: 0; margin: 0;
}

/* Project section — collapsible <li>. Header is a button (full-width
   click target), expanded content is the .reports-grid below it. */
.reports-project-section {
  list-style: none;
  margin-bottom: 18px;
}
.reports-project-section:last-child { margin-bottom: 0; }
.reports-project-toggle {
  width: 100%;
  display: flex; align-items: baseline; gap: 10px;
  padding: 10px 12px;
  background: var(--panel-2, #f7f7f9);
  border: 1px solid var(--border, #e6e6e6);
  border-radius: 6px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  transition: background 0.1s ease;
}
.reports-project-toggle:hover { background: var(--panel, #fff); }
.reports-project-section.open .reports-project-toggle {
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
}
.reports-project-caret {
  font-size: 11px; color: var(--muted);
  width: 12px; display: inline-block;
  font-family: monospace;
}
.reports-project-name {
  font-weight: 600; font-size: 13.5px;
  color: var(--text);
  letter-spacing: 0.01em;
}
.reports-project-count {
  font-size: 11.5px;
}

/* Card grid inside an open project section. Responsive — cards
   shrink to fit their min-content but won't go below 280px wide. */
.reports-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--border, #e6e6e6);
  border-top: none;
  border-radius: 0 0 6px 6px;
  background: var(--bg, #fff);
}

/* Individual report card — replaces the old <li> row layout. */
.report-card {
  position: relative;
  display: flex; flex-direction: column;
  background: var(--panel, #fff);
  border: 1px solid var(--border, #e6e6e6);
  border-top-width: 3px;  /* default accent — overridden by source class */
  border-radius: 6px;
  padding: 10px 12px 8px;  /* top trimmed by 2px to compensate for thicker border */
  /* No cursor: pointer — the card is informational; only the explicit
     Download button and kebab menu items are click targets. */
  transition: border-color 0.1s ease, box-shadow 0.1s ease;
  /* No overflow:hidden — the kebab menu pops upward out of the card
     and needs to render outside the card's bounding box. (Previous
     version used an absolutely-positioned accent strip which forced
     overflow:hidden; the strip moved to border-top here.) */
}
.report-card:hover {
  border-color: var(--muted, #999);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
.report-card.shared-in {
  background: var(--shared-bg, #fafbfc);
}

/* Accent stripe = the top border, color-coded by source. */
.report-card-accent { display: none; }  /* legacy div from earlier render path */
.report-card.report-card-source-chat { border-top-color: #2ea043; }
.report-card.report-card-source-scheduled { border-top-color: #1f6feb; }

.report-card-header {
  display: flex; align-items: flex-start; gap: 8px;
  margin-bottom: 6px;
}
.report-card-title {
  flex: 1;
  font-size: 13.5px; font-weight: 600;
  margin: 0;
  line-height: 1.3;
  /* Two-line clamp — long titles don't blow up card height. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}
.report-card-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 6px;
}
.report-card-source-pill {
  background: var(--panel-2, #f7f7f9);
  color: var(--text);
  border: 1px solid var(--border, #e6e6e6);
  border-radius: 4px;
  font-size: 10.5px;
  padding: 2px 7px;
  font-weight: 500;
}
.report-card-date { font-size: 11.5px; }
.report-card-sub {
  font-size: 11px;
  margin-bottom: 6px;
  line-height: 1.4;
  word-break: break-word;
}
.report-card-badges {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-bottom: 8px;
}
.report-card-badges .pill {
  font-size: 10.5px;
  padding: 2px 7px;
}
.report-card-footer {
  display: flex; align-items: center; gap: 6px;
  margin-top: auto;  /* push to bottom; supports varying card heights */
  padding-top: 6px;
  border-top: 1px solid var(--border, #e6e6e6);
}
.report-card-footer .btn.small {
  padding: 4px 10px; font-size: 11.5px; min-height: 26px;
}
.report-card-footer .btn.primary {
  flex: 1;
  text-align: center;
}

/* Kebab menu */
.report-card-kebab {
  position: relative;
}
.report-card-kebab > button.btn {
  padding: 4px 10px;
  font-size: 16px; line-height: 1;
  min-height: 26px;
}
.report-card-kebab-menu {
  position: absolute;
  right: 0; bottom: calc(100% + 4px);
  z-index: 10;
  background: var(--panel, #fff);
  border: 1px solid var(--border, #e6e6e6);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  display: flex; flex-direction: column;
  min-width: 140px;
  padding: 4px 0;
}
/* The CSS `display: flex` above silently overrides the browser's default
   [hidden] { display: none } UA style — without this explicit re-assert,
   the menu sits visible all the time. */
.report-card-kebab-menu[hidden] { display: none; }
.report-card-kebab-item {
  text-align: left;
  background: transparent;
  border: none;
  padding: 6px 12px;
  font: inherit; font-size: 12px;
  cursor: pointer;
  color: var(--text);
  transition: background 0.1s ease;
}
.report-card-kebab-item:hover {
  background: var(--panel-2, #f7f7f9);
}
.report-card-kebab-item.danger { color: var(--err, #c75450); }
.report-card-kebab-item.danger:hover { background: rgba(199, 84, 80, 0.08); }

/* Inline versions disclosure inside a card */
.report-card-versions {
  margin-top: 8px;
  border-top: 1px dashed var(--border, #e6e6e6);
  padding-top: 6px;
}
.report-card-versions summary {
  cursor: pointer;
  font-size: 11px;
  color: var(--muted);
  user-select: none;
  list-style: none;
}
.report-card-versions summary::before { content: "▸ "; font-family: monospace; }
.report-card-versions[open] summary::before { content: "▾ "; }
.report-card-versions-list {
  list-style: none;
  padding: 4px 0 0; margin: 0;
}
.report-card-versions-list li {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 0;
  font-size: 11.5px;
}
.report-card-versions-list .report-version-when { flex: 1; color: var(--muted); }
.report-card-versions-list .btn.small { padding: 2px 6px; font-size: 10.5px; min-height: 22px; }
/* Version chip on the headline row of a grouped report */
ul.memory-list-full .report-item .report-version-chip {
  font-size: 10.5px; padding: 2px 7px; flex-shrink: 0;
  background: var(--accent-bg, #e3edff); color: var(--accent, #1f6feb);
  border: 1px solid var(--accent, #1f6feb); border-radius: 4px;
  font-weight: 500;
}
/* Earlier-versions inline disclosure (appears as a sibling <li> right
   below the headline row in the same list). */
.report-versions-row {
  /* No marker — this row is a continuation of the row above it. */
  list-style: none;
  padding: 0 8px 8px 36px;  /* indent to align under headline title */
  margin: 0;
  background: var(--panel-2, #f7f7f9);
  border-bottom: 1px solid var(--border, #e6e6e6);
}
.report-versions-disclosure summary {
  cursor: pointer;
  font-size: 11.5px;
  color: var(--muted);
  padding: 4px 0;
  user-select: none;
}
.report-versions-disclosure summary:hover { color: var(--text); }
.report-versions-disclosure[open] summary { color: var(--text); font-weight: 500; }
.report-versions-list {
  list-style: none;
  padding: 4px 0 4px 12px;
  margin: 0;
  border-left: 2px solid var(--border, #e6e6e6);
}
.report-version-entry {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0;
  font-size: 12px;
}
.report-version-tag {
  background: var(--panel-2); color: var(--muted);
  border: 1px solid var(--border); border-radius: 4px;
  padding: 1px 6px; font-size: 10.5px; flex-shrink: 0;
}
.report-version-when { flex: 1; color: var(--muted); }
.report-version-entry .btn.small { padding: 2px 8px; font-size: 11px; min-height: 24px; }
ul.memory-list-full .report-item .report-actions {
  display: flex; gap: 4px; flex-shrink: 0;
}
ul.memory-list-full .report-item .report-actions .btn.small {
  padding: 3px 8px; font-size: 12px; min-height: 28px;
}
ul.memory-list-full .report-item .report-actions .btn.small.danger {
  color: var(--err); border-color: var(--border);
}
ul.memory-list-full .report-item .report-actions .btn.small.danger:hover {
  border-color: var(--err);
}

/* Reports view structural — mirror memory-edit but no edit form, just the
   list pane. The #reports-view main element gets its own scroll because
   long libraries blow past viewport easily. */
#reports-view {
  display: flex; flex-direction: column; height: 100%; overflow-y: auto;
  background: var(--bg);
}
#reports-view[hidden] { display: none; }

/* ---- Memory edit view ---- */
#memory-edit-view {
  display: flex; flex-direction: column; height: 100%; overflow: hidden;
  background: var(--bg);
}
#memory-edit-view[hidden] { display: none; }
/* The existing .fullview-toolbar + .fullview-body styles from the
   schema-edit refactor cover everything else. */

.block-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 6px 8px 6px 6px;
  gap: 6px;
  min-width: 0;
}
.block-header .block-label {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.block-header .btn { flex-shrink: 0; }
.block-header .btn.primary { font-size: 12px; padding: 4px 10px; }
#chats-project-name { color: var(--accent); text-transform: none; font-weight: 600; letter-spacing: 0; }

/* Reports list in sidebar */
#reports-list { list-style: none; margin: 0; padding: 6px; max-height: 50vh; overflow-y: auto; overflow-x: hidden; }
#reports-list .report-item {
  display: flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 6px;
  cursor: pointer; font-size: 12.5px; border: 1px solid transparent;
}
#reports-list .report-item:hover { background: var(--panel-2); border-color: var(--border); }
#reports-list .report-item .name {
  flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#reports-list .report-item .freq {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); padding: 1px 5px; border-radius: 3px;
  background: var(--panel);
}
#reports-list .report-item.disabled .name { opacity: 0.5; }
#reports-list .report-item .dot {
  width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
  background: var(--muted);
}
#reports-list .report-item .dot.on { background: var(--ok); }
#reports-list .report-item .dot.err { background: var(--err); }
#reports-list .empty { color: var(--muted); font-size: 12px; padding: 10px; text-align: center; }

/* Report modal specifics */
.schedule-row {
  display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 10px;
}
.schedule-row label { display: flex; flex-direction: column; gap: 4px; }
.schedule-row select, .schedule-row input {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 8px 10px; font: inherit;
}
.row-inline {
  flex-direction: row !important; align-items: center; gap: 6px !important;
}
.row-inline input[type="checkbox"] { margin: 0; }

.runs-preview {
  border-top: 1px solid var(--border); padding-top: 10px;
  display: flex; flex-direction: column; gap: 6px;
}
.runs-list {
  list-style: none; margin: 0; padding: 0;
  max-height: 220px; overflow-y: auto;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
}
.runs-list:empty::after {
  content: "(no runs yet)"; color: var(--muted); display: block; padding: 10px; font-size: 12px; text-align: center;
}
.runs-list li {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-bottom: 1px solid var(--border);
  font-family: var(--mono); font-size: 12px; cursor: pointer;
}
.runs-list li:last-child { border-bottom: none; }
.runs-list li:hover { background: var(--panel); }
.runs-list li .when { flex: 1; }
.runs-list li .status {
  font-size: 10px; padding: 2px 6px; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.05em;
}
.runs-list li .status.success { background: rgba(80, 161, 67, 0.15); color: var(--ok); }
.runs-list li .status.error { background: rgba(202, 30, 79, 0.15); color: var(--err); }
.runs-list li .status.running { background: rgba(237, 149, 0, 0.15); color: var(--warn); }

/* ---- Scheduled-query preview table --------------------------------
   First-100-rows preview rendered in the create/edit modal. Sticky
   header so column names stay visible while scrolling a wide result. */
.sq-preview-table {
  border-collapse: collapse;
  width: 100%;
  font-family: var(--mono);
  font-size: 11.5px;
}
.sq-preview-table th, .sq-preview-table td {
  border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border);
  padding: 5px 8px;
  text-align: left;
  white-space: nowrap;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: top;
}
.sq-preview-table th {
  position: sticky; top: 0;
  background: var(--panel-2);
  font-weight: 600;
  z-index: 1;
}
.sq-preview-table tbody tr:nth-child(even) { background: rgba(127, 127, 127, 0.04); }
.sq-preview-table tbody tr:hover { background: var(--panel); }
.sq-preview-table td .muted { opacity: 0.5; }

/* Searchable governed-metric picker (replaces a plain <select>). */
.metric-combo { position: relative; }
.metric-combo-input { width: 100%; }
.metric-combo-menu {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 50; margin-top: 2px;
  max-height: 260px; overflow-y: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 10px 28px rgba(27, 22, 14, 0.22);
}
.metric-combo-item {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 6px 10px; cursor: pointer; font-size: 12.5px;
}
.metric-combo-item:hover, .metric-combo-item.active { background: var(--panel-2, rgba(127,127,127,0.08)); }
.metric-combo-item .mc-name { font-weight: 600; }
.metric-combo-item .mc-key { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.metric-combo-item .mc-syn { font-size: 11px; color: var(--muted); font-style: italic; }
.metric-combo-empty { padding: 8px 10px; font-size: 12px; color: var(--muted); }

/* Multi-metric tile rows (#297) — the governed builder's "Additional metrics"
   list: metric picker · display label · remove, THREE controls on one line.
   The modal's default form controls (padding 9px 11px at the inherited body
   font) make that row far too tall to read as a compact list, so the whole row
   steps down to the same 12px scale the read-only primary chip uses — every
   control in the block ends up one height. The size rules are scoped under
   `.modal form` because that's the selector they have to outrank. */
/* `stretch`, not `center`: on phones the touch-target rule lifts `.btn` to a
   40px min-height, and a 40px × beside 28px inputs reads as a broken row.
   Stretching makes every control agree on the tallest one — compact together
   on desktop, tall together on mobile. */
.gov-metric-row { display: flex; gap: 6px; align-items: stretch; margin-bottom: 4px; }
.gov-metric-row > select,
.gov-metric-row > input[type="text"],
.gov-metric-row > .metric-combo,
.gov-metric-row > .gov-metric-name { flex: 1 1 0; min-width: 0; }
.gov-metric-row > .btn { flex: 0 0 auto; }
.modal form .gov-metric-row select,
.modal form .gov-metric-row input[type="text"],
.modal form .gov-metric-row .gov-metric-name,
.modal form .gov-metric-row .btn {
  font-size: 12px; padding: 5px 8px; border-radius: 6px;
}
/* The primary metric is shown read-only (it's chosen by the Metric field
   above), styled as the same box as the pickers under it so the list reads as
   one column of series. */
.gov-metric-name {
  display: flex; align-items: center;   /* the row stretches it — keep text centred */
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  overflow: hidden; white-space: nowrap;
}
.gov-metric-name > span { overflow: hidden; text-overflow: ellipsis; }
/* …and it has no × of its own, so it carries an inert clone: without it the
   primary's label input runs wider than every row below. */
.gov-metric-row > .btn[aria-hidden="true"] { visibility: hidden; }

/* Governed badge on dashboard cards (single-metric + composed governed). */
.card-governed-badge {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.3px;
  color: #4f7a32; background: rgba(120, 170, 90, 0.14);
  border: 1px solid rgba(120, 170, 90, 0.5); border-radius: 999px;
  padding: 1px 7px; white-space: nowrap; flex-shrink: 0;
}

/* ---- Run-history inline stats + validation badge -------------------
   Compact pills shown next to the run timestamp in the report-edit
   modal's runs list. The validation badge color-codes the peer-review
   outcome at a glance:
     vb-ok       — ✓ Reviewed   (Gemini said the report is fine)
     vb-concerns — ⚠ Concerns   (minor issues, addressed in revision)
     vb-critical — 🚨 Critical  (still critical after revision)
     vb-disagree — ⚖ Disagreement (handoff used + reviewer disagreed)
*/
.runs-list li .run-stat {
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 3px;
  background: var(--panel-2);
  color: var(--muted);
  font-family: var(--mono);
  white-space: nowrap;
}
.runs-list li .run-stat.duration {
  background: rgba(255, 255, 255, 0.04);
}
.runs-list li .run-stat.iterations {
  background: rgba(255, 255, 255, 0.04);
}
.runs-list li .validation-badge {
  font-size: 10.5px;
  letter-spacing: 0.02em;
}
.validation-badge.vb-ok       { background: rgba(80, 161, 67, 0.15); color: var(--ok, #6cc06c); }
.validation-badge.vb-concerns { background: rgba(212, 163, 90, 0.18); color: #d4a35a; }
.validation-badge.vb-critical { background: rgba(202, 30, 79, 0.18); color: var(--err, #e25555); }
.validation-badge.vb-disagree { background: rgba(180, 130, 220, 0.18); color: #b482dc; }

/* ---- Run-detail modal: richer meta panel ---------------------------
   The old version was a single textContent line ("Status: success ·
   Took: 5s · …"). The new HTML layout lays each stat out as a label/
   value pair so the eye can scan them. A peer-review card below the
   stats shows the validation summary, concerns, and suggestions.
*/
#run-meta {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.7;
}
#run-meta .meta-stat {
  display: inline-block;
  margin-right: 6px;
}
#run-meta .meta-label {
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10px;
  margin-right: 4px;
}
#run-meta .meta-stat strong { color: var(--fg); font-weight: 600; }
#run-meta .meta-stat strong.status-success { color: var(--ok, #6cc06c); }
#run-meta .meta-stat strong.status-error { color: var(--err, #e25555); }
#run-meta .meta-stat strong.status-running { color: var(--warn, #d4a35a); }
#run-meta .meta-stat.err { color: var(--err, #e25555); }

.run-validation-card {
  margin-top: 12px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 12.5px;
}
.run-validation-card .rv-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}
.run-validation-card .rv-model { font-size: 11px; }
.run-validation-card .rv-summary {
  margin: 6px 0;
  color: var(--fg);
}
.run-validation-card .rv-section-head {
  margin-top: 8px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10.5px;
  color: var(--muted);
}
.run-validation-card .rv-list {
  margin: 4px 0 0;
  padding-left: 18px;
}
.run-validation-card .rv-list li {
  margin: 3px 0;
  line-height: 1.5;
}
.run-validation-card .rv-sev {
  display: inline-block;
  font-size: 9.5px;
  padding: 1px 4px;
  border-radius: 2px;
  letter-spacing: 0.04em;
  margin-right: 4px;
  font-family: var(--mono);
}
.run-validation-card .rv-sev.sev-major {
  background: rgba(202, 30, 79, 0.18);
  color: var(--err, #e25555);
}
.run-validation-card .rv-sev.sev-minor {
  background: rgba(212, 163, 90, 0.15);
  color: #d4a35a;
}
/* Severity classes for the dependency-status list in the run-detail
   modal. FRESH = green (data is current), STALE = amber (data is
   older than the freshness window), ERRORED / MISSING / LOOKUP_FAILED
   = red (dep unhealthy). Re-uses the same shape as MAJOR/MINOR so the
   eye learns one visual vocabulary across review + dependency cards. */
.run-validation-card .rv-sev.sev-ok {
  background: rgba(80, 161, 67, 0.18);
  color: var(--ok, #6cc06c);
}
.run-validation-card .rv-sev.sev-warn {
  background: rgba(212, 163, 90, 0.18);
  color: #d4a35a;
}
.run-validation-card .rv-sev.sev-err {
  background: rgba(202, 30, 79, 0.18);
  color: var(--err, #e25555);
}
.dep-list code {
  background: rgba(255, 255, 255, 0.05);
  padding: 1px 4px;
  border-radius: 2px;
  font-family: var(--mono);
  font-size: 11px;
}
.attachment-chip.dep-chip {
  background: rgba(180, 130, 220, 0.12);
  border-color: rgba(180, 130, 220, 0.35);
}
.attachment-chip.dep-missing {
  background: rgba(202, 30, 79, 0.10);
  border-color: rgba(202, 30, 79, 0.35);
  color: var(--err, #e25555);
}
.rv-skip-note {
  margin-top: 8px;
  font-size: 12px;
  font-style: italic;
}

.btn.icon { padding: 6px 10px; font-size: 14px; }
.btn.small { font-size: 12px; padding: 5px 8px; }
.btn.danger { background: #3a1e25; border-color: var(--err); color: var(--err); }
.btn.danger:hover:not(:disabled) { background: #4a2730; }

.dashboards-block { flex: 0 0 auto; margin-top: 12px; border-left-color: var(--accent); }
#dashboard-list { list-style: none; margin: 0; padding: 6px; max-height: 50vh; overflow-y: auto; overflow-x: hidden; }
#dashboard-list .dashboard-item {
  display: flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 6px;
  cursor: pointer; font-size: 12.5px; border: 1px solid transparent;
}
#dashboard-list .dashboard-item:hover { background: var(--panel-2); border-color: var(--border); }
#dashboard-list .dashboard-item.active { background: var(--panel-2); border-color: var(--accent); }
#dashboard-list .dashboard-item .name {
  flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#dashboard-list .dashboard-item .count {
  font-size: 10px; color: var(--muted);
  padding: 1px 5px; border-radius: 3px; background: var(--panel);
}
#dashboard-list .empty { color: var(--muted); font-size: 12px; padding: 10px; text-align: center; }

/* -- Home view (project tiles) -- */
/* overflow-anchor:none — when a lazy "Load more" rebuilds a section's grid, the
   browser's scroll-anchoring would otherwise overcorrect and jump the viewport
   (most visible on the mid-page dashboards section). Disabling it keeps scrollTop
   put across the synchronous rebuild. */
#home-view { flex: 1; overflow-y: auto; overflow-anchor: none; background: var(--bg); }
#home-view[hidden] { display: none !important; }
.home-inner {
  max-width: 1200px; margin: 0 auto; padding: 40px 28px 60px;
  display: flex; flex-direction: column; gap: 36px;
}
.home-section-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 18px;
}
.home-section-head h1 { margin: 0; font-size: 22px; color: var(--text); }
.home-section-head h2 { margin: 0; font-size: 16px; color: var(--text); }

.home-projects {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;
}
.home-empty {
  grid-column: 1 / -1;
  padding: 40px; text-align: center; color: var(--muted);
  background: var(--panel); border: 1px dashed var(--border); border-radius: 10px;
}
/* Pin-to-home toggles (#859) on the dashboard header, the chat composer row and
   the scheduled-report modal. The star fills when the item is pinned. */
.pin-btn { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.pin-btn[hidden] { display: none; }
.pin-btn .pin-star { color: transparent; -webkit-text-stroke: 1.2px currentColor; font-size: 13px; line-height: 1; }
.pin-btn.is-pinned .pin-star { color: var(--warn, #ed9500); -webkit-text-stroke: 0; }
.modal-header .pin-btn { margin-left: auto; margin-right: 6px; }
/* Dashboards hub scope filter: All / Shared with me. */
.dash-scope { display: flex; gap: 6px; margin: -6px 0 16px; flex-wrap: wrap; }
.dash-scope-btn {
  font: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer;
  padding: 5px 12px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--panel); color: var(--muted);
}
.dash-scope-btn[aria-pressed="true"] { background: var(--koro-black, var(--text)); border-color: var(--koro-black, var(--text)); color: var(--koro-beige, #fff); }
/* Loading placeholder shaped like a .home-tile (see buildHomeTileSkeleton). */
.home-tile-skeleton {
  background: var(--beige-lightest, var(--panel)); border: 1px solid var(--border);
  border-radius: 18px; padding: 24px; display: flex; flex-direction: column; gap: 14px;
  box-shadow: var(--koro-shadow);
}
.home-tile-skeleton .hts-head { display: flex; align-items: center; gap: 13px; }
.home-tile-skeleton .hts-emoji { width: 46px; height: 46px; flex: 0 0 46px; border-radius: 50%; }
.home-tile-skeleton .hts-title { height: 18px; width: 55%; border-radius: 6px; }
.home-tile-skeleton .hts-line { height: 12px; width: 100%; border-radius: 6px; }
.home-tile-skeleton .hts-line.short { width: 42%; }
/* Skeleton reveal delay (#507): a skeleton that appears and vanishes within a
   couple of frames is its own kind of jank. Keep the placeholder in the DOM
   (so it reserves the row/tile height and nothing jumps) but HOLD its reveal
   for one shared ~180ms threshold — if the data lands first, the container is
   replaced before the skeleton was ever visible, so a fast/cached load shows no
   flash. One value, one place; applied to the container of any held skeleton
   set. Not motion (an opacity step, not a transition), so it's kept under
   prefers-reduced-motion; the shimmer inside `.skeleton` honours that itself. */
@keyframes skeleton-hold-in { to { opacity: 1; } }
.skeleton-hold { opacity: 0; animation: skeleton-hold-in 0.01s linear 180ms forwards; }
/* "Load more" tile — a dashed, ghost tile that reveals the next 5 items in a
   home section. Stretches to its grid row's height so it sits flush with the
   real tiles next to it. */
.home-load-more {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-height: 150px; padding: 20px; border: 1.5px dashed var(--border); border-radius: 16px;
  background: transparent; color: var(--muted); cursor: pointer; font: inherit;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.home-load-more:hover { border-color: var(--accent); color: var(--accent); background: rgba(0, 150, 191, 0.05); }
.home-load-more .hlm-plus { font-family: var(--serif); font-size: 24px; font-weight: 700; line-height: 1; }
.home-load-more .hlm-label { font-size: 13px; font-weight: 600; }
.home-load-more.loading { cursor: default; border-style: solid; border-color: var(--border); color: var(--muted); background: transparent; }
.home-load-more .hlm-spinner {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: rp-spin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .home-load-more .hlm-spinner { animation: none; } }

.home-tile {
  background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
  padding: 18px; display: flex; flex-direction: column; gap: 10px;
  cursor: pointer; transition: border-color 120ms, transform 120ms;
}
.home-tile:hover { border-color: var(--accent); transform: translateY(-1px); }
.home-tile-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.home-tile-title {
  margin: 0; font-size: 15px; font-weight: 600; color: var(--text);
  display: flex; align-items: baseline; gap: 8px; min-width: 0;
}
/* Auto-derived emoji from projectEmoji(); kept on its own span so the text
   next to it can ellipsis-truncate without clipping the glyph. */
.home-tile-emoji {
  flex-shrink: 0; font-size: 18px; line-height: 1; font-weight: normal;
}
.home-tile-name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.home-tile-desc {
  font-size: 12px; color: var(--muted); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.home-tile-counts {
  display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; padding: 8px 0;
  border-top: 1px solid var(--border);
}
.home-tile-count { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.home-tile-count .num { font-size: 17px; font-weight: 600; color: var(--text); line-height: 1; }
.home-tile-count .num.meta {
  font-size: 12px; font-weight: 500; color: var(--text);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.report-status-pill {
  font-size: 10px; padding: 2px 7px; border-radius: 999px;
  text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600;
  flex-shrink: 0;
}
.report-status-pill.on { background: rgba(80, 161, 67, 0.15); color: var(--ok); }
.report-status-pill.off { background: var(--panel-2); color: var(--muted); }
.report-status-pill.err { background: rgba(202, 30, 79, 0.15); color: var(--err); }
.home-tile-count .label { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.home-tile-actions { display: flex; gap: 6px; align-items: center; }
.home-tile-actions .open { flex: 1; }
.home-tile-meta { font-size: 11px; color: var(--muted); }

.home-shared-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.home-shared-list li {
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 14px; display: grid; grid-template-columns: auto 1fr auto auto;
  gap: 10px; align-items: center; cursor: pointer; font-size: 13px;
}
.home-shared-list li:hover { border-color: var(--accent); }
.home-shared-list .kind-tag { font-size: 14px; }
.home-shared-list .name { color: var(--text); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-shared-list .project { color: var(--muted); font-size: 11px; }
.home-shared-list .owner { color: var(--muted); font-size: 11px; font-family: var(--mono); }

/* Header "← Projects" button only shown when inside a project. */
header .home-btn { margin-right: 4px; }
header #current-project-label { margin-left: 6px; }
/* Desktop: Home lives in the nav rail as a permanent item, so the header
   back-arrow is mobile-only (the nav rail itself is hidden on mobile). */
@media (min-width: 768px) {
  header .home-btn { display: none !important; }
}

/* --- Schema / data dictionary --- */
#schema-view { flex: 1; overflow-y: auto; background: var(--bg); }
#schema-view[hidden] { display: none !important; }
.schema-list { display: flex; flex-direction: column; gap: 18px; }
.schema-dataset-group {
  background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 18px;
}
.schema-dataset-head {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.schema-dataset-head h3 { margin: 0; font-size: 14px; color: var(--text); font-family: var(--mono); }
.schema-dataset-head .muted { font-size: 11px; }
.schema-table-row {
  display: grid; grid-template-columns: 1fr 1fr auto; gap: 12px; align-items: center;
  padding: 8px 0; border-bottom: 1px dashed var(--border); cursor: pointer;
}
.schema-table-row:last-child { border-bottom: none; }
.schema-table-row:hover { background: var(--panel-2); }
.schema-table-row .name { font-family: var(--mono); font-size: 13px; color: var(--accent); }
.schema-table-row .desc { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.schema-table-row .meta { display: flex; gap: 6px; font-size: 10.5px; color: var(--muted); }
.schema-table-row .meta .pill {
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 999px;
  padding: 1px 7px; font-size: 10px;
}
.schema-table-row .meta .pill.stale { color: var(--warn); border-color: var(--warn); }
.schema-table-row .meta .pill.fresh { color: var(--ok); border-color: var(--ok); }
.schema-table-row .meta .pill.inactive {
  color: var(--warn); border-color: var(--warn); background: transparent;
  text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
}
.schema-table-row.inactive { opacity: 0.55; }
.schema-table-row.inactive .name { color: var(--muted); text-decoration: line-through; }
.schema-table-row.inactive:hover { opacity: 0.85; }

/* Edit-modal: status toggle row */
.inline-check {
  display: flex; align-items: flex-start; gap: 8px; cursor: pointer;
  padding: 8px 10px; background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 6px;
}
.inline-check input[type="checkbox"] { margin-top: 2px; }
.inline-check span .muted { font-weight: 400; }

/* Edit modal — columns grid */
.schema-fields { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.schema-field-row {
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; display: grid; gap: 8px;
  grid-template-columns: minmax(140px, 1.5fr) minmax(110px, 1fr) minmax(140px, 2fr) auto;
  align-items: start;
}
.schema-field-row label { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--muted); }
.schema-field-row label input,
.schema-field-row label select,
.schema-field-row label textarea {
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 5px; padding: 5px 7px; font: inherit; font-size: 12px;
}
.schema-field-row .row-extra {
  grid-column: 1 / -1; display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  padding-top: 4px; border-top: 1px dashed var(--border);
}
.schema-field-row .row-flags { display: flex; gap: 14px; font-size: 11px; color: var(--muted); }
.schema-field-row .row-flags label { flex-direction: row; gap: 4px; align-items: center; }
.schema-field-row .remove-field {
  background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 16px; padding: 0 6px; align-self: start;
}
.schema-field-row .remove-field:hover { color: var(--err); }

.schema-empty { color: var(--muted); font-size: 12px; padding: 12px; text-align: center; }

/* Chat-side indicators for schema-context usage + update_schema calls. */
.schema-used-chip {
  display: inline-flex; align-items: center; gap: 8px;
  background: rgba(0, 150, 191, 0.08);
  border: 1px solid rgba(0, 150, 191, 0.35);
  color: var(--accent);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 11.5px;
  margin-bottom: 6px;
  max-width: fit-content;
}
.schema-used-chip .chip-icon { font-size: 13px; line-height: 1; }
.schema-used-chip .chip-label { font-weight: 500; }
.schema-used-chip .chip-details { display: inline; }
.schema-used-chip .chip-details summary {
  cursor: pointer; font-size: 10.5px; color: var(--muted);
  list-style: none; padding: 0 4px;
}
.schema-used-chip .chip-details summary::-webkit-details-marker { display: none; }
.schema-used-chip .chip-details summary:hover { color: var(--accent); }
.schema-used-chip .chip-details[open] summary { color: var(--accent); }
.schema-used-chip .chip-list {
  list-style: none; margin: 4px 0 0 0; padding: 6px 10px;
  background: var(--panel-2); border-radius: 6px; border: 1px solid var(--border);
  font-family: var(--mono); font-size: 10.5px; color: var(--text);
  max-height: 180px; overflow-y: auto;
}
.schema-used-chip .chip-list li { padding: 2px 0; }

/* Governed-metric provenance badges (G1/M4) — reuse the chip shell, retint.
   verified = green (canonical), adhoc = amber (a governed metric existed). */
.metric-badge-verified {
  background: rgba(80, 161, 67, 0.10);
  border-color: rgba(80, 161, 67, 0.45);
  color: var(--ok, #9ece6a);
}
.metric-badge-adhoc {
  background: rgba(237, 149, 0, 0.10);
  border-color: rgba(237, 149, 0, 0.45);
  color: var(--warn, #e0af68);
}

/* Governed-metric provenance block: badge + the metric's 1-line description
   and caveat, shown under a governed answer. */
.metric-prov { margin-bottom: 6px; }
.metric-prov .metric-note {
  margin-top: 4px;
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.45;
  max-width: 640px;
}
.metric-prov .metric-caveat { color: var(--warn, #e0af68); margin-top: 2px; }

/* Answer-option chips (#513) — a reply's discrete choices rendered as clickable
   pills instead of prose the user has to retype. min-width:0 + flex-wrap so they
   wrap inside the bubble at a narrow viewport (≈380px) rather than overflowing
   (see the min-width note in CLAUDE.md). */
/* Answer options (#513; vertical rows since #580).
   A column, not a wrapped row of pills: these are ANSWERS to a question, and a
   list reads as one. Long labels then wrap inside their own row instead of
   ragging across the bubble, and the multi-select variant has somewhere
   sensible to put its checkbox. */
.answer-chips {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  margin-top: 10px;
  min-width: 0;
}
.answer-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
  text-align: left;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid var(--accent);
  background: var(--panel-2);
  color: var(--accent);
  font-size: 13px;
  line-height: 1.35;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.answer-chip > span { min-width: 0; }
.answer-chip:hover:not(:disabled):not(.spent) { background: var(--accent); color: var(--bg); }
.answer-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A multi-select row is a <label> wrapping its checkbox — box left, text right.
   The global `label:has(> input[type=checkbox])` row rules keep it on one line;
   the box must stay auto-width or it stretches and pushes the text down
   (CLAUDE.md → UI conventions). */
.answer-chips-multi .answer-chip { cursor: pointer; }
.answer-chips-multi .answer-chip:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.answer-chip > input[type="checkbox"] {
  width: auto;
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--accent);
}
/* Multi-select: a toggled-on row reads as selected before the user sends. */
.answer-chip.selected,
.answer-chip[aria-pressed="true"] { background: var(--accent); color: var(--bg); border-color: var(--accent); }
/* Answered state: the picked row stays highlighted, the rest are spent. */
.answer-chip.picked { background: var(--accent); color: var(--bg); border-color: var(--accent); }
.answer-chip.spent,
.answer-chip.inert { border-color: var(--border); color: var(--muted); background: transparent; }
.answer-chip:disabled { cursor: default; opacity: 0.85; }
.answer-chip.spent:disabled { opacity: 0.6; }
.answer-chip.spent { cursor: default; }
/* What the user typed into a free-text row, shown back on the answered row. */
.answer-opt-value::before { content: ": "; }
.answer-opt-value { font-weight: 600; }

/* Free-text ("other") option: ONE element. The option IS the card and its
   field lives inside it — not a row with a second box bolted underneath. The
   card takes over the border and the trigger goes transparent inside it, so
   the pair reads as a single control however the field is toggled. */
.answer-opt-card {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 0;
  /* NOT overflow:hidden — that clipped the focused field's outline against the
     card edge. The trigger matches the card's radius instead, which keeps its
     fill inside the rounded frame without cropping anything drawn in the card. */
}
/* The card is the frame; hovering it must not paint over its own field. */
.answer-opt-card:hover { background: var(--panel-2); color: var(--accent); }
.answer-opt-card.open { border-color: var(--accent); }
.answer-opt-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding: 9px 12px;
  border: 0;
  border-radius: 9px;          /* the card's 10px, less its 1px border */
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 13px;
  line-height: 1.35;
  text-align: left;
  overflow-wrap: anywhere;
  cursor: pointer;
}
.answer-opt-trigger > span { min-width: 0; }
.answer-opt-trigger > input[type="checkbox"] {
  width: auto;
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--accent);
}
.answer-opt-card:hover .answer-opt-trigger:not(.selected):not(.picked),
.answer-opt-trigger.selected,
.answer-opt-trigger.picked { background: var(--accent); color: var(--bg); }
/* Open: the trigger sits flush against the field below it, so square off the
   edge they share and keep the rounded pair reading as one card. */
.answer-opt-card.open .answer-opt-trigger { border-radius: 9px 9px 0 0; }
/* ...and it drops the filled treatment its sibling rows get when selected. A
   solid accent header above an accent-ringed field stacked three bands of the
   same blue around the one thing being read — the answer. The open card says
   "selected" the way Claude's own does: a highlighted border, with the tick
   and the focus ring carrying the rest. */
.answer-opt-card.open .answer-opt-trigger,
.answer-opt-card.open:hover .answer-opt-trigger {
  background: transparent;
  color: var(--accent);
}
.answer-opt-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.answer-opt-input-wrap {
  display: flex;
  min-width: 0;
  padding: 8px 10px 10px;
}
.answer-opt-input-wrap[hidden] { display: none; }
/* min-width: 0 or the input floors at its intrinsic ~20ch width and pushes the
   card wider than the bubble (CLAUDE.md → don't overflow the container). */
.answer-opt-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel);
  /* --text, not --fg: the latter is not defined in this stylesheet, so the
     declaration was dropped and the field inherited the card's accent — the
     answer the user typed came out the same blue as the option labels. What
     they type is their own words and reads as body text. */
  color: var(--text);
  font-size: 13px;
}
.answer-opt-input::placeholder { color: var(--muted); }
.answer-opt-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.answer-chip-send {
  min-width: 0;
  flex: 0 0 auto;
  align-self: flex-start;
  padding: 8px 14px;
  border-radius: 10px;
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--bg);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.answer-chip-send:disabled { background: var(--panel-2); color: var(--muted); cursor: default; }
.answer-chip-send:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .answer-chip { transition: none; } }

/* Governed mode shows NO raw SQL — answers are metric-only, so the generated
   SQL (query_metric's compiled SQL, a render's data_sql, …) is hidden in every
   governed tool row. (The audit trail still lives in metric_usage server-side.) */
#chat details.tool[data-governed] .sql { display: none; }

/* sample_text verbatims: let the redacted free-text column wrap rather than
   stretch the row grid into a horizontal-scroll mess. */
#chat details.tool[data-tool-name="sample_text"] td { white-space: normal; max-width: 560px; line-height: 1.45; }

/* GOVERNED empty-chat welcome + KPI inspirations. Centered greeting with three
   clickable starter cards that each kick off a governed analysis. */
.governed-welcome {
  max-width: 720px;
  margin: 8vh auto 0;
  padding: 0 16px 24px;
  text-align: center;
}
.governed-welcome .gw-badge {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ok, #9ece6a);
  background: rgba(80, 161, 67, 0.10);
  border: 1px solid rgba(80, 161, 67, 0.35);
  border-radius: 999px;
  padding: 3px 11px;
  margin-bottom: 14px;
}
.governed-welcome .gw-title {
  margin: 0 0 6px;
  font-size: 22px;
  font-weight: 650;
  color: var(--text);
}
.governed-welcome .gw-sub {
  margin: 0 auto 22px;
  max-width: 520px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}
.governed-welcome .gw-inspirations {
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
}
.governed-welcome .gw-card {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  background: var(--panel-2, #1a1b26);
  border: 1px solid var(--border, #2a2c3a);
  border-radius: 10px;
  padding: 13px 15px;
  cursor: pointer;
  font: inherit;
  color: var(--text);
  text-align: left;
  transition: border-color 0.12s ease, background 0.12s ease, transform 0.06s ease;
}
.governed-welcome .gw-card:hover {
  border-color: rgba(80, 161, 67, 0.55);
  background: rgba(80, 161, 67, 0.06);
}
.governed-welcome .gw-card:active { transform: translateY(1px); }
.governed-welcome .gw-card-icon { font-size: 18px; line-height: 1.4; flex: 0 0 auto; }
.governed-welcome .gw-card-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.governed-welcome .gw-card-title { font-size: 13.5px; font-weight: 600; color: var(--text); }
.governed-welcome .gw-card-prompt { font-size: 12px; line-height: 1.45; color: var(--muted); }
.governed-welcome .gw-expert-hint { margin: 16px 0 0; font-size: 12.5px; color: var(--muted); text-align: center; }
.governed-welcome .gw-expert-link {
  background: none; border: none; padding: 0; font: inherit; cursor: pointer;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
}
.governed-welcome .gw-expert-link:hover { opacity: 0.8; }

/* Inline #metric reference pill, rendered inside chat user bubbles. */
.metric-pill {
  display: inline-flex;
  align-items: center;
  background: rgba(0, 150, 191, 0.12);
  border: 1px solid rgba(0, 150, 191, 0.40);
  color: var(--accent);
  border-radius: 6px;
  padding: 0 5px;
  font-family: var(--mono, monospace);
  font-size: 0.92em;
  white-space: nowrap;
}

/* ---- Gemini second-opinion validation card -------------------------
   Lives at the bottom of an assistant bubble after the chat-loop runs
   Gemini 2.5 Pro for a read-only review (every 5 user-turns). The
   card is <details>-based — summary is the always-visible header row,
   body opens on click. Colors mirror status:
     status-ok       — green left accent, summary collapsed by default
     status-concerns — amber accent, auto-opened
     status-critical — red accent, auto-opened
     status-error    — muted red (validator itself failed, not Claude's work)
   .pending  — placeholder shown while Gemini is still thinking.
*/
.validation-card {
  margin: 10px 0 0;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted);
  border-radius: 6px;
  font-size: 12px;
  /* details default-removes the disclosure triangle in some browsers;
     we set list-style explicitly on the summary instead. */
}
.validation-card[open] { border-left-width: 4px; }
.validation-card.status-ok       { border-left-color: var(--ok, #6cc06c); }
.validation-card.status-concerns { border-left-color: #d4a35a; }
.validation-card.status-critical { border-left-color: var(--err, #e25555); }
.validation-card.status-error    { border-left-color: var(--muted); opacity: 0.85; }
.validation-card.status-skipped  { border-left-color: var(--muted); opacity: 0.85; }
.validation-card.pending { border-left-color: var(--accent); }

/* ---- /report progress + saved card ---------------------------------
   When a /report command is in flight, the assistant bubble is marked
   .report-mode and the markdown text element (.text.md) is hidden —
   the bubble shows a single .report-progress card instead. Tool blocks
   (bq_query etc.) and the validation card stay visible so the user
   sees what data was queried and what Gemini's second opinion was.
   On completion the card swaps to .rp-done (saved to library) or
   .rp-error (library save failed). The handoff-mid-flow state just
   updates the status text inline — no card replacement.
*/
.turn.assistant.report-mode .text.md {
  display: none;
}
.turn.assistant.report-mode .turn-actions {
  /* Hide the bottom Download/Copy buttons — the report card has its
     own canonical action (Open in Library). Duplicate affordances
     here would invite the user to download the markdown, which is
     exactly what we're steering them away from. */
  display: none;
}
.report-progress {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 6px 0;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 6px;
  font-size: 13px;
}
.report-progress .rp-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: rp-spin 0.8s linear infinite;
  flex-shrink: 0;
}
@keyframes rp-spin { to { transform: rotate(360deg); } }
.report-progress .rp-icon {
  font-size: 18px;
  flex-shrink: 0;
  line-height: 1;
}
.report-progress .rp-body {
  flex: 1;
  min-width: 0;
}
.report-progress .rp-title {
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.report-progress .rp-status {
  color: var(--muted);
  font-size: 12px;
}
.report-progress .rp-link {
  color: var(--accent);
  text-decoration: none;
}
.report-progress .rp-link:hover { text-decoration: underline; }
.report-progress .rp-warn {
  margin-top: 6px;
  padding: 4px 8px;
  background: rgba(212, 163, 90, 0.12);
  border-left: 2px solid #d4a35a;
  border-radius: 3px;
  font-size: 11px;
  color: #b08246;
}
.report-progress.rp-done {
  border-left-color: var(--ok, #6cc06c);
}
.report-progress.rp-done .rp-spinner { display: none; }
.report-progress.rp-error {
  border-left-color: var(--err, #e25555);
}
.report-progress.rp-error .rp-spinner { display: none; }

/* ---- Scheduled-query edit modal -------------------------------------
   SQL textarea uses the monospace font; destination preview is a tiny
   muted label inline with the SQL label so it doesn't crowd the form.
   Schedule fields live in a row so daily/weekly/monthly variants share
   the same horizontal footprint. */
#scheduled-query-modal-backdrop .modal-body label > span {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 4px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
#scheduled-query-modal-backdrop .modal-body input[type="text"],
#scheduled-query-modal-backdrop .modal-body select,
#scheduled-query-modal-backdrop .modal-body textarea {
  width: 100%;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  box-sizing: border-box;
}
#scheduled-query-modal-backdrop .modal-body textarea {
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.5;
  resize: vertical;
}
#scheduled-query-modal-backdrop .modal-body textarea:focus,
#scheduled-query-modal-backdrop .modal-body input:focus,
#scheduled-query-modal-backdrop .modal-body select:focus {
  outline: none;
  border-color: var(--accent);
}
#scheduled-query-modal-backdrop .modal-body label {
  display: block;
  margin: 10px 0;
}
.sq-schedule-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin: 10px 0;
}
.sq-schedule-row label { margin: 0 !important; }
@media (max-width: 700px) {
  .sq-schedule-row { grid-template-columns: 1fr 1fr; }
}
#sq-destination-preview {
  color: var(--accent);
  text-transform: none;
  letter-spacing: 0;
  font-weight: normal;
  margin-left: 8px;
}
#sq-runs-wrap .block-label {
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 4px;
}
#sq-runs {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
}
#sq-runs li.empty {
  padding: 14px;
  color: var(--muted);
  text-align: center;
  font-size: 12px;
}

/* ---- "Suggest more backing queries" button (report-edit step 2) ----
   Sits below the proposal cards. Click → on-demand Haiku + Gemini
   call → new proposals append to the list. Loading state changes
   the button label + shows an inline status note. */
.rf-propose-more-wrap {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--border);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}
.rf-propose-more-wrap button:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* ---- In-chat propose_scheduled_query suggestion card ---------------
   Rendered inside the assistant turn's tool block when Claude calls
   propose_scheduled_query. The card carries the proposed name +
   frequency + reason + collapsible SQL; the Create button opens the
   global SQ modal pre-filled. */
.proposal-card {
  background: rgba(180, 130, 220, 0.08);
  border: 1px solid rgba(180, 130, 220, 0.30);
  border-left: 3px solid #b482dc;
  border-radius: 6px;
  padding: 12px;
  margin: 4px 0;
}
.proposal-head {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin-bottom: 8px;
}
.proposal-title {
  font-weight: 600;
  font-size: 13.5px;
  color: var(--text);
}
.proposal-reason {
  font-size: 12.5px;
  line-height: 1.45;
  margin: 6px 0 10px;
  font-style: italic;
}
.proposal-sql-disclosure {
  margin: 6px 0 10px;
}
.proposal-sql-disclosure summary {
  cursor: pointer;
  font-size: 12px;
  color: var(--muted);
  list-style: revert;
}
.proposal-sql-disclosure summary:hover { color: var(--text); }
.proposal-sql {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.5;
  margin: 6px 0 0;
  white-space: pre-wrap;
  max-height: 280px;
  overflow-y: auto;
}
.proposal-actions {
  display: flex;
  gap: 6px;
  margin-top: 8px;
}

/* ---- Report-edit modal: advanced disclosure (backing queries) ------
   Replaces the earlier 2-step navigation. Sits below the standard
   form fields; collapsed by default; auto-opens when proposals or
   existing deps need to be surfaced. Visual treatment is a subtle
   muted card so it reads as "advanced extra" rather than "another
   required section". */
.rf-advanced {
  margin-top: 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.02);
}
.rf-advanced[open] {
  background: rgba(180, 130, 220, 0.04);
  border-color: rgba(180, 130, 220, 0.25);
}
.rf-advanced-summary {
  padding: 10px 14px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 500;
  list-style: revert;
  user-select: none;
}
.rf-advanced-summary:hover { background: rgba(255, 255, 255, 0.03); }
.rf-advanced-icon {
  margin-right: 6px;
  opacity: 0.7;
}
.rf-advanced-hint {
  font-weight: normal;
  margin-left: 6px;
  font-size: 11.5px;
}
.rf-advanced-body {
  padding: 4px 14px 14px;
  border-top: 1px dashed var(--border);
}

/* ---- (legacy) Report-edit modal: 2-step navigation indicator -------
   Shown above the form body when there are backing queries to manage
   (proposals or existing deps). Pill 1 = "Report basics", pill 2 =
   "Backing queries". Active pill is highlighted; completed pill is
   muted-green so the user knows they're past it.
*/
.step-indicator {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.step-pill {
  flex: 1;
  text-align: center;
  padding: 8px 14px;
  border-radius: 6px;
  font-size: 12.5px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--muted);
  font-weight: 500;
}
.step-pill.active {
  background: rgba(180, 130, 220, 0.15);
  border-color: rgba(180, 130, 220, 0.45);
  color: #b482dc;
  font-weight: 600;
}
.step-pill.completed {
  background: rgba(80, 161, 67, 0.10);
  border-color: rgba(80, 161, 67, 0.35);
  color: var(--ok, #6cc06c);
}

/* Review block inside each proposal card. Shows the Gemini badge
   inline with a one-line summary, then an expandable issue list, and
   (when applicable) an "Apply suggested rewrite" button. */
.rf-proposal-review {
  margin: 8px 0;
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.03);
  border-radius: 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.rf-proposal-review-summary {
  flex: 1;
  font-size: 12px;
  color: var(--text);
  min-width: 0;
}
.rf-proposal-issues {
  width: 100%;
  margin-top: 4px;
  font-size: 12px;
}
.rf-proposal-issues summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 11.5px;
  list-style: revert;
}
.rf-proposal-issues summary:hover { color: var(--text); }
.rf-proposal-issue-list {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
}
.rf-proposal-issue-list li {
  margin: 4px 0;
  padding: 4px 6px;
  background: rgba(255, 255, 255, 0.03);
  border-radius: 3px;
  font-size: 11.5px;
  line-height: 1.5;
}

/* ---- Chat-derived query proposals (report-edit modal) ---------------
   Cards shown beneath the dependency picker when /schedule fires from
   a chat and Haiku identifies aggregations worth pre-computing. Each
   card is editable; the include-checkbox controls whether it gets
   created on report save. */
#rf-proposals-wrap {
  margin-top: 10px;
  padding: 10px 12px;
  background: rgba(180, 130, 220, 0.06);
  border: 1px solid rgba(180, 130, 220, 0.25);
  border-radius: 6px;
}
.rf-proposals-head {
  font-size: 13px;
  margin-bottom: 8px;
}
.rf-proposal-card {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px;
  margin: 8px 0;
}
.rf-proposal-card.excluded {
  opacity: 0.55;
}
.rf-proposal-head {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 6px;
  flex-wrap: wrap;
}
.rf-proposal-name {
  flex: 1;
  min-width: 180px;
  background: var(--panel) !important;
  font-family: var(--mono);
  font-size: 12.5px;
}
.rf-proposal-freq {
  font-size: 12px;
  padding: 4px 8px;
}
.rf-proposal-reason {
  font-size: 11.5px;
  font-style: italic;
  margin-bottom: 6px;
}
.rf-proposal-coverage,
.rf-proposal-autocorrect,
.rf-proposal-dryrun {
  font-size: 11.5px;
  margin-bottom: 4px;
}
.rf-proposal-autocorrect {
  color: var(--accent, #1f6feb);
}
.rf-proposal-dryrun-ok {
  color: #2ea043;
  font-weight: 500;
}
.rf-proposal-sql {
  width: 100%;
  background: var(--panel) !important;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.5;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  resize: vertical;
  box-sizing: border-box;
}
.rf-proposal-sql:focus,
.rf-proposal-name:focus {
  outline: none;
  border-color: var(--accent);
}

/* ---- Scheduled-queries global view ----------------------------------
   The list is now full-page (top-bar 🔁), not a sidebar block — it
   shares the `memory-list-full` layout with the reports library, so
   no scheduled-queries-specific list styling is needed here. We keep
   the `.report-scheduled` type-pill colour rule below so the
   frequency-letter badge in each row stays visually distinct from
   the reports-library S/C pills. */
.type.report-scheduled {
  background: rgba(180, 130, 220, 0.18);
  color: #b482dc;
}

/* ---- Scheduled-report instructions-lint review modal ---------------
   Opens on Save when the linter flags issues with the instructions
   (e.g. "use generate_pdf", "full CSV export of all SKUs"). Shows the
   issue list at top and the suggested rewrite in an editable textarea
   so the user can tweak before applying.
*/
#lint-modal-backdrop {
  /* Stack above the report-edit modal — it opens with the edit modal
     still visible behind it. */
  z-index: 1010;
}
#lint-summary {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: 6px;
  font-size: 13px;
  line-height: 1.4;
}
#lint-summary.lint-status-warnings {
  background: rgba(212, 163, 90, 0.15);
  border-left: 3px solid #d4a35a;
  color: #d4a35a;
}
#lint-summary.lint-status-concerns {
  background: rgba(202, 30, 79, 0.15);
  border-left: 3px solid var(--err, #e25555);
  color: var(--err, #e25555);
}
.lint-issue-list {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
}
.lint-issue {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 8px;
  align-items: start;
  padding: 10px 12px;
  margin: 6px 0;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted);
  border-radius: 6px;
  font-size: 13px;
}
.lint-issue.sev-major { border-left-color: var(--err, #e25555); }
.lint-issue.sev-minor { border-left-color: #d4a35a; }
.lint-issue .lint-sev {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10px;
  padding: 2px 6px;
  border-radius: 3px;
  letter-spacing: 0.05em;
  align-self: start;
  margin-top: 2px;
}
.lint-issue.sev-major .lint-sev {
  background: rgba(202, 30, 79, 0.18);
  color: var(--err, #e25555);
}
.lint-issue.sev-minor .lint-sev {
  background: rgba(212, 163, 90, 0.15);
  color: #d4a35a;
}
.lint-issue .lint-topic {
  color: var(--text);
  font-weight: 600;
  grid-column: 2;
}
.lint-issue .lint-detail {
  grid-column: 2;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
  margin-top: 2px;
}
.lint-rewrite-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 6px;
}
.lint-rewrite-label {
  font-weight: 600;
  font-size: 13px;
  color: var(--text);
}
#lint-suggested {
  width: 100%;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.5;
  resize: vertical;
  box-sizing: border-box;
}
#lint-suggested:focus {
  outline: none;
  border-color: var(--accent);
}

.validation-summary {
  list-style: none;
  cursor: pointer;
  padding: 8px 12px;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.validation-summary::-webkit-details-marker { display: none; }
.validation-summary::marker { display: none; content: ""; }
.validation-summary:hover { background: rgba(255, 255, 255, 0.02); }
.validation-summary strong { font-weight: 600; }
.validation-icon { font-size: 14px; line-height: 1; }
.validation-meta { font-size: 11px; }

/* Expand-chevron at the right end of the summary row — visual hint that
   the card is clickable (the native ::marker is hidden by the rules
   above). Rotates 180° when the <details> is open. Hidden during the
   pending state since there's nothing to expand while Gemini is still
   thinking. */
.validation-summary::after {
  content: "▾";
  margin-left: auto;
  font-size: 10px;
  opacity: 0.55;
  transition: transform 0.15s ease;
}
.validation-card[open] > .validation-summary::after {
  transform: rotate(180deg);
}
.validation-card.pending > .validation-summary::after { display: none; }
/* A skipped review is a one-line note: nothing to expand. */
.validation-card.status-skipped > .validation-summary { cursor: default; }
.validation-card.status-skipped > .validation-summary::after { display: none; }
/* A message waiting for the review stream to end (see showQueuedSendNote). */
.queued-send-note {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 4px 4px 4px 12px;
  margin-bottom: 6px;
  border-left: 3px solid var(--accent);
  font-size: 12px;
  color: var(--muted);
}
.queued-send-note > span { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }

.validation-body {
  padding: 4px 12px 12px 30px;
  border-top: 1px dashed var(--border);
}
.validation-body .validation-text {
  margin: 8px 0;
  line-height: 1.45;
}
.validation-section-head {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: 10px 0 4px;
}
.validation-list {
  list-style: disc;
  margin: 0;
  padding-left: 18px;
  font-size: 12px;
}
.validation-list li { margin-bottom: 4px; line-height: 1.4; }
.concern .concern-severity {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 1px 5px;
  border-radius: 3px;
  margin-right: 4px;
  vertical-align: middle;
}
.concern.severity-major .concern-severity {
  background: rgba(226, 85, 85, 0.15);
  color: var(--err, #e25555);
  border: 1px solid rgba(226, 85, 85, 0.4);
}
.concern.severity-minor .concern-severity {
  background: rgba(212, 163, 90, 0.12);
  color: #d4a35a;
  border: 1px solid rgba(212, 163, 90, 0.35);
}
.concern .concern-topic {
  font-weight: 600;
  color: var(--text);
}
.concern .concern-detail { color: var(--text); }
.validation-footnote {
  margin-top: 10px;
  font-size: 11px;
  font-style: italic;
}

/* "Ask Claude to address these concerns" button — appears on `concerns`
   validation cards only (see buildValidationCardEl). Sits at the bottom
   of the card body, right-aligned, distinct enough from the surrounding
   prose that it reads as an interaction affordance but not so loud it
   competes with the concerns themselves. */
.validation-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 12px;
  padding-top: 8px;
  border-top: 1px dashed var(--border);
}
.validation-handoff-btn {
  font-size: 11.5px;
  padding: 4px 12px;
  background: rgba(212, 163, 90, 0.12);
  color: #d4a35a;
  border: 1px solid rgba(212, 163, 90, 0.4);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.validation-handoff-btn:hover:not(:disabled) {
  background: rgba(212, 163, 90, 0.25);
  color: var(--text);
}
.validation-handoff-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Spinning glyph for the in-flight placeholder. */
.validation-spinner {
  display: inline-block;
  animation: validation-spin 1.4s linear infinite;
  margin-right: 2px;
}
@keyframes validation-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ---- Gemini → Claude handoff divider ----
   Visual marker that sits between two consecutive assistant bubbles when
   Gemini's validation flagged the first one as critical and the server
   continued the chat-loop with a synthetic prompt. Without this, two
   assistant bubbles with no user turn between them are confusing.
*/
.handoff-divider {
  margin: 16px 0 8px;
  padding: 6px 12px;
  font-size: 11.5px;
  color: var(--muted);
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  position: relative;
}
.handoff-divider::before,
.handoff-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--border) 40%, var(--border) 60%, transparent);
  max-width: 80px;
}
.handoff-divider .handoff-icon { font-size: 13px; }

/* SQL hand-off banner — sits between the chat title bar and the message
   list. Inserted by _renderHandoffBanner when the user launches a chat
   from a flow that expects a final SQL block back (scorecard row
   generator, chart adapt, …). Apply button extracts the latest ```sql
   block and routes it back to the originating modal. */
.handoff-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  margin: 8px 12px 0;
  border: 1px solid var(--accent);
  border-left-width: 3px;
  border-radius: 8px;
  background: linear-gradient(
    to right,
    rgba(0, 150, 191, 0.10),
    rgba(0, 150, 191, 0.04)
  );
  font-size: 13px;
}
/* Metric-builder (info-only) variant — green accent, no action buttons. */
.handoff-banner.handoff-banner-info {
  border-color: rgba(80, 161, 67, 0.45);
  background: linear-gradient(to right, rgba(80, 161, 67, 0.10), rgba(80, 161, 67, 0.04));
}
.handoff-banner-context {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.handoff-banner-title {
  font-weight: 600;
  color: var(--text);
  font-size: 12.5px;
}
.handoff-banner-body {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.4;
}
.handoff-banner-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
  align-items: center;
}

/* Auto-apply status strip (chart-adapt mode) — no buttons; the hand-off
   happens automatically on Gemini approval. Thinner + quieter than the
   interactive banner. Colour shifts with the current phase. */
.handoff-status-strip {
  padding: 8px 14px;
  margin: 8px 12px 0;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  background: rgba(0, 150, 191, 0.06);
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.4;
}
.handoff-status-strip.is-validating { border-left-color: #d6a73b; background: rgba(214, 167, 59, 0.08); }
.handoff-status-strip.is-applying { border-left-color: #3fb37f; background: rgba(63, 179, 127, 0.10); color: var(--text); }
.handoff-status-strip.is-blocked { border-left-color: #e5534b; background: rgba(229, 83, 75, 0.08); }
/* Confirmation phase — Gemini approved; ask the user before applying. */
.handoff-status-strip.is-confirm {
  display: flex;
  align-items: center;
  gap: 12px;
  border-left-color: #3fb37f;
  background: rgba(63, 179, 127, 0.10);
  color: var(--text);
}
.handoff-status-msg { flex: 1 1 auto; min-width: 0; }
.handoff-status-actions { display: flex; gap: 6px; flex-shrink: 0; align-items: center; }

/* update_schema tool block — distinct from SQL/bq_query blocks. */
.tool[data-tool-name="update_schema"] .badge {
  background: rgba(0, 150, 191, 0.15); color: var(--accent); border-color: var(--accent);
}
pre.schema-patch {
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 10px; font-family: var(--mono); font-size: 11.5px;
  color: var(--text); margin: 6px 0 0 0; white-space: pre-wrap;
}

/* Full-screen login gate — shown when onAuthStateChanged reports no user. */
#login-screen {
  position: fixed; inset: 0; z-index: 500;
  background: radial-gradient(ellipse at top, rgba(0, 150, 191,0.10) 0%, var(--bg) 60%);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
#login-screen[hidden] { display: none !important; }

/* VPN-required gate — same visual language as login-screen so it doesn't
   look like a different app. Slightly higher z-index so it can layer on
   top of the workspace if a mid-session API call hits the network gate. */
#vpn-gate-screen {
  position: fixed; inset: 0; z-index: 600;
  background: radial-gradient(ellipse at top, rgba(202, 30, 79,0.10) 0%, var(--bg) 60%);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
#vpn-gate-screen[hidden] { display: none !important; }
#vpn-gate-screen .login-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 40px 44px; max-width: 480px; width: 100%;
  box-shadow: 0 20px 60px rgba(0,0,0,0.4);
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 14px;
}
#vpn-gate-screen .login-logo { font-size: 36px; line-height: 1; }
#vpn-gate-screen .login-title { margin: 0; font-size: 22px; color: var(--text); }
#vpn-gate-screen .login-sub { margin: 0; color: var(--muted); font-size: 13px; }
#vpn-gate-screen .vpn-steps {
  text-align: left; margin: 6px 0 6px 0; padding-left: 22px;
  color: var(--text); font-size: 13px; line-height: 1.7;
}
#vpn-gate-screen .vpn-steps li::marker { color: var(--muted); }
/* The misconfigured variant (#571 review) hides the steps and the IP readout:
   both tell the user to fix their own network, which is wrong advice when the
   allowlist is what's broken. Re-asserted explicitly so a future `display:`
   rule on these two can't outrank the UA [hidden] and leave them on screen. */
#vpn-gate-screen .vpn-steps[hidden],
#vpn-gate-screen .vpn-diag[hidden] { display: none !important; }
#vpn-gate-screen .vpn-diag {
  margin: 4px 0; font-size: 12px; color: var(--muted);
}
#vpn-gate-screen .vpn-diag code {
  font-family: var(--mono); background: var(--panel-2);
  padding: 2px 6px; border-radius: 4px; color: var(--text);
}
#vpn-gate-screen .login-note {
  margin: 8px 0 0 0; font-size: 11.5px; color: var(--muted); line-height: 1.5;
}

/* Maintenance mode (#739). The screen sits above EVERYTHING — modals, toasts
   (z 11000), the view-as banner — and is opaque, so nothing behind it shows or
   takes a click; applyMaintenanceMode() also marks the rest of the page inert.
   Token-driven only, so it follows whichever palette :root carries. */
#maintenance-screen {
  position: fixed; inset: 0; z-index: 2147483000;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  padding: 16px; overflow-y: auto;
}
#maintenance-screen[hidden] { display: none !important; }
#maintenance-screen .login-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 36px 32px; max-width: 480px; width: 100%; min-width: 0; box-sizing: border-box;
  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 12px; outline: none;
}
#maintenance-screen .login-logo { font-size: 36px; line-height: 1; }
#maintenance-screen .login-title { margin: 0; font-size: 22px; color: var(--text); }
#maintenance-screen .login-sub { margin: 0; color: var(--text); font-size: 14px; line-height: 1.5; }
/* The admin's note: plain text (textContent), wrapped even if it is one long
   unbroken token, so a pasted URL can't push the card past a 380px screen. */
#maintenance-screen .maintenance-note {
  margin: 4px 0 0; padding: 10px 14px; width: 100%; box-sizing: border-box;
  background: var(--panel-2); border-radius: 8px;
  color: var(--text); font-size: 13px; line-height: 1.5;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
#maintenance-screen .maintenance-note[hidden] { display: none !important; }
#maintenance-screen .maintenance-signout {
  margin-top: 4px; background: none; border: 0; padding: 4px 8px; font: inherit;
  font-size: 12px; color: var(--muted); text-decoration: underline; cursor: pointer;
}

/* Admin strip: one fixed line above the app. #app and the fixed nav rail are
   both sized off the viewport, so each is shifted down by the strip's height
   rather than letting the page grow a scrollbar. */
#maintenance-strip {
  /* Above the header (60) and nav rail; below modal backdrops (1000+), so a
     dialog still covers it. The React overlay and side chat are offset below. */
  position: fixed; top: 0; left: 0; right: 0; z-index: 990;
  height: 28px; box-sizing: border-box; padding: 0 12px;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  background: var(--warn); color: #1b160e;
  font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden;
}
#maintenance-strip[hidden] { display: none !important; }
#maintenance-strip .maintenance-strip-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
#maintenance-strip .maintenance-strip-link { flex: 0 0 auto; color: inherit; text-decoration: underline; }
body.has-maintenance-strip #app { margin-top: 28px; height: calc(100vh - 28px); height: calc(100dvh - 28px); }
body.has-maintenance-strip #nav-rail { top: calc(var(--header-h, 72px) + 28px); }
/* On desktop the header is position:fixed at top:0 (see the 768px block near
   the top), so #app's margin doesn't move it — shift it explicitly. The content
   row's padding-top (--header-h) plus #app's 28px margin then clears both. */
@media (min-width: 768px) {
  body.has-maintenance-strip #app > header { top: 28px; }
}
/* The React overlay's top:0 is an inline style, hence !important. */
body.has-maintenance-strip #react-mount { top: 28px !important; }
@media (min-width: 901px) {
  body.has-maintenance-strip .side-chat-panel { top: 28px; }
}
#login-screen .login-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 40px 44px; max-width: 440px; width: 100%;
  box-shadow: 0 20px 60px rgba(0,0,0,0.4);
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 14px;
}
#login-screen .login-logo { font-size: 40px; color: var(--accent); line-height: 1; }
#login-screen .login-title { margin: 0; font-size: 22px; color: var(--text); }
#login-screen .login-sub { margin: 0; color: var(--muted); font-size: 13px; }
#login-screen .login-google {
  margin-top: 10px; display: inline-flex; align-items: center; gap: 10px;
  padding: 10px 18px; font-size: 14px; font-weight: 500;
  background: #fff; color: #1f1f1f; border: 1px solid #dadce0;
}
#login-screen .login-google:hover { background: #f8f9fa; border-color: #c6c9ce; }
#login-screen .login-google .g-icon { display: inline-flex; line-height: 0; }
#login-screen .login-note {
  margin: 6px 0 0 0; font-size: 12px; color: var(--muted);
  line-height: 1.5;
}
#login-screen .login-domains {
  font-family: var(--mono); color: var(--text); font-size: 11.5px;
  background: var(--panel-2); padding: 2px 6px; border-radius: 4px;
  display: inline-block;
}
#login-screen .login-error {
  margin: 6px 0 0 0; font-size: 12px;
  background: rgba(202, 30, 79, 0.10); border: 1px solid var(--err);
  color: var(--err); border-radius: 6px; padding: 6px 10px;
}
#dashboard-list .dashboard-item .share {
  background: none; border: none; color: var(--muted); cursor: pointer;
  padding: 2px 6px; border-radius: 4px; font-size: 11px; line-height: 1;
  opacity: 0; transition: opacity 0.1s;
}
#dashboard-list .dashboard-item:hover .share,
#dashboard-list .dashboard-item.active .share { opacity: 1; }
#dashboard-list .dashboard-item .share:hover { background: var(--panel); color: var(--accent); }
#dashboard-list .dashboard-item .shared-tag { font-size: 11px; padding: 1px 4px; }
#dashboard-list .dashboard-item.shared-in .name { font-style: italic; color: var(--muted); }

/* PDF reports sidebar block (cross-chat, cross-project). */
.pdfs-block { border-left-color: var(--accent); }
#pdf-list { list-style: none; margin: 0; padding: 6px; overflow-y: auto; max-height: 260px; }
#pdf-list .pdf-item {
  display: grid; grid-template-columns: auto 1fr auto auto; gap: 6px; align-items: center;
  padding: 6px 10px; border-radius: 6px;
  font-size: 12.5px; border: 1px solid transparent;
}
#pdf-list .pdf-item:hover { background: var(--panel-2); border-color: var(--border); }
#pdf-list .pdf-item .pdf-body { min-width: 0; }
#pdf-list .pdf-item .name {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#pdf-list .pdf-item .meta { font-size: 10.5px; line-height: 1.3; }
#pdf-list .empty { color: var(--muted); font-size: 12px; padding: 10px; text-align: center; }

/* Shared-with-me sidebar block (cross-project). */
.shared-block { border-left-color: var(--warn); }
#shared-list { list-style: none; margin: 0; padding: 6px; overflow-y: auto; max-height: 280px; }
#shared-list .shared-item {
  display: grid; grid-template-columns: auto 1fr auto; gap: 6px; align-items: center;
  padding: 6px 10px; border-radius: 6px;
  cursor: pointer; font-size: 12.5px; border: 1px solid transparent;
}
#shared-list .shared-item:hover { background: var(--panel-2); border-color: var(--border); }
#shared-list .shared-item .kind-tag { font-size: 12px; padding: 1px 4px; }
#shared-list .shared-item .name {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#shared-list .shared-item .owner {
  font-size: 10.5px; font-family: var(--mono); max-width: 110px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#shared-list:empty::after {
  content: "Nothing shared with you yet.";
  display: block; color: var(--muted); font-size: 12px; padding: 10px; text-align: center;
}

/* Dashboard view (alternates with chat in the main column) */
#dashboard-view[hidden] { display: none; }
/* Full width of the main column with a small 32px gutter (no max-width cap),
   on the beige page colour. A flex column so the tab-content container
   (.dashboard-body) stretches to the bottom of the view when it's short. */
#dashboard-view {
  overflow-y: auto; padding: 20px 32px 24px; width: 100%; box-sizing: border-box;
  display: flex; flex-direction: column;
  background: var(--bg);
}
#dashboard-view > * { flex: 0 0 auto; }
@media (max-width: 767px) {
  /* Phones: tighter gutter than the 32px desktop one (declared after it so it wins). */
  #dashboard-view { padding: 14px 12px; }
  #dashboard-view > .dashboard-body { padding: 10px; border-radius: 12px; }
  .dashboard-tab { padding: 8px 14px; }
}
/* Title left, dashboard-wide actions right, on one line; a short gap down to
   the tab row (the freshness line that used to sit here is the data-version
   button's label now). */
.dashboard-header {
  display: flex; justify-content: space-between; align-items: center;
  padding-bottom: 0; margin-bottom: 14px;
  gap: 16px;
}
@media (max-width: 767px) {
  /* Phones stack title + actions (the column rule near the top of this file);
     keep the actions row full-width there, not centred. */
  .dashboard-header { align-items: stretch; margin-bottom: 10px; }
}
.dashboard-header-left { flex: 1; min-width: 0; }
.dashboard-header-left h2 { margin: 0; font-size: 18px; }
#dashboard-description:not([hidden]):not(:empty) { margin-top: 4px; }
.dashboard-header-right { display: flex; gap: 6px; flex-shrink: 0; }
/* Display + gap are shared across viewports, but the column template differs:
   desktop auto-fills with a 440px min so cards tile nicely on wide screens,
   while mobile uses minmax(0, 1fr) (set in the @media (max-width: 767px)
   block near the top of this file). The desktop rule was originally at
   global scope but sat in source AFTER the mobile rule with equal
   specificity — so the 440px minimum kept winning on phones and forced
   cards to 440px wide on a 345px-wide grid. Wrap it in a min-width guard
   to scope it to desktop only. */
/* align-items: start — NOT the default `stretch` (#539). Without it every card
   stretches to its grid row's height, and that height is set by the tallest card
   in the row, so resizing ONE card silently resized its row siblings (growing
   AND shrinking). The trade is visible whitespace beside a tall card where a
   short auto-height neighbour used to fill the row; independent resizing is
   worth it, and the row was never a meaningful unit here anyway. */
.dashboard-grid { display: grid; gap: 14px; align-items: start; }
/* #311 — explicit responsive column grid instead of `auto-fill`, so the
   column count is a fixed contract per breakpoint rather than an emergent
   property of viewport width. `--dash-cols` is the single source of the
   rendered column count; public/app.js mirrors these exact breakpoints
   (_dashGridCols) so JS-applied spans and this template always agree.
     mobile  (<768px)     → 1 column  (the @media (max-width:767px) block near
                                        the top of this file owns that + forces
                                        every card to `grid-column: 1 / -1`)
     tablet  (768–1279px) → 6 columns
     desktop (>=1280px)   → 12 columns  (Tailwind `xl` / react-grid-layout `lg`) */
@media (min-width: 768px) {
  .dashboard-grid {
    --dash-cols: 6;
    grid-template-columns: repeat(var(--dash-cols), minmax(0, 1fr));
    gap: 14px;
  }
}
@media (min-width: 1280px) {
  .dashboard-grid { --dash-cols: 12; }
}

/* #251 — the dashboard grid and the (optional) source-chat side panel sit in a
   flex row so the grid reflows to make room when the panel opens. The grid keeps
   its id/class, so all existing #dashboard-grid selectors are unaffected; the
   dashboard-tabs strip is injected before this wrapper (see renderDashboardTabStrip). */
.dashboard-body { display: flex; align-items: flex-start; gap: 16px; }
/* Real tabs: the tab content is a light container on the beige page, and the
   active tab (same colour, no gap) is one shape with it. With the first tab
   active the container's top-left corner is square, so the tab runs straight
   down into it. Cards go white so they still lift off the light container. */
#dashboard-view > .dashboard-body {
  flex: 1 0 auto;
  background: var(--panel); border-radius: 14px; padding: 16px;
}
#dashboard-view > .dashboard-body .dashboard-card { background: #fff; }
#dashboard-tabs:not(.dashboard-tabs-filter-only):not(.is-scrolled):has(.dashboard-tabs-scroll > .dashboard-tab.active:first-child) + .dashboard-body {
  border-top-left-radius: 0;
}
.dashboard-body > #dashboard-grid { flex: 1 1 auto; min-width: 0; }

/* #282 — the dashboard filter overlay: a popover anchored under the header's
   Filters button (position + width + max-height are set by _dfpPosition()).
   Header, removable chips of the active filters, one collapsible section per
   filter, "+ Add filter", and a Clear all / Save as default footer. */
/* The tab-level actions — New component, Chat, Filters — at the right end of
   the tab row (renderDashboardTabStrip moves #dashboard-tab-actions there). */
.dashboard-tabs-actions { margin: 0 0 8px auto; align-self: center; display: flex; flex: 0 0 auto; flex-wrap: nowrap; justify-content: flex-end; align-items: center; gap: 6px; }
/* Actions-only row (a viewer's single-tab dashboard): no tab to merge with. */
.dashboard-tabs-filter-only .dashboard-tabs-actions { margin-bottom: 10px; }
.dashboard-tabs-filter-only { justify-content: flex-end; }
/* One compact button style for the header's dashboard-wide actions and the tab
   row's tab-level ones: icon + label, 13px. */
.dashboard-header-right { align-items: center; }
.dashboard-header-right .btn,
.dashboard-tabs-actions .btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; font-size: 13px; line-height: 1.4;
}
.dashboard-header-right .btn > svg,
.dashboard-tabs-actions .btn > svg { flex: 0 0 auto; width: 15px; height: 15px; vertical-align: 0; }
/* The display above would otherwise un-hide a [hidden] button (the data-version
   button without snapshots, Chat before a dashboard loads). */
.dashboard-header-right .btn[hidden],
.dashboard-tabs-actions .btn[hidden] { display: none; }
/* Phones: the actions don't fit beside the tabs — give them their own line ABOVE
   the tabs, so a wrapped row never pushes the tabs off the container. */
@media (max-width: 767px) {
  .dashboard-tabs { flex-wrap: wrap; gap: 0; }
  .dashboard-tabs-actions { order: -1; flex: 1 0 100%; flex-wrap: wrap; margin: 0 0 8px; }
  .dashboard-header-right .btn,
  .dashboard-tabs-actions .btn { font-size: 12px; padding: 6px 10px; }
}
.dashboard-new-wrap[hidden] { display: none; }
.dashboard-filter-btn { display: inline-flex; align-items: center; gap: 6px; }
.dashboard-filter-btn.dfp-btn-active { border-color: var(--text); }
.dfp-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; box-sizing: border-box;
  border-radius: 999px; background: var(--text); color: var(--panel);
  font-size: 11px; font-weight: 600; line-height: 1;
}
.dashboard-filter-panel {
  position: fixed; z-index: 250; /* under the Advanced… popover (300) */
  display: flex; flex-direction: column; min-width: 0; box-sizing: border-box;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: 0 16px 40px rgba(27, 22, 14, 0.16), 0 2px 6px rgba(27, 22, 14, 0.06);
  overflow: hidden;
}
.dashboard-filter-panel[hidden] { display: none; }
.dfp-header {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 18px 20px 6px; flex-shrink: 0;
}
.dfp-title { margin: 0; font-size: 18px; font-weight: 600; line-height: 1.3; }
.dfp-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; margin-right: -6px; padding: 0;
  border: 0; border-radius: 8px; background: transparent; color: var(--text); cursor: pointer;
}
.dfp-close:hover { background: var(--panel-2); }
.dfp-chips {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 6px 20px 4px; flex-shrink: 0;
}
.dfp-chip {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%; min-width: 0;
  background: var(--panel-2); border-radius: 6px;
  padding: 3px 4px 3px 9px; font-size: 12px; line-height: 1.3;
}
.dfp-chip-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dfp-chip-x {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  font: inherit; font-size: 14px; line-height: 1; padding: 0 4px; border-radius: 4px;
}
.dfp-chip-x:hover { color: var(--text); background: var(--border); }
.dfp-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 8px 20px 12px; display: flex; flex-direction: column; gap: 8px;
}
.dfp-note { color: var(--muted); font-size: 12.5px; line-height: 1.45; padding: 8px 0; }
.dfp-section {
  /* Each filter is its own softly tinted card, so the groups read apart
     without lines between them. */
  display: flex; flex-direction: column;
  background: var(--panel-2); border-radius: 12px; padding: 0 14px;
}
.dfp-section-head {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 12px 0; border: 0; background: transparent; color: var(--text);
  font: inherit; font-size: 15px; font-weight: 500; text-align: left; cursor: pointer;
}
.dfp-section-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.dfp-section-title { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dfp-section-summary {
  flex: 1 1 auto; min-width: 0; text-align: right;
  color: var(--muted); font-size: 12px; font-weight: 400;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A filter that is narrowing the view says so in full-strength text. */
.dfp-section.has-value .dfp-section-summary { color: var(--text); font-weight: 600; }
.dfp-chevron { flex: 0 0 auto; transition: transform 120ms ease; }
.dfp-section.open > .dfp-section-head .dfp-chevron { transform: rotate(180deg); }
.dfp-section-body { display: flex; flex-direction: column; gap: 8px; padding: 0 0 14px; min-width: 0; }
/* Option rows (country radios, dimension checkboxes): box left, text right. */
.dfp-options { display: flex; flex-direction: column; gap: 2px; }
.dashboard-filter-panel .dfp-opt.country-pill,
.dfp-dim-val,
.dfp-check {
  display: flex; flex-direction: row; align-items: center; gap: 10px;
  padding: 4px 2px; margin: 0; border: 0; border-radius: 6px;
  background: transparent; color: var(--text);
  font-size: 13.5px; font-weight: 400; white-space: normal; cursor: pointer;
}
.dashboard-filter-panel .dfp-opt.country-pill:hover,
.dfp-dim-val:hover { background: rgba(127, 110, 80, 0.10); color: var(--text); }
.dashboard-filter-panel .dfp-opt.country-pill.active { background: transparent; color: var(--text); font-weight: 500; }
.dashboard-filter-panel .dfp-opt.country-pill small { margin-left: 4px; font-size: 11.5px; }
.dashboard-filter-panel input[type="checkbox"],
.dashboard-filter-panel input[type="radio"] {
  width: 16px; height: 16px; margin: 0; flex: 0 0 auto; accent-color: var(--text); cursor: pointer;
}
.dfp-dim-val > span, .dfp-opt > span, .dfp-check > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.dfp-dim-val > span { white-space: nowrap; }
.dfp-section-body .country-scope-note { align-self: flex-start; margin-left: 0; }
.dfp-section-body .dashboard-time-range-select {
  width: 100%; min-width: 0; box-sizing: border-box; background: var(--panel);
  border-radius: 10px; padding: 8px 10px; font-size: 13.5px;
}
.dfp-date-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.dfp-date-row label { display: flex; flex-direction: column; gap: 4px; min-width: 0; font-size: 12.5px; }
.dfp-date-row input {
  width: 100%; min-width: 0; box-sizing: border-box;
  padding: 8px 10px; border-radius: 10px; font: inherit; font-size: 13px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
}
.dfp-dim-search {
  width: 100%; box-sizing: border-box; min-width: 0; padding: 7px 10px;
  font: inherit; font-size: 13px; background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
}
.dfp-dim-values { max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.dfp-dim-more { padding: 6px 2px; color: var(--muted); font-size: 12px; }
.dfp-dim-actions { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.dfp-link {
  border: 0; background: transparent; padding: 2px 0; cursor: pointer;
  color: var(--muted); font: inherit; font-size: 12.5px; text-align: left;
  text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 2px;
}
.dfp-link:hover:not(:disabled) { color: var(--text); text-decoration-color: currentColor; }
.dfp-link:disabled { opacity: 0.5; cursor: default; }
.dfp-section-body > .dfp-link { align-self: flex-start; }
/* ⚙ settings pane (country / time configuration, editors only). */
.dfp-settings {
  display: flex; flex-direction: column; gap: 10px; min-width: 0;
  border: 1px solid var(--border); border-radius: 12px; padding: 12px; background: var(--panel);
}
.dfp-settings .filter-options-wrap { border: 0; padding: 0; background: transparent; }
.dfp-settings .filter-mode-fieldset { background: var(--panel-2); }
.dfp-settings .filter-option-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr) auto; }
.dfp-settings select { width: 100%; min-width: 0; box-sizing: border-box; }
/* The settings moved out of the modal's <form> into the overlay, so the
   `.modal form select` styling no longer reaches the market-group / comparison
   selects — mirror it here (option-row inputs keep their own rule). */
.dfp-settings select {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 7px 10px; font: inherit;
}
.dfp-settings select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(0, 150, 191, 0.14);
}
.dfp-settings-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.dfp-settings-actions .spacer { flex: 1 1 auto; }
.dfp-settings .error-text:empty { display: none; }
/* "+ Add filter" and its dimension picker. */
.dfp-add { padding: 4px 2px; }
.dfp-add-btn {
  border: 0; background: transparent; padding: 4px 0; cursor: pointer;
  color: var(--text); font: inherit; font-size: 14px; font-weight: 500;
}
.dfp-add-btn:hover { text-decoration: underline; text-underline-offset: 3px; }
.dfp-add-head { display: flex; align-items: center; gap: 10px; }
.dfp-add-head .dfp-add-search { flex: 1 1 auto; }
.dfp-add-list { display: flex; flex-direction: column; margin-top: 6px; max-height: 240px; overflow-y: auto; }
.dfp-add-item {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  border: 0; border-radius: 6px; background: transparent; padding: 7px 6px; cursor: pointer;
  color: var(--text); font: inherit; font-size: 13.5px; text-align: left;
}
.dfp-add-item:hover, .dfp-add-item:focus-visible { background: var(--panel-2); }
.dfp-add-item > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dfp-add-item > small { flex: 0 0 auto; }
/* Footer: two equal buttons (Clear all | Save as default), then who sees what. */
.dfp-footer { flex-shrink: 0; padding: 12px 20px 16px; border-top: 1px solid var(--border); }
.dfp-footer-btns { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 10px; }
.dfp-footer-btns .btn { padding: 10px 12px; font-size: 14px; font-weight: 500; }
.dfp-footer-note { margin-top: 8px; font-size: 11.5px; line-height: 1.4; }
/* Chat panel chrome, shared by the dashboard's chat overlay (#dashboard-chat-panel,
   positioned below) and the React views' .side-chat-panel. The dashboard one is
   position: fixed, so its JS-measured height can't feed back into the page's
   scroll length (the reason the old docked panel took its height from a flex row). */
.dashboard-chat-panel {
  flex: 0 0 clamp(320px, 38.2%, 560px);
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
}
.dashboard-chat-panel[hidden] { display: none; }

/* Side chat panel for the React overlay views (Process today). Fixed, because the
   overlay it sits beside is fixed; z-index one above the overlay so it is never
   painted under it. Width matches the dashboard panel so the two read as one
   affordance rather than two different chats. */
.side-chat-panel {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: min(440px, 42vw);
  z-index: 10001;
  display: flex; flex-direction: column;
  background: var(--panel, #fff);
  border-left: 1px solid var(--border, #d9cfc0);
  box-shadow: -8px 0 24px rgba(0,0,0,.08);
}
/* Make room rather than overlapping: an overlay covered by the panel would put the
   map underneath the answer about the map. */
#react-mount.rm-chat-open { right: min(440px, 42vw); }
@media (max-width: 900px) {
  /* No room to sit side by side — the panel takes the bottom half and the overlay
     keeps the top, so the thing being discussed stays visible. */
  .side-chat-panel { top: auto; left: 0; width: auto; height: 62vh; border-left: 0; border-top: 1px solid var(--border, #d9cfc0); }
  #react-mount.rm-chat-open { right: 0; bottom: 62vh; }
}
/* Floating variant (Advanced artifacts: "New sandboxed artifact", "Edit in chat",
   "Ask"): a window over the page instead of a docked column, so the gallery or the
   page stays usable underneath. Bottom right by default; the title bar drags it
   (app.js sets left/top, kept per browser); minimised it is just the title bar. */
.scp-min { display: none; }
.side-chat-panel.side-chat-floating .scp-min { display: inline-flex; }
.side-chat-panel.side-chat-floating {
  top: auto; left: auto; right: 24px; bottom: 24px;
  width: min(420px, calc(100vw - 32px)); height: min(640px, calc(100vh - 48px));
  border: 1px solid var(--border, #d9cfc0); border-radius: 14px; overflow: hidden;
  box-shadow: 0 18px 50px rgba(24, 20, 11, .18), 0 2px 6px rgba(24, 20, 11, .08);
}
.side-chat-panel.side-chat-floating[style*="left"] { right: auto; bottom: auto; }
.side-chat-panel.side-chat-floating .dcp-header { cursor: move; user-select: none; }
.side-chat-panel.side-chat-floating .dcp-header button { cursor: pointer; }
.side-chat-panel.side-chat-floating .dcp-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-chat-panel.side-chat-floating.scp-minimised { height: auto; }
.side-chat-panel.side-chat-floating.scp-minimised > :not(.dcp-header) { display: none !important; }
@media (max-width: 900px) {
  /* A phone: a bottom sheet across the width; no dragging (app.js ignores it here). */
  .side-chat-panel.side-chat-floating,
  .side-chat-panel.side-chat-floating[style*="left"] {
    left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important;
    width: auto; height: 70vh; border-radius: 14px 14px 0 0; border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .side-chat-panel.side-chat-floating .dcp-header { cursor: default; }
}
/* The dashboard's chat is an OVERLAY, like the filter overlay (#282): a fixed
   popover dropping down directly under the header's Chat button to the
   dashboard's right edge; at least ⅓ of the view wide, or wider to reach from
   the button to that edge (top/left/width/height from _dcpPosition()). Opening it never re-flows the
   grid — the cards stay exactly where they are, the chat floats over them. It
   keeps its own scrollers inside, so the composer is always on screen. */
#dashboard-chat-panel {
  position: fixed; z-index: 240; /* under the filter overlay (250) */
  flex: none; max-height: none; box-sizing: border-box;
  border-radius: 16px;
  box-shadow: 0 16px 40px rgba(27, 22, 14, 0.16), 0 2px 6px rgba(27, 22, 14, 0.06);
}
#dashboard-chat-panel .dcp-header { padding: 12px 12px 10px 18px; }
#dashboard-chat-panel .dcp-title { font-size: 15px; }
/* Neutralize main#chat's centered-column sizing (max-width:900px; margin:0 auto)
   so it fills the panel edge-to-edge, and let it be the scrolling element. */
.dashboard-chat-panel.live > #chat {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  max-width: none; margin: 0; width: auto; padding: 12px; gap: 12px;
}
.dashboard-chat-panel.live > #composer-wrap { flex: 0 0 auto; }
.dcp-header {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
/* Chat switcher row — lists every chat linked to this dashboard (general
   chats, the chats cards came from, per-card edit chats) plus ＋ for a new one. */
.dcp-switch-row {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.dcp-switch-row[hidden] { display: none; }
.dcp-switch-label { font-size: 11px; color: var(--muted); }
.dcp-chat-select {
  flex: 1; min-width: 0; font-size: 12px; padding: 3px 6px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
}
.dcp-title {
  font-weight: 600; font-size: 13px; flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dcp-body { overflow-y: auto; padding: 12px; flex: 1; }
.dcp-note { color: var(--muted); font-size: 11px; margin-bottom: 10px; }
.dcp-empty { color: var(--muted); font-size: 12px; }
.dcp-turn { margin-bottom: 14px; }
.dcp-role {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); margin-bottom: 3px;
}
.dcp-text { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13px; line-height: 1.45; }
.dcp-turn.dcp-user .dcp-text { color: var(--text); }
.dcp-turn.dcp-assistant { border-left: 2px solid var(--border); padding-left: 8px; }
/* Clickable card provenance — the "From: <chat>" link that opens the source
   chat in the panel (replaces the plain-text From: tooltip when a chat id exists). */
.card-source-chat-link {
  display: inline-flex; align-items: center; gap: 4px;
  max-width: 180px; overflow: hidden; white-space: nowrap;
}
.card-source-chat-link > svg { flex: 0 0 auto; }
.card-source-chat-link > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 900px) {
  /* Narrow screens. (The dashboard's overlay ignores these — its inline size
     from _dcpPosition() and the #dashboard-chat-panel rule win.) */
  .dashboard-body { flex-direction: column; }
  /* …and the grid spans the column: under `align-items: flex-start` it shrank to
     its content, so a tab of KPI cards rendered at half the width. */
  .dashboard-body > #dashboard-grid { align-self: stretch; }
  .dashboard-chat-panel { flex-basis: auto; width: 100%; max-height: 60vh; }
  .dashboard-chat-panel.live { height: 70vh; max-height: 70vh; }
}
.dashboard-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 12px; display: flex; flex-direction: column; gap: 8px;
  position: relative; /* anchor drop-indicator pseudo-elements */
  transition: opacity 120ms ease, transform 120ms ease, border-color 120ms ease;
}
/* No card-wide grab cursor — drag is now grip-only. Keep the body
   cursor default so users don't get false "this whole thing is
   draggable" affordance. Only the grip shows the grab cursor. */
.dashboard-card.dragging { opacity: 0.4; transform: scale(0.99); }
/* Refresh-in-progress (e.g. after a time-window switch): dim the body + show a
   spinning ↻ so it's clear the card is re-querying. Cleared when fresh data
   lands (the card re-renders) or the refresh finishes. */
.dashboard-card.is-refreshing > *:not(header) { opacity: 0.35; transition: opacity 120ms ease; }
.dashboard-card.is-refreshing::after {
  content: "↻"; position: absolute; top: 55%; left: 50%;
  transform: translate(-50%, -50%); z-index: 6;
  font-size: 24px; line-height: 1; color: var(--accent, #b4571f);
  animation: dash-card-spin 0.8s linear infinite; pointer-events: none;
}
@keyframes dash-card-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
/* Drop-target indicators: a glowing vertical bar on the appropriate edge. */
.dashboard-card.drop-target-before::before,
.dashboard-card.drop-target-after::after {
  content: ""; position: absolute; top: 4px; bottom: 4px; width: 3px;
  background: var(--accent); border-radius: 2px;
  box-shadow: 0 0 8px var(--accent);
  pointer-events: none;
}
.dashboard-card.drop-target-before::before { left: -8px; }
.dashboard-card.drop-target-after::after { right: -8px; }
/* APPEND-AT-THE-END affordance (#539). The edge bars above only read correctly
   inside a 14px inter-card gap. At the end of the list there is no next card —
   the row tail is ~150px of bare grid, where a 3px bar hard against the last
   card looks like part of its border, and for a drop below the grid the last
   card can be far up and to the right of the cursor, so the feedback appeared
   nowhere near the pointer. This is a ghost of the card's landing slot instead:
   a real grid child, so the browser puts it in the row tail or wraps it onto a
   new row, spanning the same columns the dragged card does. */
.dashboard-drop-placeholder {
  min-height: 96px;                 /* the 2-row floor — visible in a short row */
  align-self: stretch;              /* fill the row it lands in, not just 96px */
  border: 2px dashed var(--accent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;             /* never a hit-test target mid-drag */
}
.dashboard-drop-placeholder > span {
  font-size: 12px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--accent);
}

.dashboard-card header {
  display: flex; align-items: center; justify-content: space-between; padding: 0; background: none; border: none;
  gap: 6px;
}
/* When the header is the card-drag handle, give the whole strip a
   grab cursor + a subtle hover affordance so it's obvious you can
   pick up the card by its title bar. The action buttons override
   the cursor back to pointer on themselves.

   user-select: none is critical: text-selection in the title competes
   with drag-initiation on text-heavy card types (narrative, actions).
   Without this, the browser would prefer text-selection on mousedown
   over the parent's draggable=true gesture, and drag silently fails.
   Same root cause as the earlier "card-wide drag loses to body text
   selection" issue. */
.dashboard-card header.drag-handle {
  cursor: grab;
  border-radius: 4px;
  margin: -3px;
  padding: 3px;
  transition: background 100ms ease;
  user-select: none;
  -webkit-user-select: none;
}
.dashboard-card header.drag-handle * {
  user-select: none;
  -webkit-user-select: none;
}
.dashboard-card header.drag-handle:hover {
  background: rgba(255, 255, 255, 0.04);
}
.dashboard-card header.drag-handle:active { cursor: grabbing; }
.dashboard-card header.drag-handle .actions .btn { cursor: pointer; }
/* Drag handle in the card header. The whole card used to be draggable,
   now drag is grip-only — so make the grip slightly bigger and add a
   hover affordance so users notice it. Padding gives a comfortable
   click target (≈18×18px instead of the previous 14×14 visual). */
.dashboard-card header .grip {
  color: var(--muted);
  font-size: 16px;
  letter-spacing: -3px;
  cursor: grab;
  user-select: none;
  line-height: 1;
  padding: 4px 5px;
  border-radius: 4px;
  transition: background 100ms ease, color 100ms ease;
}
.dashboard-card header .grip:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}
.dashboard-card header .grip:active { cursor: grabbing; }
.dashboard-card header .title { font-weight: 600; font-size: 13px; flex: 1; margin: 0; }
.dashboard-card header .actions { display: flex; gap: 4px; }
.dashboard-card header .actions .btn.icon { padding: 2px 8px; font-size: 14px; line-height: 1; }
.dashboard-card header .actions .btn.icon.spinning { animation: spin 0.8s linear infinite; }
/* Brief failure flash on the refresh button — shows red ✗ for ~2s
   so the user can tell at a glance WHICH card's refresh failed (the
   sticky toast at the top has the actual error text). */
.dashboard-card header .actions .btn.icon.err {
  color: var(--err, #e25555);
  border-color: var(--err, #e25555);
  background: rgba(226, 85, 85, 0.08);
}

/* Edit / refresh / delete actions moved to a BOTTOM footer row (was the header).
   margin-top:auto pushes it to the card's bottom edge regardless of body height,
   so footers line up across cards in a row. A subtle top divider separates it
   from the content. */
.dashboard-card .actions.card-actions-footer {
  display: flex;
  gap: 4px;
  justify-content: flex-end;
  align-items: center;
  margin-top: auto;
  padding-top: 8px;
  /* clear the bottom-right resize grip (14px) so the last button isn't covered */
  padding-right: 10px;
  border-top: 1px solid var(--border);
}
.card-refresh-error {
  display: flex; align-items: center; gap: 7px;
  margin: -4px 0 8px; padding: 6px 9px;
  background: color-mix(in srgb, var(--err, #c0392b) 12%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--err, #c0392b) 45%, transparent);
  border-radius: 6px; font-size: 11.5px; color: var(--text);
}
.card-refresh-error .cre-icon { color: var(--err, #c0392b); font-size: 13px; flex: 0 0 auto; }
.card-refresh-error .cre-text { flex: 1 1 auto; line-height: 1.35; word-break: break-word; }
.card-refresh-error .cre-retry { flex: 0 0 auto; font-size: 11px; padding: 2px 9px; }
.dashboard-card.has-refresh-error { border-color: color-mix(in srgb, var(--err, #c0392b) 40%, var(--border)); }
.card-id-label {
  margin-top: 6px;
  padding-top: 4px;
  padding-right: 10px; /* clear the resize grip */
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10px;
  color: var(--muted);
  opacity: 0.55;
  text-align: right;
  cursor: pointer;
  user-select: all;
}
.card-id-label:hover { opacity: 0.9; }
.dashboard-card .actions.card-actions-footer .btn.icon { padding: 2px 8px; font-size: 14px; line-height: 1; }
.dashboard-card .actions.card-actions-footer .btn.icon.spinning { animation: spin 0.8s linear infinite; }
.dashboard-card .actions.card-actions-footer .btn.icon.err {
  color: var(--err, #e25555);
  border-color: var(--err, #e25555);
  background: rgba(226, 85, 85, 0.08);
}
/* The footer stays visible while a refresh dims the rest of the card, but its
   buttons are disabled (can't edit / double-refresh a card mid-refresh). */
.dashboard-card.is-refreshing > .actions.card-actions-footer { opacity: 1; }
.dashboard-card.is-refreshing .actions.card-actions-footer .btn {
  pointer-events: none;
  opacity: 0.45;
  cursor: default;
}

/* --- Compact single footer bar. Everything lives on ONE always-visible row:
   [source · flex-1] [⬇] [SQL] [↻ ⧉ ✎ ✕]. Small, quiet icons; the SQL <pre>
   breaks out to a full-width row below when expanded. No hover-reveal (that was
   confusing) — the controls are simply small and out of the way. */
.dashboard-card .actions.card-footer-bar {
  flex-wrap: wrap;
  gap: 2px 6px;
  padding-top: 6px;
  margin-top: 8px;
}
/* Every control in the bar is the SAME fixed box (centered glyph) so the ℹ /
   ⬇ / SQL / ↻ / ⧉ / ✎ / × icons all match regardless of their glyph width. */
.dashboard-card .card-footer-bar .btn.icon,
.dashboard-card .card-footer-bar .csv-download .btn,
.dashboard-card .card-footer-bar .card-sql > summary {
  box-sizing: border-box;
  height: 24px;
  min-width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 7px;
  font-size: 13px;
  line-height: 1;
}
/* CSV as a compact icon-ish control (label shortened to "⬇" in JS, row count in
   the tooltip) so it stops dominating the bar. */
.dashboard-card .card-footer-bar .csv-download { margin: 0; display: inline-flex; }
/* SQL disclosure folded into the bar: a small chip when closed; when open it
   wraps to its own full-width line below the icons. */
.dashboard-card .card-footer-bar .card-sql {
  margin: 0;
  padding: 0;
  border: none;
  flex: 0 0 auto;
}
/* Open SQL wraps to its own full-width row below the icons. min-width:0 lets the
   flex item shrink to the bar width so the <pre>'s overflow-x scroll kicks in
   (without it the long SQL lines pushed the card wider — the overflow bug). */
.dashboard-card .card-footer-bar .card-sql[open] { flex-basis: 100%; order: 99; margin-top: 4px; min-width: 0; }
.dashboard-card .card-sql-pre { max-width: 100%; }
/* SQL disclosure looks like the other icon buttons: bordered box, same height. */
.dashboard-card .card-footer-bar .card-sql > summary {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
  font-size: 10.5px;
}
.dashboard-card .card-footer-bar .card-sql > summary:hover { border-color: var(--accent); color: var(--text); }
.dashboard-card .card-footer-bar .card-sql[open] > summary { margin-bottom: 2px; }

/* Custom hover tooltip for the footer icons (styled + reliable, unlike native
   `title`). Anchored ABOVE the button (the bar is at the card bottom) and
   right-aligned so it stays inside the card. `pre-line` honors the newlines in
   the ℹ metadata tooltip while still wrapping long lines. */
.dashboard-card .card-footer-bar [data-tip] { position: relative; }
.dashboard-card .card-footer-bar [data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  right: 0;
  white-space: pre-line;
  max-width: 300px;
  width: max-content;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  font-size: 11.5px;
  line-height: 1.45;
  text-align: left;
  padding: 5px 8px;
  border-radius: 6px;
  pointer-events: none;
  z-index: 30;
  opacity: 0;
  transition: opacity 0.12s ease 0s;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}
.dashboard-card .card-footer-bar [data-tip]:hover::after { opacity: 1; transition-delay: 0.35s; }

/* Filter-context chip on summary/action cards (time range / custom filters the
   summary reflects) — muted, lighter than the active scope chip. */
.dashboard-card .card-scope-chip.card-context-chip {
  background: rgba(0, 0, 0, 0.04);
  border: 1px solid var(--border);
  color: var(--muted);
}
/* Stale summary: the default view changed but the narrative hasn't been
   regenerated yet — dim the body so the out-of-date figures don't read as
   current (the ⚠ chip + auto-heal refresh explain/fix it). */
.dashboard-card.card-narrative-stale .dashboard-card-body { opacity: 0.5; }

.dashboard-card .chart-wrap { height: 280px; margin: 0; }

/* ============================================================
   Card resize — width snaps to grid columns, height is free-form
   ============================================================ */

/* The card body becomes the scroll container when the card has a
   fixed (dragged) height. Default cards (auto height) keep current
   behavior — body sizes to content, no scrollbar appears. */
.dashboard-card-body {
  display: flex;
  flex-direction: column;
  min-height: 0;       /* allow flex children to shrink below content */
}
.dashboard-card.has-custom-size {
  overflow: hidden;    /* clip content to dragged height */
}
.dashboard-card.has-custom-size > .dashboard-card-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;      /* vertical scroll for text, both axes for tables */
}
/* When the card is sized, the chart canvas wrap should fill the body
   instead of its fixed 280px so the chart re-renders into the new
   space. Chart.js with responsive:true + maintainAspectRatio:false
   already listens for parent resize, so no manual chart.update() call
   is needed. */
.dashboard-card.has-custom-size .chart-wrap {
  height: 100%;
  flex: 1 1 auto;
  min-height: 0;
}
.dashboard-card.has-custom-size .table-card-wrap {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;    /* override mobile 320px cap when user explicitly sized */
}
.dashboard-card.has-custom-size .narrative-card-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* Bottom-right drag handle. Subtle by default, lights up on hover so
   it's discoverable without being noisy. nwse-resize cursor is the
   standard for diagonal/two-axis resize. */
.card-resize-handle {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 14px;
  height: 14px;
  cursor: nwse-resize;
  border-radius: 0 0 6px 0;
  /* Two diagonal lines drawn with linear-gradients — same idiom Mac
     uses for textarea resize grips. Muted in idle, accent on hover. */
  background:
    linear-gradient(135deg, transparent 0 6px, var(--muted) 6px 7px, transparent 7px 9px, var(--muted) 9px 10px, transparent 10px 100%);
  opacity: 0.5;
  transition: opacity 120ms ease, background 120ms ease;
  /* sit above the card body so the handle is always grabbable */
  z-index: 2;
  user-select: none;
}
.card-resize-handle:hover {
  opacity: 1;
  background:
    linear-gradient(135deg, transparent 0 6px, var(--accent) 6px 7px, transparent 7px 9px, var(--accent) 9px 10px, transparent 10px 100%);
}
/* Active resize state — disable transitions on the card so it tracks
   the cursor 1:1, dim the rest of the dashboard slightly to focus the
   user on the live size, and prevent text selection during the drag. */
.dashboard-card.resizing {
  transition: none !important;
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
  user-select: none;
}
body.resizing-card {
  cursor: nwse-resize !important;
  user-select: none;
}
body.resizing-card * {
  user-select: none !important;
}
/* Quick-size menu (#311) — a discoverable, keyboard-reachable alternative to
   the mouse-only drag handle. The ⤢ button sits just left of the resize grip;
   clicking it opens a small menu of named widths. */
.card-quick-size-btn {
  position: absolute;
  right: 18px;
  bottom: 2px;
  width: 18px;
  height: 16px;
  padding: 0;
  line-height: 1;
  font-size: 12px;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 120ms ease, color 120ms ease;
  z-index: 2;
}
.card-quick-size-btn:hover { opacity: 1; color: var(--accent); }
.card-quick-size-menu {
  position: absolute;
  right: 6px;
  bottom: 22px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  min-width: 120px;
  padding: 4px;
  background: var(--panel, #fff);
  border: 1px solid var(--border, #e2ddd4);
  border-radius: 8px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.card-quick-size-item {
  text-align: left;
  padding: 6px 10px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--fg, inherit);
  font-size: 13px;
  cursor: pointer;
}
.card-quick-size-item:hover { background: var(--hover, rgba(0, 0, 0, 0.06)); }
.card-quick-size-item.is-current { color: var(--accent); font-weight: 600; }
.card-quick-size-item.is-current::after { content: " ✓"; }
.dashboard-card .src {
  font-size: 10px; color: var(--muted); font-family: var(--mono);
}
/* Source label as the flex-1 left side of the single compact footer bar — it
   truncates so the CSV + action icons stay pinned to the right on one row. */
.dashboard-card .card-footer-bar .card-footer-src {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  margin: 0;
}
/* CSV download sits inline in the bar now (moved out of its own viz-body row);
   drop the row margins it had inside .chart-card-footer. */
.dashboard-card .card-footer-bar .csv-download { margin: 0; }
.dashboard-card .card-footer-bar .csv-download .btn { padding: 2px 8px; font-size: 12px; }

/* Read-only SQL disclosure inside each dashboard card. Collapsed by
   default — opens to reveal the source SQL with a copy button. Shown
   on both owned and shared dashboards (owner still gets the edit-SQL
   button (📝) in the actions row above). */
.dashboard-card .card-sql {
  margin-top: 4px;
  padding-top: 2px;
}
.dashboard-card .card-sql > summary {
  cursor: pointer;
  list-style: none;
  font-size: 10.5px;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 0;
  user-select: none;
}
.dashboard-card .card-sql > summary::-webkit-details-marker { display: none; }
.dashboard-card .card-sql > summary::marker { display: none; content: ""; }
.dashboard-card .card-sql > summary::after {
  content: "▾";
  margin-left: 6px;
  font-size: 9px;
  opacity: 0.6;
  transition: transform 0.15s ease;
  display: inline-block;
}
.dashboard-card .card-sql[open] > summary::after { transform: rotate(180deg); }
.dashboard-card .card-sql > summary:hover { color: var(--text); }
.dashboard-card .card-sql-meta {
  font-size: 10.5px;
  color: var(--muted);
  font-family: var(--mono);
  margin: 6px 0 4px;
}
.dashboard-card .card-sql-pre {
  margin: 6px 0;
  padding: 8px 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.45;
  color: var(--text);
  overflow-x: auto;
  max-height: 320px;
  overflow-y: auto;
  white-space: pre;
}
.dashboard-card .card-sql-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 4px;
}
.dashboard-card .card-sql-actions .btn.small {
  font-size: 11px;
  padding: 3px 10px;
}

.dashboard-empty {
  text-align: center; padding: 60px 20px; color: var(--muted); font-size: 13px;
}
/* An empty tab: the note spans the grid and sits in the middle of the
   tab-content container, both ways (#282). */
#dashboard-grid.is-empty {
  display: flex; align-items: center; justify-content: center;
  align-self: stretch; min-height: 320px;
}
#dashboard-grid.is-empty > .dashboard-empty {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  width: min(460px, 100%); padding: 40px 20px; box-sizing: border-box;
}
.dashboard-empty-eyes { color: var(--text); }
.dashboard-empty-title { color: var(--text); font-size: 16px; font-weight: 600; }
.dashboard-empty-text { margin: 0; font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
@media (prefers-reduced-motion: no-preference) {
  .dashboard-empty-pupils { animation: dashboard-empty-glance 6s ease-in-out infinite; }
}
@keyframes dashboard-empty-glance {
  0%, 30% { transform: translate(0, 0); }
  36%, 55% { transform: translate(-8px, -1px); }
  61%, 80% { transform: translate(-3px, -6px); }
  86%, 100% { transform: translate(0, 0); }
}

/* "Save to dashboard" button on chart tool blocks */
.chart-save-btn {
  margin-top: 4px; display: flex; justify-content: flex-end;
}
.chart-save-btn .btn { font-size: 11px; padding: 3px 8px; }

/* Modals — shared backdrop styles for all modal-backdrop IDs */
#modal-backdrop,
#report-modal-backdrop,
#run-modal-backdrop,
#share-modal-backdrop,
#share-report-modal-backdrop,
#dashboard-modal-backdrop,
#save-chart-modal-backdrop,
#save-table-modal-backdrop,
#build-chart-modal-backdrop,
#build-table-modal-backdrop,
#adapt-chart-modal-backdrop,
#scheduled-query-modal-backdrop,
#lint-modal-backdrop,
#edit-kpi-modal-backdrop,
#narrative-card-modal-backdrop,
#scorecard-modal-backdrop,
#notes-modal-backdrop,
#chat-modal-backdrop,
#copy-card-modal-backdrop,
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 100;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
#modal-backdrop[hidden],
#report-modal-backdrop[hidden],
#run-modal-backdrop[hidden],
#share-modal-backdrop[hidden],
#share-report-modal-backdrop[hidden],
#dashboard-modal-backdrop[hidden],
#save-chart-modal-backdrop[hidden],
#save-table-modal-backdrop[hidden],
#build-chart-modal-backdrop[hidden],
#build-table-modal-backdrop[hidden],
#adapt-chart-modal-backdrop[hidden],
#scheduled-query-modal-backdrop[hidden],
#lint-modal-backdrop[hidden],
#edit-kpi-modal-backdrop[hidden],
#narrative-card-modal-backdrop[hidden],
#scorecard-modal-backdrop[hidden],
#notes-modal-backdrop[hidden],
#chat-modal-backdrop[hidden],
#copy-card-modal-backdrop[hidden],
.modal-backdrop[hidden] { display: none; }

/* Styled confirmation (koroConfirm, #282) — replaces window.confirm() on the
   dashboard page. Above every overlay (filters 250, operator popover 300). */
.modal-backdrop.koro-confirm-backdrop { z-index: 400; background: rgba(27, 22, 14, 0.35); }
/* The share dialog opened FROM a React page (Advanced artifacts): above the React
   overlay (#react-mount, z-index 10000), and so is the confirm it opens, which is
   appended to <body> after it. Toasts (#toast-container, 11000) stay on top. */
#share-modal-backdrop.over-react { z-index: 10050; }
#share-modal-backdrop.over-react:not([hidden]) ~ .modal-backdrop.koro-confirm-backdrop { z-index: 10060; }
.modal.koro-confirm {
  width: min(460px, 100%); max-width: 460px; height: auto; max-height: 90vh;
  border-radius: 16px; padding: 22px 24px 18px; gap: 10px; box-sizing: border-box;
}
.koro-confirm-title { margin: 0; font-size: 18px; font-weight: 600; line-height: 1.3; }
.koro-confirm-body { display: flex; flex-direction: column; gap: 10px; overflow-y: auto; }
.koro-confirm-body p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--muted); }
.koro-confirm-body p.koro-confirm-warn {
  color: var(--text); background: var(--acc-yellow, #ffedc1);
  border-radius: 10px; padding: 10px 12px; font-size: 13px;
}
.koro-confirm-body p.koro-confirm-warn::before { content: "⚠ "; }
.koro-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
.koro-confirm-actions .btn { padding: 8px 16px; font-size: 13.5px; }
@media (max-width: 767px) {
  /* Not the phone full-screen modal: a centred card. */
  .modal-backdrop.koro-confirm-backdrop { padding: 16px; align-items: center; }
  .modal.koro-confirm { height: auto; max-height: 90dvh; border-radius: 16px; border: 1px solid var(--border); }
}

/* A dialog opened as an OVERLAY (#282): the dashboard's Share and Settings
   open like Filters / data version — a popover under their header button, no
   dimmed backdrop, the page stays visible (_openModalAsPopover). The backdrop
   stays in place (so the dialog's own code is untouched) but lets clicks
   through; the dialog is positioned by JS. Beats the phone full-screen rule. */
.modal-popover { background: transparent !important; pointer-events: none; padding: 0 !important; }
.modal-popover > .modal {
  position: fixed; pointer-events: auto; margin: 0;
  max-width: none; height: auto; box-sizing: border-box;
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: 0 16px 40px rgba(27, 22, 14, 0.16), 0 2px 6px rgba(27, 22, 14, 0.06);
}

/* Chart-adapt chat modal — hosts the relocated live #chat + #composer-wrap.
   Large panel; body is a flex column so #chat flexes/scrolls and the
   composer pins to the bottom. The overrides are scoped to descendants of
   .chat-modal-body, so the same elements behave normally once restored to
   .main-col on close. */
#chat-modal-backdrop .modal.chat-modal {
  /* Override the base .modal max-width:640px — a full chat needs room. */
  width: min(960px, 92vw);
  max-width: min(960px, 92vw);
  height: 85vh;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  padding: 0;
}
#chat-modal-backdrop .modal.chat-modal .modal-header {
  flex: 0 0 auto;
}
.chat-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* The relocated chat scrolls; the relocated composer stays pinned below it.
   Neutralize main#chat's max-width:900px + margin:0 auto (meant for the
   centered main-view column) so it fills the modal body edge-to-edge. */
.chat-modal-body > #chat {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  max-width: none;
  margin: 0;
  width: auto;
}
.chat-modal-body > #composer-wrap {
  flex: 0 0 auto;
}
/* #composer is a <form>, so inside .modal it inherits the generic
   `.modal form` rule (flex-direction:column + padding + overflow) which
   stacks the attach/textarea/send buttons vertically and pushes them
   right. Restore the horizontal composer row the element was designed for
   (the surrounding #composer-wrap already supplies padding). */
.chat-modal-body #composer {
  /* The composer is a column card now (message on top, controls below — see
     "Composer: one card"); restate it here against `.modal form`, and keep the
     card's own padding rather than the old row's none. */
  flex-direction: column;
  padding: 10px 10px 8px 12px;
  overflow: visible;
  gap: 6px;
}

/* Full-page views (replace per-feature modals where the form is large enough
   that overlay UX feels cramped). Used by:
     - #schema-edit-view   — editing a single schema doc
     - #settings-view      — admin/user settings with sidebar (own styles below)
   Pattern: sticky toolbar at top with title + actions, scrollable body below. */
#schema-edit-view {
  display: flex; flex-direction: column; height: 100%; overflow: hidden;
  background: var(--bg);
}
#schema-edit-view[hidden] { display: none; }
.fullview-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 24px;
  border-bottom: 1px solid var(--border); background: var(--panel);
  position: sticky; top: 0; z-index: 5;
}
.fullview-toolbar-left, .fullview-toolbar-right {
  display: flex; align-items: center; gap: 10px;
}
.fullview-toolbar h1 {
  margin: 0; font-size: 16px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 60vw;
}
.fullview-body {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 24px 32px 48px;
  display: flex; flex-direction: column; gap: 18px;
  max-width: 920px; width: 100%;
  margin: 0 auto;
}
/* The form-section blocks reuse the existing .settings-section styling so
   schema-edit looks consistent with the settings view. Modal-specific
   spacing is reset since there's no modal wrapper. */
.fullview-body .settings-section { margin: 0; }

@media (max-width: 767px) {
  .fullview-toolbar { padding: 8px 12px; }
  .fullview-toolbar h1 { font-size: 14px; max-width: 50vw; }
  .fullview-body { padding: 16px 14px 40px; }

  /* Schema + Memory list-view header rows: two search inputs in the HTML
     carry an inline `min-width: 240px` style (set for desktop density), and
     a flex container of 2-3 action buttons. On phones <420px, the inline
     min-width forces the whole .home-section-head wider than the viewport,
     which makes .home-inner overflow horizontally and clips item titles +
     the description paragraph at the right edge. Visually it looks like
     "huge whitespace above the list and items cut off at the right". Fix:
     stack the header, force the search input to fill width (overriding the
     inline min-width via !important), and let buttons take their natural
     width on a wrapping row.

     The schema page has the same input pattern, so this rule covers both. */
  #memory-view .home-section-head,
  #schema-view .home-section-head {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  /* The inner action row (search + buttons) on both pages is the section
     head's last child — pick it with > div + select to avoid touching
     unrelated layouts. */
  #memory-view .home-section-head > div,
  #schema-view .home-section-head > div {
    width: 100%;
    flex-wrap: wrap;
  }
  /* Override the inline `min-width: 240px` from index.html for these
     two specific inputs. */
  #memory-filter,
  #schema-search {
    min-width: 0 !important;
    flex: 1 1 100%;
    width: 100%;
    box-sizing: border-box;
  }
  /* Stop .home-inner itself from overflowing if any inner content tries
     to push it wider than the viewport. Belt-and-suspenders so we don't
     hit this again with new admin-added widgets. */
  .home-inner { max-width: 100%; overflow-x: hidden; }
}

/* Chat share button & shared-tag */
ul#chat-list .chat-item .share {
  background: none; border: none; color: var(--muted); cursor: pointer;
  padding: 2px 6px; border-radius: 4px; font-size: 11px; line-height: 1;
  opacity: 0; transition: opacity 0.1s;
}
ul#chat-list .chat-item:hover .share,
ul#chat-list .chat-item.active .share { opacity: 1; }
ul#chat-list .chat-item .share:hover { background: var(--panel); color: var(--accent); }
ul#chat-list .chat-item .shared-tag {
  font-size: 11px; padding: 1px 4px; margin-right: 4px;
}
/* Governed-chat marker — small green ✓ before the title. */
ul#chat-list .chat-item .gov-tag,
.report-item .gov-tag {
  font-size: 10px;
  font-weight: 700;
  color: var(--ok, #9ece6a);
  background: rgba(80, 161, 67, 0.14);
  border: 1px solid rgba(80, 161, 67, 0.40);
  border-radius: 4px;
  padding: 0 4px;
  margin-right: 5px;
  flex: 0 0 auto;
  line-height: 1.5;
}
ul#chat-list .chat-item.shared-in .title { font-style: italic; color: var(--muted); }
ul#chat-list .chat-item.shared-in .title::after {
  content: " (read-only)"; font-size: 10px; color: var(--muted);
}

.deeplink-row { display: flex; gap: 6px; align-items: center; }
.deeplink-row input {
  flex: 1; background: var(--panel-2); color: var(--muted); border: 1px solid var(--border);
  border-radius: 6px; padding: 6px 8px; font: inherit; font-size: 11px;
  font-family: var(--mono);
}
.deeplink-row .btn.copied { background: var(--ok); color: #0e1116; border-color: var(--ok); }

.readonly-banner {
  padding: 6px 10px; margin-bottom: 6px;
  background: rgba(237, 149, 0, 0.10); border: 1px solid var(--warn);
  color: var(--warn); border-radius: 6px; font-size: 12px;
}
.modal {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  width: 100%; max-width: 640px; max-height: 90vh;
  display: flex; flex-direction: column;
  overflow: hidden;
  box-shadow: var(--koro-shadow-lift, 0 18px 44px rgba(0,0,0,.34));
}
.modal.modal-wide { max-width: 920px; }
.modal-body { padding: 16px 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }
/* Column flex items also default to `min-width: auto`, so one wide child (a
   grid row of inputs, a long <pre>, a table) makes the whole body wider than
   the dialog and `.modal { overflow: hidden }` silently clips it. Let items
   shrink to the dialog width; anything genuinely wide scrolls in its OWN box. */
.modal-body > * { min-width: 0; max-width: 100%; }
.settings-section { display: flex; flex-direction: column; gap: 8px; }
.settings-section h3 { margin: 0; font-size: 14px; color: var(--text); }
.settings-section p.muted { margin: 0; font-size: 12px; }

.user-table-wrap { overflow: auto; border: 1px solid var(--border); border-radius: 6px; }
.user-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.user-table th, .user-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: middle; }
.user-table tbody tr:last-child td { border-bottom: none; }
.user-table th { background: var(--panel-2); font-weight: 600; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
.user-table .col-email { min-width: 200px; }
.user-table .col-admin { width: 80px; text-align: center; }
.user-table .col-projects { min-width: 300px; }
.user-table .email-cell { font-family: var(--mono); font-size: 12px; display: flex; align-items: center; gap: 6px; }
.user-table .email-cell .you-tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; background: var(--accent); color: #0e1116; }
.user-table .admin-cell { text-align: center; }

.project-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.project-chip {
  font-size: 11px; padding: 3px 8px; border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--border); color: var(--muted);
  cursor: pointer; user-select: none; white-space: nowrap;
}
.project-chip:hover { border-color: var(--accent); }
.project-chip.active {
  background: rgba(0, 150, 191, 0.15); border-color: var(--accent); color: var(--accent);
}
.project-chip.all-access {
  background: transparent; border-style: dashed; cursor: default;
}
.project-chip.all-access:hover { border-color: var(--border); }

.toggle {
  position: relative; display: inline-block; width: 32px; height: 18px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 999px;
  cursor: pointer; transition: background 0.15s;
}
.toggle::after {
  content: ""; position: absolute; top: 1px; left: 1px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--muted); transition: transform 0.15s, background 0.15s;
}
.toggle.on { background: rgba(0, 150, 191, 0.2); border-color: var(--accent); }
.toggle.on::after { transform: translateX(14px); background: var(--accent); }
.toggle.disabled { opacity: 0.4; cursor: not-allowed; }
.modal-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
.modal-header h2 { margin: 0; font-size: 16px; }
.modal form { padding: 16px 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }
/* Form field styling for modals AND full-page-view forms (schema-edit,
   settings forms). Originally scoped only to `.modal form` — extended when
   schema-edit became a full-page view (#schema-edit-view) so the same
   inputs / textareas pick up dark-theme styling outside the modal context.
   The settings-pane sections also benefit since they use the same markup. */
.modal form label,
.fullview-body label,
.settings-pane label {
  display: flex; flex-direction: column; gap: 4px; font-size: 12px;
}
/* The flex rule above beats the `hidden` attribute's UA display:none, so a
   label toggled via [hidden] would still show. Restore it for these forms. */
.fullview-body label[hidden],
.settings-pane label[hidden] { display: none; }
/* A <label> that DIRECTLY wraps a checkbox/radio is a toggle, never a stacked
   field label — so it must not inherit `flex-direction: column` from the rule
   above. The zero-specificity `:where(label:has(> input[type="checkbox"]))`
   safety net further up already states this intent for the whole app, but it
   loses to `.modal form label` here and sets no flex-direction, so toggles in
   these three contexts stacked the box above its text (#285, and again in the
   composed-card "Group by" chips + the filter `not` toggle). `:has()` lifts the
   specificity to (0,2,3), which beats the (0,1,2) column rule.
   Deliberately sets ONLY flex-direction: alignment stays with the component
   class (.inline-check → flex-start) or the element's own inline style, so this
   rule can't silently re-align existing toggles. */
.modal form label:has(> input[type="checkbox"]),
.modal form label:has(> input[type="radio"]),
.fullview-body label:has(> input[type="checkbox"]),
.fullview-body label:has(> input[type="radio"]),
.settings-pane label:has(> input[type="checkbox"]),
.settings-pane label:has(> input[type="radio"]) {
  flex-direction: row;
}
/* The `inline-check` variant (checkbox + sibling text) is horizontal and
   must NOT inherit the column flex above. */
.modal form label.inline-check,
.fullview-body label.inline-check,
.settings-pane label.inline-check {
  display: flex; flex-direction: row; align-items: flex-start; gap: 8px;
}
/* The text half of a toggle row takes the remaining width and wraps inside it,
   rather than forcing the row wider than the dialog and getting clipped by
   `.modal { overflow: hidden }` on a narrow window.

   This is the ONLY thing this PR adds to toggle alignment. The flex-direction
   reset itself lives in the rule above, which came from main (b8fd642, #285):
   it deliberately sets flex-direction and NOTHING else, so alignment stays with
   the component class (`.inline-check` keeps flex-start) or the element's own
   inline style. This branch originally set `display`/`align-items`/`gap` here
   too, which would have silently re-aligned exactly the toggles that rule
   protects — and web/e2e/gov-card-modal-layout.spec.ts asserts it doesn't. So
   only the sizing half survives the merge; see CLAUDE.md "Checkbox & radio
   alignment". */
:is(.modal form, .fullview-body, .settings-pane)
label:has(> input[type="checkbox"], > input[type="radio"]) > span {
  min-width: 0; flex: 1 1 auto;
}
/* Governed-card "Break down by" dimension chips (openGovCardModal → renderDims):
   checkbox + label on ONE line as a pill. Needs this `.modal form label.…`
   specificity to beat the generic column-flex `.modal form label` rule above —
   a bare `.gov-dim-chip` class would lose to it and the chip would stack (#285). */
.modal form label.gov-dim-chip {
  display: inline-flex; flex-direction: row; align-items: center; gap: 4px;
  font-size: 12px; background: var(--panel2, #f2f2f2);
  border: 1px solid var(--border, #ddd); border-radius: 999px;
  padding: 2px 9px; cursor: pointer;
}
/* Field-label hierarchy: the field NAME reads as a real label (text colour,
   medium weight); any inline helper note keeps the muted, lighter treatment so
   the two don't blur together. */
.modal form label > span,
.fullview-body label > span,
.settings-pane label > span { color: var(--text); font-weight: 500; }
.modal form label > span .muted,
.fullview-body label > span .muted,
.settings-pane label > span .muted { font-weight: 400; }

/* Field-level validation errors (#508) — the missing primitive.
   Applied by applyFieldErrors() in public/app.js: a red border on the offending
   input, a red label, and a message rendered directly under the field. The
   `!important` on border-color beats the per-component input rules (some set
   their own border) without a specificity war; width/layout are untouched so
   the field can't overflow its grid/flex row (min-width:0 discipline holds). */
.field-invalid,
input.field-invalid,
select.field-invalid,
textarea.field-invalid {
  border-color: var(--danger, #c0392b) !important;
  box-shadow: 0 0 0 1px var(--danger, #c0392b);
}
.field-invalid:focus,
input.field-invalid:focus,
select.field-invalid:focus,
textarea.field-invalid:focus {
  outline-color: var(--danger, #c0392b);
}
/* Red the label text (the wrapping <label> gets .has-field-error). The
   `.block-label` selector is 0-2-1, so it beats the 0-1-1 `.modal form label >
   span` colour rule above without !important. */
label.has-field-error > span,
label.has-field-error > .block-label,
label.has-field-error { color: var(--danger, #c0392b); }
/* The per-field message. Block-level so it stacks under the input inside the
   column-flex label and wraps rather than overflowing at a narrow width. */
.field-error-msg {
  color: var(--danger, #c0392b);
  font-size: 12px;
  line-height: 1.35;
  margin-top: 4px;
  min-width: 0;
  overflow-wrap: anywhere;
}

.modal form input[type="text"],
.modal form input[type="search"],
.modal form input[type="email"],
.modal form input[type="url"],
.modal form input[type="number"],
.modal form input[type="date"],
.modal form textarea,
.modal form select,
.fullview-body input[type="text"],
.fullview-body input[type="search"],
.fullview-body input[type="email"],
.fullview-body input[type="url"],
.fullview-body input[type="number"],
.fullview-body input[type="date"],
.fullview-body textarea,
.fullview-body select,
.settings-pane input[type="text"],
.settings-pane input[type="search"],
.settings-pane input[type="email"],
.settings-pane input[type="url"],
.settings-pane input[type="number"],
.settings-pane input[type="date"],
.settings-pane textarea,
.settings-pane select {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 9px 11px; font: inherit;
  transition: border-color .12s, box-shadow .12s;
}
/* Free-text fields read as prose (sans). Code fields (SQL) opt BACK into
   monospace explicitly below — they were the only legitimate mono textareas. */
.modal form textarea,
.fullview-body textarea,
.settings-pane textarea {
  font-family: inherit; font-size: 13px; line-height: 1.55;
  resize: vertical; min-height: 60px;
}
#bc-sql, #bt-sql, #lint-suggested {
  font-family: var(--mono); font-size: 12.5px; line-height: 1.5;
}
.modal form input:focus, .modal form textarea:focus, .modal form select:focus,
.fullview-body input:focus, .fullview-body textarea:focus, .fullview-body select:focus,
.settings-pane input:focus, .settings-pane textarea:focus, .settings-pane select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0, 150, 191, 0.14);
}
/* Read-only / disabled fields — clearly visually distinct so users know they
   can't edit (e.g. identity fields locked after creation, or every field for
   non-admin viewers). */
.fullview-body input:read-only,
.fullview-body input:disabled,
.fullview-body textarea:read-only,
.fullview-body textarea:disabled {
  opacity: 0.65; cursor: not-allowed;
}

/* Emoji + Name laid out horizontally (label left, input right), side by side,
   wrapping to stacked rows only when the modal gets too narrow. Scoped to these
   fields so the shared `.modal form label` column layout is untouched. */
.pf-top-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
.modal form label.pf-hfield { flex-direction: row; align-items: center; gap: 8px; }
.modal form label.pf-hfield > span { flex: 0 0 auto; white-space: nowrap; }
.pf-emoji-field > input { flex: 0 0 60px; width: 60px; text-align: center; }
.pf-name-field { flex: 1 1 240px; min-width: 0; }
.pf-name-field > input { flex: 1 1 auto; min-width: 0; width: auto; }

.ds-toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.ds-toolbar input { flex: 1 1 160px; min-width: 0; }
/* Keep each toolbar button's label on one line at its natural width instead of
   letting the flex row squeeze it into two lines ("Clear / selection"). */
.ds-toolbar .btn { flex: 0 0 auto; white-space: nowrap; }
/* Empty dataset selection blocks all SQL for the project (fail-closed
   allowlist, #253) — surface the count label as a warning, not muted copy. */
#pf-ds-count.ds-count-warn { color: var(--warn, #b9770e); font-weight: 600; }
.ds-list {
  /* min-height keeps the list usable when the 90vh-capped modal is short:
     without it, this flex child (flex-shrink:1, min-height:auto) collapses
     below its content instead of letting the modal form body scroll (#227). */
  min-height: 120px;
  max-height: 240px; overflow-y: auto; overflow-x: hidden;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 6px;
  display: grid; grid-template-columns: 1fr;
  gap: 2px;
}
.ds-list .ds-item {
  /* flex-direction:row overrides the generic `.modal form label` rule
     (flex-direction:column), which otherwise stacks the checkbox above the
     label instead of sitting them side by side. */
  display: flex; flex-direction: row; gap: 6px; align-items: center; padding: 4px 8px;
  font-family: var(--mono); font-size: 12px; cursor: pointer; border-radius: 4px;
  min-width: 0;
}
.ds-list .ds-item:hover { background: var(--panel); }
.ds-list .ds-item input { margin: 0; flex: 0 0 auto; }
.ds-list .ds-item > span {
  flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-list .ds-item.selected { background: rgba(0, 150, 191, 0.12); color: var(--accent); }
.ds-list .ds-empty { grid-column: 1 / -1; color: var(--muted); text-align: center; padding: 10px; font-size: 12px; }
.ds-list .ds-group-header {
  grid-column: 1 / -1;
  font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--accent); padding: 8px 6px 4px; border-top: 1px solid var(--border);
  margin-top: 4px;
}
.ds-list .ds-group-header:first-child { border-top: none; margin-top: 0; }

.member-toolbar { display: flex; gap: 6px; align-items: center; }
.member-toolbar select,
.member-toolbar input {
  flex: 1; background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 6px 8px; font: inherit;
}
.member-list {
  list-style: none; margin: 4px 0; padding: 0;
  /* min-height so the list can't be shrunk away on short viewports (#227) and
     shows at least ~5 member rows (~30px each) before it starts scrolling. */
  min-height: 180px;
  max-height: 260px; overflow-y: auto;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
}
.member-list:empty::after {
  content: "(no members)"; color: var(--muted); display: block; padding: 8px; font-size: 12px; text-align: center;
}
.member-list li {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 12.5px;
  font-family: var(--mono);
}
.member-list li:last-child { border-bottom: none; }
.member-list .email { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-list .remove {
  background: none; border: none; color: var(--muted); cursor: pointer;
  padding: 2px 6px; border-radius: 4px; font-size: 13px;
}
.member-list .remove:hover { background: #3a1e25; color: var(--err); }
.member-list .tag {
  font-size: 10px; padding: 1px 5px; border-radius: 3px;
  background: var(--panel); color: var(--muted); margin-right: 6px;
}
.member-list .tag.admin { color: var(--accent); border: 1px solid var(--accent); }

/* Settings → Data pipelines: cron-driven ingestion job cards. */
.pipeline-card {
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--panel-2); padding: 12px 14px; margin-bottom: 10px;
}
.pipeline-card.is-paused { opacity: 0.8; border-style: dashed; }
.pipeline-head { display: flex; align-items: center; gap: 8px; }
.pipeline-title { font-weight: 600; font-size: 13.5px; }
.pipeline-badge {
  margin-left: auto; font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;
  padding: 2px 7px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted);
}
.pipeline-badge.st-ok { color: var(--ok, #3fb950); border-color: var(--ok, #3fb950); }
.pipeline-badge.st-error { color: var(--err); border-color: var(--err); }
.pipeline-badge.st-paused { color: var(--warn, #e3b341); border-color: var(--warn, #e3b341); }
.pipeline-badge.st-skipped, .pipeline-badge.st-idle { color: var(--muted); }
.pipeline-desc { font-size: 12px; margin: 6px 0 4px 0; }
.pipeline-meta { font-size: 11.5px; }
.pipeline-msg { font-size: 11px; font-family: var(--mono); margin-top: 4px; word-break: break-word; }
.pipeline-controls { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.pipeline-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer; user-select: none; }
.pipeline-toggle input { margin: 0; cursor: pointer; }
.pipeline-backfill {
  display: flex; align-items: center; gap: 8px; margin-top: 10px;
  padding-top: 10px; border-top: 1px dashed var(--border); font-size: 12px;
}
.pipeline-backfill-days {
  width: 72px; background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 5px; font: inherit;
  font-size: 12px; padding: 3px 6px;
}
/* Per-email access-level picker in the dashboard share modal (View / Edit). */
.member-list .sf-role {
  flex: 0 0 auto; margin: 0 6px 0 8px;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  font: inherit; font-size: 11.5px; padding: 2px 4px; cursor: pointer;
}
.member-list .sf-role:hover { border-color: var(--accent); }

/* Share-modal people autocomplete. */
.sf-people-input-wrap { position: relative; flex: 1 1 auto; min-width: 0; }
.sf-people-input-wrap > input { width: 100%; }
.people-autocomplete {
  position: absolute; top: calc(100% + 2px); left: 0; right: 0; z-index: 60;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
  max-height: 240px; overflow-y: auto; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
}
.people-autocomplete[hidden] { display: none; }
.people-autocomplete-item {
  display: flex; flex-direction: column; gap: 1px;
  padding: 6px 10px; cursor: pointer; font-size: 12.5px;
  border-bottom: 1px solid var(--border);
}
.people-autocomplete-item:last-child { border-bottom: none; }
.people-autocomplete-item:hover,
.people-autocomplete-item.active { background: var(--panel); }
.people-autocomplete-item .pa-name { color: var(--text); }
.people-autocomplete-item .pa-email { font-family: var(--mono); font-size: 11.5px; }

/* ============ Share modal redesign ============ */
/* Small uppercase section labels ("Who can access", "Has access", "Direct link"). */
#share-form .sf-label {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin: 2px 0 -2px;
}
/* Segmented "Who can access" control — two selectable cards. */
.sf-mode { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.sf-mode-card {
  display: flex; align-items: flex-start; gap: 9px; text-align: left;
  padding: 11px 12px; border: 1px solid var(--border); border-radius: 9px;
  background: var(--panel-2); color: var(--text); cursor: pointer;
  transition: border-color .12s, background .12s, box-shadow .12s;
}
.sf-mode-card:hover { border-color: var(--accent); }
.sf-mode-card.active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--panel-2));
  box-shadow: 0 0 0 1px var(--accent) inset;
}
.sf-mode-radio {
  flex: 0 0 auto; width: 15px; height: 15px; margin-top: 1px; border-radius: 50%;
  border: 2px solid var(--border); position: relative;
}
.sf-mode-card.active .sf-mode-radio { border-color: var(--accent); }
.sf-mode-card.active .sf-mode-radio::after {
  content: ""; position: absolute; inset: 2px; border-radius: 50%; background: var(--accent);
}
.sf-mode-txt { display: flex; flex-direction: column; gap: 1px; line-height: 1.3; }
.sf-mode-txt b { font-size: 12.5px; }
.sf-mode-txt .muted { font-size: 11px; }

/* Add-row (people input + role + Add button; vertical select + Add button). */
.sf-add-row { display: flex; gap: 6px; align-items: center; }
.sf-add-row > select#sf-vertical-add { flex: 1 1 auto; min-width: 0; }
.sf-add-row .sf-role {
  flex: 0 0 auto; background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  font: inherit; font-size: 12px; padding: 4px 6px; cursor: pointer;
}
.sf-vertical-add-row { margin-top: 6px; }

/* Unified "Has access" list (owner + people + verticals). */
.sf-access-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.sf-access-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 2px; border-bottom: 1px solid var(--border);
}
.sf-access-row:last-child { border-bottom: none; }
/* #289: not-a-project-member warning shown under an editor row. */
.sf-project-warning {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: -2px 0 6px 40px; padding: 6px 9px;
  font-size: 11.5px; line-height: 1.35;
  color: var(--text);
  background: color-mix(in srgb, var(--warn, #b7791f) 12%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--warn, #b7791f) 40%, var(--border));
  border-radius: 7px;
}
.sf-project-warning .sf-pw-text { flex: 1 1 180px; min-width: 0; }
.sf-project-warning .sf-pw-icon { color: var(--warn, #b7791f); font-weight: 700; }
.sf-project-warning .sf-pw-add { flex: 0 0 auto; padding: 3px 9px; font-size: 11.5px; }
.sf-project-warning .sf-pw-hint { flex: 0 0 auto; color: var(--muted); font-style: italic; }
.sf-av {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  background: color-mix(in srgb, var(--accent) 22%, var(--panel)); color: var(--text);
}
.sf-av-vertical, .sf-av-everyone {
  background: var(--panel); border: 1px solid var(--border); color: var(--muted);
}
.sf-av svg { width: 17px; height: 17px; }
.sf-access-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.sf-access-main b { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sf-access-main .muted { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sf-row-controls { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.sf-row-controls .sf-role {
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  font: inherit; font-size: 11.5px; padding: 3px 5px; cursor: pointer;
}
.sf-row-controls .sf-role:hover { border-color: var(--accent); }
.sf-role-pill {
  font-size: 11px; color: var(--muted);
  padding: 3px 8px; border-radius: 999px; border: 1px solid var(--border);
}
.sf-remove {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border-radius: 6px;
  background: transparent; border: none; color: var(--muted); cursor: pointer;
}
.sf-remove:hover { background: rgba(202, 30, 79, .10); color: var(--err); }
.sf-remove svg { width: 15px; height: 15px; }

/* "Everyone at KoRo" summary body (shown when that mode is active). */
.sf-everyone-body .sf-everyone-card {
  border: 1px solid var(--border); border-radius: 9px; padding: 12px;
  background: color-mix(in srgb, var(--accent) 8%, var(--panel-2));
}
.sf-everyone-body .sf-everyone-card .muted { white-space: normal; }
.sf-av-everyone svg { width: 17px; height: 17px; }

.modal-footer {
  display: flex; gap: 8px; align-items: center;
  border-top: 1px solid var(--border); padding: 14px 20px; margin: 16px -20px -16px;
  background: var(--panel-2);
}
/* The negative margins above assume the footer is nested INSIDE the padded
   `.modal-body`, cancelling that padding to reach the modal edges. When the
   footer is a DIRECT child of `.modal` (which has no padding and clips overflow),
   those negatives push it past the box and the overflow crops the very spacing
   that separates the buttons from the rounded corners (#285). Reset them. */
.modal > .modal-footer { margin: 16px 0 0; }
/* Inside the body, `.modal-body > * { max-width: 100% }` capped the footer at
   the body's content width, so its negative margins left a 40px strip unpainted
   on the right (visible on the Share dialog). */
.modal-body > .modal-footer { max-width: none; }
.modal-footer .spacer { flex: 1; }
/* Destructive action in a modal footer reads as QUIET (the primary action is
   Save). Ghost treatment — danger text, no heavy filled/outlined box — so it
   doesn't compete with the primary button; tints on hover. */
.modal-footer .btn.danger {
  background: transparent; border-color: transparent; color: var(--err);
}
.modal-footer .btn.danger:hover:not(:disabled) {
  background: rgba(202, 30, 79, 0.10); border-color: transparent; color: var(--err);
}
/* Inline error / validation message shown inside edit modals (save failures,
   missing required fields). Used by every dashboard edit modal's `.error-text`
   slot — red, compact, only takes space once it has text. */
.error-text {
  color: var(--err);
  font-size: 12px;
  line-height: 1.45;
  margin: 2px 0 0;
  white-space: pre-wrap;
  word-break: break-word;
}
.error-text:empty { display: none; }
ul#chat-list {
  list-style: none; margin: 0; padding: 6px;
  max-height: 50vh; overflow-y: auto; overflow-x: hidden;
}
ul#chat-list .chat-item {
  display: flex; align-items: center; gap: 6px; padding: 8px 10px; border-radius: 6px;
  cursor: pointer; font-size: 13px; color: var(--text);
  border: 1px solid transparent;
}
ul#chat-list .chat-item:hover { background: var(--panel-2); }
ul#chat-list .chat-item.active { background: var(--panel-2); border-color: var(--border); }
ul#chat-list .chat-item .title {
  flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
ul#chat-list .chat-item .del,
ul#chat-list .chat-item .edit {
  background: none; border: none; color: var(--muted); cursor: pointer;
  padding: 2px 6px; border-radius: 4px; font-size: 13px; line-height: 1;
  opacity: 0; transition: opacity 0.1s;
}
ul#chat-list .chat-item:hover .del,
ul#chat-list .chat-item.active .del,
ul#chat-list .chat-item:hover .edit,
ul#chat-list .chat-item.active .edit { opacity: 1; }
ul#chat-list .chat-item .del:hover { background: #3a1e25; color: var(--err); }
ul#chat-list .chat-item .edit:hover { background: var(--panel); color: var(--accent); }
ul#chat-list .chat-item.editing { background: var(--panel-2); border-color: var(--accent); }
ul#chat-list .chat-item .title-input {
  flex: 1; min-width: 0;
  background: transparent; color: var(--text); border: none; outline: none;
  font: inherit; padding: 0;
}
ul#chat-list .empty { color: var(--muted); font-size: 12px; padding: 10px; text-align: center; }

/* CRITICAL: explicit `grid-template-columns: minmax(0, 1fr)` — bare `1fr` (or
   omitting columns entirely) resolves to `minmax(auto, 1fr)`, and `auto`'s
   minimum is the grid item's min-content. If any descendant (a wide SQL pre,
   tool-block table, long URL) has min-content > parent width, the track
   expands → the entire main-col gets pushed wider than viewport → composer
   Send button gets clipped, mobile-back-bar title goes offscreen, chat
   bubbles overflow on the right. Same trap that hit .settings-layout.
   minmax(0, …) lets the inner overflow-x/word-wrap do the constraining. */
.main-col {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  min-height: 0; min-width: 0;
}

main#chat { overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 16px; max-width: 900px; width: 100%; margin: 0 auto; }
/* Higher-specificity rule needed because main#chat sets display:flex above,
   which overrides the user-agent [hidden] { display: none }. */
main#chat[hidden] { display: none; }

.turn.user .bubble {
  background: var(--panel-2); padding: 10px 14px; border-radius: 10px; margin-left: auto; max-width: 75%;
  border: 1px solid var(--border);
}
.turn.user { display: flex; }

.turn.assistant { }
.turn.assistant .content { display: flex; flex-direction: column; gap: 10px; }
.turn-actions {
  display: flex; gap: 6px; padding-top: 4px;
  border-top: 1px dashed var(--border);
}
.turn-actions .btn.small { font-size: 11px; padding: 3px 8px; }
.turn.assistant .text { white-space: pre-wrap; }

/* Markdown rendering inside assistant turns */
.md { white-space: normal; }
.md > *:first-child { margin-top: 0; }
.md > *:last-child { margin-bottom: 0; }
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 {
  margin: 14px 0 6px; font-weight: 600; line-height: 1.3;
}
.md h1 { font-size: 18px; border-bottom: 1px solid var(--border); padding-bottom: 4px; }
.md h2 { font-size: 16px; border-bottom: 1px solid var(--border); padding-bottom: 3px; }
.md h3 { font-size: 14.5px; color: var(--accent); }
.md h4 { font-size: 13.5px; color: var(--muted); }
.md p { margin: 6px 0; }
.md ul, .md ol { margin: 6px 0; padding-left: 24px; }
.md li { margin: 2px 0; }
.md li > p { margin: 2px 0; }
.md strong { font-weight: 600; color: var(--text); }
.md em { color: var(--text); }
.md code {
  font-family: var(--mono); font-size: 12.5px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 5px;
}
.md pre {
  margin: 8px 0; padding: 10px 12px;
  background: #0a0d12; border: 1px solid var(--border); border-radius: 6px;
  overflow-x: auto;
}
.md pre code { background: none; border: none; padding: 0; font-size: 12px; color: #c0caf5; }
.md blockquote {
  margin: 8px 0; padding: 4px 12px;
  border-left: 3px solid var(--accent); color: var(--muted);
  background: rgba(0, 150, 191, 0.05);
}
.md a { color: var(--accent); text-decoration: none; }
.md a:hover { text-decoration: underline; }
.md hr { border: none; border-top: 1px solid var(--border); margin: 12px 0; }
.md table {
  border-collapse: collapse; margin: 8px 0; width: auto; max-width: 100%;
  font-size: 12.5px;
}
.md th, .md td {
  border: 1px solid var(--border); padding: 5px 8px; text-align: left; vertical-align: top;
}
.md th { background: var(--panel-2); font-weight: 600; }
.md tbody tr:nth-child(even) { background: rgba(255,255,255,0.02); }
.turn.assistant .thinking {
  font-family: var(--mono); font-size: 12px; color: var(--muted);
  border-left: 2px solid var(--border); padding: 4px 10px; white-space: pre-wrap;
}

.tool {
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px;
  font-family: var(--mono); font-size: 12.5px;
}
.tool summary { cursor: pointer; display: flex; gap: 8px; align-items: center; outline: none; }
.tool .badge {
  background: #2a3140; color: var(--accent); padding: 1px 6px; border-radius: 4px; font-size: 11px;
}
.tool .status { color: var(--muted); }
.tool .status.ok { color: var(--ok); }
.tool .status.err { color: var(--err); }
.tool pre.sql {
  margin: 8px 0; padding: 8px; background: #0a0d12; border: 1px solid var(--border); border-radius: 6px;
  white-space: pre-wrap; word-break: break-word; color: #c0caf5;
}
.tool-body { max-height: 400px; overflow: auto; }
.tool-body table {
  width: 100%; border-collapse: collapse; font-size: 11.5px;
}
.tool-body th, .tool-body td {
  border: 1px solid var(--border); padding: 4px 6px; text-align: left; vertical-align: top;
}
.tool-body th { background: var(--panel-2); position: sticky; top: 0; }
.tool-body .meta { color: var(--muted); margin-top: 6px; font-size: 11px; }
.tool-body .csv-download { margin-top: 6px; display: flex; justify-content: flex-end; }
.tool-body .csv-download .btn { font-size: 11px; padding: 3px 8px; }
.chart-wrap {
  position: relative; width: 100%;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 8px; margin: 6px 0;
}
.tool-body .chart-wrap { height: 300px; }
.tool-body .err { color: var(--err); white-space: pre-wrap; }

/* Shimmer placeholder shown inside a .chart-wrap while Chart.js lazy-loads, so a
   card reads as "loading" rather than flashing empty text → blank → chart.
   Fills the wrap (which already has a responsive height) so there's no jump. */
/* Global toasts (showBanner) — fixed, stacked, auto-dismissing. Above modals
   (≤1300) + the mobile bottom bar (9999). */
#toast-container {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 11000; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none; max-width: min(92vw, 440px);
}
.toast {
  pointer-events: auto; cursor: pointer; max-width: 100%; word-break: break-word;
  background: var(--panel, #faf6f1); color: var(--text, #1b160e);
  border: 1px solid var(--border, #ddcdb6); border-left-width: 4px;
  border-radius: 10px; padding: 10px 14px; font-size: 13px; line-height: 1.35;
  box-shadow: 0 8px 28px rgba(27, 22, 14, 0.18);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.toast-info { border-left-color: var(--accent, #0096bf); }
.toast-success { border-left-color: var(--ok, #50a143); }
.toast-error { border-left-color: var(--danger, #ca1e4f); }

/* "A new version is available" (#468). A .toast for the shared look and
   positioning, but persistent and actionable rather than a passing message:
   it stays until the user refreshes or dismisses it, so unlike its siblings it
   is NOT click-to-dismiss (that would fire when reaching for Refresh) and gets
   the default cursor. Pinned as #toast-container's first child, so transient
   toasts stack underneath it. */
.toast-update {
  cursor: default; display: flex; align-items: flex-start; gap: 12px;
  /* .toast sets border-left-width: 4px and leaves the colour to the variant, so
     without this the accent stripe renders in the plain border colour and the
     notice reads as an unstyled box. Accent, like .toast-info: this is news,
     not a failure. */
  border-left-color: var(--accent, #0096bf);
}
/* min-width: 0 so the text column can shrink below its min-content width instead
   of pushing the buttons out of the toast on a narrow window. */
.update-toast-body { display: flex; flex-direction: column; gap: 3px; flex: 1 1 auto; min-width: 0; }
.update-toast-body strong { font-weight: 600; }
/* Two labelled buttons side by side (Refresh primary, Dismiss secondary). The
   ROW is what align-items: center centres the buttons against each other; the
   container itself is centred against the multi-line text block by align-self,
   which overrides the flex-start .toast-update sets for the text column — so the
   pair sits opposite the middle of the copy instead of riding up against its
   first line. */
.update-toast-actions { display: flex; align-items: center; align-self: center; gap: 8px; flex: 0 0 auto; }
/* Both buttons, not just Refresh: .btn's 8px/12px default is too tall for a
   13px toast row, and sizing only one of a pair makes them visibly mismatched. */
.update-toast-actions .btn { padding: 5px 12px; font-size: 12.5px; white-space: nowrap; }
/* Below 640px #toast-container switches to align-items: stretch, so the toast
   spans the viewport — stack the actions under the text and let the two buttons
   split the row rather than squeezing a button pair against the edge. */
@media (max-width: 640px) {
  .toast-update { flex-direction: column; gap: 8px; }
  .update-toast-actions { align-self: stretch; }
  .update-toast-actions .btn { flex: 1 1 auto; }
}
@media (max-width: 640px) {
  #toast-container { bottom: 14px; left: 8px; right: 8px; transform: none; max-width: none; align-items: stretch; }
}

/* A11y: a visible keyboard-focus ring app-wide (many controls set outline:none
   for mouse use; keyboard users still need it). :focus-visible only shows for
   keyboard/AT, not mouse clicks, so it doesn't add visual noise. */
:focus-visible { outline: 2px solid var(--accent, #0096bf); outline-offset: 2px; border-radius: 3px; }

.chart-skeleton {
  width: 100%; height: 100%; min-height: 120px; border-radius: 6px;
  background: var(--panel-2, #f2f2f2);
  background-image: linear-gradient(100deg, transparent 30%, rgba(127, 127, 127, 0.10) 50%, transparent 70%);
  background-size: 220% 100%; background-repeat: no-repeat;
  animation: chart-skeleton-shimmer 1.25s ease-in-out infinite;
}
@keyframes chart-skeleton-shimmer {
  0% { background-position: 220% 0; }
  100% { background-position: -120% 0; }
}
@media (prefers-reduced-motion: reduce) { .chart-skeleton { animation: none; } }

/* React overlay: gentle opacity fade-in on each mount so route changes read as
   intentional rather than an instant pop. Opacity-only (NO transform) so we
   never create a containing block that would trap position:fixed children in a
   view. Toggled by syncReactOverlay() in app.js. */
#react-mount.rm-enter { animation: rm-enter 150ms ease-out; }
@keyframes rm-enter { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { #react-mount.rm-enter { animation: none; } }

/* Generic loading skeleton — reuses the chart-skeleton shimmer. Use for list /
   table placeholders in React views instead of a bare "Loading…" line so the
   layout doesn't jump when data arrives. */
.skeleton {
  background: var(--panel-2, #f4eee4);
  background-image: linear-gradient(100deg, transparent 30%, rgba(127, 127, 127, 0.10) 50%, transparent 70%);
  background-size: 220% 100%; background-repeat: no-repeat;
  animation: chart-skeleton-shimmer 1.25s ease-in-out infinite;
  border-radius: 6px;
}
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }
/* Governed tile editor: holds the config area's shape (date range + format
   row, advanced toggle) while the metric catalog is still loading on a cold
   cache. Normally the catalog is prefetched when the dashboard opens and this
   is never rendered. */
.gov-cfg-skeleton { display: flex; flex-direction: column; gap: 10px; margin: 6px 0 4px; }
.gov-cfg-skeleton .gcs-row { display: flex; gap: 10px; }
.gov-cfg-skeleton .gcs-field { display: block; flex: 1; min-width: 0; height: 38px; }
.gov-cfg-skeleton .gcs-bar { display: block; height: 34px; }
.gov-cfg-skeleton .gcs-bar.short { width: 45%; }

footer#composer-wrap {
  border-top: 1px solid var(--border); padding: 10px 16px 14px; background: var(--panel);
  position: relative;  /* anchor for the absolutely-positioned slash-command dropdown */
}

/* ---- Slash-command autocomplete ----
   Floats above the composer (bottom-anchored) so the user sees suggestions
   without the dropdown pushing the chat content around. Each item shows the
   command syntax + a short description. Keyboard nav highlights the active
   row via .active; click also fills the input. */
.command-autocomplete {
  position: absolute;
  left: 16px; right: 16px;
  bottom: calc(100% - 8px);  /* sits just above the composer-wrap */
  max-height: 280px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.35);
  z-index: 50;
  padding: 4px;
}
.command-autocomplete[hidden] { display: none; }
.command-autocomplete .cmd-item {
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px; border-radius: 6px;
  cursor: pointer; user-select: none;
}
.command-autocomplete .cmd-item:hover,
.command-autocomplete .cmd-item.active {
  background: var(--panel-2);
}
.command-autocomplete .cmd-syntax {
  font-family: var(--mono); font-size: 12.5px; color: var(--accent);
  font-weight: 600;
}
.command-autocomplete .cmd-syntax .cmd-args {
  color: var(--muted); font-weight: 400; margin-left: 4px;
}
.command-autocomplete .cmd-desc {
  font-size: 11.5px; color: var(--muted); line-height: 1.35;
}
.command-autocomplete .cmd-empty {
  padding: 12px; text-align: center; color: var(--muted); font-size: 12px;
}

/* Command-echo bubble — shown in chat when a slash-command fires, so the
   user has a visible record of what they ran (and the result). Looks
   slimmer than a real turn; never persisted with the chat body. */
.turn.system-cmd {
  display: flex; justify-content: center;
}
.turn.system-cmd .bubble {
  background: var(--panel); border: 1px dashed var(--border);
  border-radius: 8px; padding: 6px 12px;
  font-size: 12px; color: var(--muted);
  font-family: var(--mono); max-width: 92%;
}
.turn.system-cmd .bubble strong { color: var(--text); }
.turn.system-cmd .bubble .cmd-status-ok { color: var(--ok); }
.turn.system-cmd .bubble .cmd-status-err { color: var(--err); }
.turn.system-cmd .bubble pre.cmd-body {
  margin: 6px 0 0;
  padding: 8px 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 360px;
  overflow-y: auto;
}
.attachment-chips {
  list-style: none; margin: 0 0 6px; padding: 0;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.attachment-chips[hidden] { display: none; }
.attachment-chips li {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 4px 8px; font-size: 12px; font-family: var(--mono);
  max-width: 240px;
}
.attachment-chips li .icon { flex-shrink: 0; }
.attachment-chips li .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attachment-chips li .size { color: var(--muted); font-size: 10.5px; }
.attachment-chips li .remove {
  background: none; border: none; color: var(--muted); cursor: pointer;
  padding: 0 2px; font-size: 13px; line-height: 1;
}
.attachment-chips li .remove:hover { color: var(--err); }
.attachment-chips li.uploading { border-color: var(--warn); opacity: 0.7; }

/* Upload progress on a staging chip. The chip becomes a positioning context so
   the bar can sit flush along its bottom edge without adding a row that would
   make the chip jump in height when the upload starts. */
.attachment-chips li.uploading { position: relative; overflow: hidden; }
.attachment-chips li .progress-track {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--border);
}
.attachment-chips li .progress-bar {
  display: block; height: 100%; width: 0;
  background: var(--accent);
  transition: width 0.15s linear;
}
/* No determinate value to show — either no progress event has arrived yet, or
   the bytes are all in and the server is still working. Sweep instead of sitting
   frozen at a number we'd be making up. */
.attachment-chips li.indeterminate .progress-track { overflow: hidden; }
.attachment-chips li.indeterminate .progress-bar {
  animation: chip-progress-sweep 1.2s ease-in-out infinite;
  transition: none;
}
@keyframes chip-progress-sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

/* An image the app knows it has but cannot display — the filename plus a reason,
   rather than the empty space that hid a misconfiguration. */
.chat-image.unavailable { background: var(--panel-2); border-style: dashed; }
.chat-image-missing {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px; font-size: 12px; font-family: var(--mono);
}
.chat-image-missing .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-image-missing .why { font-size: 10.5px; }

/* ── Inline chat images + full-screen viewer (#320 follow-up) ──────────────────
   A stored image attachment renders as the picture itself instead of a filename
   chip. The grid is auto-fill rather than a fixed column count so one pasted
   screenshot is a comfortable size and five wrap tidily.

   `min-width: 0` on the figure is load-bearing, not defensive: a grid child
   floors at min-content, and an <img>'s min-content is its intrinsic pixel
   width, so a 2000px-wide screenshot would blow out of the bubble (and out of
   the panel-docked composer at ~380px) no matter what max-width the img has. */
.chat-images {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr));
  gap: 8px;
  margin: 6px 0 0;
}
.chat-image {
  margin: 0; min-width: 0;
  border: 1px solid var(--border); border-radius: 8px;
  overflow: hidden; background: var(--panel-2);
}
.chat-image.loading { min-height: 72px; }

/* A user bubble shrinks to its text, so a short caption ("what's this?") would
   squeeze a screenshot down to the width of the question. When the bubble holds
   pictures, grow it to a legible preview width — still bounded by the bubble's
   own max-width (75%, or 88%/92% on narrow viewports) and by `100%` of the turn,
   so this can never push the bubble out of the transcript. */
.turn.user .bubble:has(.chat-images) { min-width: min(320px, 100%); }
.chat-image-open {
  display: block; width: 100%; padding: 0;
  background: none; border: none; cursor: zoom-in; line-height: 0;
}
.chat-image-open:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.chat-image img {
  display: block; width: 100%; height: auto;
  /* `contain`, not `cover`: these are screenshots of dashboards and charts, and
     a centre-crop of one is unreadable — the whole point of the thumbnail is
     recognising which screenshot it is. Wide images simply scale to the column;
     max-height only bites on portrait ones, which then letterbox. */
  max-height: 260px; object-fit: contain;
}

/* The overlay. `hidden` must win over the flex display, hence the explicit
   [hidden] rule — a bare `display: flex` would keep it visible. */
.image-lightbox {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 24px;
  background: rgba(0, 0, 0, 0.86);
  backdrop-filter: blur(2px);
}
.image-lightbox[hidden] { display: none; }
body.lightbox-open { overflow: hidden; }

.lightbox-figure {
  margin: 0; min-width: 0; max-width: 100%; max-height: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.lightbox-figure img {
  min-width: 0;
  max-width: 100%;
  /* Leave room for the caption under the image; without the subtraction a tall
     screenshot pushes the filename off the bottom of the viewport. */
  max-height: calc(100vh - 110px);
  object-fit: contain;
  border-radius: 6px;
  background: #000;
}
.lightbox-caption {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  justify-content: center;
  font-family: var(--mono); font-size: 12px;
  /* Fixed light-on-dark: the overlay is its own dark surface in BOTH themes, so
     var(--text) would be near-black (and invisible) in the KoRo beige theme. */
  color: rgba(255, 255, 255, 0.92);
}
.lightbox-counter { color: rgba(255, 255, 255, 0.6); }

.lightbox-close, .lightbox-nav {
  flex: 0 0 auto;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #fff; cursor: pointer;
  border-radius: 999px;
  display: grid; place-items: center;
  line-height: 1;
}
.lightbox-close:hover, .lightbox-nav:hover { background: rgba(255, 255, 255, 0.2); }
.lightbox-close:focus-visible, .lightbox-nav:focus-visible {
  outline: 2px solid #fff; outline-offset: 2px;
}
.lightbox-nav { width: 44px; height: 44px; font-size: 26px; }
.lightbox-nav[hidden] { display: none; }
.lightbox-close {
  position: absolute; top: 14px; right: 16px;
  width: 36px; height: 36px; font-size: 20px;
}

/* Narrow viewports: the arrows would eat the image, so overlay them on top of
   it at the edges rather than stealing horizontal space from the figure. */
@media (max-width: 640px) {
  .image-lightbox { padding: 12px; }
  .lightbox-nav { position: absolute; top: 50%; transform: translateY(-50%); }
  .lightbox-nav.prev { left: 8px; }
  .lightbox-nav.next { right: 8px; }
  .lightbox-figure img { max-height: calc(100vh - 130px); }
}

.work-indicator {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; margin-bottom: 6px;
  background: rgba(0, 150, 191, 0.08);
  border: 1px solid var(--border); border-radius: 6px;
  font-size: 12px; color: var(--muted);
}
.work-indicator[hidden] { display: none; }
.work-indicator .work-label { font-family: var(--mono); }
.spinner {
  width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
  flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* In-thread "assistant is working" indicator — animated dots shown inside the
   assistant bubble until the first streamed text / tool card arrives. */
.thinking-dots {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 2px;
  /* Match the 30px assistant "K" avatar (::before, pinned top:0) so the dots
     sit at the avatar's vertical CENTER, not its top edge, while we wait for
     the first response. align-items:center already centers the dots in the row. */
  min-height: 30px;
}
.thinking-dots span {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  animation: thinking-bounce 1.25s ease-in-out infinite;
}
.thinking-dots span:nth-child(2) { animation-delay: 0.16s; }
.thinking-dots span:nth-child(3) { animation-delay: 0.32s; }
@keyframes thinking-bounce {
  0%, 75%, 100% { opacity: 0.28; transform: translateY(0); }
  35% { opacity: 1; transform: translateY(-4px); }
}
@media (prefers-reduced-motion: reduce) {
  .thinking-dots span { animation: thinking-fade 1.25s ease-in-out infinite; }
  @keyframes thinking-fade { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }
}

/* Sidebar chat-list loading skeleton (shown until the first chats snapshot). */
ul#chat-list .chat-item-skeleton {
  height: 34px; margin: 3px 0; border-radius: 8px;
  background: var(--panel-2);
  position: relative; overflow: hidden;
  opacity: 0; animation: sk-fade-in 0.2s ease forwards;
}
ul#chat-list .chat-item-skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.06), transparent);
  transform: translateX(-100%);
  animation: sidebar-skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes sidebar-skeleton-shimmer { to { transform: translateX(100%); } }
@keyframes sk-fade-in { to { opacity: 1; } }

/* Sidebar Reports / Dashboards list loading skeleton (#507) — same row shape as
   the chat-list one above, shown while the owned snapshot is still loading
   instead of the "No … yet" empty <li>. Reveal held ~180ms (matches
   .skeleton-hold) so a fast load doesn't flash it; buildSidebarRowSkeleton adds
   a small per-row stagger on top via inline animation-delay. */
.sidebar-item-skeleton {
  height: 34px; margin: 3px 0; border-radius: 8px;
  background: var(--panel-2);
  position: relative; overflow: hidden;
  list-style: none;
  opacity: 0; animation: skeleton-hold-in 0.01s linear 180ms forwards;
}
.sidebar-item-skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.06), transparent);
  transform: translateX(-100%);
  animation: sidebar-skeleton-shimmer 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) { .sidebar-item-skeleton::after { animation: none; } }

/* Chat-body loading skeleton — transcript-shaped placeholder (alternating
   user/assistant bubbles) shown while a chat's turns are fetched on open. */
.chat-loading-skel { display: flex; flex-direction: column; gap: 16px; padding: 16px 4px; }
.chat-loading-skel .cls-row {
  display: flex; align-items: flex-start; gap: 10px;
  opacity: 0; animation: sk-fade-in 0.25s ease forwards;
}
.chat-loading-skel .cls-row.user { justify-content: flex-end; }
.chat-loading-skel .cls-avatar {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  background: var(--border);
}
.chat-loading-skel .cls-bubble {
  display: flex; flex-direction: column; gap: 9px;
  max-width: 78%; padding: 13px 15px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--border);
}
.chat-loading-skel .cls-row.user .cls-bubble { border-radius: 12px 12px 4px 12px; }
.chat-loading-skel .cls-row.assistant .cls-bubble { border-radius: 12px 12px 12px 4px; }
/* Shimmer bars use currentColor-style neutral alpha that reads on both the
   light (beige) and dark panel themes — a mid-grey alpha works on either. */
.chat-loading-skel .cls-line {
  height: 9px; border-radius: 5px; min-width: 40px;
  background: linear-gradient(90deg, rgba(128,128,128,0.16) 25%, rgba(128,128,128,0.34) 37%, rgba(128,128,128,0.16) 63%);
  background-size: 400% 100%;
  animation: cls-shimmer 1.4s ease-in-out infinite;
}
@keyframes cls-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) {
  .chat-loading-skel .cls-line { animation: cls-pulse 1.4s ease-in-out infinite; }
  @keyframes cls-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.9; } }
}

#composer { display: flex; gap: 8px; align-items: flex-end; }
#prompt {
  flex: 1;
  /* iOS Safari auto-zooms into a focused input whose font-size resolves to
     less than 16px. `font: inherit` resolves to the body's 14px, which
     triggered that zoom on phones (visible in the screenshot where the
     composer + buttons were oversized and clipped at the viewport edges).
     Hardcode 16px here so the page never zooms on focus. */
  font-size: 16px;
  line-height: 1.4;
  resize: vertical; min-height: 40px; max-height: 200px;
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px;
  /* Prevent the textarea from overflowing the composer flex row on phones
     where its scroll-width could exceed available space. */
  min-width: 0;
}
#prompt:focus { outline: none; border-color: var(--accent); }
.btn {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  padding: 8px 12px; border-radius: 6px; cursor: pointer; font: inherit;
}
.btn:hover:not(:disabled) { border-color: var(--accent); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn.primary { background: var(--accent); color: #0e1116; border-color: var(--accent); }
.btn.primary:hover:not(:disabled) { background: #93b8ff; }
.hint { margin-top: 6px; }
/* Composer toggles row (admin-only auto-continue, etc). Sits inline with the
   composer hint text — small, unobtrusive, doesn't fight the Send button. */
.composer-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 12px;
  padding-left: 12px;
  border-left: 1px solid var(--border);
  cursor: pointer;
  user-select: none;
  font-size: 12px;
  color: var(--muted);
}
/* display:inline-flex beats the default [hidden] rule — needed so gating a
   composer toggle (e.g. admin auto-continue, hidden in view-as) actually hides it. */
.composer-toggle[hidden] { display: none; }
.composer-toggle input[type="checkbox"] {
  margin: 0;
  cursor: pointer;
}
.composer-toggle:hover {
  color: var(--text);
}
/* Chat mode toggle — colour-code by active mode so the label can never disagree
   with the checkbox. Governed = green pill, Exploratory = neutral/amber. */
#chat-mode-toggle {
  border-radius: 999px;
  border: 1px solid var(--border);
  padding: 3px 10px;
  margin-left: 12px;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
#chat-mode-toggle[data-mode="governed"] {
  background: rgba(120, 170, 90, 0.14);
  border-color: rgba(120, 170, 90, 0.55);
  color: #4f7a32;
}
#chat-mode-toggle[data-mode="exploratory"] {
  background: rgba(196, 150, 70, 0.12);
  border-color: rgba(196, 150, 70, 0.5);
  color: #9a6f25;
}
#chat-mode-toggle.is-locked {
  opacity: 0.7;
  cursor: not-allowed;
}
/* Constant-width label so the pill (and the toggles after it) don't shift when
   the text swaps between the shorter "Governed" and the wider "Exploratory". */
#chat-mode-toggle #chat-mode-label {
  font-weight: 600;
  display: inline-block;
  min-width: 92px;
  text-align: center;
}
/* Model picker dropdown in the composer hint row. */
.composer-toggle select {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 5px;
  font: inherit;
  font-size: 12px;
  padding: 2px 4px;
  cursor: pointer;
}
.composer-toggle select:hover { border-color: var(--accent); }
.composer-toggle select:disabled { opacity: 0.6; cursor: default; }

.error-banner {
  background: #3a1e25; border: 1px solid var(--err); color: var(--err);
  padding: 8px 12px; border-radius: 6px; margin: 8px 0;
}

/* Sticky save-failure toast — lives at the top of the viewport,
   independent of the chat scroll. The regular .error-banner appends to
   chatEl, which gets cleared on every renderChat(), so a persistChat
   failure banner would flash and disappear on the very reload that
   would expose the bug. This stays until the user dismisses it. */
.sticky-toast {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10000;
  max-width: min(640px, calc(100vw - 24px));
  background: #3a1e25;
  border: 1px solid var(--err);
  color: var(--err);
  padding: 10px 14px 10px 16px;
  border-radius: 8px;
  font-size: 13px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: flex-start;
  gap: 12px;
  line-height: 1.4;
}
.sticky-toast-dismiss {
  background: transparent;
  border: none;
  color: var(--err);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
  margin-top: -2px;
  flex-shrink: 0;
}
.sticky-toast-dismiss:hover { opacity: 0.7; }

/* Dashboard table card */
.table-card-wrap {
  max-height: 360px;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.dashboard-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 13px;
}
.dashboard-table thead {
  position: sticky;
  top: 0;
  background: var(--panel-2);
  z-index: 1;
}
.dashboard-table th, .dashboard-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dashboard-table th {
  font-weight: 600;
  font-size: 12px;
  text-transform: none;
}
.dashboard-table th.sortable {
  cursor: pointer;
  user-select: none;
}
.dashboard-table th.sortable:hover {
  background: var(--panel);
}
.dashboard-table tr:hover td {
  background: var(--panel-2, #f4eee4);
}
/* Cross-filter: a governed dimension cell is clickable to filter the dashboard. */
.dashboard-table td.xf-cell {
  cursor: pointer;
  text-decoration: underline dotted var(--border, #c9bfa9);
  text-underline-offset: 2px;
}
.dashboard-table td.xf-cell:hover {
  background: rgba(122, 62, 0, 0.08);
  color: var(--accent, #7a3e00);
}
/* Totals row (opt-in conditional formatting). */
.dashboard-table tr.table-totals-row td {
  font-weight: 700;
  border-top: 2px solid var(--border, #c9bfa9);
  background: var(--panel);
}
.table-preview-wrap {
  max-height: 280px;
  overflow: auto;
}
/* Footer row under a table card: row-count note on the left, CSV download
   on the right. Reuses .csv-download (defined for bq_query results) so the
   button styling stays consistent across the app. Same shape used under
   chart cards for the CSV + Save-to-dashboard pair. */
.table-card-footer,
.chart-card-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
}
.table-card-footer .src {
  flex: 1 1 auto;
  min-width: 0;
}
.table-card-footer .csv-download,
.chart-card-footer .csv-download {
  margin-top: 0;
}
.table-pager {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}
.table-pager .table-pager-info {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}
.table-pager .btn.small[disabled] { opacity: 0.45; cursor: default; }

/* --- Schema-proposal cards (Settings → Pending / My proposals) ---------- */
#schema-proposals-list,
#my-proposals-list {
  display: flex; flex-direction: column; gap: 8px;
}
.proposal-card {
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--panel-2); padding: 10px 12px;
  font-size: 13px;
}
.proposal-card .row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.proposal-card .target {
  font-family: var(--mono); color: var(--accent); font-size: 12.5px;
  word-break: break-all;
}
.proposal-card .summary {
  margin-top: 4px; color: var(--muted); font-size: 12px;
}
.proposal-card .meta {
  margin-top: 2px; color: var(--muted); font-size: 11px;
}
.proposal-card .actions {
  display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap;
}
.proposal-card details {
  margin-top: 6px;
}
.proposal-card details summary {
  cursor: pointer; color: var(--muted); font-size: 12px;
}
.proposal-card .diff {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 6px;
}
.proposal-card .diff > div {
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--panel); padding: 6px 8px; overflow: auto; max-height: 240px;
}
.proposal-card .diff h5 {
  margin: 0 0 4px 0; font-size: 11px; color: var(--muted); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.4px;
}
.proposal-card .diff pre {
  margin: 0; font-family: var(--mono); font-size: 11.5px; white-space: pre-wrap; word-break: break-word;
}
.proposal-card .review-comment {
  margin-top: 6px; padding: 6px 8px; border-left: 3px solid var(--border);
  background: var(--panel); color: var(--muted); font-size: 12px; white-space: pre-wrap;
}
.proposal-pill {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; border: 1px solid var(--border); background: var(--panel);
  text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
}
.proposal-pill.pending  { color: var(--warn); border-color: var(--warn); }
.proposal-pill.approved { color: var(--ok); border-color: var(--ok); }
.proposal-pill.rejected { color: var(--warn); border-color: var(--warn); opacity: 0.85; }
.proposal-pill.new-table {
  color: var(--accent); border-color: var(--accent); margin-left: 6px;
}
/* ---- Memory consolidation proposal cards ---- */
.memory-proposal-card {
  display: flex; flex-direction: column; gap: 10px;
  margin-bottom: 16px;
}
.memory-proposal-card .proposal-card-header h4 {
  font-size: 14px; font-weight: 600; color: var(--text);
  margin: 0;
}
.memory-proposal-notes {
  font-size: 11.5px; padding: 6px 8px;
  border-left: 3px solid var(--warn);
  background: rgba(237, 149, 0, 0.06);
}
.memory-proposal-sources summary {
  cursor: pointer; color: var(--muted); font-size: 12px;
  padding: 4px 0;
}
.memory-proposal-sources .memory-proposal-source-item {
  margin: 6px 0;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--panel); padding: 6px 8px;
}
.memory-proposal-source-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 4px;
}
.memory-proposal-source-head .type {
  font-size: 9px; padding: 1px 6px; border-radius: 3px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em;
}
.memory-proposal-source-head .type.finding { background: rgba(80, 161, 67, 0.15); color: var(--ok); }
.memory-proposal-source-head .type.hypothesis { background: rgba(237, 149, 0, 0.15); color: var(--warn); }
.memory-proposal-source-head .type.decision { background: rgba(0, 150, 191, 0.15); color: var(--accent); }
.memory-proposal-source-head .type.reference { background: rgba(150, 120, 211, 0.15); color: var(--accent-2); }
.memory-proposal-source-title { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.memory-proposal-source-content {
  margin: 0; font-family: var(--mono); font-size: 11px;
  white-space: pre-wrap; word-break: break-word;
  max-height: 180px; overflow-y: auto;
  color: var(--text);
}
.memory-proposal-merge {
  border: 1px solid var(--accent); border-radius: 6px;
  background: rgba(0, 150, 191, 0.04);
  padding: 10px 12px;
}
.memory-proposal-merge-head {
  font-size: 11px; font-weight: 600; color: var(--accent);
  text-transform: uppercase; letter-spacing: 0.05em;
  margin-bottom: 8px;
}
.memory-proposal-merge label span {
  display: block; font-size: 11px; color: var(--muted); margin-bottom: 2px;
}
.memory-proposal-merge input,
.memory-proposal-merge textarea,
.memory-proposal-merge select {
  width: 100%; background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 8px; font: inherit; font-size: 12.5px;
  box-sizing: border-box;
}
.memory-proposal-merge textarea { font-family: var(--mono); resize: vertical; min-height: 100px; }
.memory-proposal-card .proposal-card-actions {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
}
.memory-proposal-card .proposal-comment-input {
  flex: 1; min-width: 200px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 8px; font: inherit; font-size: 12px;
}

/* ---- Inconsistency card variant ---- */
.memory-inconsistency-card .proposal-card-header h4 {
  color: var(--warn);
}
.memory-inconsistency-claims {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px 10px;
  border-left: 3px solid var(--warn);
  background: rgba(237, 149, 0, 0.06);
  border-radius: 0 6px 6px 0;
  font-size: 12.5px;
}
.memory-inconsistency-claim {
  display: flex; gap: 8px; align-items: baseline;
}
.memory-inconsistency-tag {
  display: inline-block; min-width: 18px; padding: 1px 6px;
  border-radius: 3px; font-weight: 700; font-size: 10.5px;
  text-align: center; flex-shrink: 0;
}
.memory-inconsistency-tag.tag-a {
  background: rgba(0, 150, 191, 0.18); color: var(--accent);
}
.memory-inconsistency-tag.tag-b {
  background: rgba(202, 30, 79, 0.18); color: var(--err);
}
.memory-inconsistency-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
}
.memory-inconsistency-col {
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--panel); padding: 8px 10px;
  min-width: 0;
}
.memory-inconsistency-col-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 4px;
}
.memory-inconsistency-col-title {
  font-weight: 600; flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.memory-inconsistency-content {
  margin: 4px 0 0; padding: 0;
  font-family: var(--mono); font-size: 11px;
  white-space: pre-wrap; word-break: break-word;
  max-height: 220px; overflow-y: auto;
  color: var(--text);
}
@media (max-width: 767px) {
  .memory-inconsistency-grid { grid-template-columns: 1fr; }
}

#schema-proposals-count,
#memory-proposals-count {
  display: inline-block; margin-left: 8px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 8px; font-size: 11px; color: var(--muted);
  font-weight: 400;
}
#schema-proposals-count:empty { display: none; }
#my-proposals-list .empty,
#schema-proposals-list .empty {
  color: var(--muted); font-size: 12px; padding: 6px 0;
}

/* ---- Settings full-view ---- */
#settings-view {
  display: flex; flex-direction: column; flex: 1;
  height: 100%; overflow: hidden;
  background: var(--bg);
}
#settings-view[hidden] { display: none !important; }

/* Base styling for the settings view — applies to ALL viewports. Only
   LAYOUT-specific properties (grid template, flex direction, padding
   amounts that differ on mobile) live inside the media-query blocks
   below. Without this base layer, on phones the nav items rendered as
   purple underlined <a>-defaults because earlier the *entire* styling
   block was wrapped in @media (min-width: 768px). Lesson: only wrap
   the properties that genuinely differ between viewports, not the
   colors / fonts / borders that apply everywhere.

   Layout-differs-by-viewport properties are at the bottom in two
   guarded blocks. Everything else is shared. */
.settings-layout {
  display: grid;
  height: 100%;
  min-height: 0;
}

/* "View as group" admin preview banner — fixed bottom bar across the whole app. */
.view-as-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center; gap: 14px;
  padding: 8px 16px; font-size: 13px; font-weight: 600;
  background: #7c3aed; color: #fff; box-shadow: 0 -2px 10px rgba(0,0,0,0.25);
}
.view-as-banner .btn { background: rgba(255,255,255,0.18); color: #fff; border: 1px solid rgba(255,255,255,0.5); }
.view-as-banner .btn:hover { background: rgba(255,255,255,0.3); }
body.has-view-as-banner { padding-bottom: 38px; }

/* ============================================================================
   KoRo brand reskin — light theme (appended override layer)
   ----------------------------------------------------------------------------
   Mirrors KoRo's Sales-Slides style guide (Downloads/bq-chat-koro-redesign).
   Lives LAST in the cascade, so equal-specificity selectors here win over the
   original dark-theme rules. Strategy: (1) remap the app's core design tokens
   to the light palette so every token-driven surface re-skins at once, then
   (2) reskin the shell + home + core components to match the mockup, then
   (3) patch the most visible hardcoded dark colors.
   ============================================================================ */
:root {
  /* KoRo primary */
  --koro-beige: #ede2d1;
  --beige-light: #f4eee4;
  --beige-lightest: #faf6f1;
  --koro-black: #1b160e;          /* warm near-black for text + CTAs */
  /* KoRo secondary "lighter" (badge / highlight backgrounds) */
  --acc-green: #e9edc6; --acc-yellow: #ffedc1; --acc-purple: #e5e1ff;
  --acc-blue: #d7eef3;  --acc-pink: #fedadf;
  /* KoRo secondary "darker" (accents only) */
  --ink-green: #50a143; --ink-orange: #ed9500; --ink-purple: #9678d3;
  --ink-blue: #0096bf;  --ink-red: #ca1e4f;
  --serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  --sans:  "Poppins", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --koro-shadow: 0 1px 2px rgba(60,40,15,.04), 0 8px 24px rgba(60,40,15,.06);
  --koro-shadow-lift: 0 2px 4px rgba(60,40,15,.06), 0 18px 44px rgba(60,40,15,.12);

  /* Remap the app's existing tokens onto the KoRo palette. Everything that
     references these (panels, borders, text, accents across all views) flips
     to the light theme automatically. */
  --bg: var(--koro-beige);
  --panel: var(--beige-lightest);
  --panel-2: var(--beige-light);
  --border: #ddcdb6;
  --text: var(--koro-black);
  --muted: #5c5345;
  --accent: #0096bf;              /* interactive accent: focus, links, active */
  --accent-2: #9678d3;
  --ok: #50a143; --err: #ca1e4f; --warn: #ed9500;
}

html, body {
  background: var(--koro-beige);
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}
/* Subtle brand depth (warm + cool radial glows). Fixed so it doesn't scroll. */
body {
  background-image:
    radial-gradient(900px 500px at 88% -8%, rgba(255,225,151,.16), transparent 60%),
    radial-gradient(700px 480px at -6% 110%, rgba(214,238,243,.14), transparent 60%);
  background-attachment: fixed;
}
h1, h2, h3 { font-family: var(--serif); letter-spacing: -.3px; }
a { color: var(--ink-blue); }
::selection { background: var(--acc-yellow); }

/* ---------------- Top bar ---------------- */
header {
  background: rgba(237,226,209,.82);
  -webkit-backdrop-filter: saturate(140%) blur(12px);
  backdrop-filter: saturate(140%) blur(12px);
  border-bottom: 1px solid var(--border);
  padding: 13px 28px;
  gap: 16px; /* guarantees space between the brand (.left) and controls (.right) */
}
/* Vertically center the left cluster. The base `.left` uses align-items:baseline,
   which — once the ← back button (a 38px control) sits next to the 46px logo —
   reconciles their two different baselines by pushing the button below the logo,
   growing the header's height and dropping the button low. Centering keeps the
   header height stable whether or not the back button is showing, and aligns the
   button neatly with the logo. */
header .left { align-items: center; flex-shrink: 0; }
.brand-lockup { display: flex; align-items: center; gap: 12px; }
/* Real KoRo brush-script wordmark. The asset is black-on-white, so multiply
   blends the white box into the beige bar, leaving just the black strokes. */
.brand-logo {
  height: 46px; width: auto; display: block;
  mix-blend-mode: multiply;
  -webkit-user-select: none; user-select: none; -webkit-user-drag: none;
}
@media (max-width: 680px) { .brand-logo { height: 36px; } }
.logo-word {
  font-family: var(--serif); font-weight: 700; font-size: 26px; letter-spacing: -.5px;
  line-height: 1; color: var(--koro-black); position: relative;
}
.logo-word sup { font-size: 10px; font-weight: 600; top: -1.1em; margin-left: 1px; }
.logo-word i { font-style: italic; }
.tagline {
  font-family: var(--serif); font-weight: 700; font-size: 10px; line-height: 1.05;
  border-left: 1.5px solid var(--koro-black); padding-left: 10px; color: var(--koro-black);
}
.product-pill {
  padding: 4px 11px; border-radius: 999px; background: var(--koro-black);
  color: var(--koro-beige); font-family: var(--sans); font-weight: 600;
  font-size: 12px; letter-spacing: .3px;
}
.right { gap: 13px; min-width: 0; }
/* Truncate a long email instead of letting it collide with the brand lockup. */
#user-email { color: var(--muted); font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 22ch; }

/* spend bar → KoRo track + green fill */
.spend-indicator { background: transparent; border: none; padding: 0; font-family: var(--sans); gap: 10px; }
.spend-indicator .spend-text { font-size: 12.5px; font-weight: 600; color: var(--koro-black); }
.spend-indicator .spend-bar { width: 120px; height: 7px; background: #e2d4bf; border: none; border-radius: 999px; }
.spend-indicator .spend-bar-fill { background: var(--ink-green); border-radius: 999px; }

/* role / admin pill → green */
#admin-badge, .pill.accent {
  background: var(--acc-green); color: #34611d; border: none;
  text-transform: uppercase; letter-spacing: .6px; font-weight: 600;
  font-size: 11px; padding: 4px 10px; border-radius: 999px;
}
/* Non-super-admin role (e.g. Auditor, or an admin previewing "view as") — the
   same pill shape but a neutral tone, so a green badge never implies privilege
   the caller doesn't have. */
#admin-badge.role {
  background: var(--beige-lightest); color: var(--koro-black); border: 1px solid var(--border);
}

/* header icon buttons → rounded squares */
header .btn.icon {
  width: 38px; height: 38px; padding: 0; display: grid; place-items: center;
  border-radius: 11px; background: var(--beige-lightest); border: 1px solid var(--border);
  color: var(--koro-black); font-size: 16px;
  transition: transform .15s ease, background .15s ease;
}
header .btn.icon:hover:not(:disabled) { background: #fff; transform: translateY(-1px); border-color: var(--border); }
/* Restore [hidden] semantics: the display:grid above outranks the UA
   [hidden]{display:none}, which leaked JS-hidden header icons (the ← back
   button on home, plus any permission-gated buttons). This wins it back. */
header .btn.icon[hidden] { display: none !important; }
/* Custom tooltip: 0.5 s appear delay, instant hide. Anchored to the button,
   right-aligned so it stays inside the viewport for right-side nav icons. */
header .btn.icon[data-tip] { position: relative; }
header .btn.icon[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  font-size: 11.5px;
  line-height: 1.4;
  padding: 3px 7px;
  border-radius: 6px;
  pointer-events: none;
  z-index: 10;
  opacity: 0;
  transition: opacity 0.12s ease 0s;
}
header .btn.icon[data-tip]:hover::after { opacity: 1; transition-delay: 0.5s; }
/* Locked nav button: visible but inaccessible. Override .btn:disabled opacity so
   only the icon fades, not the badge, and disable the hover lift. */
header .btn.icon.nav-locked { opacity: 1; cursor: not-allowed; border-style: dashed; }
header .btn.icon.nav-locked > .ic { opacity: 0.35; }
header .btn.icon.nav-locked:hover { background: var(--beige-lightest); transform: none; }
.nav-lock-badge {
  position: absolute; top: -5px; right: -5px;
  width: 14px; height: 14px;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted);
  pointer-events: none;
}
header .btn.icon .nav-lock-badge .ic { width: 8px; height: 8px; }
#metric-alerts-btn { color: var(--ink-red); }

/* sign in / sign out → outlined black pill */
#signout-btn, #signin-btn, #admin-bootstrap {
  border: 1.5px solid var(--koro-black); background: transparent; color: var(--koro-black);
  border-radius: 999px; padding: 9px 18px; font-weight: 600; font-family: var(--sans);
}
#signout-btn:hover, #signin-btn:hover, #admin-bootstrap:hover { background: var(--koro-black); color: var(--koro-beige); }

/* ---------------- Global buttons ---------------- */
.btn { border-radius: 10px; font-family: var(--sans); }
.btn:hover:not(:disabled) { border-color: var(--koro-black); }
.btn.primary { background: var(--koro-black); color: var(--koro-beige); border-color: var(--koro-black); }
.btn.primary:hover:not(:disabled) { background: #2c2519; border-color: #2c2519; }
.btn.danger { background: transparent; border-color: var(--ink-red); color: var(--ink-red); }
.btn.danger:hover:not(:disabled) { background: var(--ink-red); color: #fff; }
.btn.ghost:hover:not(:disabled) { border-color: var(--ink-blue); color: var(--ink-blue); background: var(--beige-light); }

/* ---------------- Home / Projects ---------------- */
#home-view { background: transparent; }
.home-inner { max-width: 1320px; padding: 48px 40px 80px; }
.home-section-head { align-items: flex-end; gap: 24px; flex-wrap: wrap; }
.home-section-head-text { min-width: 0; }
.home-section-head h1 { font-family: var(--serif); font-weight: 700; font-size: 30px; color: var(--koro-black); }
#home-view .home-section .home-section-head h1 { font-size: clamp(40px, 5.5vw, 60px); letter-spacing: -1.2px; line-height: .98; }
.home-section-head h2 { font-family: var(--serif); font-weight: 700; font-size: 22px; color: var(--koro-black); }
.home-intro { font-family: var(--sans); font-size: 16px; color: var(--muted); margin: 12px 0 0; max-width: 560px; line-height: 1.5; }
.home-projects { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 26px; }

.home-tile {
  background: var(--beige-lightest); border: 1px solid var(--border); border-radius: 18px;
  padding: 24px 24px 20px; gap: 14px; box-shadow: var(--koro-shadow);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.home-tile:hover { transform: translateY(-4px); box-shadow: var(--koro-shadow-lift); border-color: #cdb79b; }
.home-tile-head { align-items: center; }
.home-tile-title { font-family: var(--serif); font-size: 19px; font-weight: 700; align-items: center; gap: 13px; }
.home-tile-emoji {
  width: 46px; height: 46px; flex: 0 0 46px; border-radius: 50%;
  display: grid; place-items: center; font-size: 23px; background: var(--acc-yellow);
}
.home-tile:nth-child(5n+1) .home-tile-emoji { background: var(--acc-yellow); }
.home-tile:nth-child(5n+2) .home-tile-emoji { background: var(--acc-blue); }
.home-tile:nth-child(5n+3) .home-tile-emoji { background: var(--acc-green); }
.home-tile:nth-child(5n+4) .home-tile-emoji { background: var(--acc-purple); }
.home-tile:nth-child(5n+5) .home-tile-emoji { background: var(--acc-pink); }
.home-tile-name { font-weight: 700; }
.home-tile-desc { font-size: 13.5px; line-height: 1.55; color: var(--muted); border-top: 1px solid var(--border); padding-top: 16px; }
.home-tile-counts { border-top: none; padding: 2px 4px 16px; }
.home-tile-count .num { font-family: var(--serif); font-size: 26px; font-weight: 700; color: var(--koro-black); }
.home-tile-count .label { font-size: 10px; letter-spacing: .9px; color: var(--muted); }
.home-tile .open.btn.primary {
  background: transparent; color: var(--koro-black); border: 1.5px solid var(--koro-black);
  border-radius: 13px; padding: 12px; font-size: 14.5px; font-weight: 600;
}
.home-tile .open.btn.primary:hover:not(:disabled) { background: var(--koro-black); color: var(--koro-beige); }
.home-tile .btn.icon {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 10px;
  background: var(--beige-light); border: 1px solid var(--border); color: var(--muted);
}
.home-tile .btn.icon:hover:not(:disabled) { background: #fff; color: var(--koro-black); }

/* Preview-led dashboard tiles (home "Your dashboards"). A mini-chart preview in
   the tile's accent hue sits above a serif title + edit pencil, a meta line, and
   an "Open →" footer. Scoped to .dash-tile so project tiles keep their design. */
.dash-tile {
  display: flex; flex-direction: column; background: var(--beige-lightest);
  border: 1px solid var(--border); border-radius: 20px; overflow: hidden;
  box-shadow: var(--koro-shadow); cursor: pointer;
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.dash-tile:hover { transform: translateY(-5px); box-shadow: var(--koro-shadow-lift); border-color: #cdb79b; }
.dash-tile .preview {
  height: 128px; margin: 16px 16px 0; border-radius: 14px; background: var(--beige-light);
  border: 1px solid var(--border); position: relative; overflow: hidden;
}
.dash-tile .prev-svg { width: 100%; height: 100%; display: block; }
.dash-tile .prev-svg .gl { stroke: #d8c8b0; stroke-width: 1; vector-effect: non-scaling-stroke; }
.dash-tile:hover .prev-line { stroke-width: 3.2; }
.dash-tile .dbody { flex: 1 1 auto; display: flex; flex-direction: column; padding: 18px 22px 0; }
.dash-tile .titlerow { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.dash-tile h3 { margin: 0; font-family: var(--serif); font-weight: 700; font-size: 20px; letter-spacing: -.3px; line-height: 1.15; color: var(--koro-black); }
.dash-tile .edit {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 10px; display: grid; place-items: center;
  color: var(--muted); background: var(--beige-light); border: 1px solid var(--border);
  cursor: pointer; font-size: 14px; padding: 0; transition: background .15s, color .15s;
}
.dash-tile:hover .edit { background: #fff; color: var(--koro-black); }
.dash-tile .edit:hover { background: var(--koro-black); color: var(--koro-beige); }
.dash-tile .desc {
  font-size: 13px; color: var(--muted); margin-top: 8px; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.dash-tile .meta { font-size: 13px; color: var(--muted); margin-top: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dash-tile .meta .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.dash-tile .meta b { font-family: var(--serif); font-weight: 700; color: var(--koro-black); font-size: 15px; }
.dash-tile .meta .sep { color: #c3b298; }
/* "Shared with me" marker on non-owned dashboard tiles (Dashboards page, #284). */
.dash-tile .meta .dash-shared-badge {
  font-size: 11px; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
  color: var(--koro-black); background: var(--beige-light); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 8px;
}
.dash-tile .foot { margin-top: auto; padding: 16px 22px 18px; display: flex; align-items: center; justify-content: flex-end; gap: 7px; font-weight: 600; font-size: 14px; color: var(--koro-black); }
.dash-tile .foot .arrow { display: inline-flex; transition: transform .18s ease; }
.dash-tile .foot svg { width: 16px; height: 16px; }
.dash-tile:hover .foot .arrow { transform: translateX(4px); }
/* Chat-transcript preview (shared-chat tiles): a question bubble + decorative
   reply, inside the same .preview box as the dashboard chart. */
.dash-tile .chatprev { height: 100%; padding: 14px 16px; display: flex; flex-direction: column; gap: 8px; justify-content: center; }
.dash-tile .chatprev .bubble { font-size: 11.5px; line-height: 1.35; padding: 8px 11px; border-radius: 13px; max-width: 82%; }
.dash-tile .chatprev .bubble.user { align-self: flex-end; background: var(--tile-fill, var(--acc-purple, #e5e1ff)); color: var(--tile-stroke, #4a3a82); border-bottom-right-radius: 4px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 88%; }
.dash-tile .chatprev .bubble.bot { align-self: flex-start; background: #fff; border: 1px solid var(--border); border-bottom-left-radius: 4px; display: flex; flex-direction: column; gap: 5px; min-width: 54%; }
.dash-tile .chatprev .bubble.bot .ln { height: 4px; border-radius: 2px; background: #e2d6c2; }
.dash-tile .chatprev .bubble.bot .ln.s { width: 60%; }

/* ---------------- Login ---------------- */
#login-screen, #vpn-gate-screen { background: var(--koro-beige); }
.login-logo { color: var(--koro-black); font-family: var(--serif); }
.login-title { font-family: var(--serif); }

/* ---------------- Hardcoded-dark patches ---------------- */
.btn.primary { color: var(--koro-beige); }               /* was #0e1116 dark text */
.report-status-pill.on  { background: rgba(80,161,67,.16);  color: var(--ink-green); }
.report-status-pill.err { background: rgba(202,30,79,.14);  color: var(--ink-red); }
.report-status-pill.off { background: var(--beige-light);   color: var(--muted); }
/* dark-maroon holdouts → brand-appropriate on the light theme */
.error-banner { background: #fbe3e8; border-color: var(--ink-red); color: #8f1538; }
.sticky-toast { background: var(--koro-black); color: var(--koro-beige); }
.member-list .remove:hover { background: rgba(202,30,79,.12); }
ul#chat-list .chat-item .del:hover { background: rgba(202,30,79,.12); }

/* chat "tool used" chips → high-contrast on the light theme. The old styling
   was a dark-navy pill (#2a3140) + ink-blue text + near-black SQL block — all
   low-contrast / off-theme islands on beige. */
.tool .badge {
  background: var(--koro-black); color: var(--koro-beige);
  padding: 2px 9px; border-radius: 6px; font-weight: 600; letter-spacing: .2px;
}
.tool .status { color: #5c5345; }
.tool .status.ok  { color: #357a2b; }   /* darker ink-green so small meta text stays legible on beige */
.tool .status.err { color: var(--ink-red); }
.tool pre.sql {
  background: var(--beige-light); border: 1px solid var(--border); color: #2c2519;
}

/* governed-chat reply feedback (👍/👎) */
.gov-feedback { display: inline-flex; align-items: center; gap: 4px; margin-left: auto; }
.gov-feedback-label { font-size: 11.5px; color: var(--muted); margin-right: 2px; }
.gov-fb-btn {
  border: 1px solid var(--border); background: var(--beige-lightest); cursor: pointer;
  border-radius: 8px; padding: 3px 8px; font-size: 13px; line-height: 1;
  filter: grayscale(.45); opacity: .7; transition: filter .12s ease, opacity .12s ease, background .12s ease, transform .12s ease, border-color .12s ease;
}
.gov-fb-btn:hover:not(:disabled) { opacity: 1; filter: none; background: #fff; transform: translateY(-1px); }
.gov-fb-btn.active { opacity: 1; filter: none; border-color: var(--koro-black); }
.gov-fb-btn.up.active { background: var(--acc-green); }
.gov-fb-btn.down.active { background: var(--acc-pink); }

/* governed-chat feedback modal */
.gov-fb-overlay {
  position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center;
  background: rgba(27,22,14,.45); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.gov-fb-box {
  width: min(440px, 92vw); background: var(--beige-lightest); border: 1px solid var(--border);
  border-radius: 16px; padding: 22px 24px; box-shadow: var(--koro-shadow-lift);
}
.gov-fb-box h3 { font-family: var(--serif); font-weight: 700; font-size: 19px; margin: 0 0 6px; color: var(--koro-black); }
.gov-fb-sub { font-size: 13px; color: var(--muted); margin: 0 0 14px; line-height: 1.5; }
.gov-fb-box textarea {
  width: 100%; box-sizing: border-box; background: #fff; border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 12px; font: inherit; font-size: 14px; resize: vertical; color: var(--text);
}
.gov-fb-box textarea:focus { outline: none; border-color: var(--ink-blue); }
.gov-fb-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }

/* ============================================================================
   REPORTS PAGE — clean-up pass
   Flatter cards · calmer badges · lighter filter bar · more whitespace.
   These override the heavier base rules earlier in the file; placed last so
   they win on source order (matched specificity). Scoped to .reports-* /
   .report-card so nothing else is affected.
   ========================================================================== */

/* --- Lighter filter bar: chips sit on the page bg (no panel box), with a thin
       divider; active chip uses the KoRo black pill instead of ink-blue. ----- */
.reports-chips-bar {
  gap: 14px 22px; padding: 2px 0 18px;
  border-bottom: 1px solid var(--border); margin-bottom: 22px;
}
.reports-chip-label { font-weight: 600; color: var(--muted); }
.reports-chips-bar button.chip {
  background: transparent; border: 1px solid var(--border); color: var(--muted);
  border-radius: 999px; padding: 4px 13px; font-size: 12px; font-weight: 500;
}
.reports-chips-bar button.chip:hover {
  background: var(--panel-2); border-color: var(--koro-black); color: var(--koro-black);
}
.reports-chips-bar button.chip.active {
  background: var(--koro-black); border-color: var(--koro-black);
  color: var(--koro-beige); font-weight: 600;
}

/* --- More whitespace: roomier sections + grid; the group "header" becomes a
       plain serif heading (no boxed toggle button). ---------------------------- */
.reports-project-section { margin-bottom: 30px; }
.reports-project-toggle {
  background: transparent; border: none; border-radius: 0;
  padding: 4px 0; gap: 12px; align-items: baseline;
}
.reports-project-toggle:hover { background: transparent; }
.reports-project-section.open .reports-project-toggle { border: none; }
.reports-project-name { font-family: var(--serif); font-weight: 700; font-size: 19px; }
.reports-project-caret { font-size: 12px; }
.reports-grid {
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 18px; padding: 14px 0 0;
  border: none; border-radius: 0; background: transparent;
}

/* --- Flatter cards: hairline border + thin source-colored top accent, roomier
       padding, no shadow, no fold. ---------------------------------------------- */
.report-card {
  border: 1px solid var(--border); border-top-width: 2px;
  border-radius: 12px; padding: 16px 16px 12px;
}
.report-card:hover { border-color: #cdb79b; box-shadow: none; }
.report-card.report-card-source-chat { border-top-color: var(--ink-green); }
.report-card.report-card-source-scheduled { border-top-color: var(--ink-blue); }

/* --- Calmer badges: the meta + sharing chips lose their pill chrome and read as
       one muted line; "Reviewed" shrinks to a small green tick (the green top
       edge already flags it) while concern/critical/disagreement stay loud. ----- */
.report-card-title { font-family: var(--serif); font-weight: 700; font-size: 16px; }
.report-card-meta { gap: 8px; margin-bottom: 4px; }
.report-card-source-pill {
  background: transparent; border: none; padding: 0;
  color: var(--muted); font-weight: 500; font-size: 12px;
}
.report-card-date { color: var(--muted); }
.report-card-date::before { content: "· "; color: var(--border); }
.report-card-badges { gap: 8px 10px; margin-bottom: 10px; }
.report-card-badges .pill,
.report-share-pill {
  background: transparent; border: none; padding: 0;
  color: var(--muted); font-size: 12px; font-weight: 500;
}
.report-card-badges .validation-badge.vb-ok {
  background: transparent; border: none; padding: 0;
  color: var(--ink-green); font-weight: 600; font-size: 12px;
}
.report-version-chip {
  background: var(--panel-2); border: 1px solid var(--border); color: var(--muted);
  border-radius: 999px; font-size: 11px; padding: 1px 9px; font-weight: 600;
}
.report-card-footer { border-top: none; padding-top: 10px; }

/* Scheduled-report run transcript (run-detail modal) */
.run-transcript { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 10px; }
.run-transcript > summary { cursor: pointer; font-weight: 600; font-size: 13px; color: var(--muted); }
.run-transcript .rt-body { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.rt-phase { font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; color: var(--muted); margin-top: 8px; border-top: 1px dashed var(--border); padding-top: 6px; }
.rt-elided { font-size: 11px; color: var(--muted); font-style: italic; }
.rt-turn { background: var(--panel2, #f4eee4); border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; }
.rt-meta { font-size: 11px; color: var(--muted); font-weight: 600; }
.rt-text { font-size: 12px; margin-top: 3px; white-space: pre-wrap; }
.rt-tool { font-size: 11.5px; margin-top: 3px; }
.rt-tool code { font-size: 10.5px; word-break: break-all; background: rgba(0,0,0,0.05); padding: 0 3px; border-radius: 3px; }
.rt-results { display: flex; flex-direction: column; gap: 2px; padding-left: 6px; }
.rt-result { font-size: 11.5px; font-family: ui-monospace, monospace; color: var(--ok, #50a143); }
.rt-result.rt-err { color: var(--danger, #ca1e4f); }
.rt-note { font-size: 10.5px; margin-top: 8px; }

/* Card-based viewer-facing custom filter (dashboard tiles) */
.card-view-filter-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 6px 10px; margin: 0 8px 6px; border: 1px solid var(--border);
  border-radius: 8px; background: var(--panel2, #f4eee4); font-size: 12px;
}
.card-view-filter-bar .card-vf-dim { font-size: 12px; padding: 2px 6px; }
.btn.icon.card-vf-active { color: var(--accent, #0096bf); background: rgba(0, 150, 191, 0.14); border-radius: 6px; }

/* Multi-select tag input for filter values */
.card-vf-ms { position: relative; flex: 1; min-width: 180px; }
.card-vf-chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  border: 1px solid var(--border); border-radius: 6px; background: #fff;
  padding: 3px 5px; min-height: 26px;
}
.card-vf-chip {
  display: inline-flex; align-items: center; gap: 3px;
  background: rgba(0, 150, 191, 0.14); color: var(--accent, #0096bf);
  border: 1px solid rgba(0, 150, 191, 0.40); border-radius: 999px;
  padding: 0 4px 0 8px; font-size: 11.5px; font-weight: 600; white-space: nowrap;
}
.card-vf-chip-x { border: none; background: none; cursor: pointer; color: inherit; font-size: 13px; line-height: 1; padding: 0 2px; }
.card-vf-input { flex: 1; min-width: 70px; border: none; outline: none; font: inherit; font-size: 12px; background: transparent; }
.card-vf-dd {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 40;
  margin-top: 2px; max-height: 180px; overflow-y: auto;
  background: #fff; border: 1px solid var(--border); border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.14);
}
.card-vf-opt { padding: 4px 9px; font-size: 12px; cursor: pointer; }
.card-vf-opt:hover { background: var(--panel2, #f2f2f2); }

/* Card body loading overlay (filter switch / re-render) */
.dashboard-card-body { position: relative; }
.card-loading-overlay {
  position: absolute; inset: 0; z-index: 30;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.55);
  border-radius: inherit;
}
.card-loading-spinner {
  width: 26px; height: 26px; border-radius: 50%;
  border: 3px solid rgba(0, 150, 191, 0.25);
  border-top-color: var(--accent, #0096bf);
  animation: card-vf-spin 0.7s linear infinite;
}
@keyframes card-vf-spin { to { transform: rotate(360deg); } }

/* ── Notification center (top-bar bell) ───────────────────────────────── */
#notif-btn { position: relative; }
#notif-badge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 9px; background: #be3c28; color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
  box-shadow: 0 0 0 2px var(--panel, #faf6f1);
}
/* Notification center — tiered cards (broken → needs-you → clear), sorted by
   urgency, with a collapsed lazy-loaded archive. Tokens mirror the KoRo brand
   ramp from the design mock. */
#notif-panel {
  --nf-canvas:#f4eee4; --nf-beige:#faf6f1; --nf-line:rgba(0,0,0,.10);
  --nf-ink:#1b160e; --nf-soft:#4a4339; --nf-mute:#8a8073;
  --nf-broken-fill:#fedadf; --nf-broken-ink:#ca1e4f;
  --nf-watch-fill:#ffedc1; --nf-watch-ink:#a86200; --nf-watch-rail:#ed9500;
  --nf-clear-fill:#e9edc6; --nf-clear-ink:#3a7530; --nf-clear-rail:#50a143;
  position: absolute; right: 0; top: calc(100% + 6px); width: 420px; max-width: calc(100vw - 24px);
  max-height: 78vh; overflow: auto; background: var(--nf-canvas);
  border: 1px solid #ddcdb6; border-radius: 16px;
  box-shadow: 0 18px 50px rgba(27,22,14,0.22); z-index: 5000; font-size: 13px; padding: 14px;
}
.notif-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
.notif-head strong { font-size: 19px; color: var(--nf-ink); font-family: 'Source Serif 4', Georgia, serif; }
.notif-count { font-size: 12px; color: var(--nf-mute); font-family: var(--mono, monospace); }
.notif-sub { font-size: 12px; color: var(--nf-mute); margin-top: 2px; }
.notif-readall { background: none; border: none; font: inherit; font-size: 12.5px; color: var(--nf-soft);
  cursor: pointer; padding: 4px 2px; border-bottom: 1px solid transparent; white-space: nowrap; }
.notif-readall:hover { border-bottom-color: var(--nf-ink); }
.notif-filters { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.notif-filter { font: inherit; font-size: 12px; font-weight: 500; background: #f0e8db; color: var(--nf-soft);
  border: 1px solid transparent; border-radius: 999px; padding: 5px 11px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; }
.notif-filter:hover { background: #ece4d6; }
.notif-filter.on { background: var(--nf-ink); color: #fff; }
.notif-filter .n { font-family: var(--mono, monospace); font-size: 11px; opacity: .75; }
.notif-list { display: flex; flex-direction: column; }
.notif-card { position: relative; display: flex; background: var(--nf-beige);
  border: 1px solid var(--nf-line); border-radius: 14px; margin-bottom: 10px; overflow: hidden; cursor: pointer; transition: opacity .15s, border-color .12s; }
.notif-card:hover { border-color: #cdb79b; }
.notif-card.archived { opacity: .66; }
.notif-card.archived:hover { opacity: 1; }
.notif-rail { flex: 0 0 5px; width: 5px; }
.notif-card[data-tier="broken"] .notif-rail { background: var(--nf-broken-ink); }
.notif-card[data-tier="watch"]  .notif-rail { background: var(--nf-watch-rail); }
.notif-card[data-tier="clear"]  .notif-rail { background: var(--nf-clear-rail); }
.notif-cbody { padding: 13px 15px; flex: 1; min-width: 0; }
.notif-meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 8px; }
.notif-pill { font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px; white-space: nowrap; }
.notif-card[data-tier="broken"] .notif-pill { background: var(--nf-broken-fill); color: var(--nf-broken-ink); }
.notif-card[data-tier="watch"]  .notif-pill { background: var(--nf-watch-fill); color: var(--nf-watch-ink); }
.notif-card[data-tier="clear"]  .notif-pill { background: var(--nf-clear-fill); color: var(--nf-clear-ink); }
.notif-chip { font-size: 11.5px; font-weight: 500; background: #f0e8db; color: var(--nf-soft);
  padding: 3px 9px; border-radius: 999px; white-space: nowrap; }
.notif-time { margin-left: auto; font-family: var(--mono, monospace); font-size: 11.5px; color: var(--nf-mute); white-space: nowrap; }
.notif-archive { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--nf-line);
  background: #fff; color: var(--nf-mute); font-size: 12px; line-height: 1; cursor: pointer; padding: 0; }
.notif-archive:hover { background: var(--nf-ink); color: #fff; border-color: var(--nf-ink); }
.notif-ctitle { font-size: 14px; font-weight: 500; line-height: 1.3; margin: 0 0 4px; color: var(--nf-ink); }
.notif-x { font-family: var(--mono, monospace); font-size: 11px; color: var(--nf-mute); }
.notif-cdesc { font-size: 13px; line-height: 1.5; color: var(--nf-soft); margin: 0; white-space: pre-wrap; word-break: break-word; }
.notif-empty { padding: 26px 12px; color: var(--nf-mute); text-align: center; }
/* "Anyone at KoRo" toggle row in the unified share modal (report-library). */
.sf-everyone-row { display: flex; align-items: flex-start; gap: 9px; margin-top: 12px; font-size: 13px; color: var(--text); cursor: pointer; }
.sf-everyone-row input { margin-top: 2px; flex: 0 0 auto; }
.sf-everyone-row b { font-weight: 600; }

.notif-arch-toggle { display: flex; align-items: center; justify-content: space-between; width: 100%;
  background: none; border: none; border-top: 1px solid var(--nf-line); font: inherit; font-size: 13px; font-weight: 500;
  color: var(--nf-soft); cursor: pointer; padding: 12px 2px 8px; margin-top: 2px; }
.notif-arch-toggle .n { font-family: var(--mono, monospace); font-size: 11.5px; color: var(--nf-mute); }
.notif-arch-list { display: flex; flex-direction: column; }
.notif-more { background: none; border: 1px dashed var(--nf-line); border-radius: 999px; font: inherit;
  font-size: 12.5px; color: var(--nf-soft); cursor: pointer; padding: 7px; margin: 2px 0 6px; }
.notif-more:hover { border-color: var(--nf-ink); color: var(--nf-ink); }

/* ── Mobile: React-view data tables scroll horizontally ─────────────────────
   Wide tables in the React admin tabs (Usage, Users, Table-usage, etc.) would
   overflow a phone viewport. Make them scroll instead. Scoped to #react-mount
   so the vanilla dashboard tables (own mobile handling) are untouched, and only
   below 640px so desktop is unchanged. matches the useIsMobile() breakpoint. */
@media (max-width: 640px) {
  #react-mount table { display: block; overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; }

  /* Systemic guard against sideways page-shift in React views on phones.
     (1) Never let the overlay scroll horizontally — wide bits scroll/clip
     locally instead of dragging the whole view (header + tabs) off-screen.
     (2) overflow-wrap:anywhere is INHERITED, so long unbreakable identifiers
     (table/metric/column names, URLs, SQL idents like foo__bar_baz) wrap rather
     than force width. (3) code/pre blocks wrap too. Buttons/tab strips that set
     white-space:nowrap are unaffected (they keep their own overflow-x:auto). */
  /* break-word (NOT anywhere): wraps genuinely long tokens but PRESERVES
     min-content width, so table columns + flex items don't collapse to a single
     character (which produced vertical one-letter-per-line text). */
  #react-mount { overflow-x: hidden !important; overflow-wrap: break-word; }
  #react-mount pre { white-space: pre-wrap; overflow-wrap: break-word; }
  /* Table cells must NOT break mid-word — they keep natural width so the table
     scrolls horizontally (display:block + overflow-x:auto above) instead of
     squishing columns into vertical text. */
  #react-mount th, #react-mount td { overflow-wrap: normal; }

  /* Notification overlay: the desktop dropdown is absolutely-positioned under a
     tiny bell that, on a phone, lives inside the expanded burger menu — so it
     dropped from the middle of a multi-row header and overlapped it.
     NOTE: the header sets backdrop-filter, which makes IT the containing block
     for our position:fixed panel — so `top`/`bottom` resolve against the header,
     not the viewport (a plain `bottom:0` lands at the header's bottom = top of
     screen). We use that on purpose: anchor full-width just BELOW the header
     (`top:100%` = the header's own height) and cap the height to the space left
     beneath it. Scoped to ≤640px → desktop (position:absolute) unchanged. */
  #notif-panel {
    position: fixed;
    top: calc(100% + 4px); bottom: auto;
    left: 8px; right: 8px;
    width: auto; max-width: none;
    max-height: calc(100vh - 100% - 16px);
    border-radius: 16px;
    box-shadow: 0 18px 50px rgba(27, 22, 14, 0.28);
  }
}

/* ============================================================================
   Premium polish pass (appended LAST so equal-specificity selectors win)
   ----------------------------------------------------------------------------
   (1) line-icons: <svg class="ic"><use href="#i-..."/></svg> replaces emoji in
   the header. (2) card depth: softer multi-layer shadow + larger radius + hover
   lift so cards float off the beige canvas. (3) KPI deltas → pill chips and a
   serif headline. (4) tabular figures so numbers align in columns.
   ========================================================================== */
.ic {
  width: 18px; height: 18px; flex: 0 0 auto;
  display: inline-block; vertical-align: -3px;
  stroke: currentColor; fill: none;
}
header .btn.icon .ic { width: 19px; height: 19px; }

/* Depth + contrast — cards lift off the canvas. */
.dashboard-card {
  border-color: #e2d4bd; border-radius: 14px;
  box-shadow: 0 1px 2px rgba(60, 40, 15, 0.04), 0 6px 20px rgba(60, 40, 15, 0.07);
  transition: opacity 120ms ease, transform 150ms ease, box-shadow 150ms ease, border-color 120ms ease;
}
@media (hover: hover) {
  .dashboard-card:hover {
    box-shadow: 0 2px 4px rgba(60, 40, 15, 0.06), 0 14px 34px rgba(60, 40, 15, 0.12);
    transform: translateY(-2px);
  }
}

/* Tabular figures everywhere numbers line up. */
.dashboard-table, .dashboard-table td, .kpi-value, .num { font-variant-numeric: tabular-nums; }

/* KPI headline → serif; delta → colored pill chip. */
.kpi-value { font-family: 'Source Serif 4', Georgia, serif; font-size: 32px; }
.kpi-delta {
  display: inline-flex; align-items: center; gap: 4px; width: fit-content;
  font-size: 12px; font-weight: 600; padding: 2px 9px; border-radius: 999px; margin-top: 8px;
}
.kpi-delta-good { background: #e3f0d6; color: #3a7530; }
.kpi-delta-bad  { background: #fcdbe2; color: #ca1e4f; }
.kpi-delta-flat { background: #efe7d8; color: #5c5345; }
/* Comparison-unavailable note stays a plain muted line, not a pill. */
.kpi-delta-muted { display: block; width: auto; padding: 0; background: none; border-radius: 0; font-weight: 400; }

/* ============================================================================
   Chat & sidebar polish (appended LAST)
   ========================================================================== */
/* Assistant identity — a small KoRo avatar in the left gutter. Positioned
   absolutely so it never disturbs the turn's child layout (tables, cards,
   charts, thinking blocks). Excludes the full-width report + system-cmd turns. */
.turn.assistant:not(.report-mode):not(.system-cmd) { position: relative; padding-left: 44px; }
.turn.assistant:not(.report-mode):not(.system-cmd)::before {
  content: "K"; position: absolute; left: 0; top: 0;
  width: 30px; height: 30px; border-radius: 50%;
  background: linear-gradient(135deg, #00a6d1, #007ba0); color: #fff;
  font-family: 'Source Serif 4', Georgia, serif; font-weight: 700; font-size: 15px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 6px rgba(0, 123, 160, 0.35);
}

/* In-chat markdown tables — match the dashboard table polish. */
.md table { border-collapse: collapse; width: 100%; font-size: 13px; margin: 6px 0; }
.md table th {
  text-align: left; padding: 8px 12px; border-bottom: 1px solid #e2d4bd;
  color: #8a7d6a; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
}
.md table td { padding: 8px 12px; border-bottom: 1px solid #f0e7d6; font-variant-numeric: tabular-nums; }
.md table tr:last-child td { border-bottom: none; }
.md table tbody tr { transition: background .1s ease; }
.md table tbody tr:hover { background: rgba(0, 150, 191, 0.05); }

/* Sidebar active chat — accent bar + tint so "where am I" is unmistakable. */
ul#chat-list .chat-item { position: relative; }
ul#chat-list .chat-item.active { background: rgba(0, 150, 191, 0.08); border-color: transparent; }
ul#chat-list .chat-item.active::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--accent);
}
ul#chat-list .chat-item.active .title { color: var(--accent); font-weight: 600; }
ul#chat-list .chat-item .edit .ic, ul#chat-list .chat-item .del .ic { width: 14px; height: 14px; }

/* Composer → a single rounded pill with a focus ring; the textarea blends in,
   the icons sit inside, and send is a round accent button. */
#composer {
  /* Bottom-align, not centre: once #prompt auto-grows past its resting height
     (#416) a centred attach/send pair floats in the middle of a tall box.
     Pinning them to the end keeps both buttons on the composer's last line, in
     the chat page and in the relocated dashboard/modal panel alike. */
  align-items: flex-end;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 14px;
  padding: 5px 6px 5px 8px; gap: 6px; transition: border-color .12s, box-shadow .12s;
}
#composer:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(0, 150, 191, 0.15); }
#prompt {
  background: transparent; border: none; border-radius: 8px; padding: 8px 6px;
  /* #416: auto-grow. JS (autoGrowPrompt) sets an explicit height from the
     content; min/max-height cap it so an empty composer keeps its resting size
     and a long prompt scrolls internally instead of pushing the transcript
     (or the dashboard chat panel's) out of view. Both contexts share this
     single #prompt, so the cap lives here. */
  min-height: 38px; max-height: 200px; resize: none; overflow-y: auto;
}
#prompt:focus { border: none; box-shadow: none; outline: none; }
#composer .btn.icon#attach-btn {
  border: none; background: transparent; color: var(--muted);
  /* Same 38px square as #send-btn so the two bottom-align on one line rather
     than sitting at slightly different heights (their padding boxes differ). */
  width: 38px; height: 38px; padding: 0; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
}
#composer .btn.icon#attach-btn:hover:not(:disabled) { background: rgba(0, 0, 0, 0.05); color: var(--text); }
#send-btn {
  width: 38px; height: 38px; padding: 0; border-radius: 10px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
}
#send-btn .ic { width: 18px; height: 18px; }

/* ============================================================================
   Final mobile guard (must be LAST in the cascade)
   ----------------------------------------------------------------------------
   `.home-inner` is a flex item of its flex <main> shell, and a base rule later
   in this file sets `max-width: 1320px`, which beat every earlier mobile
   override (equal specificity, later source order) — so on phones the Memory /
   Schema / Home inner grew to its content's intrinsic width and overflowed the
   viewport (rows ran off-screen; titles never reached their ellipsis). Pin it
   to the viewport here, after that base rule. min-width:0 removes the flex
   item's auto min so children can truncate. */
@media (max-width: 767px) {
  .home-inner { max-width: 100% !important; min-width: 0; overflow-x: hidden; }
}

/* ── Governed tile editor (#601; interim step towards the #772 panel) ──────
   The "Advanced options" toggle is replaced by four collapsible groups
   (Rows · Columns · Filters · Display) whose headers carry a count of what is
   configured inside, the metric description is a two-line preview, and the
   selects get the field look in every browser. */
.gov-tile-modal .gov-mdesc { font-size: 12px; line-height: 1.5; margin: -4px 0 0; }
.gov-tile-modal .gov-mdesc.is-clamped {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.gov-tile-modal .gov-mdesc-more {
  background: none; border: 0; padding: 0; margin: 2px 0 6px; font: inherit; font-size: 12px;
  color: var(--accent); cursor: pointer;
}
.gov-tile-modal .gov-mdesc-more[hidden] { display: none; }
.gov-tile-modal .gov-mdesc-more:hover { text-decoration: underline; }

/* Safari (and macOS Chrome with some settings) draws a native <select> as a
   thin system control and ignores its padding, so Date range / Number format
   looked nothing like the Title and Metric fields beside them. `appearance:
   none` + a drawn chevron gives every select the same field box. The chevron
   is two gradient triangles in var(--muted), so it follows the theme. */
.modal.gov-tile-modal form select:not([multiple]) {
  -webkit-appearance: none; appearance: none;
  padding-right: 30px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 16px) 52%, calc(100% - 11px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.gov-tile-modal .gov-groups { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
.gov-tile-modal .gov-group {
  border: 1px solid var(--border); border-radius: 10px; background: var(--panel);
  min-width: 0; /* a flex child must be allowed to shrink at 380px (CLAUDE.md) */
}
.gov-tile-modal .gov-group-head {
  display: flex; align-items: baseline; gap: 8px; width: 100%; min-width: 0;
  padding: 10px 12px; background: none; border: 0; border-radius: 10px;
  font: inherit; color: var(--text); text-align: left; cursor: pointer;
}
.gov-tile-modal .gov-group-head:hover { background: var(--panel-2); }
.gov-tile-modal .gov-group-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.gov-tile-modal .gov-group-chev::before { content: "▸"; color: var(--muted); font-size: 11px; }
.gov-tile-modal .gov-group-head[aria-expanded="true"] .gov-group-chev::before { content: "▾"; }
.gov-tile-modal .gov-group-title { font-weight: 600; font-size: 13px; flex: 0 0 auto; }
.gov-tile-modal .gov-group-count {
  flex: 0 0 auto; min-width: 18px; padding: 0 6px; border-radius: 9px;
  background: var(--accent); color: #fff; font-size: 11px; font-weight: 600;
  line-height: 18px; text-align: center;
}
.gov-tile-modal .gov-group-count[hidden] { display: none; }
.gov-tile-modal .gov-group-sub {
  margin-left: auto; min-width: 0; color: var(--muted); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gov-tile-modal .gov-group-head[aria-expanded="true"] { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.gov-tile-modal .gov-group-body { padding: 0 12px 12px; border-top: 1px solid var(--border); }
.gov-tile-modal .gov-group-body[hidden] { display: none; }
/* The moved blocks carry their old inline top margins; the first one in a box
   sits on the box edge instead. */
.gov-tile-modal .gov-group-body > :first-child { margin-top: 10px !important; }
/* The footer's two button clusters are inline-flex spans that never wrap, so
   at phone width (where the generic rule stacks the footer and widens every
   .btn to 100 %) Duplicate and Save ran off the modal and were clipped. Let
   each cluster wrap and share its row instead. */
.gov-tile-modal .modal-footer > span { flex-wrap: wrap; min-width: 0; max-width: 100%; }
@media (max-width: 480px) {
  .gov-tile-modal .modal-footer > span > .btn { width: auto; flex: 1 1 auto; }
}


/* ======================================================================
   Answer layout (app.js "Answer layout"): one progress card per reply that
   gathers its tool calls as steps, one answer card around the answer, the
   "Data & context" column / sheet, and the composer's follow-up scope chips.
   Tokens are the KoRo light theme's; the near-white card surface is local.
   ====================================================================== */
:root {
  --card-surface: #fffdf9;
  --line-soft: #ebe2d4;
  --trust-ok-bg: #e5f2e4;   --trust-ok-ink: #2f6b27;
  --trust-warn-bg: #fff1d6; --trust-warn-ink: #7f5200;
  --trust-info-bg: #e3f1f7; --trust-info-ink: #00627e;
}

/* ---- Progress card ---------------------------------------------------- */
/* Sits first in the reply. Rows: status circle · label over specifics · time ·
   chevron — the circle spins while the call runs and settles to ✓ / ! / ×. */
#chat .turn-steps {
  border: 1px solid var(--line-soft); border-radius: 16px; background: var(--card-surface);
  box-shadow: var(--koro-shadow); font-family: var(--sans); font-size: 13px; color: var(--text);
  min-width: 0; overflow: hidden;
}
#chat .turn-steps .ts-head {
  display: flex; align-items: center; gap: 10px; width: 100%; min-width: 0;
  padding: 13px 18px; background: none; border: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
#chat .turn-steps .ts-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 16px; }
#chat .turn-steps .ts-title { font-weight: 500; white-space: nowrap; color: var(--text); }
#chat .turn-steps[data-state="running"] .ts-title { color: var(--muted); }
#chat .turn-steps .ts-sum { color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
#chat .turn-steps .ts-chev { margin-left: auto; color: var(--muted); transition: transform .15s; }
#chat .turn-steps .ts-chev::before { content: "⌄"; }
#chat .turn-steps .ts-head[aria-expanded="true"] .ts-chev { transform: rotate(180deg); }
/* Header mark: a refresh-style ring while running, ✓ / ! when done. */
#chat .turn-steps .ts-mark {
  flex: 0 0 18px; width: 18px; height: 18px; border-radius: 99px; display: inline-grid; place-items: center;
  font-size: 10px; font-weight: 700; color: #fff; background: var(--ink-green);
}
#chat .turn-steps[data-state="done"] .ts-mark::before { content: "✓"; }
#chat .turn-steps[data-state="flagged"] .ts-mark { background: var(--ink-orange); }
#chat .turn-steps[data-state="flagged"] .ts-mark::before { content: "!"; }
#chat .turn-steps[data-state="running"] .ts-mark {
  background: none; border: 2px solid var(--line-soft); border-top-color: var(--muted); animation: ts-spin .9s linear infinite;
}
@keyframes ts-spin { to { transform: rotate(360deg); } }
#chat .turn-steps .ts-list { padding: 0 18px 6px; border-top: 1px solid var(--line-soft); }
#chat .turn-steps .ts-list[hidden] { display: none; }

/* A step is the unchanged tool row, re-dressed: its summary carries the step
   line; the legacy badge + status stay in the DOM (still written to) but hidden. */
#chat .turn-steps details.tool.ts-step,
#chat .turn-steps .ts-write {
  background: none; border: 0; border-top: 1px solid var(--line-soft); border-radius: 0;
  padding: 0; font-family: var(--sans); font-size: 13px; opacity: 1;
}
#chat .turn-steps .ts-list > :first-child { border-top: 0; }
#chat .turn-steps details.tool.ts-step > summary { display: block; list-style: none; padding: 11px 0; cursor: pointer; }
#chat .turn-steps .ts-write { padding: 11px 0; }
/* A result the answer shows (table / chart / forecast) is a step as well; its
   query opens below the row in an exploratory chat. */
#chat .turn-steps .ts-result { border-top: 1px solid var(--line-soft); font-family: var(--sans); font-size: 13px; }
#chat .turn-steps .ts-result > .ts-result-head { padding: 11px 0; }
#chat .turn-steps .ts-result > .ts-result-head .ts-line::after { content: ""; }
#chat .turn-steps details.ts-result > summary { display: block; list-style: none; padding: 11px 0; cursor: pointer; }
#chat .turn-steps details.ts-result > summary::-webkit-details-marker { display: none; }
#chat .turn-steps details.ts-result > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
#chat .turn-steps details.ts-result[open] > summary .ts-line::after { transform: rotate(90deg); }
#chat .turn-steps details.ts-result.ts-result-empty > summary { cursor: default; }
#chat .turn-steps details.ts-result.ts-result-empty .ts-line::after { content: ""; }
#chat .turn-steps .ts-result-sql {
  margin: 0 0 12px 38px; padding: 10px 12px; max-height: 260px; overflow: auto; border-radius: 10px;
  background: var(--code-bg, #f6f2ec); border: 1px solid var(--line-soft); font-size: 12px; white-space: pre; min-width: 0;
}
/* While a reply works through its steps, only the progress card (and a plan
   proposal or an error) shows; the answer appears whole, in its card, once it
   is complete (finalizeTurnLayout drops the class). */
#chat .turn.assistant .content.answer-pending > :not(.turn-steps):not(.plan-card):not(.error-banner):not(.thinking-dots) { display: none !important; }
#chat .turn-steps details.tool.ts-step > summary::-webkit-details-marker { display: none; }
#chat .turn-steps details.tool.ts-step > summary > .badge,
#chat .turn-steps details.tool.ts-step > summary > .status { display: none; }
#chat .turn-steps details.tool.ts-step > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
#chat .turn-steps .ts-line {
  display: grid; grid-template-columns: 24px minmax(0, 1fr) minmax(36px, max-content) 14px;
  column-gap: 14px; align-items: center; min-width: 0;
}
#chat .turn-steps .ts-line::after { content: "›"; color: var(--muted); font-size: 15px; transition: transform .15s; text-align: center; }
#chat .turn-steps .ts-write .ts-line::after { content: ""; }
#chat .turn-steps details.tool.ts-step[open] > summary .ts-line::after { transform: rotate(90deg); }
#chat .turn-steps .ts-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
#chat .turn-steps .ts-label { font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#chat .turn-steps .ts-detail { color: var(--muted); font-size: 12.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#chat .turn-steps .ts-detail:empty { display: none; }
#chat .turn-steps .ts-time { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
/* The per-row status circle. */
#chat .turn-steps .ts-state {
  width: 22px; height: 22px; border-radius: 99px; display: inline-grid; place-items: center; box-sizing: border-box;
  font-size: 12px; font-weight: 700; color: #fff; background: var(--ink-green);
}
#chat .turn-steps .ts-state[data-state="ok"]::before { content: "✓"; }
#chat .turn-steps .ts-state[data-state="refused"] { background: var(--ink-orange); }
#chat .turn-steps .ts-state[data-state="refused"]::before { content: "!"; }
#chat .turn-steps .ts-state[data-state="error"] { background: var(--ink-red); }
#chat .turn-steps .ts-state[data-state="error"]::before { content: "×"; font-size: 14px; }
#chat .turn-steps .ts-state[data-state="running"] {
  background: none; border: 2.5px solid var(--acc-blue); border-top-color: var(--ink-blue); animation: ts-spin .8s linear infinite;
}
#chat .turn-steps [data-step-state="running"] .ts-label { color: var(--text); }
/* The opened row: whatever the tool row always showed, under its step's text. */
#chat .turn-steps details.tool.ts-step[open] > :not(summary) { margin-left: 38px; }
#chat .turn-steps details.tool.ts-step[open] { padding-bottom: 12px; }
#chat .turn-steps details.tool.ts-step pre.sql { font-size: 11.5px; }
/* A call that never compiled (a refusal) has no SQL: no empty box for it. */
#chat .turn-steps details.tool.ts-step pre.sql:empty { display: none; }
#chat .turn-steps .ts-narration {
  border-top: 1px solid var(--line-soft); padding: 9px 0 9px 38px; color: var(--muted); font-size: 12.5px; margin: 0;
  font-style: italic; white-space: normal;
}
#chat .turn-steps .ts-narration p { margin: 0; }
#chat .turn-steps .ts-narration code { font-style: normal; }
@media (max-width: 767px) {
  #chat .turn-steps .ts-head { padding: 12px 14px; }
  #chat .turn-steps .ts-list { padding: 0 14px 4px; }
  #chat .turn-steps .ts-line { grid-template-columns: 22px minmax(0, 1fr) max-content 12px; column-gap: 10px; }
  #chat .turn-steps .ts-detail { white-space: normal; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
  #chat .turn-steps details.tool.ts-step[open] > :not(summary) { margin-left: 0; }
  #chat .turn-steps .ts-narration { padding-left: 32px; }
}
@media (prefers-reduced-motion: reduce) {
  #chat .turn-steps[data-state="running"] .ts-mark,
  #chat .turn-steps .ts-state[data-state="running"] { animation: none; border-top-color: var(--ink-blue); border-right-color: var(--ink-blue); }
  #chat .turn-steps .ts-chev, #chat .turn-steps .ts-line::after { transition: none; }
}

/* ---- Answer card -------------------------------------------------------- */
#chat .content > .answer-card {
  border: 1px solid var(--line-soft); border-radius: 16px; background: var(--card-surface);
  box-shadow: var(--koro-shadow); padding: 14px 18px 16px; min-width: 0;
  display: flex; flex-direction: column; gap: 10px;
}
#chat .content > .answer-card.ac-selected { box-shadow: 0 0 0 2px rgba(0,150,191,.28), var(--koro-shadow); }
#chat .answer-card .ac-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
#chat .answer-card .ac-meta { color: var(--muted); font-size: 12px; }
#chat .answer-card .ac-context-btn { margin-left: auto; }
#chat .answer-card .ac-title {
  font-family: var(--serif); font-weight: 700; font-size: 19px; letter-spacing: -.3px; line-height: 1.25;
  color: var(--koro-black); text-wrap: balance; overflow-wrap: anywhere;
}
.ac-trust {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 99px;
  font-family: var(--sans); font-size: 12px; font-weight: 600; white-space: nowrap; width: fit-content;
}
.ac-trust .ac-dot { width: 7px; height: 7px; border-radius: 99px; background: currentColor; }
.ac-trust-verified { background: var(--trust-ok-bg); color: var(--trust-ok-ink); }
.ac-trust-under_review { background: var(--trust-warn-bg); color: var(--trust-warn-ink); }
.ac-trust-exploratory { background: var(--trust-info-bg); color: var(--trust-info-ink); }
.ac-trust-qualitative { background: #efe8f7; color: #5b3f8c; }
.ac-trust-proposal { background: #efe7da; color: #5c4a2e; }
/* A drafted request inside the answer card: the card is its frame and its
   title, so the proposal drops its own box, icon and "Proposed …:" title. */
#chat .answer-card .proposal-card { background: none; border: 0; padding: 0; margin: 0; font-family: var(--sans); }
#chat .answer-card .proposal-head { margin-bottom: 4px; }
#chat .answer-card .proposal-head > span:first-child,
#chat .answer-card .proposal-title { display: none; }
#chat .answer-card .proposal-reason { font-style: normal; font-size: 14px; color: var(--text); margin: 4px 0 8px; }
#chat .answer-card .ac-body { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* A table / chart IS the answer: no <details> chrome around it. */
#chat .answer-card details.tool.result-viz { border: none; background: none; padding: 0; font-family: var(--sans); font-size: inherit; }
#chat .answer-card details.tool.result-viz > summary { display: none; }
#chat .answer-card .ac-body > .text.md > :first-child { margin-top: 0; }
#chat .answer-card .ac-body > .text.md > :last-child { margin-bottom: 0; }
/* The newlines marked leaves between blocks are not lines: under the reply's
   pre-wrap they showed as an empty line under the last paragraph. Inside a
   paragraph or list item they still are. */
#chat .answer-card .ac-body > .text.md { white-space: normal; }
#chat .answer-card .ac-body > .text.md :is(p, li, td, th, blockquote) { white-space: pre-wrap; }
#chat .answer-card .ac-foot { border-top: 1px solid var(--line-soft); padding-top: 10px; }
#chat .answer-card .ac-foot .metric-prov { margin: 0; }
/* The governed metric, named once in the header line. */
#chat .answer-card .ac-meta .ac-metric-keys { font-weight: 600; color: var(--text); }
/* Data-quality issues the answer ran into: quiet, opens Data & context. */
#chat .answer-card .ac-dq {
  border: 0; background: none; padding: 0; font: inherit; font-size: 12px; color: var(--trust-warn-ink);
  text-decoration: underline; text-decoration-color: rgba(127,82,0,.35); text-underline-offset: 2px; cursor: pointer;
}
#chat .answer-card .ac-dq::before { content: "⚠ "; text-decoration: none; }
#chat .answer-card .ac-dq:hover { text-decoration-color: currentColor; }
#chat .answer-card .ac-dq:focus-visible { outline: 2px solid var(--ink-blue); outline-offset: 2px; border-radius: 4px; }
/* A metric's caveat: one callout, the method one click away. */
#chat .answer-card .ac-method {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; font-family: var(--sans); font-size: 13px; line-height: 1.5;
  color: var(--muted); min-width: 0;
}
#chat .answer-card .ac-method-caveat {
  grid-template-columns: 18px minmax(0, 1fr); column-gap: 10px;
  border: 1px solid rgba(237,149,0,.35); background: var(--trust-warn-bg); border-radius: 12px; padding: 9px 12px; color: #5d4300;
}
#chat .answer-card .ac-method-ic {
  width: 18px; height: 18px; border-radius: 99px; background: var(--ink-orange, #ed9500); color: #fff;
  font-size: 11px; font-weight: 700; display: grid; place-items: center; margin-top: 1px;
}
#chat .answer-card .ac-method-lead { font-weight: 500; color: #4a3500; overflow-wrap: anywhere; }
#chat .answer-card .ac-method-caveat .ac-method-more { grid-column: 2; }
#chat .answer-card .ac-method-more > summary {
  cursor: pointer; list-style: none; width: fit-content; font-size: 12.5px; color: var(--trust-warn-ink);
}
#chat .answer-card .ac-method:not(.ac-method-caveat) .ac-method-more > summary { color: var(--muted); }
#chat .answer-card .ac-method-more > summary::-webkit-details-marker { display: none; }
#chat .answer-card .ac-method-more > summary::after { content: "›"; display: inline-block; margin-left: 5px; transition: transform .15s; }
#chat .answer-card .ac-method-more[open] > summary::after { transform: rotate(90deg); }
#chat .answer-card .ac-method-more > summary:focus-visible { outline: 2px solid var(--ink-blue); outline-offset: 2px; border-radius: 4px; }
#chat .answer-card .ac-method-body { margin-top: 6px; display: flex; flex-direction: column; gap: 6px; overflow-wrap: anywhere; }
#chat .answer-card .ac-method-body p { margin: 0; }
/* A one-number answer: the number is the hero, its change a chip beside it. */
#chat .answer-card .ac-kpi { display: flex; align-items: flex-end; gap: 6px 22px; flex-wrap: wrap; padding: 2px 0; min-width: 0; }
#chat .answer-card .ac-kpi-value {
  font-family: var(--serif); font-size: 46px; font-weight: 700; letter-spacing: -1px; line-height: 1;
  color: var(--koro-black); font-variant-numeric: tabular-nums;
}
#chat .answer-card .ac-kpi-side { display: flex; flex-direction: column; gap: 6px; padding-bottom: 4px; min-width: 0; }
#chat .answer-card .ac-kpi-delta {
  display: inline-flex; align-items: center; gap: 6px; width: fit-content; padding: 3px 10px; border-radius: 99px;
  font-family: var(--sans); font-size: 12.5px; font-weight: 600; background: #efe7da; color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
#chat .answer-card .ac-kpi-compare { font-family: var(--sans); font-size: 12.5px; color: var(--muted); }
#chat .answer-card.ac-single .ac-body > .text.md p { margin: 0 0 8px; }
#chat .answer-card.ac-single .ac-body > .text.md p:last-child { margin-bottom: 0; }
/* What to do next with the number: each fills the composer. */
#chat .answer-card .ac-next { display: flex; gap: 8px; flex-wrap: wrap; }
@media (max-width: 767px) {
  #chat .answer-card .ac-kpi-value { font-size: 38px; }
}
@media (max-width: 767px) {
  #chat .content > .answer-card { padding: 12px 12px 14px; border-radius: 14px; }
  #chat .answer-card .ac-title { font-size: 17px; }
}
.metric-badge-partial { background: var(--trust-info-bg); border-color: rgba(0,150,191,.35); color: var(--trust-info-ink); }
.metric-badge-review { background: var(--trust-warn-bg); border-color: rgba(237,149,0,.45); color: var(--trust-warn-ink); }

/* ---- Data & context: column + sheet ----------------------------------- */
.main-col.has-context-col { grid-template-columns: minmax(0, 1fr) 340px; }
.main-col.has-context-col > :not(#answer-context-col) { grid-column: 1; }
#answer-context-col {
  grid-column: 2; grid-row: 1 / -1; min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  border-left: 1px solid var(--border); background: rgba(250,246,241,.7); padding: 16px 16px 24px;
  font-family: var(--sans);
}
#answer-context-col[hidden] { display: none; }
/* Hide / show, like the left sidebar: a chevron in the column's head folds it
   away, and a tab poking out of the right edge brings it back. */
.main-col { position: relative; }
.acc-collapse { display: inline-flex; align-items: center; justify-content: center; padding: 0; }
.acc-extender {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%); z-index: 6;
  width: 30px; height: 52px; padding: 0; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-right: none; background: var(--panel);
  color: var(--koro-black, #1a150d); cursor: pointer;
  border-radius: 14px 0 0 14px; box-shadow: -2px 2px 10px rgba(60,40,15,.14);
  transition: transform .15s, background .15s;
}
.acc-extender[hidden] { display: none; }
.acc-extender:hover { background: #fff; transform: translateY(-50%) translateX(-3px); }
.acc-extender:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .acc-extender, .acc-extender:hover { transition: none; transform: translateY(-50%); } }
.acc-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.acc-title, .acx-sheet-title { margin: 0; font-family: var(--serif); font-weight: 700; font-size: 19px; letter-spacing: -.3px; color: var(--koro-black); }
.acc-title:focus { outline: none; }
.acc-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.acx-close {
  margin-left: auto; width: 32px; height: 32px; border-radius: 99px; border: 1px solid var(--line-soft);
  background: var(--card-surface); color: var(--text); font-size: 16px; line-height: 1; cursor: pointer; flex: 0 0 auto;
}
.acx-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.acx { display: flex; flex-direction: column; gap: 10px; font-size: 13px; color: var(--text); min-width: 0; }
.acx-sec { border: 1px solid var(--line-soft); border-radius: 12px; background: var(--card-surface); min-width: 0; }
.acx-sec > summary {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px; cursor: pointer; list-style: none; min-width: 0;
}
.acx-sec > summary::-webkit-details-marker { display: none; }
.acx-sec > summary::after { content: "⌄"; color: var(--muted); margin-left: 4px; transition: transform .15s; }
.acx-sec[open] > summary::after { transform: rotate(180deg); }
.acx-sec > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 12px; }
.acx-sec-title { font-weight: 600; flex: 1 1 auto; min-width: 0; }
.acx-sec-sum { color: var(--muted); font-size: 12px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acx-sec[open] .acx-sec-sum { visibility: hidden; }
.acx-sec-body { padding: 0 12px 10px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.acx-row { border-top: 1px solid var(--line-soft); padding-top: 6px; min-width: 0; }
.acx-main { overflow-wrap: anywhere; display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
.acx-sub { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
.acx-tag { font-size: 11px; font-weight: 600; padding: 1px 7px; border-radius: 99px; }
.acx-tag-warn { background: var(--trust-warn-bg); color: var(--trust-warn-ink); }
.acx-tag-ok { background: var(--trust-ok-bg); color: var(--trust-ok-ink); }
.acx-tag-info { background: var(--trust-info-bg); color: var(--trust-info-ink); }
.acx-note { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
.acx-caveat { margin: 0; padding: 6px 9px; border-left: 2px solid var(--ink-orange); background: #fff8ea; border-radius: 0 6px 6px 0; font-size: 12.5px; line-height: 1.5; }
.acx-meter { height: 5px; border-radius: 99px; background: var(--line-soft); overflow: hidden; }
.acx-meter > span { display: block; height: 100%; background: var(--ink-green); }
.acx-meter > span.warn { background: var(--ink-orange); }

.acx-backdrop {
  position: fixed; inset: 0; z-index: 10050; background: rgba(27,22,14,.38);
  display: flex; align-items: flex-end; justify-content: center;
}
.acx-sheet {
  width: 100%; max-width: 560px; max-height: 88vh; display: grid; grid-template-rows: auto minmax(0, 1fr);
  background: var(--beige-light); border-radius: 18px 18px 0 0; box-shadow: 0 -8px 32px rgba(27,22,14,.18);
  font-family: var(--sans); animation: acx-up .18s ease-out;
}
.acx-sheet-head { display: flex; align-items: center; gap: 8px; padding: 14px 14px 8px; position: relative; }
.acx-grab { position: absolute; top: 6px; left: 50%; width: 36px; height: 4px; margin-left: -18px; border-radius: 99px; background: #dccfbb; }
.acx-sheet-body { overflow-y: auto; overscroll-behavior: contain; padding: 4px 14px 24px; min-width: 0; }
@keyframes acx-up { from { transform: translateY(24px); } to { transform: none; } }
@media (min-width: 700px) {
  .acx-backdrop { align-items: center; }
  .acx-sheet { border-radius: 18px; max-height: 80vh; }
  .acx-grab { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .acx-sheet { animation: none; }
  .acx-sec > summary::after { transition: none; }
}

/* ---- Composer scope chips ------------------------------------------------ */
.composer-scope {
  display: flex; align-items: center; gap: 8px; min-width: 0; padding: 0 2px 6px;
  font-family: var(--sans); font-size: 12px;
}
.composer-scope[hidden] { display: none; }
.composer-scope .cs-label { color: var(--muted); white-space: nowrap; flex: 0 0 auto; }
.composer-scope .cs-chips { display: flex; gap: 6px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.composer-scope .cs-chips::-webkit-scrollbar { display: none; }
.cs-chip {
  display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 99px; white-space: nowrap;
  background: var(--card-surface); border: 1px solid var(--line-soft); color: var(--text); font-size: 12px;
}
.cs-chip-metric { font-weight: 600; }
.composer-scope .cs-clear {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 99px; border: 1px solid var(--line-soft);
  background: var(--card-surface); color: var(--muted); cursor: pointer; line-height: 1; font-size: 13px; padding: 0;
}
.composer-scope .cs-clear:hover { color: var(--text); }
.composer-scope .cs-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.turn.user .user-scope { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.turn.user .user-scope .cs-chip { font-size: 11px; padding: 1px 7px; }


/* ---- Plans (update_plan) -------------------------------------------------
   The proposal card (a plan asking for approval) and the progress card's plan
   mode share the checklist rows: state circle · label over detail · tag. */
#chat .plan-card {
  border: 1px solid var(--line-soft); border-radius: 18px; background: var(--card-surface);
  box-shadow: var(--koro-shadow); font-family: var(--sans); font-size: 13px; color: var(--text);
  overflow: hidden; min-width: 0;
}
#chat .plan-card .plan-head { display: flex; align-items: center; gap: 10px; padding: 16px 20px 2px; flex-wrap: wrap; }
#chat .plan-card .plan-icon {
  width: 26px; height: 26px; border-radius: 99px; background: #f6e9d8; color: #7a4f1d;
  display: inline-grid; place-items: center; flex: 0 0 auto;
}
#chat .plan-card .plan-title { font-weight: 600; font-size: 14.5px; }
#chat .plan-card .plan-meta { color: var(--muted); }
#chat .plan-card .plan-lead { margin: 4px 20px 12px; color: #4a4237; }
#chat .plan-card .plan-assumptions {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 8px; padding: 0 20px 14px;
}
#chat .plan-card .plan-assumption { background: #f8f3ec; border-radius: 12px; padding: 10px 12px; display: grid; gap: 1px; min-width: 0; }
#chat .plan-card .plan-assumption-label { font-size: 11.5px; color: var(--muted); }
#chat .plan-card .plan-assumption-value { font-weight: 600; overflow-wrap: anywhere; }
#chat .plan-card .plan-assumption-detail { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
#chat .plan-card > .plan-steps { border-top: 1px solid var(--line-soft); padding: 0 20px; }
#chat .plan-steps { list-style: none; margin: 0; padding: 0; }
#chat .plan-steps .plan-step {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; column-gap: 14px; align-items: center;
  padding: 11px 0; border-top: 1px solid var(--line-soft); min-width: 0;
}
#chat .plan-steps .plan-step:first-child { border-top: 0; }
/* The optional step's <label> wraps its checkbox and text; `contents` keeps both
   in the row's grid (box in the state column, text beside it, on one line). */
#chat .plan-steps .plan-step > label.plan-step-include { display: contents; cursor: pointer; }
#chat .plan-steps .plan-step > label.plan-step-include > input[type="checkbox"] { width: 16px; height: 16px; margin: 0 0 0 3px; accent-color: var(--koro-black); }
#chat .plan-step-text { display: grid; gap: 1px; min-width: 0; }
#chat .plan-step-label { font-weight: 600; overflow-wrap: anywhere; }
#chat .plan-step-detail { color: var(--muted); font-size: 12.5px; overflow-wrap: anywhere; }
#chat .plan-step-tag { font-size: 11px; color: var(--muted); border: 1px solid var(--line-soft); border-radius: 99px; padding: 1px 8px; white-space: nowrap; }
#chat .plan-step-optional { border-top-style: dashed !important; }
#chat .plan-step-dropped .plan-step-label { text-decoration: line-through; color: var(--muted); font-weight: 500; }
#chat .plan-card .plan-foot {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 12px 20px 14px;
  border-top: 1px solid var(--line-soft); background: #f8f3ec;
}
#chat .plan-card .plan-foot-note { margin-left: auto; color: var(--muted); font-size: 12px; }
#chat .plan-card .plan-answered { color: var(--muted); font-size: 12.5px; }
#chat .plan-card .plan-run { border-radius: 10px; }
#chat .plan-card .plan-change { border-radius: 10px; background: var(--card-surface); }
/* The extra circle states the plan needs. */
#chat .ts-state[data-state="planned"] { background: none; border: 1.5px solid #dccfbb; }
#chat .ts-state[data-state="skipped"] { background: none; border: 1.5px dashed #dccfbb; color: var(--muted); }
#chat .ts-state[data-state="skipped"]::before { content: "–"; }
#chat .ts-state[data-state="dropped"] { background: none; border: 1.5px dashed #dccfbb; color: var(--muted); }
#chat .ts-state[data-state="dropped"]::before { content: "×"; }
#chat .ts-state[data-state="stopped"] { background: none; border: 2px solid var(--ink-orange); }
#chat .plan-card .ts-state,
#chat .turn-steps .ts-plan .ts-state {
  width: 22px; height: 22px; border-radius: 99px; display: inline-grid; place-items: center; box-sizing: border-box;
  font-size: 12px; font-weight: 700; color: #fff;
}
#chat .turn-steps .ts-plan .ts-state[data-state="ok"] { background: var(--ink-green); }
#chat .turn-steps .ts-plan .ts-state[data-state="ok"]::before { content: "✓"; }
#chat .turn-steps .ts-plan .ts-state[data-state="running"] {
  background: none; border: 2.5px solid var(--acc-blue); border-top-color: var(--ink-blue); animation: ts-spin .8s linear infinite;
}
#chat .turn-steps .ts-plan .ts-state[data-state="planned"],
#chat .turn-steps .ts-plan .ts-state[data-state="dropped"],
#chat .turn-steps .ts-plan .ts-state[data-state="skipped"] { color: var(--muted); }
#chat .turn-steps .ts-plan .plan-step[data-state="planned"] .plan-step-label { color: var(--muted); font-weight: 500; }
/* Plan mode of the progress card: a step bar under the header, the checklist
   first, the tool calls behind a toggle. */
#chat .turn-steps .ts-planbar { height: 4px; margin: 0 18px 10px; border-radius: 99px; background: #f1ebe2; overflow: hidden; }
#chat .turn-steps .ts-planbar > span { display: block; height: 100%; background: var(--ink-blue); border-radius: 99px; transition: width .3s ease; }
#chat .turn-steps .ts-plan { padding: 2px 0 6px; }
#chat .turn-steps .ts-plan .plan-revision {
  margin: 6px 0 2px; padding: 7px 10px; border-radius: 8px; background: #e3f1f7; color: #00627e; font-size: 12.5px;
}
#chat .turn-steps .plan-calls-toggle {
  margin: 6px 0 2px; background: none; border: 0; padding: 4px 0; font: inherit; font-size: 12.5px;
  color: var(--accent); cursor: pointer;
}
#chat .turn-steps .plan-calls-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
#chat .turn-steps[data-plan]:not(.calls-open) .ts-list > details.tool,
#chat .turn-steps[data-plan]:not(.calls-open) .ts-list > .ts-result,
#chat .turn-steps[data-plan]:not(.calls-open) .ts-list > .ts-narration,
#chat .turn-steps[data-plan] .ts-list > .ts-write { display: none; }
#chat .turn-steps[data-plan].calls-open .ts-list > details.tool:first-of-type { border-top: 1px solid var(--line-soft); }
@media (max-width: 767px) {
  #chat .plan-card .plan-head, #chat .plan-card > .plan-steps, #chat .plan-card .plan-foot { padding-left: 14px; padding-right: 14px; }
  #chat .plan-card .plan-lead { margin-left: 14px; margin-right: 14px; }
  #chat .plan-card .plan-assumptions { padding: 0 14px 12px; }
  #chat .plan-card .plan-foot-note { margin-left: 0; }
  #chat .turn-steps .ts-planbar { margin: 0 14px 10px; }
}
@media (prefers-reduced-motion: reduce) {
  #chat .turn-steps .ts-plan .ts-state[data-state="running"] { animation: none; }
  #chat .turn-steps .ts-planbar > span { transition: none; }
}

/* ---- Clarifying questions (answer options, #513/#580) as a card -----------
   Same rows and behaviour as before, drawn like the answer layout: a card with
   the question as its title, each option a row with a ring (single) or a box
   (multi) and an optional one-line meaning; once answered only the picked
   row stays, so the conversation reads on. Scoped to the chat. */
#chat .answer-chips {
  border: 1px solid var(--line-soft); border-radius: 18px; background: var(--card-surface);
  box-shadow: var(--koro-shadow); padding: 16px 18px; gap: 8px; max-width: 760px; font-family: var(--sans);
}
#chat .answer-chips-question {
  font-family: var(--serif); font-weight: 700; font-size: 19px; letter-spacing: -.3px; line-height: 1.25;
  color: var(--koro-black); margin: 0 0 4px; text-wrap: balance;
}
#chat .answer-chips .answer-chip,
#chat .answer-chips .answer-opt-card {
  border: 1px solid var(--line-soft); border-radius: 12px; background: var(--card-surface); color: var(--text);
  padding: 11px 14px; font-size: 13.5px; font-weight: 600; align-items: flex-start; gap: 12px;
}
#chat .answer-chips .answer-opt-card { padding: 0; }
#chat .answer-chips .answer-opt-trigger { padding: 11px 14px; font-weight: 600; font-size: 13.5px; color: var(--text); gap: 12px; align-items: flex-start; }
#chat .answer-chips .answer-chip:hover:not(:disabled):not(.spent),
#chat .answer-chips .answer-opt-card:hover .answer-opt-trigger:not(.selected):not(.picked) {
  background: #f8f3ec; color: var(--text); border-color: #dccfbb;
}
#chat .answer-chips .answer-opt-card:hover { background: #f8f3ec; color: var(--text); }
/* Single select: a ring before the label; picked fills it. */
#chat .answer-chips:not(.answer-chips-multi) .answer-opt-btn::before,
#chat .answer-chips:not(.answer-chips-multi) .answer-opt-trigger::before {
  content: ""; flex: 0 0 16px; width: 16px; height: 16px; margin-top: 1px; box-sizing: border-box;
  border-radius: 99px; border: 1.5px solid #dccfbb; background: var(--card-surface);
}
#chat .answer-chips .answer-chip.picked,
#chat .answer-chips .answer-chip.selected,
#chat .answer-chips .answer-opt-trigger.picked,
#chat .answer-chips .answer-opt-trigger.selected { background: #f8f3ec; color: var(--text); border-color: #dccfbb; }
#chat .answer-chips:not(.answer-chips-multi) .answer-chip.picked::before,
#chat .answer-chips:not(.answer-chips-multi) .answer-opt-trigger.picked::before {
  border: 5px solid var(--koro-black);
}
#chat .answer-chips .answer-chip > input[type="checkbox"],
#chat .answer-chips .answer-opt-trigger > input[type="checkbox"] { accent-color: var(--koro-black); margin-top: 2px; }
#chat .answer-chips .answer-opt-detail {
  display: block; font-weight: 400; font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 2px;
}
#chat .answer-chips .answer-chip-send {
  background: var(--koro-black); color: #fff; border-radius: 10px; padding: 8px 16px; font-family: var(--sans);
}
#chat .answer-chips .answer-chip-send:disabled { background: #f1ebe2; color: var(--muted); }
/* Answered: keep what was picked, under a quiet "You answered". */
#chat .answer-chips.answered .spent,
#chat .answer-chips.answered .answer-opt-card:not(:has(.picked)) { display: none; }
#chat .answer-chips.answered { box-shadow: none; background: transparent; border-style: dashed; padding: 12px 14px; }
#chat .answer-chips.answered::before { content: "You answered"; font-size: 12px; color: var(--muted); }
#chat .answer-chips.answered .answer-chips-question { font-size: 15px; }

/* ---- A new chat: greeting, nut bowl, starter cards -------------------------- */
#chat .chat-welcome { display: grid; gap: 34px; padding: 48px 4px 16px; font-family: var(--sans); max-width: 1100px; width: 100%; margin: 0 auto; }
#chat .chat-welcome .cw-head { display: grid; grid-template-columns: minmax(0, 1fr) 200px; gap: 20px; align-items: center; }
#chat .chat-welcome .cw-text { display: grid; gap: 12px; min-width: 0; }
#chat .chat-welcome .cw-eyebrow {
  display: flex; align-items: center; gap: 10px; font-size: 13px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--muted); font-weight: 500;
}
#chat .chat-welcome .cw-mode {
  letter-spacing: normal; text-transform: none; font-size: 12px; font-weight: 600; color: #2f6b27;
  background: #e5f2e4; border-radius: 99px; padding: 2px 9px;
}
#chat .chat-welcome .cw-title {
  margin: 0; font-family: var(--serif); font-weight: 700; font-size: 34px; letter-spacing: -.6px; line-height: 1.15;
  color: var(--koro-black); text-wrap: balance;
}
#chat .chat-welcome .cw-sub { margin: 0; font-size: 16.5px; color: var(--muted); line-height: 1.55; max-width: 62ch; }
#chat .chat-welcome .cw-art { justify-self: end; }
#chat .chat-welcome .cw-starters { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; }
#chat .chat-welcome .cw-card {
  display: grid; gap: 14px; align-content: start; text-align: left; font: inherit; cursor: pointer; min-width: 0;
  background: #fffdfa; border: 1px solid var(--line-soft); border-radius: 20px; padding: 24px 24px 28px; color: var(--text);
  box-shadow: 0 1px 2px rgba(27,22,14,.03), 0 6px 18px rgba(60,40,15,.04);
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
#chat .chat-welcome .cw-card:hover { transform: translateY(-2px); border-color: #dccfbb; box-shadow: var(--koro-shadow-lift); }
#chat .chat-welcome .cw-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
#chat .chat-welcome .cw-card-top { display: flex; align-items: flex-start; justify-content: space-between; }
#chat .chat-welcome .cw-card-icon { width: 60px; height: 60px; border-radius: 99px; display: grid; place-items: center; }
#chat .chat-welcome .cw-card-arrow { color: #4a4237; display: grid; }
#chat .chat-welcome .cw-card-title { font-weight: 600; font-size: 17px; }
#chat .chat-welcome .cw-card-prompt { color: var(--muted); font-size: 15px; line-height: 1.7; overflow-wrap: anywhere; }
#chat .chat-welcome .cw-foot { display: flex; gap: 18px; flex-wrap: wrap; justify-content: center; }
#chat .chat-welcome .cw-link { background: none; border: 0; font: inherit; font-size: 13px; color: var(--accent); cursor: pointer; padding: 2px 0; }
#chat .chat-welcome .cw-link:hover { text-decoration: underline; }
@media (max-width: 767px) {
  #chat .chat-welcome { padding: 12px 0; gap: 20px; }
  #chat .chat-welcome .cw-head { grid-template-columns: minmax(0, 1fr); }
  #chat .chat-welcome .cw-art { display: none; }
  #chat .chat-welcome .cw-title { font-size: 26px; }
  #chat .chat-welcome .cw-sub { font-size: 14.5px; }
  #chat .chat-welcome .cw-starters { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  #chat .chat-welcome .cw-card { padding: 16px 18px; gap: 8px; }
  #chat .chat-welcome .cw-card-icon { width: 44px; height: 44px; }
  #chat .chat-welcome .cw-card-prompt { font-size: 13.5px; }
}
@media (prefers-reduced-motion: reduce) { #chat .chat-welcome .cw-card { transition: none; } #chat .chat-welcome .cw-card:hover { transform: none; } }

/* ---- Quick actions (right column) ------------------------------------------- */
.quick-actions {
  border: 1px solid var(--line-soft); border-radius: 16px; background: var(--card-surface);
  padding: 14px 14px 6px; margin-top: 10px; font-family: var(--sans);
}
.quick-actions .qa-title { margin: 0 0 4px; font-family: var(--sans); font-weight: 600; font-size: 15px; color: var(--text); letter-spacing: 0; }
.quick-actions .qa-row {
  display: grid; grid-template-columns: 24px minmax(0, 1fr) 14px; gap: 12px; align-items: center; width: 100%;
  background: none; border: 0; font: inherit; font-size: 14px; color: #4a4237; padding: 9px 4px; cursor: pointer;
  text-align: left; border-radius: 8px;
}
.quick-actions .qa-row:hover { background: #f8f3ec; color: var(--text); }
.quick-actions .qa-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.quick-actions .qa-icon { color: var(--text); display: grid; }
.quick-actions .qa-chev { color: var(--muted); display: grid; }
.acx-empty-note {
  margin-top: 10px; border: 1px dashed #dccfbb; border-radius: 16px; padding: 14px 16px; font-size: 13px;
  color: var(--muted); line-height: 1.55; font-family: var(--sans);
}
.acx-empty-note strong { display: block; color: var(--text); font-weight: 600; margin-bottom: 2px; }


/* ---- Composer: one card — the message on top, the controls below ---------
   attach · follow-up scope · mode pill · model pill · send. Same elements and
   ids as before, regrouped (index.html); the admin auto-continue toggle stays
   in the hint line under the card. */
#composer {
  flex-direction: column; align-items: stretch; gap: 6px;
  background: var(--card-surface); border: 1px solid var(--line-soft); border-radius: 16px;
  padding: 10px 10px 8px 12px; box-shadow: 0 2px 10px rgba(27,22,14,.06);
}
#composer #prompt { font-family: var(--sans); font-size: 14.5px; line-height: 1.5; padding: 4px 4px 2px; min-height: 30px; color: var(--text); }
#composer #prompt::placeholder { color: var(--muted); }
/* `#prompt { flex: 1 }` is for the old row layout. In this column card a 0
   flex-basis pins the textarea to its min-height and drops the height
   autoGrowPrompt() sets, so the box never grew past one line (#852). Here the
   height comes from the content. */
#composer #prompt { flex: 0 0 auto; }
/* #852: the message rests at ONE line and grows a line at a time (rows="1"; the
   30px min-height is one 21.75px line plus padding, and fitComposerHeight()
   never measures an empty box, so no captured two-line rest comes back). The
   placeholder stays on that one line and ends in an ellipsis in the narrow
   dashboard panel and on phones. */
#prompt:placeholder-shown { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#prompt::placeholder { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#composer .composer-bar { display: flex; align-items: center; gap: 8px; min-width: 0; }
#composer .composer-bar .composer-scope { flex: 1 1 auto; padding: 0; min-width: 0; }
#composer .composer-bar .composer-scope[hidden] { display: none; }
#composer .composer-bar .composer-toggle { margin-left: 0; padding-left: 0; border-left: 0; }
/* The first toggle after attach (and scope) takes the free space, so the pills
   and send sit at the right edge whatever is shown. */
#composer .composer-bar #chat-mode-toggle { margin-left: auto; padding: 5px 11px; font-size: 12.5px; }
#composer .composer-bar #chat-model-picker > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
#composer .composer-bar #chat-model-picker select {
  border-radius: 99px; border: 1px solid var(--line-soft); background: var(--card-surface); padding: 5px 10px; font-size: 12.5px;
}
#composer .composer-bar #send-btn { border-radius: 12px; width: 38px; height: 38px; background: var(--koro-black); border-color: var(--koro-black); color: #fff; }
#composer .composer-bar #send-btn:disabled { background: #e8e0d4; border-color: #e8e0d4; color: var(--muted); }
#composer-wrap .hint { display: flex; justify-content: center; align-items: center; gap: 10px; flex-wrap: wrap; text-align: center; font-size: 11.5px; }
#composer-wrap .hint .hint-text { color: var(--muted); }
@media (max-width: 767px) {
  /* 16px keeps iOS from zooming into the field (the earlier mobile #prompt rule
     loses to `#composer #prompt` above); 24px line + padding still fits the
     30px one-line rest. */
  #composer #prompt { font-size: 16px; }
  #composer .composer-bar #chat-mode-toggle { padding: 4px 8px; }
  #composer-wrap .hint .hint-text { display: none; }
}

/* ---- Answer card header: title over trust, actions at the right ------------- */
#chat .answer-card .ac-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
#chat .answer-card .ac-headline { display: grid; gap: 7px; min-width: 0; flex: 1 1 320px; }
#chat .answer-card .ac-title { font-size: 21px; }
#chat .answer-card .ac-sub { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
#chat .answer-card .ac-actions { display: flex; gap: 8px; align-items: center; flex: 0 0 auto; }
#chat .answer-card .ac-actions .btn { margin-left: 0; border-radius: 10px; background: var(--card-surface); border: 1px solid var(--line-soft); color: var(--text); font-size: 13px; padding: 6px 12px; display: inline-flex; align-items: center; gap: 6px; }
#chat .answer-card .ac-actions .btn:hover { background: #f8f3ec; }
#chat .answer-card .ac-export { position: relative; }
#chat .answer-card .ac-export-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 20; min-width: 230px; display: grid;
  background: var(--card-surface); border: 1px solid var(--line-soft); border-radius: 12px; padding: 6px;
  box-shadow: 0 8px 24px rgba(27,22,14,.12);
}
#chat .answer-card .ac-export-menu[hidden] { display: none; }
#chat .answer-card .ac-export-item { background: none; border: 0; text-align: left; font: inherit; font-size: 13px; padding: 8px 10px; border-radius: 8px; cursor: pointer; color: var(--text); }
#chat .answer-card .ac-export-item:hover, #chat .answer-card .ac-export-item:focus-visible { background: #f8f3ec; outline: none; }
#chat .answer-card .ac-export-note { font-size: 11.5px; color: var(--muted); padding: 6px 10px 4px; border-top: 1px solid var(--line-soft); margin-top: 4px; }
/* Footer: one quiet line. The metric's description is in Data & context; its
   caveat stays here (the governance envelope travels with the number). */
#chat .answer-card .ac-foot .metric-note > div:not(.metric-caveat) { display: none; }
#chat .answer-card .ac-foot .metric-note { margin-top: 6px; font-size: 12px; }
#chat .answer-card .ac-foot .metric-badge { font-size: 12px; }
/* The turn's action bar (PDF, feedback, review) sits flush under the card. */
#chat .content > .answer-card + .turn-actions,
#chat .content > .answer-card ~ .turn-actions { border-top: 0; padding-top: 0; }
/* …or, when the reply has an answer card, inside it as its last row. */
#chat .content > .answer-card > .turn-actions { border-top: 1px solid var(--line-soft); padding-top: 10px; align-items: center; flex-wrap: wrap; }
@media (max-width: 767px) {
  #chat .answer-card .ac-title { font-size: 18px; }
  #chat .answer-card .ac-actions { width: 100%; }
}

/* ---- Assistant avatar: the KoRo logo ------------------------------------------ */
.turn.assistant:not(.report-mode):not(.system-cmd)::before {
  content: ""; width: 34px; height: 34px;
  background: #fffdf9 url("/koro-logo.png") center / 24px auto no-repeat;
  border: 1px solid #ebe2d4; box-shadow: 0 1px 2px rgba(27,22,14,.06);
}

/* ---- Project sidebar: lighter section headers ---------------------------------- */
#sidebar .collapsible { border-left-color: transparent; }
#sidebar .block-header .block-label {
  font-size: 13px; text-transform: none; letter-spacing: 0; color: var(--text); font-weight: 600;
}
#sidebar .block-header .block-label #chats-project-name,
#sidebar .block-header .block-label span { color: var(--muted); font-weight: 400; }
#sidebar .block-header .btn.primary {
  background: var(--card-surface); color: var(--text); border: 1px solid var(--line-soft);
  font-weight: 500; font-size: 12px; padding: 3px 9px; box-shadow: none;
}
#sidebar .block-header .btn.primary:hover { background: #f8f3ec; border-color: #dccfbb; }
/* The welcome replaces the old governed one (whose class it keeps for the
   governed mode): left-aligned like the mockup, not centred. */
#chat .chat-welcome, #chat .chat-welcome.governed-welcome { text-align: left; align-items: stretch; max-width: 1100px; }
#chat .chat-welcome .cw-sub { margin-left: 0; margin-right: 0; }
/* Buttons don't inherit the page font: the question card's rows set it. */
#chat .answer-chips .answer-chip,
#chat .answer-chips .answer-opt-trigger,
#chat .answer-chips .answer-opt-input { font-family: var(--sans); }
#chat .chat-welcome .cw-card-title { overflow-wrap: anywhere; }

/* ---- Second opinion inside the answer card (syncAnswerReview) ---------------- */
/* The verdict: a second pill next to the trust pill; a click opens the review. */
.ac-review-pill {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 99px;
  font-family: var(--sans); font-size: 12px; font-weight: 600; white-space: nowrap; cursor: pointer;
  border: 1px solid var(--line-soft); background: #fff; color: #5d5245; line-height: 1.3;
}
.ac-review-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-review-pill .rv-ico {
  width: 14px; height: 14px; border-radius: 99px; display: inline-grid; place-items: center;
  font-size: 9px; color: #fff; flex: 0 0 auto; box-sizing: border-box;
}
.ac-review-pill[data-state="pending"] .rv-ico { border: 2px solid var(--line-soft); border-top-color: var(--muted); animation: ts-spin .9s linear infinite; }
.ac-review-pill[data-state="ok"] { color: var(--trust-ok-ink); }
.ac-review-pill[data-state="ok"] .rv-ico { background: var(--ink-green); }
.ac-review-pill[data-state="ok"] .rv-ico::before { content: "✓"; }
.ac-review-pill[data-state="concerns"] .rv-ico::before,
.ac-review-pill[data-state="critical"] .rv-ico::before,
.ac-review-pill[data-state="superseded"] .rv-ico::before { content: "!"; }
.ac-review-pill[data-state="concerns"] { color: var(--trust-warn-ink); background: var(--trust-warn-bg); border-color: rgba(237,149,0,.35); }
.ac-review-pill[data-state="concerns"] .rv-ico { background: var(--ink-orange); }
.ac-review-pill[data-state="critical"],
.ac-review-pill[data-state="superseded"] { color: #8f1238; background: #fbe4ea; border-color: rgba(202,30,79,.35); }
.ac-review-pill[data-state="critical"] .rv-ico,
.ac-review-pill[data-state="superseded"] .rv-ico { background: var(--ink-red); }
.ac-review-pill[data-state="error"] .rv-ico,
.ac-review-pill[data-state="skipped"] .rv-ico { display: none; }
/* The review: the card's last section, in the card's own type and rhythm. */
#chat .answer-card > .validation-card.ac-review {
  margin: 0; border: 0; border-top: 1px solid var(--line-soft); border-radius: 0; background: none;
  padding: 10px 0 0; font-family: var(--sans); font-size: 13px; opacity: 1; box-shadow: none;
}
#chat .answer-card > .validation-card.ac-review > .validation-summary {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; column-gap: 10px; row-gap: 1px;
  align-items: center; list-style: none; padding: 0; font-family: var(--sans); font-size: 13px;
}
#chat .answer-card > .validation-card.ac-review > .validation-summary::-webkit-details-marker { display: none; }
#chat .answer-card > .validation-card.ac-review > .validation-summary::before {
  content: ""; grid-row: 1 / span 2; width: 22px; height: 22px; border-radius: 99px; box-sizing: border-box;
  display: grid; place-items: center; color: #fff; font-size: 11px; font-weight: 700;
}
#chat .answer-card > .ac-review[data-review-state="ok"] > .validation-summary::before { content: "✓"; background: var(--ink-green); }
#chat .answer-card > .ac-review[data-review-state="concerns"] > .validation-summary::before { content: "!"; background: var(--ink-orange); }
#chat .answer-card > .ac-review[data-review-state="critical"] > .validation-summary::before { content: "!"; background: var(--ink-red); }
#chat .answer-card > .ac-review[data-review-state="pending"] > .validation-summary::before {
  width: 18px; height: 18px; margin: 2px; border: 2px solid var(--line-soft); border-top-color: var(--muted); animation: ts-spin .9s linear infinite;
}
#chat .answer-card > .ac-review[data-review-state="error"] > .validation-summary::before,
#chat .answer-card > .ac-review[data-review-state="skipped"] > .validation-summary::before { content: "–"; color: var(--muted); border: 1px solid var(--line-soft); }
#chat .answer-card > .ac-review > .validation-summary::after { display: none; }
#chat .answer-card > .ac-review .validation-icon,
#chat .answer-card > .ac-review .validation-spinner { display: none; }
#chat .answer-card > .ac-review .validation-summary > strong { grid-column: 2; font-weight: 600; }
#chat .answer-card > .ac-review .validation-summary > .validation-meta,
#chat .answer-card > .ac-review .validation-summary > .muted { grid-column: 3; grid-row: 1; color: var(--muted); font-size: 12px; white-space: nowrap; }
#chat .answer-card > .ac-review .rv-sum {
  grid-column: 2 / span 2; color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#chat .answer-card > .ac-review[open] .rv-sum { white-space: normal; }
/* The summary line carries the reviewer's text: the body doesn't repeat it. */
#chat .answer-card > .ac-review .validation-body > .validation-text { display: none; }
#chat .answer-card > .ac-review .validation-body { margin: 8px 0 0 32px; padding: 0; border: 0; }
#chat .answer-card > .ac-review[data-review-state="critical"] {
  background: #fdf3f5; margin: 0 -18px -16px; padding: 10px 18px 14px; border-radius: 0 0 16px 16px;
}
#chat .answer-card .rv-revising { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 8px; }
#chat .answer-card .rv-revising-text { font-weight: 600; color: #8f1238; }
#chat .answer-card .rv-jump { border: 0; background: none; padding: 0; color: var(--accent); font: inherit; font-size: 12.5px; text-decoration: underline; cursor: pointer; }
#chat .answer-card .rv-jump:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
@media (max-width: 767px) {
  #chat .answer-card > .ac-review[data-review-state="critical"] { margin: 0 -12px -14px; padding: 10px 12px 12px; border-radius: 0 0 14px 14px; }
  #chat .answer-card > .ac-review .validation-body { margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .ac-review-pill[data-state="pending"] .rv-ico,
  #chat .answer-card > .ac-review[data-review-state="pending"] > .validation-summary::before { animation: none; }
}
#chat .answer-card > .ac-review[data-review-state="skipped"] .validation-summary > .validation-meta { grid-column: 2; }

/* ---- Tables inside a chat answer --------------------------------------------- */
/* The answer card's own rhythm: no cell grid, quiet uppercase headers, a hairline
   between rows, numbers right-aligned in tabular figures. Dashboards keep theirs. */
#chat .answer-card table.dashboard-table,
#chat .answer-card .text.md table {
  border: 0; border-collapse: separate; border-spacing: 0; width: 100%; font-size: 13px; font-family: var(--sans);
}
#chat .answer-card table.dashboard-table thead { position: static; background: none; }
#chat .answer-card table.dashboard-table th,
#chat .answer-card .text.md table th {
  border: 0; border-bottom: 1px solid var(--line-soft); background: none; padding: 7px 12px 7px 0;
  color: var(--muted); font-size: 11.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap;
}
#chat .answer-card table.dashboard-table td,
#chat .answer-card .text.md table td {
  border: 0; border-bottom: 1px solid var(--line-soft); background: none; padding: 9px 12px 9px 0; color: var(--text);
}
#chat .answer-card table.dashboard-table th:last-child,
#chat .answer-card table.dashboard-table td:last-child,
#chat .answer-card .text.md table th:last-child,
#chat .answer-card .text.md table td:last-child { padding-right: 0; }
#chat .answer-card table.dashboard-table tbody tr:last-child td,
#chat .answer-card .text.md table tbody tr:last-child td { border-bottom: 0; }
#chat .answer-card table.dashboard-table tr:hover td,
#chat .answer-card .text.md table tbody tr:hover td { background: rgba(0, 150, 191, 0.04); }
#chat .answer-card table.dashboard-table td.num { font-variant-numeric: tabular-nums; }
#chat .answer-card table.dashboard-table th.num { text-align: right; }
#chat .answer-card table.dashboard-table th.sortable:hover { background: none; color: var(--text); }
#chat .answer-card .table-card-wrap { border: 0; border-radius: 0; }

/* flag_data_issue / get_table_schema rows in the progress card: the issue in
   counts and columns, and a table's known issues under its schema. */
#chat details.tool .dq-desc { margin: 0 0 4px; overflow-wrap: anywhere; }
#chat details.tool .dq-known-title { font-weight: 600; font-size: 12px; margin: 8px 0 2px; }
#chat details.tool .dq-known { margin: 0 0 8px; padding-left: 18px; font-size: 12px; display: flex; flex-direction: column; gap: 2px; }
