:root {
  --bg: #f7f7f8;
  --surface: #ffffff;
  /* §3 new: sidebar and right panel get a subtle tint distinct from the
     center chat column, so the three-column structure reads at a glance
     (same pattern as Claude/ChatGPT/Gemini) — a structural cue only. */
  --sidebar-bg: #efeff2;
  --panel-bg: #efeff2;
  --border: #e2e2e6;
  --text: #1a1a1e;
  --text-muted: #6b6b76;
  /* Brand (KotiOne): violet -> blue, matching the logo's gradient. --accent
     is the single solid color used everywhere -- sidebar, right panel,
     chat bubbles, borders/links/focus states/buttons/active rows -- all
     read the exact same var(--accent), no per-area shade or tint.
     Direct feedback went through two intermediate states before landing
     here (#6d4fe0 site-wide, then a #7c5cff revert + a separate lighter
     --accent-soft tint just for .bubble-user) -- both retired in favor
     of one flat rule: every surface, including chat, uses this one
     color, full stop. */
  --accent: #876afe;
  --accent-contrast: #ffffff;
  --accent-gradient: linear-gradient(135deg, #876afe 0%, #6d5cf6 35%, #3b82f6 70%, #38bdf8 100%);
  /* Accessibility fix (WCAG 2.2 AA audit) — --accent itself is ~4.1-4.4:1
     against light backgrounds, just under the 4.5:1 text-contrast
     requirement (WCAG 1.4.3); it's fine for borders/icons/backgrounds
     (non-text 3:1 threshold) but not for body text/links. This darker
     variant is for TEXT/link color specifically; --accent already passes
     comfortably in dark mode (~6:1+) so this just aliases back to it there. */
  --accent-text: #6f4aea;
  --tension: #b45309;
  --risk-high: #dc2626;
  --risk-medium: #d97706;
  --risk-low: #65a30d;
  --sev-high-bg: #fee2e2; --sev-high-text: #991b1b;
  --sev-medium-bg: #fef3c7; --sev-medium-text: #92400e;
  --sev-low-bg: #dcfce7; --sev-low-text: #166534;
  /* "Research notebook" restyle (2026-09-22) — Notes/Lines panel tokens.
     --paper/--paper-raised deliberately NOT added: they're the exact same
     concepts as --bg/--surface above (page ground / raised card surface),
     just the mockup's own naming — reusing --bg/--surface keeps one
     vocabulary instead of two names for the same thing. --ink-faint/--rule/
     --rule-soft ARE genuinely new (no third muted-text tier or two-tier
     hairline-border system existed before this), scoped to this panel
     rather than repointing the app-wide --text-muted/--border used
     throughout unrelated components. Evidence/hypothesis/risk are
     semantic-only (status, never category) and deliberately their own
     tokens rather than reusing --sev-* / --risk-* above — those are a
     severity scale (low/medium/high); these are a different axis
     (validated vs. assumed vs. open-risk) that only happens to share the
     visual family, not the meaning. */
  --ink-faint: #9a9ea5;
  --rule: #dcdfe3;
  --rule-soft: #e8eaed;
  --evidence: #2f7d5a; --evidence-soft: #e2ede8;
  --hypothesis: #93722a; --hypothesis-soft: #f0ebda;
  --risk-notebook: #a8452f; --risk-notebook-soft: #f4e4dd;
  /* Icon/row hover fill. Deliberately a translucent overlay, NOT
     var(--bg): --bg (#f7f7f8) is *lighter* than the sidebar (#efeff2)
     and all but identical to --surface (#ffffff), so "background:
     var(--bg)" on a sidebar icon or a white card read as no hover at
     all. A translucent black darkens whatever it sits on, so one value
     works on every surface in the app. */
  --hover-fill: rgba(0, 0, 0, 0.07);
  --hover-fill-strong: rgba(0, 0, 0, 0.11);
  /* Custom tooltip chip (below) — a fixed dark chip on both themes,
     deliberately not tied to --surface/--text: a tooltip needs to read
     the same regardless of what it's floating over, the way Claude's own
     tooltips do. */
  --tooltip-bg: #232329;
  --tooltip-text: #f2f2f5;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16161a;
    --surface: #1f1f24;
    --sidebar-bg: #1b1b20;
    --panel-bg: #1b1b20;
    --border: #303038;
    --text: #ececef;
    --text-muted: #9a9aa5;
    --accent: #a78bfa;
    --accent-contrast: #16161a;
    --accent-gradient: linear-gradient(135deg, #a78bfa 0%, #8b7bf6 35%, #60a5fa 70%, #38bdf8 100%);
    --accent-text: var(--accent);
    /* Inverted for dark mode — a translucent *white* lifts a dark
       surface the same way the black overlay darkens a light one. */
    --hover-fill: rgba(255, 255, 255, 0.09);
    --hover-fill-strong: rgba(255, 255, 255, 0.14);
    --ink-faint: #6c6f76;
    --rule: #34363b;
    --rule-soft: #2a2c30;
    --evidence: #7fb896; --evidence-soft: #20291f;
    --hypothesis: #d4ab5c; --hypothesis-soft: #2c2517;
    --risk-notebook: #e08a71; --risk-notebook-soft: #2e211b;
  }
}

/* Custom tooltip (replaces the native title="" browser tooltip, which reads
   as an OS-default popup — no hover-intent delay, wrong font, square
   corners — rather than something that belongs to this app). One shared
   floating element (#app-tooltip in index.html), positioned in JS per
   target on hover (app.js) rather than a per-button CSS ::after — a
   pseudo-element would get silently clipped by #sidebar's own
   overflow:hidden (needed for the collapse-width transition) whenever a
   sidebar icon's tooltip needs to extend past the collapsed 52px rail.
   Every icon-only control whose label isn't otherwise visible sets
   data-tooltip instead of title; i18n.js's applyStaticTranslations()
   writes data-tooltip for every [data-i18n-title] element. aria-label is
   set separately and unaffected — this is visual-only, screen readers are
   unaffected. */
.app-tooltip {
  position: fixed;
  z-index: 500;
  background: var(--tooltip-bg);
  color: var(--tooltip-text);
  font-size: 0.72rem;
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
  padding: 4px 8px;
  border-radius: 5px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.1s ease;
}
.app-tooltip.visible { opacity: 1; }

/* GDPR cookie consent banner — found live (direct feedback): at bottom:
   16px this physically overlapped #chat-form (a normal-flow element
   pinned to the bottom of the viewport-height chat column, not itself
   fixed), so a brand-new visitor's first click/keystrokes into the
   composer were silently swallowed by the banner sitting on top of it
   instead of reaching the textarea -- looked like "nothing responds to
   typing" with no visible cause. 92px clears #chat-form's default
   single-line height (14px+14px padding + ~36px textarea row) plus
   margin; the composer can auto-grow taller than that (up to
   CHAT_INPUT_MAX_PX) while actively typing multiple lines, but by then
   the user has already successfully interacted with the input, so this
   only needs to clear the untouched, single-line starting state. */
#cookie-banner {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: 92px;
  max-width: 520px;
  z-index: 400;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.16);
}
#cookie-banner p { font-size: 0.8rem; line-height: 1.45; color: var(--text); margin: 0 0 10px; }
#cookie-banner a { color: var(--accent-text); }
#cookie-banner-actions { display: flex; gap: 8px; justify-content: flex-end; }
#cookie-banner-actions button { padding: 7px 14px; border-radius: 6px; font-size: 0.8rem; }
#cookie-accept-btn { background: var(--accent-gradient); color: #fff; border-color: transparent; font-weight: 600; }

* { box-sizing: border-box; }

/* An author rule like `.modal { display: flex }` otherwise beats the
   browser's built-in `[hidden] { display: none }` default, since UA rules
   lose to author rules regardless of the hidden attribute being set. Keeps
   every hidden-toggled element (modal, columns, cards) actually hidden,
   not just attribute-marked. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden; /* the page itself never scrolls — only the chat
    messages, right panel, and sidebar list scroll internally, so the
    header and chat input never move */
}
body {
  /* "Research notebook" restyle (2026-09-22) — Work Sans as the one UI/
     body sans everywhere (was the bare system-font stack). Rubik stays in
     the fallback chain, not for its own look, but because it's this app's
     established Hebrew-safe font — Work Sans has no Hebrew glyph coverage
     at all, and this app renders real Hebrew chat/note content (see
     unicode-bidi:plaintext usage throughout) that must never silently
     fall through to a mismatched system font. Same precedent the old
     .wb-cluster-title comment already established for Permanent Marker. */
  font-family: "Work Sans", "Rubik", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
}

button {
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 0.85rem;
}
button:hover { border-color: var(--accent); }

/* ---------- Focus ring (polish batch 3) ----------
   Global override, not element-by-element — the browser default (a
   black rectangle on most engines) showed on every focusable element
   (inputs, buttons, links), not just the couple that got noticed first.
   :focus-visible means a mouse click doesn't show it (matches how
   Claude/ChatGPT/etc. behave) while keyboard navigation still gets a
   clear, brand-colored ring — suppressing it entirely would break
   keyboard accessibility. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- Thin, hover-only scrollbars (polish batch 3) ----------
   Applied to every scrollable region (sidebar list, chat, right panel) —
   invisible at rest, a slim brand-neutral thumb only while hovering/
   scrolling, matching Claude/ChatGPT/Gemini rather than the browser's
   default thick, always-visible gray bar. */
#sidebar-scroll, #chat-messages, #panel-scroll {
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
#sidebar-scroll:hover, #chat-messages:hover, #panel-scroll:hover {
  scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
}
#sidebar-scroll::-webkit-scrollbar, #chat-messages::-webkit-scrollbar, #panel-scroll::-webkit-scrollbar {
  width: 6px;
}
/* The up/down arrow buttons some engines render at the scrollbar's ends
   by default — Claude/ChatGPT/Gemini's scrollbars don't have these
   either, just a bare thumb. */
#sidebar-scroll::-webkit-scrollbar-button, #chat-messages::-webkit-scrollbar-button, #panel-scroll::-webkit-scrollbar-button {
  display: none;
}
/* The track itself, not just the thumb — without this it falls back to
   the browser's default track background (a visible light-gray groove
   even with a transparent thumb on top of it), which is what was still
   showing. */
#sidebar-scroll::-webkit-scrollbar-track, #chat-messages::-webkit-scrollbar-track, #panel-scroll::-webkit-scrollbar-track {
  background: transparent;
}
#sidebar-scroll::-webkit-scrollbar-thumb, #chat-messages::-webkit-scrollbar-thumb, #panel-scroll::-webkit-scrollbar-thumb {
  background: transparent;
  border-radius: 999px;
}
#sidebar-scroll:hover::-webkit-scrollbar-thumb, #chat-messages:hover::-webkit-scrollbar-thumb, #panel-scroll:hover::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.25);
}
@media (prefers-color-scheme: dark) {
  #sidebar-scroll:hover, #chat-messages:hover, #panel-scroll:hover { scrollbar-color: rgba(255, 255, 255, 0.25) transparent; }
  #sidebar-scroll:hover::-webkit-scrollbar-thumb, #chat-messages:hover::-webkit-scrollbar-thumb, #panel-scroll:hover::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.25);
  }
}

/* ---------- App shell: sidebar + body ---------- */

#app-shell {
  display: flex;
  height: 100vh;
  height: 100dvh; /* mobile browsers compute 100vh including the address
    bar, so real content can overflow that and drag the whole page (header
    included) into a page-level scroll despite `overflow:hidden` above —
    100dvh tracks the actual visible viewport. Kept as a second
    declaration (not a replacement) so browsers without dvh support keep
    the 100vh fallback. */
}

#sidebar {
  width: 220px;
  flex-shrink: 0;
  background: var(--sidebar-bg);
  display: flex;
  flex-direction: column;
  transition: width 0.15s ease;
  overflow: hidden;
}
#sidebar.collapsed { width: 52px; }

/* ---------- Sidebar logo (was the full-width header's logo) ----------
   Full wordmark when expanded, just the icon mark when collapsed — same
   element toggle pattern as .sidebar-new-btn's label below, driven by
   the sidebar's own existing collapse state, not a separate control. */
#sidebar-logo { display: flex; align-items: center; justify-content: space-between; padding: 14px 8px 2px 14px; }
.sidebar-logo-full { height: 27px; width: auto; display: block; }
.sidebar-logo-icon { height: 28px; width: 28px; border-radius: 7px; display: none; }
/* width: fit-content + margin: 0 auto — without this, #sidebar-logo stays
   the full 52px row width (it's a block-flow nav child, not sized to its
   own content), so the hover rect below would span the entire row instead
   of snugly hugging the 28px icon like every other icon-button hover in
   this sidebar (#sidebar-toggle, #report-btn). */
#sidebar.collapsed #sidebar-logo { justify-content: center; padding: 8px; width: fit-content; margin: 12px auto 2px; }
#sidebar.collapsed .sidebar-logo-full { display: none; }
#sidebar.collapsed .sidebar-logo-icon { display: block; }
/* Collapsed: the separate toggle button goes away (below) and the logo
   itself becomes the click target to expand back open — same pattern as
   Claude/Gemini's collapsed rail. Expanded state is unaffected: the logo
   isn't a click target there, since the dedicated toggle button already
   handles collapsing. */
#sidebar.collapsed #sidebar-logo { cursor: pointer; }
/* Rounded hover background (polish pass) — matches the site's own hover
   convention for clickable rows (.account-row:hover, .sidebar-new-btn:hover
   both use this same var(--bg) treatment) instead of relying on the
   brightness filter alone to signal "this is clickable". */
#sidebar.collapsed #sidebar-logo:hover { background: var(--hover-fill); border-radius: 7px; }
#sidebar.collapsed #sidebar-logo:hover .sidebar-logo-icon { filter: brightness(1.12); }

#sidebar-top {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Tightened top padding (polish batch 4) — the logo row above and
     this "New Project" button should read as one compact header area,
     not two separately-spaced blocks. The collapse toggle itself now
     lives in the logo row, same line as the wordmark, not here. */
  padding: 4px 8px 10px;
  border-bottom: 1px solid var(--border);
}
#sidebar-toggle {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  padding: 6px;
  border-radius: 7px;
  color: var(--text-muted);
}
#sidebar-toggle:hover { background: var(--hover-fill); color: var(--accent); }
/* Collapsed: hidden entirely (the logo takes over as the expand control,
   see #sidebar-logo above) rather than kept around rotated. */
#sidebar.collapsed #sidebar-toggle { display: none; }

/* Quiet utility button, not a marketing CTA (polish pass) — matches the
   "New chat" convention in Claude/ChatGPT rather than being the most
   visually loud/saturated element on screen. */
.sidebar-new-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  border: none;
  background: transparent;
  color: var(--text);
  width: 100%;
  justify-content: flex-start;
  font-weight: 500;
  padding: 7px 10px;
  border-radius: 6px;
  flex: 1;
}
.sidebar-new-btn:hover { color: var(--accent); background: var(--bg); }
.sidebar-new-btn .icon { font-weight: 700; color: inherit; flex-shrink: 0; }
#sidebar.collapsed .sidebar-new-btn { justify-content: center; padding: 7px; }
#sidebar.collapsed .sidebar-new-btn .label { display: none; }

/* Scroll container for the whole sidebar list area — product groups
   (#product-groups) stacked above the ungrouped chat list (#project-list),
   one shared scroll region rather than two independently-scrolling ones.
   #project-list itself is now a plain list (no longer its own flex/scroll
   child) — see .project-row below, shared by both locations. */
#sidebar-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#sidebar.collapsed #sidebar-scroll { display: none; }
#project-list { list-style: none; margin: 0; padding: 0; }
#product-groups { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }

/* Shared row style — an ungrouped chat in #project-list, or an idea chat
   nested under a product group; identical treatment either way so
   grouping is purely organizational, not a different visual tier. */
.project-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 2px;
  padding: 7px 6px 7px 10px;
  border-radius: 6px;
  cursor: pointer;
  margin-bottom: 2px;
  overflow: hidden;
}
.project-row:hover { background: var(--hover-fill); }
.project-row.active { background: var(--accent); }
.project-row-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
}
/* Three-dot menu button (polish pass) — hidden until the row is hovered/
   focused or its own menu is open, matching Claude's own chat-history
   hover-reveal convention rather than a permanently-visible control on
   every row.
   Bug fix (direct feedback, 2026-09-25) — this used to toggle `display`
   (none <-> flex), which removes the button from layout entirely while
   hidden, so its neighbor (.project-row-label / .product-group-name,
   both flex:1 with an ellipsis) got MORE width when the button was
   hidden and LESS the instant it appeared, causing a visible jump/reflow
   on every hover. `visibility` keeps the button's box in the layout at
   all times (space always reserved) and only toggles its own paint +
   interactivity — nothing else on the row shifts. */
.project-row-menu-btn {
  flex-shrink: 0;
  border: none;
  background: none;
  padding: 3px;
  border-radius: 5px;
  color: var(--text-muted);
  display: flex;
  visibility: hidden;
  align-items: center;
  justify-content: center;
}
.project-row:hover .project-row-menu-btn,
.project-row-menu-btn.menu-open { visibility: visible; }
.project-row-menu-btn:hover { background: rgba(0, 0, 0, 0.08); color: var(--text); border-color: transparent; }
.project-row.active .project-row-menu-btn { color: var(--accent-contrast); }
.project-row.active .project-row-menu-btn:hover { background: rgba(255, 255, 255, 0.2); color: var(--accent-contrast); }
/* Width follows content, so the one-item initial step is a compact
   "Delete" chip rather than a 220px-wide bar with a hover fill stretching
   far past its own label. Only the confirmation step (which holds a
   sentence) needs the wider box — see its own rule below. */
.project-row-menu {
  position: fixed;
  z-index: 60;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  width: max-content;
}
/* Delete itself stays red (a genuinely destructive action) — but the
   in-app confirmation step's Cancel button (and this popover's other
   controls, if any get added later) should not be, per direct feedback
   that non-destructive controls shouldn't borrow the delete color. */
.project-row-menu button {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  padding: 7px 10px;
  border-radius: 6px;
  font-size: 0.8rem;
  color: var(--text);
}
.project-row-menu button:hover { background: var(--hover-fill); border-color: transparent; }
#project-row-delete-btn,
#project-row-confirm-delete-btn { color: #dc2626; }
#project-row-menu-step-confirm { width: 210px; }
.project-row-menu-confirm-text { font-size: 0.78rem; color: var(--text); line-height: 1.35; margin: 4px 6px 8px; }
.project-row-menu-confirm-actions { display: flex; gap: 4px; }
.project-row-menu-confirm-actions button { flex: 1; text-align: center; }
/* UI/UX audit follow-up (2026-10-08) — project/product delete used to
   fail with zero feedback; same red-text convention as #product-modal-error
   and #delete-account-error, empty when there's nothing to show. */
.row-menu-error { color: #dc2626; font-size: 0.74rem; margin: 2px 6px 0; }

/* Product picker (GCP-style "move to project" submenu) — wider than the
   plain initial step since it holds product names, and scrolls instead of
   growing unbounded once someone has a lot of products. */
#project-row-menu-step-product { width: 220px; }
.project-row-product-list { max-height: 220px; overflow-y: auto; }
.project-row-product-list button { display: flex; align-items: center; gap: 6px; }
.project-row-product-check { width: 14px; flex: none; color: var(--accent); }
.project-row-product-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.project-row-product-empty { font-size: 0.78rem; color: var(--text-muted); padding: 7px 10px; line-height: 1.35; }
#project-row-product-back-btn { color: var(--text-muted); border-top: 1px solid var(--border); margin-top: 2px; padding-top: 9px; }
.project-row-label {
  font-size: 0.82rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  unicode-bidi: plaintext;
}
.project-row-status {
  font-size: 0.68rem;
  color: var(--text-muted);
  opacity: 0.75;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.project-row:hover .project-row-label,
.project-row:hover .project-row-status { color: var(--text); }
.project-row.active .project-row-label,
.project-row.active .project-row-status { color: var(--accent-contrast); opacity: 1; }

/* ---------- Product groups (sidebar) ----------
   A collapsible group per Product workspace, its idea chats nested
   beneath. Direct feedback — this used to share .project-row's exact
   visual weight ("purely organizational, not a different visual tier"),
   which made a Product workspace indistinguishable from an ordinary
   chat at a glance. Now reads as its own container (card surface, own
   border, folder icon) so a Product and the plain chats under
   "Recents" below are visually two different kinds of thing, closer to
   Claude.ai's own Projects/Recents split. */
.product-group { margin-bottom: 6px; }
.product-group-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 6px 7px 8px;
  border-radius: 6px;
  cursor: pointer;
}
.product-group-header:hover { background: var(--hover-fill); }
.product-group-icon { flex-shrink: 0; display: flex; color: var(--accent-text); }
.product-group-header:hover .project-row-menu-btn,
.product-group-header:hover .product-new-idea-btn { visibility: visible; }
/* "+ new idea in this product" (direct feedback, 2026-09-25) — now
   hover-revealed like the three-dot options menu beside it, matching
   the plain chat rows' own hover-reveal convention (.project-row-menu-btn
   above) instead of sitting permanently visible. This does mean it's
   undiscoverable via hover alone on a touch device — same tradeoff the
   three-dot menu already accepted; direct feedback preferred consistency
   with "Recent Chats" row behavior over that edge case.
   `visibility` (not `display`), same bug-fix reasoning as
   .project-row-menu-btn above — reserves this button's space in the
   layout at all times so .product-group-name (flex:1, ellipsis) never
   resizes/jumps on hover. */
.product-new-idea-btn {
  flex-shrink: 0;
  border: none;
  background: none;
  padding: 5px;
  border-radius: 5px;
  color: var(--text-muted);
  display: flex;
  visibility: hidden;
  align-items: center;
  justify-content: center;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1;
}
.product-new-idea-btn:hover { background: rgba(0, 0, 0, 0.08); color: var(--accent); border-color: transparent; }
.product-group-chevron {
  flex-shrink: 0;
  display: flex;
  transition: transform 0.15s ease;
  color: var(--text-muted);
}
.product-group.collapsed .product-group-chevron { transform: rotate(-90deg); }
/* Direct feedback (2026-09-25) — matches .sidebar-section-label's look
   ("RECENT CHATS" above the plain chat list) so a Product's own name
   reads as the same KIND of thing (a section title), not just a bigger
   chat-row label sitting above its own nested rows. */
.product-group-name {
  flex: 1;
  min-width: 0;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.product-group-count { flex-shrink: 0; font-size: 0.68rem; color: var(--text-muted); }
/* Which file(s) (if any) are actually grounding this product's chats —
   easy to lose track of once you have more than one product, since only
   the three-dot menu's "Add spec file(s)" mentions it otherwise. A
   product can hold several now (app.models.ProductSpecFile) — one row
   each, not a single line, each with its own remove control. */
.product-group-spec {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0 6px 5px 34px;
  font-size: 0.68rem;
  color: var(--text-muted);
}
.product-group-spec-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
/* UI/UX audit follow-up (2026-10-08) — removing a spec file used to fail
   with zero feedback beyond the button re-enabling. flex-basis: 100% on
   the row above wraps this onto its own line under the file it belongs
   to, rather than squeezing into the icon/name/button row. */
.product-group-spec-remove-error { flex-basis: 100%; color: #dc2626; font-size: 0.68rem; padding-left: 20px; }
/* UI/UX audit follow-up (2026-10-12) — the sibling "replace spec" alert()
   fix; same red-text convention, own line under the spec file list. */
.product-group-replace-error { color: #dc2626; font-size: 0.68rem; padding: 2px 6px 5px 34px; }
.product-group.collapsed .product-group-spec { display: none; }
#sidebar.collapsed .product-group-spec { display: none; }
.product-group-spec-icon { flex-shrink: 0; display: flex; }
.product-group-spec-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.product-group-spec-empty { font-style: italic; }
.product-group-spec-truncated {
  flex-shrink: 0;
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  color: var(--risk-medium);
  border: 1px solid var(--risk-medium);
  border-radius: 4px;
  padding: 0 4px;
}
.product-group-spec-remove {
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 4px;
  cursor: pointer;
  opacity: 0;
}
.product-group-spec-row:hover .product-group-spec-remove,
.product-group-spec-remove:focus-visible { opacity: 1; }
.product-group-spec-remove:hover { background: var(--hover-fill); color: var(--risk-high); }
.product-group-spec-remove:disabled { opacity: 0.4; cursor: default; }
.product-group-projects {
  list-style: none;
  margin: 0;
  padding: 2px 2px 2px 14px;
  border-left: 1px solid var(--border);
  margin-left: 12px;
}
.product-group.collapsed .product-group-projects { display: none; }
.product-group-empty { padding: 4px 0 4px 16px; font-size: 0.74rem; color: var(--text-muted); font-style: italic; }
.sidebar-signed-out-hint { list-style: none; padding: 10px 16px; font-size: 0.78rem; color: var(--text-muted); font-style: italic; }
#sidebar.collapsed .product-group-name,
#sidebar.collapsed .product-group-count { display: none; }
/* "Recents" section label (direct feedback) — only rendered/shown when a
   Product group exists above (see loadProjectList), so it never appears
   as a lone, redundant header over the only list on screen. */
.sidebar-section-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  padding: 12px 10px 4px;
}
#sidebar.collapsed .sidebar-section-label { display: none; }
/* "PRODUCT CHATS" section label (direct feedback, 2026-09-26) — replaces
   the old standalone, always-visible "Product" button. Unlike
   .sidebar-section-label above (only a plain text row), this one is a
   flex row so its own "+" (creates a new product) can sit hover-revealed
   on the same line, same convention as each product group's own header.
   Deliberately NOT given `hidden` like #recents-label -- this is the
   only entry point into creating a product at all, so it must always be
   visible regardless of whether any products exist yet. */
.sidebar-section-label-action {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding-right: 6px;
}
#sidebar-new-product {
  flex-shrink: 0;
  border: none;
  background: none;
  padding: 4px;
  border-radius: 5px;
  color: var(--text-muted);
  display: flex;
  visibility: hidden;
  align-items: center;
  justify-content: center;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
/* `visibility`, not `display` -- same hover-jump fix as
   .project-row-menu-btn/.product-new-idea-btn above: keeps this button's
   space reserved in the layout at all times so the label text never
   shifts on hover. */
.sidebar-section-label-action:hover #sidebar-new-product { visibility: visible; }
#sidebar-new-product:hover { background: rgba(0, 0, 0, 0.08); color: var(--accent); }

/* ---------- Product creation modal ---------- */
#product-modal .field { margin-bottom: 14px; text-align: left; }
#product-modal label { display: block; font-size: 0.78rem; font-weight: 600; margin: 0 0 5px; }
#product-modal input[type="text"] {
  width: 100%;
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
}
#product-modal input[type="file"] { font-size: 0.78rem; color: var(--text); }
#product-modal .hint { font-size: 0.72rem; color: var(--text-muted); margin: 5px 0 0; }
#product-modal-error { color: #dc2626; font-size: 0.8rem; min-height: 1.1em; margin: 6px 0; }
#product-modal-create-btn {
  width: 100%;
  margin-top: 6px;
  border: none;
  background: var(--accent-gradient);
  color: #fff;
  font-weight: 600;
  padding: 10px;
}
#product-modal-create-btn:hover { filter: brightness(1.06); border-color: transparent; }
#product-modal-create-btn:disabled { opacity: 0.6; cursor: default; filter: none; }

/* ---------- Sidebar account menu (§23 new) ---------- */

#sidebar-account {
  border-top: 1px solid var(--border);
  padding: 8px;
  position: relative;
  /* Pins this to the bottom of the sidebar column regardless of whether
     #sidebar-scroll is actually present in the flex layout — when
     collapsed, #sidebar-scroll is display:none (removed from flex flow
     entirely, not just visually hidden), so without this the account
     row would ride up to sit right under the logo/toggle instead of at
     the bottom like Claude/ChatGPT's collapsed rail. */
  margin-top: auto;
}
.account-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  border: none;
  background: none;
  padding: 6px;
  text-align: left;
}
.account-row:hover { background: var(--hover-fill); border-radius: 6px; }
.account-row .avatar {
  width: 22px; height: 22px; flex-shrink: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  display: flex; align-items: center; justify-content: center;
  font-size: 0.7rem; font-weight: 700;
}
.account-row .label { font-size: 0.82rem; }
#sidebar.collapsed .account-row .label { display: none; }

/* position: fixed, positioned in JS (toggleAccountMenu in app.js) rather
   than CSS-anchored to the account row — #sidebar has overflow:hidden
   (needed for its own collapse-width transition), which would silently
   clip an absolutely-positioned popup extending past the sidebar's own
   box, especially in the collapsed (52px) state. Same fix as the
   floating tooltip (#app-tooltip) for the same underlying reason. */
#account-menu {
  position: fixed;
  width: 200px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
#account-menu-list { display: flex; flex-direction: column; }
#account-menu-list button, #account-menu-back {
  border: none;
  background: none;
  text-align: left;
  padding: 8px;
  font-size: 0.82rem;
  border-radius: 6px;
}
#account-menu-list button:hover, #account-menu-back:hover { background: var(--hover-fill); }
/* Separated from the other entries above it (matches the common
   account-menu convention of grouping sign-out below a divider). */
#account-menu-list button[data-account-action="logout"] {
  margin-top: 4px;
  padding-top: 9px;
  border-top: 1px solid var(--border);
}
#account-menu-detail-body { padding: 4px 8px 8px; font-size: 0.8rem; color: var(--text-muted); line-height: 1.4; }
#account-menu-detail-body p { margin: 0 0 6px; }
.account-plan-badge {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  padding: 2px 7px;
  border-radius: 4px;
  background: var(--hover-fill-strong);
  color: var(--text-muted);
}
.account-plan-badge.paid { background: var(--accent-gradient); color: #fff; }

/* GDPR delete-account — same in-app two-step confirm visual language as
   the project/product delete popovers (red action, neutral cancel). */
.settings-link { background: none; border: none; padding: 0; font-size: 0.78rem; color: var(--accent-text); cursor: pointer; text-decoration: underline; display: block; margin-bottom: 10px; }
.danger-link { background: none; border: none; padding: 0; font-size: 0.78rem; color: #dc2626; cursor: pointer; text-decoration: underline; }
.delete-account-warning { color: var(--text); }
.delete-account-actions { display: flex; gap: 6px; margin-top: 6px; }
.delete-account-actions button { flex: 1; text-align: center; padding: 6px 10px; border-radius: 6px; font-size: 0.78rem; }
.danger-btn { background: #dc2626; color: #fff; border-color: transparent; }
#delete-account-error { color: #dc2626; font-size: 0.76rem; margin-top: 6px; }

#app-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100vh;
  height: 100dvh; /* see #app-shell */
}

/* ---------- Chat column title bar ----------
   Replaces the old full-width app header — just the current project's
   title, scoped to the chat column, pinned above the scrolling messages
   (a plain flex-shrink:0 sibling above #chat-messages, the same "outside
   the scroll area" trick #chat-form already relied on — no need for
   position:sticky when the element already isn't inside the scroller). */
#chat-column-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  flex-shrink: 0;
}
#project-name-label {
  color: var(--text-muted);
  font-size: 0.85rem;
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.decision-buttons button[data-decision="GO"] { color: #16a34a; }
.decision-buttons button[data-decision="PIVOT"] { color: #d97706; }
.decision-buttons button[data-decision="KILL"] { color: #dc2626; }

/* #decision-modal's own content spacing — decision-recording lives in its
   own modal now, not appended after the report, so this no longer needs
   the old #report-decision-section separator (margin-top/border-top). */
#decision-modal h2 { margin: 0 0 10px; }
#decision-modal p { margin: 0 0 12px; font-size: 0.85rem; color: var(--text-muted); }
#decision-rationale-input {
  width: 100%;
  padding: 8px;
  margin-bottom: 8px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
/* Success confirmation (real user report, 2026-09-26) — see
   makeDecision()'s own comment: without this, a successful save had no
   visible feedback beyond a small label-text change above the buttons. */
.decision-saved-note {
  color: #16a34a;
  font-weight: 600;
  background: rgba(22, 163, 74, 0.1);
  border-radius: 6px;
  padding: 6px 10px;
}
/* UI/UX audit follow-up (2026-10-08) — the failure mirror of
   .decision-saved-note above: a failed save used to leave the modal
   with zero feedback beyond the buttons silently re-enabling. */
.decision-modal-error {
  color: #dc2626;
  font-weight: 600;
  background: rgba(220, 38, 38, 0.1);
  border-radius: 6px;
  padding: 6px 10px;
}
.decision-buttons { display: flex; gap: 8px; }
/* UX fix — recording a decision is a real network round-trip (and can
   take a few seconds on a cold-started Fly machine) with previously zero
   visual feedback: the buttons just sat there looking clickable, which
   read as broken and invited an accidental double-click. */
.decision-buttons button:disabled { opacity: 0.5; cursor: default; }

/* Always visible, and now a real <button> — reset borders/cursor
   accordingly rather than relying on .badge being a bare <span>. */
.badge {
  border: none;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.75rem;
}
/* The panel's own decision badge (#decision-badge) stays .badge-unset
   ALWAYS, whether or not a decision is recorded — see the comment on it
   in index.html for why. .badge-go/.badge-pivot/.badge-kill/.badge-
   continue below are used inside the decision modal instead, once you've
   actually opened it, not as a preview on the main panel. */
.badge-unset { background: rgba(0, 0, 0, 0.08); color: var(--text-muted); }
/* Small "a decision is on file" signal on the otherwise-neutral badge —
   deliberately just a dot, not the decision word/color itself. */
.decision-recorded-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  margin-left: 5px;
  vertical-align: middle;
}
.badge-go { background: var(--sev-low-bg); color: var(--sev-low-text); }
.badge-pivot { background: var(--sev-medium-bg); color: var(--sev-medium-text); }
.badge-kill { background: var(--sev-high-bg); color: var(--sev-high-text); }
.badge-continue { background: #e0e7ff; color: #3730a3; }

/* ---------- Main: chat + panel ---------- */

#app-main {
  flex: 1;
  display: grid;
  /* Modest widen (was 1.3fr 1fr) so 3 sticky-note columns fit in the panel
     at common laptop widths without shrinking notes back down — chat
     stays clearly the larger column, just less dominantly so. */
  grid-template-columns: 1.15fr 1fr;
  /* Without this, the single implicit row sizes to "auto" — i.e. to
     whichever column's content is tallest. Once the right panel populates
     with real whiteboard content (taller than the viewport), the row
     itself grows to fit it, and #chat-column (grid default
     align-items:stretch) gets stretched to match that same inflated row
     height, pushing #chat-form below the clipped viewport with no way to
     scroll back to it — same class of bug as the flex min-height:0 fix
     elsewhere, just the grid-track equivalent. minmax(0, 1fr) pins the
     row to the container's own height instead of the content's. */
  grid-template-rows: minmax(0, 1fr);
  overflow: hidden;
}

#chat-column {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}

#chat-messages {
  flex: 1;
  /* Without this, a flex item's default min-height is "auto" (its own
     content size), not 0 — on a long conversation, #chat-messages would
     refuse to shrink to the space actually available and instead grow to
     fit all its content, pushing #chat-form (and even #chat-column-
     header) below the viewport entirely, clipped by an ancestor's
     overflow:hidden with no way to scroll back to it. Classic flexbox
     pitfall; only shows up once a chat has enough history to hit it,
     which no short test conversation reproduces. */
  min-height: 0;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* §9 revised: Trust Loop chips render as a sibling of the bubble inside
   this wrapper, not as a separate panel card — the wrapper is what carries
   the alignment/max-width that used to live on .bubble alone. */
.bubble-wrapper {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 75%;
  animation: bubble-in 0.18s ease;
}
@keyframes bubble-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
.bubble-wrapper-user { align-self: flex-end; align-items: flex-end; }
.bubble-wrapper-assistant { align-self: flex-start; align-items: flex-start; }

.bubble {
  padding: 10px 14px;
  border-radius: 12px;
  white-space: pre-wrap;
  line-height: 1.4;
  /* Only the chat text itself is RTL-aware (each message's own content
     decides its direction) — the surrounding UI (sidebar, buttons,
     layout) stays LTR regardless of language, not a whole-page mirror. */
  unicode-bidi: plaintext;
}
.bubble-user {
  background: var(--accent);
  color: var(--accent-contrast);
}
.bubble-assistant {
  background: var(--surface);
  border: 1px solid var(--border);
}

/* §34 — inline thinking placeholder, swapped for the real reply on arrival. */
.bubble-thinking { display: flex; align-items: center; gap: 4px; padding: 12px 14px; }
.thinking-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-muted);
  animation: thinking-bounce 1.2s infinite ease-in-out;
}
.thinking-dot:nth-child(2) { animation-delay: 0.15s; }
.thinking-dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes thinking-bounce {
  0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

/* Free-tier message cap (app/limits.py) — a distinct in-chat prompt, not
   just the generic error bubble every other failed send falls back to,
   with a real clickable link to the shared upgrade modal. Accent-tinted
   rather than the plain assistant-bubble surface so it doesn't read as
   just another chat reply. */
.bubble-upgrade-prompt {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  background: rgba(124, 92, 255, 0.08);
  border: 1px solid rgba(124, 92, 255, 0.25);
}
.upgrade-inline-link {
  background: var(--accent-gradient);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 7px 14px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

/* A genuine send failure (5xx / network error) — same distinct-bubble
   shape as .bubble-upgrade-prompt above, but tinted with the existing
   --risk-high red instead of the accent purple, so a real failure never
   visually reads as just another upsell prompt. */
.bubble-error-retry {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.25);
}
.error-retry-btn {
  background: var(--risk-high);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 7px 14px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

#chat-form {
  display: flex;
  align-items: center; /* keeps the round send button vertically centered
    on the input's box regardless of its height, rather than pinned to
    its bottom edge (polish batch 3) */
  gap: 8px;
  padding: 14px 16px;
  flex-shrink: 0;
}
/* Chat input wrapper — holds the textarea plus an inline "+" attach
   affordance on the left (Gemini-style), so the icon lives inside the
   input's own box rather than as a separate toolbar button. */
#chat-input-wrap { position: relative; flex: 1; display: flex; }
#chat-input {
  flex: 1;
  padding: 8px 10px 8px 34px; /* left space for #attach-btn */
  border-radius: 12px;
  /* Floating card, not a box sitting under a hard divider line (polish
     batch 3) — the shadow alone reads as "floating"; a border on top of
     it looked over-defined (polish batch 4), so there's no border at all
     now, just the shadow. */
  border: none;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
  background: var(--surface);
  color: var(--text);
  font-size: 0.88rem;
  resize: none;
  font-family: inherit;
  overflow-y: hidden; /* no internal scroll -- the box grows with content instead (app.js autoResizeChatInput) */
}
@media (prefers-color-scheme: dark) {
  #chat-input { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); }
}
#attach-btn {
  position: absolute;
  left: 6px;
  bottom: 6px;
  width: 22px;
  height: 22px;
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 1.05rem;
  line-height: 1;
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
#attach-btn:hover { background: var(--hover-fill); color: var(--accent); border-color: transparent; }
#attach-tip {
  position: absolute;
  left: 6px;
  bottom: 32px;
  background: var(--text);
  color: var(--bg);
  font-size: 0.7rem;
  padding: 4px 8px;
  border-radius: 6px;
  white-space: nowrap;
}
/* Icon-only, circular — Enter is the primary way to send; this button is
   for accessibility/touch, not meant to visually dominate the input row
   the way a wide "Send" label did. */
#chat-form button[type="submit"] {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 50%;
  background: var(--accent-gradient);
  color: #fff;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
#chat-form button[type="submit"]:hover { filter: brightness(1.06); }
#chat-form button[type="submit"]:disabled { opacity: 0.6; cursor: default; filter: none; }

/* #panel-column no longer scrolls itself — it's a flex column with a
   fixed-size top area (#panel-sticky-top: progress bar, report icon,
   headline note) and #panel-scroll as the one scrolling region below it,
   the same "pinned sibling outside the scroller" pattern as
   #chat-column-header/#chat-messages. Progress bar/report icon/
   headline note stay visible while the whiteboard scrolls underneath. */
#panel-column {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--panel-bg);
}
#panel-sticky-top { flex-shrink: 0; padding: 16px 16px 0; }

/* "Research notebook" restyle (2026-09-22) — Current Focus strip,
   replacing the old readiness-bar + headline-note card. The whole card is
   still a click target (see app.js's delegated panel-column listener)
   opening "Why this read" — #decision-badge/#report-btn keep their own
   separate click handlers, excluded from that delegation same as before.
   Soft shadow only, no hard border, unchanged from before this restyle. */
#panel-summary-card {
  background: var(--surface);
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.07);
  padding: 15px 17px 14px;
  margin-bottom: 14px;
  cursor: pointer;
  transition: box-shadow 0.15s ease;
}
#panel-summary-card:hover { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12); }
.focus-eyebrow {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.focus-eyebrow-left { display: flex; align-items: center; gap: 8px; }
.focus-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: none; }
.focus-dot-muted { background: var(--ink-faint); }
#report-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 5px;
  border: 1px solid var(--rule);
  background: none;
  color: var(--text-muted);
  padding: 0;
}
#report-btn:hover { border-color: var(--accent); color: var(--accent); background: none; }
.focus-q, .empty-q {
  font-family: "Fraunces", "Rubik", Georgia, serif;
  font-weight: 500;
  unicode-bidi: plaintext;
  font-size: 1.14rem;
  line-height: 1.34;
  margin: 0 0 9px;
  color: var(--text);
}
.focus-sub {
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1.5;
  margin: 0 0 12px;
  max-width: 52ch;
}
.focus-meta { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.readiness-tag { font-family: "IBM Plex Mono", monospace; font-size: 0.76rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.readiness-tag b { color: var(--text); font-weight: 600; }
/* Was #decision-badge's own filled .badge-unset pill, then a quiet muted
   dotted-underline text link — direct feedback moved it to accent purple,
   underline-only-on-hover (the exact treatment the removed "Explore this"
   link had): a real clickable action belongs in the accent color, not the
   passive-meta-text tone. Same action (showDecisionModal), same id/child
   spans (#decision-badge-label/-dot), CSS-only change. The dot keeps
   carrying real information (a decision already exists) that the
   mockup's own record-btn didn't need to show since it had no such state
   to depict — dropping it would silently lose a signal the app already
   has, not just simplify styling. */
#decision-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--accent);
  background: none;
  border: none;
  padding: 0;
  text-decoration: none;
}
#decision-badge:hover { text-decoration: underline; }
.decision-recorded-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
#panel-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 0 16px 16px; }

/* §4 whiteboard refinement — the board fills essentially the whole panel
   height, not just its top half: #panel is a column flexbox with the
   whiteboard as the one flex-growing child, everything else content-sized. */
#panel { display: flex; flex-direction: column; min-height: 100%; }

/* "Research notebook" restyle (2026-09-22) — the true pre-first-message
   state (see point 5): no Current Focus hero (nothing to focus on yet),
   just a quiet "nothing here yet" intro using the same eyebrow/serif-line/
   sub pattern as the real Current Focus strip, so the two states read as
   the same design language rather than two different UIs. */
#empty-explanation {
  background: var(--surface);
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.07);
  padding: 15px 17px 14px;
  margin-bottom: 20px;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
/* Transition out (see yieldEmptyExplanation in app.js) rather than an
   instant pop the moment the user starts typing. */
#empty-explanation.fading-out { opacity: 0; transform: translateY(-6px); pointer-events: none; }
#empty-explanation .focus-sub { margin-bottom: 0; max-width: none; }
.example-questions-label { font-size: 0.75rem; color: var(--text-muted); font-weight: 600; margin-bottom: 6px !important; }

/* Chat column's own empty state (a brand-new project, before the first
   message) — ghost example bubbles, separate from and in addition to the
   right panel's plain explainer (#empty-explanation above). Centered in
   the message area rather than pinned to a corner. Direct feedback: was
   a column of clickable Suggested Prompt buttons; now non-interactive
   text styled as real (muted) chat bubbles, illustration rather than a
   wall of options, and fades out the instant the user starts typing
   (yieldChatEmptyState in app.js) rather than staying until send. */
#chat-empty-state {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 24px;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
#chat-empty-state.fading-out { opacity: 0; transform: translateY(-6px); pointer-events: none; }
#chat-empty-state .bubble-wrapper-ghost { max-width: 420px; }
/* Overrides .bubble-user's solid accent fill (same specificity, later in
   the cascade) -- a semi-transparent accent fill still read as "just
   another button" in practice; an outline with muted text reads clearly
   as illustration, not a real sent message or a control to press. */
.bubble-ghost {
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--text-muted);
  font-style: italic;
  cursor: default;
  user-select: none;
}

/* Pacing-choice buttons (app.core.pacing) — own class/rules, nothing to
   collide with now that the empty-state examples above are plain
   non-interactive text rather than buttons. Laid out as a short
   horizontal row instead of a vertical stack since these are two short
   labels, not multi-word prompt starters. */
.pacing-choice { margin-top: 8px; }
.pacing-choice-label { font-size: 0.78rem; color: var(--text-muted); margin: 0 0 6px; }
.pacing-choice-buttons { display: flex; gap: 6px; }
.pacing-choice-btn {
  flex: 1;
  text-align: center;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.8rem;
  line-height: 1.3;
}
.pacing-choice-btn:hover { border-color: var(--accent); color: var(--accent); }
.pacing-choice-btn:disabled { opacity: 0.5; pointer-events: none; }

/* ---------- Whiteboard (§3 V4 final) ----------
   Supersedes every earlier panel design (dots -> large labeled tiles ->
   this) entirely — the panel is now exactly three things, top to bottom:
   an abstract progress bar, the whiteboard of sticky notes, and a "Show
   all" button opening the formal document view. No stepper, no separate
   Risk/Evidence/NBA cards, no interactive controls — risks are notes on
   the board like everything else, just visually marked (§9: Trust Loop
   confirmations live in chat, not here). */

.progress-bar-label {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 3px;
}
.progress-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
.progress-track {
  flex: 1;
  height: 8px;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  /* Brand gradient, not green — green is reserved on the whiteboard for
     "this note is fully evidenced"; using it here too made the bar read
     as "% validated" instead of "% overall progress" (polish pass). */
  background: var(--accent-gradient);
  border-radius: 999px;
  transition: width 0.5s ease;
}
.progress-count { font-size: 0.75rem; font-weight: 600; color: var(--text-muted); flex-shrink: 0; }

/* Whiteboard surface: a fixed, deliberately non-UI palette — this is the
   one area of the interface meant to break from the rest of the flat/clean
   chat-UI visual language, per the "senior PM's whiteboard" concept, not a
   bug in the theme. White (not the earlier brown/cork tone) so notes read
   clearly against it — a faint dot texture keeps it from looking like a
   flat blank rectangle without fighting the content. `flex: 1 1 auto` (not
   a fixed height) so the board fills essentially the whole panel between
   the progress bar and the "Show all" button. */
/* Local preview only (2026-09-19), not committed — Notes/Lines toggle and
   the lines rows themselves. Deliberately reuses .progress-track/.progress-
   fill (already used by the main readiness bar and the headline-detail
   popup's mini bars) rather than inventing new bar styling. */
/* "Research notebook" restyle (2026-09-22) — quiet mono underline tabs,
   replacing the pill-toggle look. Same markup/ids, CSS-only change.
   Direct feedback — now sticky: a long list of dimension cards/rows
   could otherwise push the tabs out of view, forcing a scroll back to
   the top just to switch views. Sticks to the top of #panel-scroll (the
   one scrolling region -- #panel-sticky-top/Current Focus lives outside
   it and deliberately stays non-sticky, it's fine to scroll away).
   Opaque --panel-bg background (matches what's already behind it at
   rest) so scrolled-past cards disappear behind the bar instead of
   showing through; z-index above the cards' own stacking. */
.board-view-toggle {
  display: flex;
  gap: 22px;
  margin-bottom: 4px;
  padding: 0;
  background: var(--panel-bg);
  border-radius: 0;
  border-bottom: 1px solid var(--rule-soft);
  width: auto;
  position: sticky;
  top: 0;
  z-index: 2;
}
.board-view-btn {
  border: none;
  background: none;
  padding: 0 2px 10px;
  border-radius: 0;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  font-weight: 500;
  /* UI/UX audit follow-up (2026-10-08) -- was var(--ink-faint), ~2.3:1
     against --panel-bg in light mode (~3.4:1 dark), below the 4.5:1 AA
     minimum for small text. --text-muted passes in both themes here
     (~4.6:1 light, ~6.2:1 dark, measured against --panel-bg, the real
     background this tab bar sits on) while staying visibly less
     prominent than the active tab's full var(--text). This also
     surfaced, and fixed separately, a real parser bug near the top of
     this file: a comment there referenced two custom property names
     side by side, separated only by a slash, with no space -- a
     wildcard-asterisk suffix on the first one landing directly against
     that slash accidentally read as a comment terminator to every
     browser, silently dropping the ORIGINAL --ink-faint declaration
     from the parsed CSSOM entirely -- so every var(--ink-faint)
     consumer in this file was actually falling back to its inherited
     color, not really rendering pale gray at all, until that comment
     was reworded. (Deliberately not spelling out the exact two
     characters here, to avoid reproducing the same mistake inside
     THIS comment.) */
  color: var(--text-muted);
  position: relative;
}
.board-view-btn.active {
  background: none;
  color: var(--text);
  box-shadow: none;
}
.board-view-btn.active::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--accent);
}

/* Lines rows — quiet, not colored progress cards: name + percentage on
   one line, a status word underneath (colored only when genuinely strong
   or weak — see app.js's LINE_STRONG_THRESHOLD/LINE_WEAK_THRESHOLD), a
   thin flat track. Per-category color (--line-color) is gone entirely. */
.lines-view {
  display: flex;
  flex-direction: column;
  padding: 2px 2px 0;
}
.line-row {
  border-bottom: 1px solid var(--rule-soft);
  padding: 13px 2px;
  cursor: pointer;
}
.line-row:first-child { padding-top: 2px; }
.line-row:last-child { border-bottom: none; }
.line-row-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 4px; }
.line-row-label { font-weight: 600; font-size: 0.92rem; color: var(--text); }
.line-row-pct { font-family: "IBM Plex Mono", monospace; font-size: 0.86rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.line-row-status { font-size: 0.78rem; color: var(--text-muted); margin: 0 0 7px; }
.line-row-status.evidence { color: var(--evidence); }
.line-row-status.risk { color: var(--risk-notebook); }
.line-row-track { height: 3px; background: var(--rule); border-radius: 2px; overflow: hidden; }
.line-row-track .progress-fill { background: var(--ink-faint); border-radius: 2px; }
.line-row-track .progress-fill.strong { background: var(--evidence); }
.line-row-track .progress-fill.weak { background: var(--risk-notebook); }
.discuss-btn {
  border: none;
  background: none;
  color: var(--accent);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.72rem;
  font-weight: 500;
  padding: 8px 0 0;
}
.discuss-btn:hover { text-decoration: underline; }
.discuss-btn:disabled { color: var(--ink-faint); cursor: default; }
.discuss-btn:disabled:hover { text-decoration: none; }
/* "Never discussed" state -- dashed everything, muted, except the Discuss
   button, which stays in its real accent color since an untouched
   dimension is exactly the thing worth prompting about. */
.line-row-untouched { opacity: 0.72; }
.line-row-untouched .line-row-label { color: var(--text-muted); font-weight: 500; }
/* Open-risk strip below the Lines grid -- reuses .headline-risk-chip/
   .headline-risk-text (already used in the "Why this score" popup) so a
   risk reads identically wherever it's shown. */
.lines-risks {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--rule-soft);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.lines-risks-title { font-size: 0.8rem; font-weight: 600; color: var(--text-muted); margin: 0 0 2px; }
/* Inside the note-modal popup specifically -- separated from the evidence
   list above it with a divider rather than sitting flush underneath. */
.note-modal-discuss-wrap {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

/* "Research notebook" restyle (2026-09-22) — flat paper surface, no
   texture/dot-grid, no inset "sits on a desk" shadow. Clusters stack top
   to bottom now (not a wrapped flex grid of stacks) — a notebook page,
   not a corkboard. */
.whiteboard {
  flex: 1 1 auto;
  min-height: 320px;
  border-radius: 10px;
  padding: 18px 20px;
  margin-bottom: 14px;
  background: var(--surface);
  border: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 22px;
}

/* Cluster = italic serif title + a wrapping row of its own cards. */
.cluster { display: flex; flex-direction: column; gap: 10px; }
.card-row { display: flex; flex-wrap: wrap; gap: 12px; }

/* Direct feedback — the right panel read as "too typographic" with the
   serif/italic treatment repeated across every cluster title (up to 8 per
   project), not just the one Current Focus headline. Fraunces now stays
   reserved for that single hero moment (.focus-q/.empty-q) only — cluster
   titles are plain Work Sans, small caps via letter-spacing/uppercase
   instead of a second display face, matching the rest of the app's quiet
   UI-chrome typography. */
.wb-cluster-title {
  display: inline-block;
  font-family: "Work Sans", -apple-system, sans-serif;
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  unicode-bidi: plaintext; /* Hebrew titles flow RTL, Latin ones stay LTR */
}

/* Flat index card — replaces the old post-it (paper texture, rotation,
   drop shadow, pin/fold). Hairline border, small radius, Work Sans body
   text (Fraunces is reserved for the Current Focus question and these
   cluster titles only — see the site-wide font pass). A hypothesis card
   gets a dashed border; a risk card gets its border colored
   --risk-notebook. No per-category fill anywhere — category identity
   comes from the cluster title alone now. */
.wb-card {
  flex: 1 1 220px;
  min-width: 0;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 14px 15px 13px;
  cursor: pointer;
}
.wb-card.hypothesis { border-style: dashed; }
.wb-card.risk { border-color: var(--risk-notebook); }
.wb-card-text {
  font-family: "Work Sans", "Rubik", sans-serif;
  font-weight: 500;
  font-size: 0.94rem;
  line-height: 1.48;
  color: var(--text);
  margin: 0 0 12px;
  unicode-bidi: plaintext; /* Hebrew note text flows RTL, Latin stays LTR */
}
.wb-card-foot { display: flex; align-items: center; }
/* Drop-in entrance for a genuinely new card (see noteCardHtml in app.js —
   this class only lands on a note's first-ever render, not on every
   board re-render). Fade + settle only now — no rotation, no paper-glow
   highlight; those belonged to the post-it metaphor this restyle drops. */
@keyframes card-enter {
  0% { opacity: 0; transform: translateY(-8px); }
  100% { opacity: 1; transform: translateY(0); }
}
.wb-card.note-enter { animation: card-enter 0.25s ease-out; }

/* Status tag (was .note-status-tag, sev-low/sev-high colors) — same
   lifecycle_status mapping (assumption/evidence/risk), recolored to the
   semantic evidence/hypothesis/risk tokens instead of the severity-scale
   ones, since this is a different axis (validated vs. assumed vs. open),
   not a severity. Word only, no count — see noteCardHtml's own comment
   for why (no real per-note evidence count exists in the API). */
.wb-card-status {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.68rem;
  letter-spacing: 0.03em;
  padding: 2px 8px;
  border-radius: 3px;
}
.wb-card-status.status-assumption { color: var(--hypothesis); background: var(--hypothesis-soft); }
.wb-card-status.status-evidence { color: var(--evidence); background: var(--evidence-soft); }
.wb-card-status.status-risk { color: var(--risk-notebook); background: var(--risk-notebook-soft); }

/* Empty-state dimension slot (see renderEmptyStateSlots in app.js) — a
   dashed box naming the dimension only, no fabricated example content. */
.slot {
  flex: 1 1 220px;
  min-width: 0;
  border: 1px dashed var(--rule);
  border-radius: 3px;
  padding: 16px 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
}
.slot-name {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.74rem;
  letter-spacing: 0.03em;
  /* UI/UX audit follow-up (2026-10-08) -- see .board-view-btn's own
     comment above for the contrast numbers and the comment-parser bug
     this also uncovered. This is the first thing a brand-new user sees
     (the empty-state dimension slots), so it's worth the same fix. */
  color: var(--text-muted);
}

.headline-detail-section { margin-bottom: 16px; }
.headline-detail-section:last-child { margin-bottom: 0; }
.headline-detail-section h3 { font-size: 0.9rem; margin: 0 0 8px; color: var(--text); }

/* "Why" — each weak dimension as its own mini card, each open risk as its
   own severity chip, instead of a flat bullet list the reader has to
   parse line by line (polish pass). Direct feedback — now shares
   .line-row's label/pct/status/track styling (see headlineDimensionCardHtml)
   so this popup and the Discovery Report overview both read as the same
   quiet system as the Lines panel, not the old thick colorful bar. Only
   overrides what doesn't fit a grid-of-cards context (.line-row's own
   list-style border-bottom/cursor/padding) with a real card boundary. */
.headline-why-grid { display: flex; flex-direction: column; gap: 8px; }
.headline-dim-card.line-row {
  border: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  border-radius: 8px;
  padding: 10px 12px;
  cursor: default;
}
.headline-dim-caption { font-size: 0.72rem; color: var(--text-muted); }
.headline-risk-chip {
  display: flex;
  align-items: center;
  gap: 9px;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 9px 12px;
}
.headline-risk-text { font-size: 0.84rem; color: var(--text); line-height: 1.35; }

/* "What would change it" — an arrow-marked checklist rather than a bare
   bullet list, reading as concrete next steps rather than commentary. */
.headline-checklist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.headline-checklist li { font-size: 0.84rem; color: var(--text); line-height: 1.4; padding-left: 20px; position: relative; }
.headline-checklist li::before {
  content: "→";
  position: absolute;
  left: 0;
  color: var(--accent);
  font-weight: 700;
}

/* ---------- Shared bits (note/headline-detail popups) ---------- */

.card-summary { margin: 0; font-size: 0.82rem; color: var(--text); line-height: 1.4; }

/* Bug fix, re-broken then re-fixed — originally matched to the old pill-
   shaped .note-status-tag so "medium"/"RISK" read as one tag family in
   the note-modal popup's same row. The "research notebook" restyle gave
   that tag a new look (.wb-card-status: mono font, 3px radius, 2px 8px
   padding) without updating this one to match, so the two diverged again
   -- direct feedback confirmed this reads as visibly misaligned/off-
   center next to each other. Re-matched to .wb-card-status's current
   metrics instead of the old pill shape. */
.badge-severity {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 2px 8px;
  border-radius: 3px;
  flex-shrink: 0;
}
.badge-severity.sev-high { background: var(--sev-high-bg); color: var(--sev-high-text); }
.badge-severity.sev-medium { background: var(--sev-medium-bg); color: var(--sev-medium-text); }
.badge-severity.sev-low { background: var(--sev-low-bg); color: var(--sev-low-text); }

.evidence-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }
.evidence-list li { font-size: 0.85rem; color: var(--text); line-height: 1.4; }
/* A risk item is already marked by its severity badge + status tag
   (badge-severity, note-status-tag.status-risk below) — coloring the body
   text too was redundant and hurt readability/contrast, so risk text
   reads the same var(--text) as every other note. */

/* Note lifecycle status tag — see .wb-card-status above (this is now the
   only rule for it; shared by both the flat cards and the note-modal's
   per-note list, same noteStatusTagHtml() call in app.js either way). */

.note-modal-row-top { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
/* Slightly larger + more line-height than the base .evidence-list li
   (0.85rem/1.4) — this is the "read the full note" popup, not a compact
   list, so body-text legibility matters more here than density. ~15px at
   a 16px root, in line with typical modal/dialog body text (14-16px). */
.note-modal-text { display: block; unicode-bidi: plaintext; font-size: 0.92rem; line-height: 1.5; }

/* ---------- Trust Loop ---------- */

.trust-loop { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
/* Follow-up fix — only rendered when a turn touched more than one note
   (trustWidgetHtml), so several stacked chip-sets can each say which
   claim they're about; a normal single-claim turn never shows this. */
.trust-loop-label { font-size: 0.72rem; font-weight: 600; color: var(--text); }
.trust-loop-prompt { font-size: 0.76rem; color: var(--text-muted); }
/* Direct feedback (2026-09-29) — the "action" fallback note is purely
   informational (no real inferred claim behind it, see
   trustLoopPendingHtml's own comment), so it's styled a touch quieter
   than the real confirmation prompt above, not identical to it. */
.trust-loop-focus-note { font-style: italic; opacity: 0.85; }
.trust-loop.resolved { margin-top: 6px; animation: bubble-in 0.2s ease; }
.trust-resolved-label {
  /* Bug fix — .trust-loop is a column flexbox with the default
     align-items: stretch, which was stretching this pill to the full
     width of its container (same width as the .trust-loop-label text
     above it) instead of sizing to its own short content. align-self
     opts this one child out of the stretch. */
  align-self: flex-start;
  font-size: 0.72rem;
  color: var(--text-muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 10px;
}

.status { color: var(--text-muted); font-size: 0.8rem; margin: 4px 0 0; }

/* Chat-side reinforcement (polish pass) — a small, quiet signal that this
   turn added/revised a whiteboard note, distinct from .msg-why below (that
   explains the NEXT question; this looks back at what just got captured).
   Deliberately just a short label, never the note's own content — the
   board is the one place that content lives. */
.msg-board-signal { font-size: 0.74rem; color: var(--text-muted); }

/* Collapsed trust-item group (direct feedback, 2026-09-29) -- replaces
   a wall of repeated per-item confirmation blocks when a turn captures
   2+ claims/tensions at once (see trustCollapsedGroupHtml, app.js). A
   single item still renders the old way (trustLoopPendingHtml), so this
   only applies to the genuinely cluttered multi-item case. */
.trust-loop-summary-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(124, 92, 255, 0.08);
  border: 1px solid rgba(124, 92, 255, 0.25);
  border-radius: 999px;
  padding: 5px 12px;
  margin-top: 8px;
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--accent-text);
  cursor: pointer;
}
.trust-loop-summary-pill:hover { background: rgba(124, 92, 255, 0.14); }
.trust-loop-group-detail {
  margin-top: 6px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.78rem;
}
/* Direct feedback (2026-09-29) — .trust-loop-label's base font-weight:600
   (below) reads fine for the single-item case it was designed for (one
   bold label next to plain prompt text), but stacking several bold
   labels here reads heavy. Lightened for this group view specifically
   -- the single-item path is untouched. */
.trust-loop-group-detail .trust-loop-label { padding: 2px 0; display: block; font-weight: 500; }
.trust-loop-group-detail .trust-loop-prompt { margin-top: 6px; }
/* Consolidation design (2026-09-30) -- intro line for a sub-group of
   tensions sharing the same new-entity id ("This seems to conflict with
   a few things you said before:"), sitting right above their old-claim
   names within the already-expanded detail view. */
.trust-loop-group-intro { font-weight: 600; margin: 6px 0 2px; }
.trust-loop-group-intro:first-child { margin-top: 0; }

/* ---------- "Why I'm asking" (polish pass) ---------- */

.msg-why { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.why-short { font-size: 0.76rem; color: var(--text-muted); font-style: italic; }
.why-toggle {
  border: none;
  background: none;
  padding: 0;
  font-size: 0.72rem;
  color: var(--accent-text);
  text-decoration: underline;
}
.why-toggle:hover { border-color: transparent; }
.why-detail {
  flex-basis: 100%;
  font-size: 0.74rem;
  color: var(--text-muted);
  line-height: 1.4;
}

/* ---------- Modal ---------- */

.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Without an explicit z-index here, .wb-stack-count's z-index:3 (needed
     to sit its badge above the stack's own layered cards) was winning the
     stacking comparison against this modal at the root context — none of
     the ancestors in between (#app-shell, #panel-column, etc.) establish
     their own stacking context, so that z-index:3 bubbled all the way up
     and painted through the modal. A comfortably-higher value here beats
     every z-index used elsewhere in the app (max otherwise is 100, the
     mobile sidebar drawer). */
  z-index: 1000;
}
.modal-content {
  background: var(--surface);
  border-radius: 10px;
  padding: 24px;
  max-width: 640px;
  width: 90%;
  max-height: 80vh;
  overflow-y: auto;
  position: relative;
}
/* Rounded hover background (polish pass) — matches the app's standard
   hover treatment (account menu rows, the project three-dot menu, etc.):
   a soft neutral background, not just a bare color change. Neutral/muted
   text on hover, not red — this dismisses the modal, it isn't a
   destructive action like the project menu's Delete. */
.close-btn {
  position: absolute;
  top: 8px;
  right: 12px;
  border: none;
  background: none;
  font-size: 1.4rem;
  line-height: 1;
  padding: 4px 9px;
  border-radius: 6px;
  color: var(--text-muted);
}
.close-btn:hover { background: var(--hover-fill); color: var(--text); border-color: transparent; }

/* Explains why the "Why this read" popup only shows 2 dimensions instead
   of reading as "these are the only categories that exist" — points to
   the full Report for the rest. */
.note-modal-subtitle {
  margin: -2px 0 16px;
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--text);
}
.link-btn {
  border: none;
  background: none;
  padding: 0;
  font-size: inherit;
  color: var(--accent-text);
  text-decoration: underline;
}
.link-btn:hover { border-color: transparent; }

/* ---------- Report modal header (export button) + upgrade popup ---------- */

#report-modal-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-right: 24px; }
#report-modal-header h2 { margin: 0; }
#export-report-btn {
  flex-shrink: 0;
  border: none;
  background: none;
  padding: 6px;
  border-radius: 7px;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
}
#export-report-btn:hover { background: var(--hover-fill); color: var(--accent); border-color: transparent; }
/* UX fix — a visible "this is actually working" state for the real
   duration of PDF generation (an LLM call, not instant), and a disabled
   button so a second click during that window can't fire a second
   export — see the click handler for why this replaced a bare
   window.open() straight at the endpoint. */
#export-report-btn:disabled { opacity: 0.45; cursor: default; }
#export-report-btn.exporting svg { animation: export-spin 0.8s linear infinite; }
@keyframes export-spin { to { transform: rotate(360deg); } }
#report-export-error { color: #dc2626; font-size: 0.8rem; margin: 0 0 10px; }

/* Free vs Pro comparison (polish pass) — two side-by-side cards rather
   than a single bullet dump, matching the Claude/Gemini/ChatGPT upgrade-
   modal convention: both plans' scope is visible at a glance, not just
   "here's what you're missing." No fabricated price on the Pro card —
   there's no real billing wired up yet (see the Pro card's disabled
   "Coming soon" button in index.html), so a made-up "$X/mo" would just
   be misleading. */
/* Deliberately compact (tighter than the default .modal-content padding/
   max-height) — this content should fit without an inner scrollbar on an
   ordinary viewport, not just technically be reachable by scrolling. */
.upgrade-modal-content { max-width: 560px; padding: 20px 24px; max-height: 90vh; }
.upgrade-modal-content h2 { margin: 0 0 4px; font-size: 1.05rem; }
.upgrade-modal-content #upgrade-modal-reason { margin: 0 0 4px; }
.plan-compare { display: flex; gap: 12px; margin: 12px 0; align-items: stretch; }
.plan-card {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
}
.plan-card-pro {
  position: relative;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.plan-badge {
  position: absolute;
  top: -10px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--accent-gradient);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 3px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.plan-name { font-size: 0.92rem; font-weight: 700; }
.plan-price { font-size: 1.15rem; font-weight: 700; margin: 2px 0 10px; }
.plan-price span { font-size: 1.15rem; }
.plan-card:not(.plan-card-pro) .plan-price { color: var(--text-muted); }
.plan-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; flex: 1; }
.plan-features li { font-size: 0.8rem; line-height: 1.3; padding-left: 20px; position: relative; }
.plan-features li.included { color: var(--text); }
.plan-features li.included::before {
  content: "✓";
  position: absolute;
  left: 0;
  color: var(--accent);
  font-weight: 700;
}
.plan-features li.excluded { color: var(--text-muted); }
.plan-features li.excluded::before {
  content: "–";
  position: absolute;
  left: 2px;
  color: var(--text-muted);
}
@media (max-width: 560px) {
  .plan-compare { flex-direction: column; }
}

/* ---------- Waitlist / early-access interest form (§6 monetization,
   Fake-Door instrument) — replaces the old disabled #confirm-upgrade-btn.
   Shares the modal with the plan-compare cards above it. */
#waitlist-form { display: flex; flex-direction: column; gap: 4px; margin-top: 14px; }
.waitlist-label { font-size: 0.78rem; font-weight: 600; color: var(--text-muted); margin-top: 8px; }
#waitlist-form input[type="email"],
#waitlist-form input[type="text"],
#waitlist-form textarea {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  font: inherit;
  font-size: 0.85rem;
  background: var(--bg);
  color: var(--text);
  resize: vertical;
}
#waitlist-form input:focus, #waitlist-form textarea:focus { outline: none; border-color: var(--accent); }
.waitlist-toggle-group { display: flex; flex-wrap: wrap; gap: 6px; }
.waitlist-toggle-btn {
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.78rem;
  padding: 6px 12px;
  cursor: pointer;
}
.waitlist-toggle-btn:hover { border-color: var(--accent); }
.waitlist-toggle-btn.selected {
  background: var(--accent-gradient);
  border-color: transparent;
  color: #fff;
  font-weight: 600;
}
.waitlist-error { color: #dc2626; font-size: 0.78rem; margin: 6px 0 0; }
#waitlist-submit-btn {
  width: 100%;
  margin-top: 14px;
  border: none;
  border-radius: 8px;
  background: var(--accent-gradient);
  color: #fff;
  font-weight: 600;
  padding: 10px;
  cursor: pointer;
}
#waitlist-submit-btn:hover { filter: brightness(1.06); }
#waitlist-submit-btn:disabled { opacity: 0.6; cursor: default; filter: none; }
.waitlist-success { text-align: center; padding: 16px 4px 4px; font-size: 0.9rem; font-weight: 500; }

/* §35 — modals fade/scale in on open instead of snapping to full opacity;
   `animation` restarts automatically whenever [hidden] toggles off (the
   element goes from display:none back into the render tree), so no JS
   timing code is needed here. */
.modal:not([hidden]) { animation: modal-fade-in 0.15s ease; }
.modal:not([hidden]) .modal-content { animation: modal-scale-in 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes modal-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes modal-scale-in {
  from { opacity: 0; transform: scale(0.96) translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* ---------- Mobile layout (§23 new) ---------- */

.mobile-only { display: none; }

@media (max-width: 768px) {
  .mobile-only { display: flex; }

  #mobile-menu-toggle {
    border: none;
    background: none;
    padding: 6px;
    color: var(--text);
  }

  /* Sidebar becomes a slide-in drawer, not a persistent column — the
     desktop collapse-to-rail concept doesn't apply here, it's binary
     open/closed, so force full width regardless of any "collapsed" state
     carried over from a desktop session. */
  #sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 260px;
    z-index: 100;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
  }
  #sidebar.collapsed { width: 260px !important; }
  #sidebar.collapsed .label,
  #sidebar.collapsed .account-row .label { display: revert !important; }
  /* #sidebar-scroll needs its flex layout explicitly restored (not
     revert !important) — it's a <div>, whose UA default display is
     block, not the flex it actually needs for its internal product-
     groups/project-list gap layout, unlike #project-list's own <ul>
     (block either way, so revert was fine there). */
  #sidebar.collapsed #sidebar-scroll { display: flex !important; }
  /* The above restored the New/Product buttons' TEXT (shared .label
     class) but not their layout, nor the logo — a desktop "collapsed"
     session carried into the mobile drawer still showed the icon-only
     logo mark and centered New/Product buttons, contradicting the
     drawer's own "binary open/closed" comment above. */
  #sidebar.collapsed .sidebar-logo-full { display: block !important; }
  #sidebar.collapsed .sidebar-logo-icon { display: none !important; }
  #sidebar.collapsed .sidebar-new-btn { justify-content: flex-start !important; padding: 7px 10px !important; }
  #sidebar.mobile-open { transform: translateX(0); }

  #sidebar-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 99;
  }

  /* Right panel: no split-screen — compact strip pinned above chat,
     full-screen overlay on tap (§23). */
  #app-main { grid-template-columns: 1fr; }
  #chat-column { border-right: none; }

  #mobile-panel-strip {
    align-items: center;
    gap: 10px;
    width: 100%;
    text-align: left;
    /* Bumped from 8px/0.78rem — closer to a real ~44px touch target
       (was reading as thin/cramped), and the chevron reads more clearly
       as tappable with a bit more room around it. */
    padding: 13px 16px;
    border: none;
    border-bottom: 1px solid var(--border);
    background: var(--panel-bg);
    font-size: 0.84rem;
    color: var(--text-muted);
  }
  /* Mobile strip reuses the same .progress-bar markup as the desktop
     panel — just narrower, no separate mini-tile vocabulary to maintain. */
  #mobile-progress { flex-shrink: 0; width: 70px; }
  #mobile-progress .progress-bar { margin-bottom: 0; }
  #mobile-progress .progress-bar-label { display: none; } /* no room in the compact strip */
  /* Bug fix — at 0% (every brand-new/signed-out visitor's first view),
     the track was reading as blank: #mobile-panel-strip's own background
     is --panel-bg, and the desktop track color (--border) is only about
     a 2% luminance step away from it -- fine on the desktop panel, which
     sits on a white --surface card instead, but nearly invisible here.
     --hover-fill-strong is already the app's own "works on any surface"
     translucent-black overlay (see its definition) -- reused here for
     exactly that reason, instead of tuning yet another light/dark pair. */
  #mobile-progress .progress-track { background: var(--hover-fill-strong); }
  #mobile-current-focus { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Nothing about a plain text strip reads as tappable on a touch
     screen — this chevron is the only visual cue that it opens the full
     panel, so it needs to actually be visible, not just decorative. */
  /* Accent color, not muted — matches the app's existing convention that
     the accent color signals "this is interactive" (links, buttons), so
     the chevron reads as tappable at a glance instead of decorative. */
  #mobile-panel-strip-chevron { flex-shrink: 0; display: flex; color: var(--accent); }

  #panel-column { display: none; }
  #panel-column.mobile-open {
    /* flex (not block) — #panel-column is a flex column internally now
       (sticky top area + scrolling whiteboard below), same structure as
       desktop, just full-screen here instead of side-by-side. */
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0;
    z-index: 90;
    /* Was 44px, plus #panel-sticky-top's own 16px top padding stacked
       right underneath it (60px combined) — a visibly empty gap below
       the button before the readiness card even starts, called out
       directly as wasted space on a screen where every inch matters.
       36px clears the button (top:8px + its own ~29px height) with a
       few px of breathing room; #panel-sticky-top's own top padding is
       cut way down right below, instead of stacking a second full gap
       on top of this one. */
    padding-top: 36px;
    animation: modal-fade-in 0.15s ease;
  }
  #panel-column.mobile-open #panel-sticky-top { padding-top: 6px; }
  #panel-close-mobile {
    position: fixed;
    top: 8px;
    /* Was 8px — 8px off from #panel-sticky-top's own 16px left padding
       just below it, so the button read as not lined up with the card
       and everything else on the page. Now matches. */
    left: 16px;
    z-index: 91;
    /* Was using the global button's default padding/font-size (sized for
       a normal desktop button), reading as oversized here. */
    display: flex;
    align-items: center;
    line-height: 1;
    gap: 5px;
    padding: 6px 10px;
    font-size: 0.78rem;
    border: 1px solid var(--border);
    background: var(--surface);
  }
  /* The arrow is now a real SVG (see index.html), not the &larr; text
     entity that used to sit here — a font's arrow glyph has its own
     ascent/descent baked in and doesn't optically center against Latin
     text next to it even with align-items:center on the flex parent; an
     SVG has an explicit, predictable box so this actually centers. */
  #panel-close-mobile svg { flex-shrink: 0; display: block; }
}
/* Real HTML now (see markdownLiteToHtml in app.js), not raw text in a
   <pre> — the LLM's report text is deliberately Markdown-lite (a
   controlled subset: paragraphs, occasional "##" section headings,
   "**bold**"), rendered as actual <p>/<h4>/<ul>/<strong> rather than
   showing "##"/"**" as literal characters. */
#report-body { font-family: inherit; font-size: 0.86rem; line-height: 1.55; color: var(--text); }
#report-body p { margin: 0 0 12px; }
#report-body p:last-child { margin-bottom: 0; }
#report-body .report-heading { font-size: 0.92rem; font-weight: 700; margin: 18px 0 8px; }
#report-body .report-heading:first-child { margin-top: 0; }
#report-body ul { margin: 0 0 12px; padding-left: 20px; }
#report-body li { margin-bottom: 4px; }
#report-overview:not(:empty) { margin: 14px 0; }
.report-overview-decision { margin-bottom: 10px; }
.headline-detail-section-label { font-size: 0.9rem; margin: 0 0 8px; color: var(--text); padding-top: 12px; border-top: 1px solid var(--border); }
