/* ═══════════════════════════════════════════════════════════════════════════
   Chat page components.
   ───────────────────────────────────────────────────────────────────────────
   Loaded only by index.html. Every colour here is a token from app.css — this
   file adds no palette of its own, which is the difference from the ~350-line
   private token block the old index.html carried. That block is why the Ask page
   looked like a different product from the Reports page.
   ═══════════════════════════════════════════════════════════════════════════ */

/* `.main__bare` — the shared bare-page content slot — is defined in app.css,
   beside the rest of the is-bare height chain. It used to live here, which
   meant the two OTHER bare pages (dashboard, upload) never loaded it at all.
   The chat's OWN rows live below. */
.chat{flex:1; min-height:0; display:grid; grid-template-rows:1fr auto;}

.chat__scroll{
  overflow-y:auto; overscroll-behavior:contain; padding:22px 20px 8px;
  scroll-behavior:smooth;
}
/* `min-height:100%` is what lets the empty state fill the scrollport instead
   of hanging off its top — see the note above `.welcome`. It resolves because
   `.chat__scroll` is the definite `1fr` row of `.chat{grid-template-rows:1fr
   auto}`; if that ever becomes `auto`, the empty state silently collapses
   back to a short block at the top with the void beneath it. It costs a
   loaded transcript nothing: a column of turns is already taller than this. */
.chat__col{max-width:var(--page-max); margin:0 auto; display:flex;
  flex-direction:column; gap:22px; min-height:100%;}

/* ── messages ───────────────────────────────────────────────────────────── */
.turn{display:flex; flex-direction:column; gap:12px;}

.ask{display:flex; gap:11px; align-items:flex-start; justify-content:flex-end;}
.ask__text{
  background:var(--bubble-user-bg); color:var(--bubble-user-fg);
  border:1px solid var(--bubble-user-border); border-radius:var(--radius-lg);
  padding:10px 14px; font-size:14px; line-height:1.55; max-width:min(46rem,78%);
  white-space:pre-wrap; overflow-wrap:anywhere;
}
.avatar{
  width:28px; height:28px; border-radius:50%; flex:none; display:grid;
  place-items:center; font-size:11.5px; font-weight:700;
}
.avatar--user{background:var(--bg-surface-alt); color:var(--text-secondary);
  border:1px solid var(--border-color);}
.avatar--bot{background:var(--color-accent); color:var(--color-accent-on);}
.avatar--bot svg{width:15px; height:15px;}

.reply{display:flex; gap:11px; align-items:flex-start;}
.reply__body{min-width:0; flex:1; display:flex; flex-direction:column; gap:12px;}

/* ── the reasoning trail ────────────────────────────────────────────────── */
.trail{
  display:flex; flex-direction:column; gap:2px;
  border-left:2px solid var(--border-color); padding:2px 0 2px 12px;
}
.trail__step{
  display:flex; align-items:center; gap:8px; font-size:12.5px;
  color:var(--text-muted); padding:2px 0;
}
.trail__step.is-active{color:var(--text-primary); font-weight:550;}
.trail__step.is-bad{color:var(--color-danger);}
.trail__icon{width:14px; height:14px; flex:none; display:grid; place-items:center;}
.trail__icon svg{width:13px; height:13px;}
.trail__spin{
  width:11px; height:11px; border-radius:50%;
  border:2px solid var(--color-accent-soft-strong); border-top-color:var(--color-accent);
  animation:spin .8s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg);}}
.trail__el{font-variant-numeric:tabular-nums; color:var(--text-faint); font-size:11.5px;}

/* Collapsed once the answer starts arriving: the detail stays reachable but
   stops competing with the answer for attention. */
.trail-done{margin:0;}
.trail-done>summary{
  cursor:pointer; font-size:12px; color:var(--text-muted); list-style:none;
  display:inline-flex; align-items:center; gap:6px; padding:3px 8px;
  border:1px solid var(--border-color); border-radius:var(--radius-pill);
  background:var(--bg-surface-alt);
}
.trail-done>summary::-webkit-details-marker{display:none;}
.trail-done>summary:hover{color:var(--text-primary);}
.trail-done[open]>summary{margin-bottom:8px;}

/* ── the answer ─────────────────────────────────────────────────────────── */
.prose--answer{font-size:14.5px; line-height:1.62; max-width:52rem;}
.prose--answer>*:first-child{margin-top:0;}
.prose--answer>*:last-child{margin-bottom:0;}

.provrow{display:flex; align-items:center; gap:8px; flex-wrap:wrap;}

.answer__foot{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  flex-wrap:wrap; padding-top:11px; border-top:1px solid var(--border-color);
}
.answer__by{font-size:12px; color:var(--text-muted); min-width:0;}
.answer__by.is-bad{color:var(--color-danger);}
.answer__by.is-warn{color:var(--color-warning);}
.tools{display:flex; align-items:center; gap:3px; flex-wrap:wrap;}
.tool{
  display:inline-flex; align-items:center; gap:6px; padding:5px 9px;
  border:1px solid transparent; border-radius:var(--radius-sm); background:none;
  font:inherit; font-size:12.5px; color:var(--text-muted); cursor:pointer;
  transition:background var(--transition-fast), color var(--transition-fast);
}
.tool:hover{background:var(--bg-hover); color:var(--text-primary);}
.tool svg{width:14px; height:14px;}
.tool--accent{color:var(--color-accent); font-weight:600;}
.tool--accent:hover{background:var(--color-accent-soft); color:var(--color-accent);}
/* "Flag issue" — the one tool in the row that reports a fault, and it sat in
   --text-muted beside five neutral siblings. It takes the status family, not a
   theme colour: --color-danger is declared once in :root and once per MODE and
   is deliberately NOT owned by a palette (gen.js keeps status out of the theme
   blocks — "danger" must look the same everywhere and is never a ninth series),
   so this is identical in all 22 themes and clears 4.5:1 on the card in both.
   Ink and a hover tint, no fill at rest: it is a report, not a destructive
   action, and commitment #1 keeps colour to an edge. */
.tool--danger{color:var(--color-danger); font-weight:600;}
.tool--danger svg{color:var(--color-danger);}
.tool--danger:hover{background:var(--color-danger-soft); color:var(--color-danger);}

/* feedback loop (dev-cycle only) — the "Flag issue" toggle-reveal panel,
   appended after .answer__foot exactly like the "View SQL" box. */
.fb{
  margin-top:8px; display:flex; flex-direction:column; gap:8px;
  padding:10px 12px; border:1px solid var(--border-color);
  border-radius:var(--radius-md); background:var(--bg-surface-alt);
}
.fb__actions{display:flex; justify-content:flex-end;}

/* ── the result panel ───────────────────────────────────────────────────── */
.result{display:flex; flex-direction:column; gap:13px;}
.result__grid{display:grid; grid-template-columns:1.25fr .75fr; gap:14px; align-items:stretch;}
.result__grid.is-one{grid-template-columns:1fr;}
@media (max-width:1000px){.result__grid{grid-template-columns:1fr;}}

/* A turn reopened from history: its rows are fetched when it scrolls into view.
   Deliberately quiet — this is a placeholder for a table, not a warning, and it
   sits exactly where the table will appear. `.result--offloaded` carries the
   collapsed panel's measured min-height, so a panel evicted by the LRU above the
   viewport leaves the scroll position where it was. */
.result--offloaded{justify-content:flex-start;}
.offload{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:10px 12px; border:1px dashed var(--border-color);
  border-radius:var(--radius-md); background:var(--bg-panel);
}
.offload__label{font-size:12.5px; color:var(--text-muted);}
.offload__action{display:flex; align-items:center; gap:8px; min-height:22px;}
.offload__spin{
  width:11px; height:11px; border-radius:50%;
  border:2px solid var(--color-accent-soft-strong); border-top-color:var(--color-accent);
  animation:spin .8s linear infinite;
}
.panel{
  background:var(--bg-card); border:1px solid var(--border-color);
  border-radius:var(--radius-md); display:flex; flex-direction:column; min-width:0;
  overflow:hidden;
}
/* One centre line for everything in it, and the TITLE is the only element
   allowed to shrink — the controls keep their size, which is the opposite of
   what happened before (a long title squashed the filter box to 45px). */
.panel__head{
  display:flex; align-items:center; gap:var(--sp-2);
  padding:6px 10px; min-height:38px;
  border-bottom:1px solid var(--border-color); background:var(--bg-surface-alt);
}
.panel__title{
  flex:1 1 auto; min-width:0;
  font-size:var(--fs-chrome); font-weight:650; color:var(--text-secondary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.panel__body{padding:0; min-width:0; flex:1;}
.panel__body--pad{padding:12px;}
/* The height belongs to `.tablescroll`, not to `.tablewrap` — see the note in
   dash.css. With it on the wrap, the toolbar and the sort caption scrolled away
   with the rows, which is the whole thing splitting the scrollport out was for.
   Putting a bounded height here is also what finally makes the sticky header
   do something: it had a scrolling ancestor but never a short one. */
.panel .tablewrap{border:0; border-radius:0;}
.panel .tablescroll{max-height:340px;}
.panel--modal .tablescroll{max-height:none;}


/* The chart-type picker. This IS a segmented control — a choice between views
   of one thing — so it looks like one: an inset track with a raised chip for
   the current option, the same component language as .seg. It was three
   independent pills at 21px that agreed with nothing.  */
.chartpills{
  display:flex; align-items:center; gap:2px; margin-left:auto; flex:none;
  height:var(--ctl-h-sm); padding:2px;
  background:var(--bg-surface-alt); border:1px solid var(--border-color);
  border-radius:var(--radius-sm);
}
.chartpill{
  display:inline-flex; align-items:center; justify-content:center;
  height:calc(var(--ctl-h-sm) - 6px); padding:0 9px;
  border:0; border-radius:3px; background:none;
  font:inherit; font-size:11.5px; font-weight:600; line-height:1;
  color:var(--text-muted); cursor:pointer; white-space:nowrap;
  transition:background var(--transition-fast), color var(--transition-fast);
}
.chartpill:hover{color:var(--text-primary);}
.chartpill[aria-pressed="true"]{
  background:var(--bg-card); color:var(--text-primary);
  box-shadow:var(--shadow-xs);
}
.chartpill:focus-visible{outline:2px solid var(--color-accent);
  outline-offset:-1px;}

/* ── a chart head that gives way in priority order ───────────────────────
   Six word-buttons shared one row with the title, and the title is the only
   thing in it allowed to shrink — so it was the thing that lost: "Invoice
   amount b…". It is also the one thing on the row a reader actually reads.

   So the row degrades by what matters least, measured against the PANEL's
   width (a container query), not the window's: the chart is the narrow .75fr
   column beside the table, so a wide screen still leaves this head cramped.
     1. the actions (Pin, Expand) drop their words first — least read, and
        their icons are unambiguous;
     2. then the form switcher, whose glyphs are the forms themselves.
   Every control keeps its words in aria-label and title, so nothing becomes
   an unlabelled icon to a screen reader or a hover. */
.panel--chart{container-type:inline-size;}
.ctl__ico{display:none; width:15px; height:15px; flex:none;}
.panel__act{display:inline-flex; align-items:center; gap:5px; flex:none;}

@container (max-width: 640px){
  .panel__act .ctl__lbl{display:none;}
  .panel__act .ctl__ico{display:block;}
  .panel__act{padding-inline:6px;}
}
@container (max-width: 520px){
  .chartpill .ctl__lbl{display:none;}
  .chartpill .ctl__ico{display:block; width:14px; height:14px;}
  .chartpill{padding:0 7px;}
}
/* GROWS into the space the panel is given — and ONLY that space.

   Two requirements that pull against each other, and the flex BASIS is what
   reconciles them:
   - It must grow. The chart is the narrow .75fr column beside the table, and the
     grid stretches both to the taller one; at a fixed 290px every surplus pixel
     became dead space under the slider.
   - It must never take its height from its CONTENT. A horizontal bar chart with
     more categories than fit gives its .chartsizer an explicit tall height
     (rows x 22px — see resize() in page-ask.js) inside this frame, and the frame
     is meant to SCROLL. With `flex:1 1 auto` the frame's basis WAS that content,
     so it grew to 660px, the scroll never engaged, and the whole panel — and via
     the grid, the table beside it — stretched to match.

   `flex-basis:0` makes the height come from free space alone; the explicit
   min-height replaces the content-based automatic minimum, so a tall sizer
   cannot inflate the frame that way either. */
.chartwrap{flex:1 1 0; min-height:290px; padding:10px;}

/* ── the "more views" disclosure ──────────────────────────────────────────
   A full-width .btn read as an empty grey slab across the answer — a big
   nothing between the charts and the follow-ups. This is a disclosure, not an
   action: a faded accent wash that deepens on hover, a hairline that firms up,
   and a chevron that turns when the panels come down. */
.morebar{
  display:flex; align-items:center; justify-content:center; gap:8px;
  width:100%; height:var(--ctl-h); padding:0 14px; margin-top:2px;
  border:1px solid transparent; border-radius:var(--radius-md);
  background:var(--color-accent-soft);
  font:inherit; font-size:12px; font-weight:600; letter-spacing:.01em;
  color:var(--color-accent); cursor:pointer;
  transition:background var(--transition-fast), border-color var(--transition-fast),
             box-shadow var(--transition-fast);
}
.morebar:hover{
  background:var(--color-accent-soft-strong);
  border-color:color-mix(in srgb, var(--color-accent) 26%, transparent);
}
.morebar:focus-visible{outline:2px solid var(--color-accent); outline-offset:2px;}
.morebar__chev{
  width:13px; height:13px; flex:none;
  transition:transform var(--transition-base);
}
.morebar[aria-expanded="true"] .morebar__chev{transform:rotate(180deg);}
/* The panels themselves arrive rather than appear. `hidden` is display:none, so
   this runs on reveal — one pass, no layout thrash.
   NO fill mode, deliberately: the `to` keyframe IS the panel's resting state, so
   there is nothing to hold, and a filled animation would keep a transform on the
   element for the life of the page — promoting a layer and out-ranking every
   later declaration in the cascade. That combination is what made hovering one
   card blink all of them; see the note in controls.test.js. */
.chart-extra{animation:chartIn var(--transition-base) ease-out;}
@keyframes chartIn{
  from{opacity:0; transform:translateY(-6px);}
  to{opacity:1; transform:none;}
}
@media (prefers-reduced-motion:reduce){
  .chart-extra{animation:none;}
  .morebar,.morebar__chev{transition:none;}
}

/* ── what a plot is actually showing ──────────────────────────────────────
   A windowed chart must say so, and say it in a way that reads as a sentence
   rather than a debug line: the COUNT is the fact, the rest is context. */
.plotnote{
  display:flex; align-items:baseline; gap:5px; flex-wrap:wrap;
  padding:0 10px 2px; font-size:11.5px; color:var(--text-muted);
}
.plotnote__n{color:var(--text-primary); font-weight:650;
  font-variant-numeric:tabular-nums;}
.plotnote__span{margin-left:auto; color:var(--text-faint);}
.plotcaveat{
  display:flex; gap:6px; padding:0 10px 8px;
  font-size:11.5px; line-height:1.45; color:var(--color-warning);
}
.plotrow{display:flex; align-items:center; gap:9px; padding:0 10px 10px;}


/* A horizontal bar draws EVERY category label (autoSkip is off — an unnamed bar
   is not a bar chart), so the canvas has to be tall enough for them and the
   frame scrolls. At a fixed height 37 dates overlapped into an unreadable
   smear down the y axis. */
.chartsizer{height:100%; min-height:100%;}
.chartwrap--scroll{overflow-y:auto; overscroll-behavior:contain;}
.chartwrap--scroll .chartsizer{min-height:0;}

/* ── composer ───────────────────────────────────────────────────────────── */
/* ── the composer ────────────────────────────────────────────────────────────
   The single most-used control in the product, and the one a user judges the
   whole thing by: it is what they touch before every answer.

   The bar it sits in is a PANEL — one step behind the canvas — with the box a
   white sheet raised off it. That is the same surface logic as a visual on the
   report canvas, applied to the one control that matters most, and it is why
   the composer reads as an object you type into rather than a border at the
   bottom of the page.

   Six things make the difference between a styled textarea and a finished one:

     1. THE WHOLE BOX IS THE TARGET. `cursor:text` on the container and a click
        handler is not needed — the textarea fills it. Clicking the padding used
        to do nothing, which on a 56px-tall control is most of its area.

     2. FOCUS IS A LIFT, NOT A RING ALONE. Accent border, a soft accent glow, a
        deeper shadow and 1px of rise. It is 200ms and reads as the control
        coming forward to meet you.

     3. THE SEND BUTTON IS A SQUARE WITH A RADIUS, NOT A CIRCLE. A circle beside
        a 10px-radius box is two different languages; matching the box's inner
        radius makes them one object. It is also DISABLED until there is
        something to send, so the primary action is never a lie.

     4. THE TEXTAREA GROWS, AND THE BOX GROWS WITH IT. Up to 8 lines, then the
        textarea scrolls inside a box that has stopped growing — so the page
        never loses its answer to a long question.

     5. STREAMING IS A STATE OF THE BAR. While an answer streams, the send
        button becomes Stop and the bar carries a moving accent hairline along
        its top edge — the progress signal is on the control you are waiting at,
        not somewhere else on the page.

     6. THE HINT IS ONE LINE AND IT RECEDES. It matters on the first visit and
        never again, so it is 11px, faint, and drops out entirely on a narrow
        screen where it would wrap to two lines.                              */
/* The bar stops being a chrome band. `--bg-panel` + a top border made it a
   separate strip of furniture pinned under the page — the composer sat ON
   something rather than IN the page. On the canvas colour with no rule, the
   composer box is the only object there, which is the correct reading: it is
   the instrument, the bar is just where it lives. The box keeps its own
   border and shadow, so it is still clearly a surface you type into. */
.composerbar{
  flex:none; position:relative;
  background:var(--bg-app);
  padding:var(--sp-3) var(--sp-5) var(--sp-4);
}
.composerbar__inner{max-width:var(--page-max); margin:0 auto; min-width:0;}

/* While an answer streams: a 2px accent line travelling along the top edge of
   the bar. On the control the user is waiting at — not a spinner elsewhere. */
.composerbar.is-streaming::before{
  content:''; position:absolute; left:0; right:0; top:-1px; height:2px;
  background:linear-gradient(90deg,
    transparent 0%, var(--color-accent) 42%, var(--color-accent) 58%,
    transparent 100%);
  background-size:220% 100%;
  animation:composerflow 1.6s linear infinite;
}
@keyframes composerflow{
  from{background-position:120% 0;} to{background-position:-120% 0;}
}

.composerbox{
  display:flex; align-items:flex-end; gap:var(--sp-2);
  padding:7px 7px 7px var(--sp-4);
  background:var(--bg-card);
  border:1px solid var(--border-strong);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow-sm);
  /* The whole box is the text target, not just the textarea's own rectangle. */
  cursor:text;
  transition:border-color var(--transition-base) var(--ease-out),
             box-shadow var(--transition-base) var(--ease-out),
             transform var(--transition-base) var(--ease-out);
}
.composerbox:hover:not(:focus-within){border-color:var(--text-faint);}
.composerbox:focus-within{
  border-color:var(--color-accent);
  box-shadow:var(--shadow-md), 0 0 0 4px var(--color-accent-soft);
  transform:translateY(-1px);
}
.composerbox textarea{
  flex:1 1 auto; min-width:0;
  border:0; outline:none; background:none; resize:none;
  font:inherit; font-size:var(--fs-lede); line-height:1.5;
  color:var(--text-primary);
  /* 8 lines, then the textarea scrolls and the BOX stops growing — so a long
     question never pushes the answer off the screen. */
  max-height:calc(1.5em * 8); min-height:calc(1.5em * 1);
  padding:8px 0; margin:0;
  font-variant-numeric:normal;   /* prose, not a column of figures */
}
.composerbox textarea::placeholder{color:var(--text-faint);}
/* The textarea's own scrollbar, once it has one: thin and only over the box. */
.composerbox textarea{scrollbar-width:thin;}

/* ── the send / stop button ──────────────────────────────────────────────────
   A square with the box's inner radius, not a circle: a circle beside a 10px
   box is two languages in one object. Same press and focus behaviour as every
   other button in the product. */
/* 36px from --ctl-h-lg, not a hand-set 34: DESIGN.md #4 requires every
   interactive control to claim its height from a token, and 34 was off the
   scale by two pixels for no reason.

   The gloss is gone — an inset white highlight plus a coloured drop shadow is
   a 2010 button, and against the flat surfaces everything else in this
   product uses it was the one object pretending to be three-dimensional. It
   is still a filled accent button, because it IS the primary action; it just
   stops advertising. */
.iconbtn{
  flex:none; width:var(--ctl-h-lg); height:var(--ctl-h-lg);
  display:grid; place-items:center;
  border:1px solid var(--color-accent); border-radius:var(--radius-sm);
  background:var(--color-accent); color:var(--color-accent-on);
  cursor:pointer;
  transition:background var(--transition-fast), border-color var(--transition-fast),
             box-shadow var(--transition-fast), transform 60ms var(--ease-out),
             opacity var(--transition-fast);
}
.iconbtn:hover:not([disabled]){
  background:var(--color-accent-dark); border-color:var(--color-accent-dark);
}
.iconbtn:active:not([disabled]){
  transform:translateY(1px); box-shadow:inset 0 1px 2px rgba(0,0,0,.18);
}
.iconbtn:focus-visible{outline:2px solid var(--color-accent); outline-offset:2px;}
/* Disabled until there is something to send. A primary action that is always
   available but does nothing is a lie about the state of the form. */
.iconbtn[disabled]{
  background:var(--bg-surface-alt); border-color:var(--border-color);
  color:var(--text-faint); box-shadow:none; cursor:not-allowed;
}
.iconbtn svg{width:16px; height:16px;}
/* Stop swaps the accent for danger and nothing else — it occupies the same
   slot as Send, so it must be the same object in a different state, not a
   second button design. It lost the same gloss for the same reason. */
.iconbtn--stop{
  background:var(--color-danger); border-color:var(--color-danger); color:#fff;
}
.iconbtn--stop:hover:not([disabled]){
  background:var(--color-danger-dark, var(--color-danger));
  border-color:var(--color-danger-dark, var(--color-danger));
}

/* A secondary control inside the box (a model picker, an attach) — ghost, so it
   does not compete with Send for the eye. */
.composerbox__tool{
  flex:none; width:30px; height:30px; margin-bottom:2px;
  display:grid; place-items:center;
  border:0; border-radius:var(--radius-sm); background:none;
  color:var(--text-faint); cursor:pointer;
  transition:background var(--transition-fast), color var(--transition-fast);
}
.composerbox__tool:hover{background:var(--bg-hover); color:var(--color-accent);}
.composerbox__tool:focus-visible{outline:2px solid var(--color-accent);
  outline-offset:-2px;}
.composerbox__tool svg{width:16px; height:16px;}

/* The hint. Matters on the first visit and never again — and on the first
   visit the WELCOME is showing one, in its own colophon. Two copies of the
   same three shortcuts, ten pixels apart, is the page telling you twice.
   This copy stands down while the empty state is up and takes over once
   there is a transcript, which is the only time it is the only copy.
   (The markup stays in index.html either way: controls.test.js greps it for
   the <kbd> literals, and the element is the one that survives.) */
.chat.is-empty .composerbar__hint{display:none;}
.composerbar__hint{
  display:flex; align-items:center; justify-content:center; gap:10px;
  font-size:11px; color:var(--text-faint); margin-top:7px;
  min-height:1.2em;
}
.welcome__foot kbd,
.composerbar__hint kbd{
  font-family:var(--font-mono); font-size:10px;
  padding:1px 4px; border-radius:3px;
  background:var(--bg-surface-alt); border:1px solid var(--border-color);
  color:var(--text-muted);
}
/* On a narrow screen the hint wraps to two lines and costs more than it teaches. */
@media (max-width:720px){
  .composerbar__hint{display:none;}
  .composerbar{padding-left:var(--sp-3); padding-right:var(--sp-3);}
}

@media (prefers-reduced-motion:reduce){
  .composerbox,.iconbtn{transition:border-color var(--transition-fast);}
  .composerbox:focus-within{transform:none;}
  .iconbtn:active{transform:none;}
  .composerbar.is-streaming::before{animation:none; background:var(--color-accent);}
}


.scrollbtn{
  position:absolute; right:26px; bottom:110px; z-index:20;
  width:34px; height:34px; border-radius:50%; display:grid; place-items:center;
  background:var(--bg-card); border:1px solid var(--border-color);
  box-shadow:var(--shadow-md); color:var(--text-secondary); cursor:pointer;
}
.scrollbtn svg{width:16px; height:16px;}

/* ── welcome ──────────────────────────────────────────────────────────────
   THE DEAD SPACE, and why it was structural rather than a padding value.

   `.welcome` hung off the TOP of the scrollport (`margin:5vh auto 0`) while
   the composer is pinned to the bottom by `.chat{grid-template-rows:1fr auto}`.
   Everything between them was one pooled void — on a 900px window that is
   roughly 500px of nothing, and no amount of tuning the margin fixes it,
   because the slack all collects in a single place.

   The fix is to let the empty state OWN the height and distribute the slack
   as gaps instead: `.chat__col{min-height:100%}` (it resolves because
   `.chat__scroll` is the definite `1fr` row) and `.welcome` then fills that
   column and centres its own bands. The head sits above the optical centre,
   the starters at it, the hint at the foot — three measured gaps rather than
   one hole.

   Two things went with it, both flagged by the design language rather than by
   taste:
     * `.welcome__mark` — a 46x46 SOLID --color-accent square at an off-scale
       13px radius. DESIGN.md #1: the accent marks state as an EDGE, and there
       is never "an accent background larger than a chip". It was also the
       loudest object on a page whose subject is the question you are about to
       type.
     * the centred column. Every other page in the product opens with a
       left-aligned eyebrow -> title -> lede; this one alone was centre-set,
       which is what made it read as a splash screen rather than a page. */
.welcome{
  flex:1 1 auto; min-height:0;
  display:flex; flex-direction:column; justify-content:center;
  gap:var(--sp-6); max-width:none; margin:0; text-align:left;
}
.welcome__eyebrow{
  font-size:var(--fs-micro); font-weight:700; letter-spacing:.09em;
  text-transform:uppercase; color:var(--text-faint); margin:0 0 var(--sp-2);
}
.welcome h1{
  font-size:var(--fs-title); font-weight:670; letter-spacing:-.02em;
  margin:0 0 var(--sp-2); text-wrap:balance;
}
/* The lede takes a reading measure of its own rather than the full column —
   a 1280px-wide sentence is a line the eye has to track back across. */
.welcome p{
  color:var(--text-muted); margin:0; font-size:var(--fs-lede);
  line-height:1.55; max-width:58ch;
}

/* The starters, as lanes. `auto-fit` + `minmax` means the module groups sit
   side by side on a wide window and stack on a narrow one without a
   breakpoint per count. */
.welcome__deck{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(19rem, 1fr));
  gap:var(--sp-5) var(--sp-6);
}
.welcome__group{min-width:0;}
.welcome__label{
  font-size:var(--fs-micro); font-weight:700; letter-spacing:.09em;
  text-transform:uppercase; color:var(--text-muted);
  padding-bottom:var(--sp-2);
  /* The group's own hue, as a 2px EDGE under its heading. Colour marks which
     group this is; the words say what it is. Ink stays --text-muted: a coloured
     micro-label would break commitment #2, and a coloured rule does not. */
  border-bottom:2px solid var(--hue, var(--border-color));
  margin-bottom:var(--sp-1);
  display:flex; align-items:center; gap:7px;
}
/* The 6px dot, the same device the dashboard tiles use for a card's series hue. */
.welcome__label::before{
  content:''; width:6px; height:6px; border-radius:50%; flex:none;
  background:var(--hue, var(--color-accent));
}
.welcome__group .suggest__go{color:var(--hue, var(--text-faint));}
.welcome__chips{display:flex; flex-direction:column;}

/* THE CHIP IS GONE. It was `border + --bg-card` — a bordered-and-filled box,
   which made the starters a THIRD such family on a screen DESIGN.md #6 allows
   exactly one of (the result panel is the one that earns it, because it is
   the substance). As hairline rows they read as a list of questions, which is
   what they are, and ten of them stop looking like ten buttons.

   Also the reason follow-ups improve for free: `.suggest--inline` builds on
   this same base, so the answer's "Next:" block becomes a list too. */
.suggest{
  display:flex; align-items:center; gap:var(--sp-3);
  width:100%; text-align:left; padding:9px 0 9px 2px;
  border:0; border-bottom:1px solid var(--border-color);
  border-radius:0; background:none; font:inherit;
  font-size:var(--fs-body); line-height:1.4; color:var(--text-secondary);
  cursor:pointer;
  transition:color var(--transition-fast) var(--ease-out),
             padding-left var(--transition-fast) var(--ease-out),
             border-color var(--transition-fast) var(--ease-out);
}
.suggest:last-child{border-bottom:0;}
/* A row reveals rather than grows: it takes the ink it was missing and steps
   toward the reader by 3px. The same gesture the conversation rows and the
   report rows already use, so a hover means one thing across the product. */
.suggest:hover{
  color:var(--text-primary); padding-left:5px;
  border-color:var(--border-strong);
}
.suggest:focus-visible{
  outline:2px solid var(--color-accent); outline-offset:2px; border-radius:3px;
}
/* The arrow is the affordance, and it only exists on approach — a static
   chevron on every one of ten rows is ten marks competing with the text. */
.suggest__go{
  margin-left:auto; flex:none; width:13px; height:13px; color:var(--text-faint);
  opacity:0; transform:translateX(-3px);
  transition:opacity var(--transition-fast) var(--ease-out),
             transform var(--transition-fast) var(--ease-out);
}
.suggest:hover .suggest__go,.suggest:focus-visible .suggest__go{
  opacity:1; transform:none; color:var(--color-accent);
}
@media (hover:none){
  /* No hover to reveal it, so it is simply always there. */
  .suggest__go{opacity:1; transform:none;}
}

/* The keyboard hint belongs to the empty state, where it is read once, rather
   than under the composer forever. */
.welcome__foot{
  display:flex; align-items:center; gap:var(--sp-4); flex-wrap:wrap;
  padding-top:var(--sp-4); border-top:1px solid var(--border-color);
  font-size:var(--fs-micro); color:var(--text-faint);
}

/* ── follow-ups ─────────────────────────────────────────────────────────────
   The model ends its answer with a "Next:" block. Those lines used to render
   TWICE — as inert markdown bullets inside the prose, and again as chips below
   the attribution footer, where nobody scrolls. The block is now cut out of the
   prose and rendered once, here, as real buttons directly under the answer. */
.followups{
  display:flex; flex-direction:column; gap:7px;
  padding-top:2px;
}
.followups__label{
  font-size:9.5px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--text-faint);
}
.followups__row{display:flex; flex-direction:column; gap:6px; align-items:stretch;}
/* One per line rather than wrapped chips: these are whole sentences, and a row
   of wrapped sentence-length chips is unreadable. Left-aligned with the icon in
   a fixed gutter so the text starts at the same x on every row. */
.suggest--inline{
  display:flex; align-items:flex-start; gap:9px;
  padding:8px 12px; font-size:13px; line-height:1.45;
  max-width:52rem;   /* the prose measure, so they read as part of the answer */
}
.suggest--inline svg{
  width:13px; height:13px; flex:none; margin-top:3px; color:var(--text-faint);
  transition:color var(--transition-fast);
}
.suggest--inline:hover svg{color:var(--color-accent);}
.suggest--inline span{min-width:0;}

/* ── the conversations rail ────────────────────────────────────────────────
   A chat history is a primary object here, so the rail is built like one:
   a primary action, a search field, recency-grouped rows, and a footer that
   stays reachable. The rows themselves used to be styled `.thread` — the same
   class app.css used for the message LIST — which is why every one of them
   rendered as a centred column with its title clipped at both ends. See the
   note on `.convo` below.                                                   */
/* `.sidebar__extra` — the rail's page slot — is in app.css: reports.html uses
   the same slot and does not load this file. */

/* The rail's primary action — but NOT the page's. It was a full-width solid
   accent slab with a shadow, which made the loudest, highest-contrast object
   on the entire screen a button you press once at the start of a session,
   sitting above the conversation you are actually reading. DESIGN.md #1 is
   explicit that the accent is an edge and never a fill this size, and the
   product's own hierarchy allows one filled accent action per view — which on
   this page is Send, the thing you press constantly.

   So it de-escalates to surface contrast: a --bg-card row on the panel, with
   the accent kept on the glyph. It still reads as the primary thing in the
   rail (nothing else there has a border and a fill) without competing with
   the composer for the eye. Left-aligned, because it now sits above a list of
   left-aligned rows and a centred label in a full-width control was the odd
   one out. */
.newchat{
  display:flex; align-items:center; justify-content:flex-start; gap:8px;
  width:100%; flex:none; height:var(--ctl-h); padding:0 10px;
  border:1px solid var(--border-on-chrome); border-radius:var(--radius-md);
  background:var(--surface-on-chrome); font:inherit; font-size:var(--fs-chrome);
  font-weight:600; color:var(--text-on-chrome); cursor:pointer;
  transition:border-color var(--transition-fast) var(--ease-out),
             background var(--transition-fast) var(--ease-out);
}
.newchat:hover{border-color:var(--accent-on-chrome); background:var(--hover-on-chrome);}
.newchat:focus-visible{
  outline:2px solid var(--accent-on-chrome); outline-offset:2px;
}
.newchat:active{background:var(--active-on-chrome);}
.newchat svg{width:14px; height:14px; flex:none; color:var(--accent-on-chrome);}

/* ── heading ── */
.convohead{
  display:flex; align-items:baseline; gap:7px; flex:none; padding:2px 2px 0;
}
.convohead__label{
  font-size:10px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--text-on-chrome-muted);
}
/* The count, so a long history reads as a quantity rather than just a long
   scroll. Tabular so it does not jiggle as it changes. */
.convohead__n{
  margin-left:auto; font-size:10.5px; color:var(--text-on-chrome-muted);
  font-variant-numeric:tabular-nums;
}

/* ── search ── */
.convosearch{
  display:flex; align-items:center; gap:6px; flex:none;
  padding:0 8px; min-height:30px;
  border:1px solid var(--border-on-chrome); border-radius:var(--radius-md);
  background:var(--surface-on-chrome);
  transition:border-color var(--transition-fast), background var(--transition-fast),
             box-shadow var(--transition-fast);
}
.convosearch:focus-within{
  border-color:var(--accent-on-chrome); background:var(--surface-on-chrome);
  box-shadow:0 0 0 3px var(--color-accent-soft);
}
.convosearch>svg{width:13px; height:13px; flex:none; color:var(--text-on-chrome-muted);}
.convosearch__input{
  flex:1 1 auto; min-width:0; border:0; background:none; outline:none;
  font:inherit; font-size:12px; color:var(--text-on-chrome); padding:5px 0;
}
.convosearch__input::placeholder{color:var(--text-on-chrome-muted);}
/* Hidden until there is something to clear — a permanently visible × on an
   empty field is a control that does nothing. */
.convosearch__x{
  flex:none; border:0; background:none; padding:2px; border-radius:4px;
  color:var(--text-on-chrome-muted); cursor:pointer; display:grid; place-items:center;
}
.convosearch__x:hover{background:var(--hover-on-chrome); color:var(--text-on-chrome);}
.convosearch__x svg{width:11px; height:11px;}

/* ── the list ── */
/* Scrolls on its own so the footer stays reachable at the foot of the rail
   instead of being pushed off the bottom by a long history. The negative
   margin lets a row's hover background reach the rail's edges while the
   scrollbar stays inside. */
.convolist{
  flex:1 1 auto; min-height:0; overflow-y:auto; overflow-x:hidden;
  margin:0 -4px; padding:0 4px 2px;
  display:flex; flex-direction:column; gap:1px;
}
/* The recency divider. Shown only when there is more than one group — with a
   single bucket the label is a heading over the whole list, which is noise. */
/* The recency divider (YESTERDAY / PREVIOUS 30 DAYS). On-chrome ink: it lives
   in the rail, which a theme may paint as a deep block. */
.convogroup{
  flex:none; padding:10px 10px 4px;
  font-size:9.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:var(--text-on-chrome-muted);
}
.convogroup:first-child{padding-top:2px;}

.convoquiet{
  padding:14px 10px; border:1px dashed var(--border-on-chrome);
  border-radius:var(--radius-md); background:var(--surface-on-chrome);
}
.convoquiet__t{font-size:12px; font-weight:600; color:var(--text-on-chrome);}
.convoquiet__s{font-size:11px; color:var(--text-on-chrome-muted); margin-top:3px;
  line-height:1.45;}

/* Components the rail SHARES with the answer column. They are styled for a
   light card, which is right where they normally live and wrong in a rail a
   theme has painted as a deep block -- 'Clear all' was dark ink on dark blue.
   Scoped rather than re-pointed: .tool is also the Copy and Export buttons
   under every answer, and those must keep the ordinary ink. */
.sidebar .tool{color:var(--text-on-chrome-muted);}
.sidebar .tool:hover{background:var(--hover-on-chrome); color:var(--text-on-chrome);}
.sidebar .tool--accent{color:var(--accent-on-chrome);}
.sidebar .convoclear__ask{color:var(--text-on-chrome);}
.sidebar .btn{
  background:var(--surface-on-chrome); border-color:var(--border-on-chrome);
  color:var(--text-on-chrome);
}
.sidebar .btn--danger{background:transparent; color:var(--color-danger);}

/* ── one row ────────────────────────────────────────────────────────────────
   One line, never reflowing. The row is a DIV holding a button, not a button
   itself: nesting the rename/delete buttons inside a button is invalid HTML and
   browsers drop or reparent the inner ones.

   The action buttons are ABSOLUTELY POSITIONED over the timestamp rather than
   appended as flex siblings — as siblings they added width on hover, the row
   wrapped to three lines and the title spilled out of the rail. Nothing here
   changes size when the pointer arrives; only what is painted.               */
.convo{
  position:relative;
  /* flex-direction and justify-content are stated explicitly, not left to the
     default. This component was previously called `.thread`, the same name
     app.css used for the MESSAGE LIST — a flex column. app.css set
     flex-direction:column and this rule did not override it, so every row in
     the rail became a centred stack: title on one line, timestamp beneath, and
     a long title clipped at both ends. The name is fixed; declaring the axis is
     what makes the rule immune to it happening again. */
  display:flex; flex-direction:row; justify-content:flex-start;
  align-items:stretch; flex-wrap:nowrap; flex:none;
  width:100%; max-width:100%; overflow:hidden;
  border-radius:var(--radius-sm);
  transition:background var(--transition-fast), color var(--transition-fast);
}
.convo__open{
  flex:1 1 auto; min-width:0;
  display:flex; flex-direction:row; align-items:center; gap:8px; flex-wrap:nowrap;
  padding:7px 10px; border:0; background:none; font:inherit;
  font-size:12.5px; line-height:1.35; color:var(--text-on-chrome-muted);
  cursor:pointer; text-align:left; min-height:32px;
}
.convo:hover{background:var(--hover-on-chrome);}
.convo:hover .convo__open{color:var(--text-on-chrome);}
.convo.is-active{background:var(--active-on-chrome);}
.convo.is-active .convo__open{color:var(--accent-on-chrome); font-weight:600;}
.convo__open:focus-visible{outline:2px solid var(--accent-on-chrome);
  outline-offset:-2px; border-radius:var(--radius-sm);}
/* A 2px accent rule marks the open conversation without shouting — the same
   device the sidebar nav and the dashboard page tabs use. */
.convo.is-active::before{
  content:''; position:absolute; left:0; top:6px; bottom:6px; width:2px;
  border-radius:0 2px 2px 0; background:var(--accent-on-chrome); z-index:1;
}
.convo__title{flex:1 1 auto; min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap;}
.convo__when{
  flex:none; font-size:10.5px; color:var(--text-on-chrome-muted); white-space:nowrap;
  font-variant-numeric:tabular-nums;
}
/* The action buttons sit over the end of the title and mask it as they fade in.
   That mask has to be OPAQUE where the buttons are, and this is where it is easy
   to get wrong: `--bg-hover` and `--bg-active` are translucent overlays (4.5%
   and 9–17% alpha), so a gradient ending in one of them ends in something you
   can still read through — the icons paint straight over the words.

   So the mask is TWO stacked gradients: an opaque `--convo-bg` underneath and
   the row's own translucent tint on top, both fading from transparent at the
   same rate. At the left edge both are clear, so the fade is real; under the
   buttons the pair composites to exactly the colour of the row behind them.

   `--convo-bg` is a local variable rather than `--bg-sidebar` directly, so the
   rail can be moved out of the sidebar without this rule going wrong silently. */
.convolist{--convo-bg:var(--bg-sidebar);}
.convo__acts{
  position:absolute; right:4px; top:50%; transform:translateY(-50%);
  display:none; gap:1px; align-items:center; padding-left:20px;
  background:
    linear-gradient(90deg, transparent, var(--bg-hover) 45%),
    linear-gradient(90deg, transparent, var(--convo-bg) 45%);
}
.convo:hover .convo__acts,.convo:focus-within .convo__acts{display:flex;}
.convo:hover .convo__when,.convo:focus-within .convo__when{visibility:hidden;}
.convo.is-active .convo__acts{
  background:
    linear-gradient(90deg, transparent, var(--bg-active) 45%),
    linear-gradient(90deg, transparent, var(--convo-bg) 45%);
}
/* Hover on an active row stacks both tints, matching the row underneath. */
.convo.is-active:hover .convo__acts{
  background:
    linear-gradient(90deg, transparent, var(--bg-hover) 45%),
    linear-gradient(90deg, transparent, var(--bg-active) 45%),
    linear-gradient(90deg, transparent, var(--convo-bg) 45%);
}
.convo__acts button{
  border:0; background:none; padding:4px; border-radius:5px; cursor:pointer;
  color:var(--text-on-chrome-muted); display:grid; place-items:center;
}
.convo__acts button:hover{background:var(--surface-on-chrome); color:var(--text-on-chrome);}
.convo__acts button svg{width:12px; height:12px;}

/* Rename in the row. Sized to the row so the list does not jump when it opens. */
.convo__edit{
  width:100%; margin:1px 0; padding:6px 9px;
  border:1px solid var(--accent-on-chrome); border-radius:var(--radius-sm);
  /* Ink stays PAGE ink: this paints its own --bg-card, so it is a surface of
     its own rather than something sitting on the rail. Only the ring, which
     is drawn against the rail, takes the chrome's accent. */
  background:var(--bg-card); color:var(--text-primary);
  font:inherit; font-size:12.5px; font-weight:600; outline:none;
  box-shadow:0 0 0 3px var(--color-accent-soft);
}

/* ── footer ── */
.convoclear{flex:none; padding-top:8px; border-top:1px solid var(--border-color);}
.convoclear .tool{width:100%; justify-content:flex-start;}
.convoclear__ask{
  font-size:11.5px; color:var(--text-secondary); line-height:1.4;
  padding:2px 2px 7px;
}
.convoclear__row{display:flex; gap:6px;}
.convoclear__row .btn{flex:1 1 auto; justify-content:center;}

/* ── login gate ─────────────────────────────────────────────────────────── */
.gate{
  position:fixed; inset:0; z-index:100; display:grid; place-items:center;
  background:var(--bg-page); padding:24px;
}
.gate__card{
  width:min(400px,100%); background:var(--bg-card);
  border:1px solid var(--border-color); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-lg); padding:28px;
}
.gate__mark{
  width:44px; height:44px; border-radius:12px; margin:0 auto 16px;
  background:var(--color-accent); color:var(--color-accent-on);
  display:grid; place-items:center;
}
.gate__mark svg{width:23px; height:23px;}

/* ── pin dialog ─────────────────────────────────────────────────────────── */
.pin{display:grid; grid-template-columns:1fr 1fr; gap:18px;}
@media (max-width:820px){.pin{grid-template-columns:1fr;}}
.pin__preview{
  background:var(--bg-surface-alt); border:1px solid var(--border-color);
  border-radius:var(--radius-md); padding:13px; min-height:230px;
  display:flex; flex-direction:column; gap:9px;
}
/* The preview hosts a real visual, so it needs the same shape a tile body has:
   a bounded, flexible box the chart can measure itself against. */
.pin__frame{flex:1; min-height:0; display:flex; flex-direction:column; gap:7px;}
.pin__vizbody{
  flex:1; min-height:170px; display:flex; flex-direction:column; gap:7px;
  overflow:auto;
}
.pin__why{
  font-size:12px; color:var(--text-muted); background:var(--color-info-soft);
  border-radius:var(--radius-sm); padding:7px 10px;
}
.pin__form{display:flex; flex-direction:column; gap:11px; min-width:0;}
.pin__row{display:grid; grid-template-columns:1fr 1fr; gap:9px;}
.pin__form .label{margin-bottom:3px;}
/* Long-hand on the select, and this is the reason `.select--sm` carries the same
   note: the `padding` SHORTHAND resets padding-right to 9px, and 9px is not the
   30px the chevron occupies — the chosen option then reads straight through the
   arrow. The input has no chevron and can keep the shorthand. */
.pin__form .input{padding:6px 9px; font-size:13px;}
.pin__form .select{padding-top:6px; padding-bottom:6px; padding-left:9px;
  font-size:13px;}

/* Email dialog. Its own classes rather than reusing .is-bad bare: that
   modifier is only ever DEFINED scoped (.trail__step.is-bad,
   .answer__by.is-bad), so an unscoped one here would have silently coloured
   nothing and the over-budget warning would have read as ordinary grey. */
.mailform{display:flex; flex-direction:column; gap:12px; min-width:0;}
/* Monospace, and not a preference: the table is space-PADDED to line up.
   In the proportional body font the columns come out ragged and the preview
   misrepresents what is actually being sent. */
.mailform .textarea{
  min-height:260px; font-size:12.2px; line-height:1.5;
  font-family:var(--font-mono);
}
.mailbudget{
  font-size:12.4px; color:var(--text-muted);
  margin-right:auto; align-self:center; min-width:0;
}
.mailbudget.is-bad{color:var(--color-danger);}

.usage__grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); gap:10px;}

@media print{
  .composerbar,.scrollbtn,.trail,.tools{display:none !important;}
  .chat__scroll{overflow:visible;}
}
