/* ==========================================================================
   chat.css — the assistant surface (CONTRACT §7 `/` and `/t/<thread_id>`)

   Everything here lives in `@layer chat`, the slot tokens.css declares between
   `components` and `surfaces`. That placement is load-bearing: several classes
   in the chat templates collide with components.css names but wrap *different
   elements* (`.radio` is a <label> here, not an <input>; `.switch` ships
   without a `__track`), and the layer lets this file correct them without an
   arms race of selector specificity or a single `!important`.

   Rules of the house observed throughout:
   · Every length, colour, duration and radius is a token. The only literals
     are hairlines (`1px`), percentages, and the `85dvh` the dialog spec names.
   · No purple, no glow, no neon, no gradient fill, no blurred chrome.
   · Amber (`--warning`) means one thing on this screen: a step that was not
     deterministic. It is never spent on decoration or on "slow".
   · Motion is never the only signal. Every animation here degrades to a
     static, still-legible state under `prefers-reduced-motion`.

   Not owned here, deliberately:
   · `.sr-only`, `.icon`, `.tabular`, `.truncate`  → reset.css `@layer utilities`
     (which outranks this layer — redeclaring them here would silently lose).
   · `.btn` `.badge` `.chip` `.table` `.field` `.popover` `.avatar` `.kbd`
     `.segmented` `.tabs`                          → components.css
     This file only ever adds chat-specific *modifiers* to those, and does so
     through their documented custom-property hooks (`--badge-bg`, …).

   Breakpoints are app.css's four, and only those: 1440 / 1200 / 900 / 640.
   ========================================================================== */

@layer chat {
  /* ======================================================================
     Motion primitives owned by this surface
     ====================================================================== */

  /* The live stage marker: a 1px underline that travels the width of the row
     once and repeats. Chosen over a spinner or bouncing dots because it says
     "this stage is open" without implying a percentage of completion that the
     runtime does not know. */
  @keyframes agp-trace-scan {
    from { transform: translateX(-100%); }
    to   { transform: translateX(400%); }
  }

  /* Steps, not a fade: a text caret is either drawn or it is not. */
  @keyframes agp-caret-blink {
    0%, 49%   { opacity: 1; }
    50%, 100% { opacity: 0; }
  }

  /* ======================================================================
     Surface frame — scroller, dock, connection state
     ====================================================================== */

  /* Two rows and nothing else: the transcript scrolls, the dock does not.
     `block-size: 100%` makes the chat fill whatever box the shell hands it;
     `minmax(0, 1fr)` is what actually lets the scroller shrink below its
     content instead of pushing the composer off the bottom of the viewport. */
  .chat {
    /* One gutter for the transcript, the empty state and the composer, so all
       three share an edge. Narrowed at the handheld breakpoint only. */
    --chat-gutter: var(--space-6);

    position: relative;
    display: grid;
    grid-template-rows: minmax(0, 1fr) auto;
    block-size: 100%;
    min-block-size: 0;
    min-inline-size: 0;
    background: radial-gradient(circle at 50% 0%, rgba(16, 185, 129, 0.04) 0%, transparent 45%),
                radial-gradient(circle at 100% 100%, rgba(99, 102, 241, 0.03) 0%, transparent 40%),
                var(--bg-canvas);
  }

  .chat__scroll {
    min-block-size: 0;
    min-inline-size: 0;
    overflow-y: auto;
    /* The page body is never allowed a sideways scrollbar (app.css sets
       `overflow-x: hidden` on it). Anything wider than the column scrolls
       inside `.artifact__scroll`, never here. */
    overflow-x: hidden;
    overscroll-behavior-y: contain;
    /* Scroll anchoring fights the autoscroll chat.js runs while prose streams
       in: the browser keeps pinning to a node that is still growing. */
    overflow-anchor: none;
    /* Keeps `scrollIntoView` from parking a message under the dock. */
    scroll-padding-block-end: var(--space-8);
    padding: var(--space-6) var(--chat-gutter) var(--space-8);
  }

  /* The transcript, the empty state and the composer all resolve to the same
     measure and the same centre line. */
  .chat__scroll > *,
  .chat__dock > * {
    inline-size: 100%;
    max-inline-size: var(--chat-measure);
    margin-inline: auto;
  }

  /* `position: sticky` is belt and braces. In the intended layout the dock is
     simply the second grid row and never moves; if the chat is ever mounted
     inside a scrolling ancestor instead, sticky is what keeps the composer
     reachable rather than stranded at the bottom of a long page. */
  .chat__dock {
    position: sticky;
    inset-block-end: 0;
    z-index: var(--z-sticky);
    padding: var(--space-2) var(--chat-gutter) var(--space-4);
    background: linear-gradient(180deg, transparent 0%, color-mix(in srgb, var(--bg-canvas) 85%, transparent) 25%, var(--bg-canvas) 100%);
    backdrop-filter: blur(8px);
    /* `viewport-fit=cover` is set in base.html, so the home indicator is ours
       to clear. */
    padding-block-end: max(var(--space-4), env(safe-area-inset-bottom));
  }

  /* A lost stream is a state, not an event (see assistant.html): it holds its
     ground until it resolves, and it carries the control that fixes it. */
  .chat__connection {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-block-end: var(--space-2);
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
    background-color: var(--warning-subtle);
    border: 1px solid var(--warning-border);
    border-radius: var(--radius-control);
    color: var(--warning-text);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  /* Degraded further than "retrying": nothing is coming back on its own. */
  .chat__connection.is-lost {
    background-color: var(--danger-subtle);
    border-color: var(--danger-border);
    color: var(--danger-text);
  }

  .chat__connection-mark {
    flex: 0 0 auto;
    inline-size: var(--size-dot);
    block-size: var(--size-dot);
    border-radius: var(--radius-pill);
    background-color: currentColor;
    animation: agp-pulse 2s var(--ease-in-out) infinite;
  }

  .chat__connection-text {
    flex: 1 1 auto;
    min-inline-size: 0;
  }

  .chat__connection .btn {
    flex: 0 0 auto;
    margin-inline-start: auto;
  }

  /* ======================================================================
     Empty state
     ----------------------------------------------------------------------
     app.css owns `.empty-state`, the product-wide "nothing here yet" block.
     `.empty` is a different thing and is declared nowhere else: it is the
     chat's opening screen, so it lives with the chat.
     ====================================================================== */

  .empty {
    padding-block: var(--space-8) var(--space-6);
  }

  .empty__head {
    margin-block-end: var(--space-6);
  }

  .empty__badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 3px 12px;
    border-radius: var(--radius-pill);
    background: var(--accent-subtle);
    border: 1px solid var(--accent-border);
    color: var(--accent-text);
    font-size: 0.6875rem;
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    margin-block-end: var(--space-3);
  }

  .empty__badge-pulse {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: var(--accent);
    box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25);
    animation: pulse 2s infinite ease-in-out;
  }

  .empty__greeting {
    font-size: clamp(1.75rem, 3vw, 2.25rem);
    line-height: var(--lh-2xl);
    font-weight: var(--weight-bold);
    letter-spacing: -0.025em;
    color: var(--fg-primary);
  }

  .empty__lede {
    margin-block-start: var(--space-2);
    max-inline-size: 42rem;
    color: var(--fg-muted);
    font-size: var(--text-base);
    line-height: var(--lh-base);
  }

  .empty__head .assumed {
    margin-block-start: var(--space-4);
  }

  .starters {
    display: grid;
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(100%, calc((var(--chat-measure) - var(--space-3) * 2) / 3)), 1fr)
    );
    gap: var(--space-3);
  }

  .starters__cell {
    display: flex;
    min-inline-size: 0;
  }

  .starter {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    inline-size: 100%;
    padding: var(--space-4) var(--space-5);
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-card);
    text-align: start;
    box-shadow: var(--shadow-xs);
    transition: all var(--dur-fast) var(--ease-out);
    position: relative;
    overflow: hidden;
    cursor: pointer;
  }

  .starter:hover {
    background-color: var(--bg-surface);
    border-color: var(--accent-border);
    box-shadow: 0 8px 22px -4px rgba(16, 185, 129, 0.12), var(--shadow-sm);
    transform: translateY(-2px);
  }

  .starter:active {
    background-color: var(--bg-active);
    transform: translateY(0);
  }

  .starter__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-block-end: 2px;
  }

  .starter__glyph {
    font-size: 1.125rem;
    line-height: 1;
  }

  .starter__arrow {
    font-size: 0.9375rem;
    color: var(--fg-faint);
    opacity: 0.35;
    transform: translateX(-3px);
    transition: all var(--dur-fast) var(--ease-out);
  }

  .starter:hover .starter__arrow {
    opacity: 1;
    transform: translateX(0);
    color: var(--accent);
  }

  .starter__title {
    color: var(--fg-primary);
    font-size: var(--text-sm);
    line-height: 1.4;
    font-weight: var(--weight-semibold);
    letter-spacing: -0.01em;
  }

  .starter__desc {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
  }

  .starter__meta {
    margin-block-start: auto;
    padding-block-start: var(--space-2);
    color: var(--accent-text);
    font-size: 0.6875rem;
    line-height: var(--lh-xs);
    font-weight: var(--weight-medium);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }

  .starters__empty {
    padding: var(--space-5);
    background-color: var(--bg-surface);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-card);
    color: var(--fg-muted);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  /* Telemetry Engine Bar */
  .statstrip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: var(--space-3);
    margin-block-start: var(--space-6);
    padding: var(--space-3) var(--space-4);
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-xs);
  }

  .statstrip__item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-inline-size: 0;
    padding: var(--space-2);
  }

  .statstrip__icon {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    background-color: var(--bg-hover);
    color: var(--fg-secondary);
  }

  .statstrip__icon--accent {
    background-color: var(--accent-subtle);
    color: var(--accent);
  }

  .statstrip__content {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-inline-size: 0;
  }

  .statstrip__label {
    color: var(--fg-muted);
    font-size: 0.6875rem;
    line-height: 1.1;
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .statstrip__value {
    color: var(--fg-primary);
    font-size: var(--text-md);
    line-height: var(--lh-md);
    font-weight: var(--weight-bold);
    font-variant-numeric: tabular-nums;
    font-family: var(--font-numeric);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ======================================================================
     Transcript and messages
     ----------------------------------------------------------------------
     Two shapes, one for each voice, and no third thing.

     The assistant turn is the page's primary content and is set as a
     document: it starts on the leading edge, takes the reading measure, and
     wears no bubble, no avatar and no tint. The user turn is the question
     quoted back: a quiet bubble on the trailing edge, narrower, one step down
     in type. That single contrast — document on one side, bubble on the other
     — is what makes a long thread scannable with no name label, no avatar
     column and no rule per turn. It is also the only fill on this screen that
     is not a card, which is why it is `--bg-quote` and not `--bg-surface`.

     Rhythm comes from `--turn-gap` and nothing else. A question and the
     answer to it are one unit, so the gap inside the pair is derived from
     that one token rather than being a second number to keep in step.
     ====================================================================== */

  .transcript {
    display: flow-root;
  }

  .msg {
    display: block;
    min-inline-size: 0;
  }

  /* Turn rhythm. One token, both roles: `--turn-gap` between exchanges, half
     of it between a question and its own answer. */
  .msg + .msg {
    margin-block-start: var(--turn-gap);
  }

  .msg--user + .msg--assistant {
    margin-block-start: calc(var(--turn-gap) / 2);
  }

  .msg__body {
    min-inline-size: 0;
  }

  /* ---- User turn ------------------------------------------------------ */

  /* `fit-content` inside a `max-inline-size` is what makes a three-word
     question a three-word bubble instead of a full-width band. */
  .msg--user .msg__body {
    inline-size: fit-content;
    max-inline-size: min(100%, var(--quote-measure));
    margin-inline-start: auto;
    padding: var(--space-3) var(--space-4);
    background: linear-gradient(135deg, rgba(16, 185, 129, 0.09) 0%, rgba(99, 102, 241, 0.05) 100%);
    border: 1px solid var(--accent-border);
    border-radius: 14px 14px 4px 14px;
    box-shadow: 0 2px 6px rgba(16, 185, 129, 0.08);
  }

  /* A question is quoted, never interpreted: `_message_user.html` renders it
     as a bare text node and relies on this rule to keep the author's own line
     breaks. */
  .msg--user .msg__prose {
    white-space: pre-wrap;
    overflow-wrap: break-word;
    color: var(--fg-primary);
    font-size: var(--text-answer);
    line-height: var(--lh-answer);
  }

  /* Follows the bubble to the trailing edge; the bubble is the only thing it
     annotates, so it lines up with the bubble and not with the column. */
  .msg__meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-block-start: var(--space-1);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
  }

  .msg__time {
    font-variant-numeric: tabular-nums;
  }

  /* ---- Assistant turn ------------------------------------------------- */
  .msg__agent-header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-block-end: var(--space-2);
  }

  .msg__agent-avatar {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    border-radius: 6px;
    background: linear-gradient(135deg, #059669 0%, #10b981 100%);
    color: #ffffff;
    box-shadow: 0 2px 6px rgba(16, 185, 129, 0.3);
  }

  .msg__agent-meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .msg__agent-name {
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--fg-primary);
    letter-spacing: -0.01em;
  }

  .msg__agent-tag {
    font-size: 0.6875rem;
    font-weight: var(--weight-semibold);
    color: var(--accent-text);
    background: var(--accent-subtle);
    border: 1px solid var(--accent-border);
    padding: 1px 7px;
    border-radius: var(--radius-pill);
    letter-spacing: 0.02em;
  }

  .msg--assistant .msg__prose {
    /* The reading measure, counted in the answer's own characters — see
       tokens.css. The column may be wider; the answer stops here. */
    max-inline-size: var(--prose-measure);
    color: var(--fg-primary);
    font-size: var(--text-md);
    line-height: var(--lh-md);
    font-weight: var(--weight-medium);
    overflow-wrap: break-word;
    /* An answer arrives as a TEXT NODE, not as markup: chat.js appends
       `answer.delta` to one text node and the server renders `message.body`
       escaped. So the blank lines the model wrote are the only paragraph
       breaks there are, and without this they collapse into one run-on wall
       of text. Element children opt back out immediately below, so this stays
       correct on the day a real renderer lands. */
    white-space: pre-wrap;
    /* Reserves the first line so the caret has somewhere to stand before the
       first token lands, and so the answer does not jolt downward. */
    min-block-size: var(--lh-md);
    margin-block-end: var(--space-3);
  }

  /* Any element child of the prose is markup and manages its own wrapping —
     `pre-wrap` inherited into a rendered <p> would preserve the source's line
     breaks on top of the paragraph's own. */
  .msg--assistant .msg__prose > * {
    white-space: normal;
  }

  /* The caveat under the lead: "Listing prices, not transactions and not a
     valuation". Quieter than the answer and never louder, because it qualifies
     a figure rather than stating one — but it is ordinary body text, not a
     footnote, because a reader who misses it reads the number above as
     something it is not. Same measure as the prose so the two read as one
     column. */
  .msg--assistant .msg__caveat {
    max-inline-size: var(--prose-measure);
    margin-block-start: var(--space-2);
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    overflow-wrap: break-word;
  }

  /* What the figure covers, from the catalogue's `disclose`. One step quieter
     again than the caveat: the caveat stops a number being read as something
     it is not, while this says which population it was measured over — "used
     listings only", "across every model year and trim". A reader scanning the
     answer should not be stopped by it; a reader asking "used or new?" must be
     able to find it without opening the trace. Same measure as the prose so
     the column stays one column. */
  /* The closed container the disclosures sit in. Carries the block spacing so
     the <p> inside keeps its own margin at zero and opening the panel does not
     add a second gap. */
  .msg--assistant .msg__disclosure-wrap {
    max-inline-size: var(--prose-measure);
    margin-block-start: var(--space-1);
  }

  /* Quieter than the disclosures it opens: this is a door, not a claim. The
     native triangle is kept -- it is the one affordance every browser already
     draws consistently and that assistive technology already announces as
     expandable, so replacing it would cost more than it buys. */
  .msg--assistant .msg__disclosure-summary {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    font-weight: var(--weight-medium);
    cursor: pointer;
    inline-size: fit-content;
    padding: 3px 8px;
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    transition: all var(--dur-fast) var(--ease-out);
  }

  .msg--assistant .msg__disclosure-summary:hover {
    color: var(--fg-primary);
    background: var(--bg-hover);
    border-color: var(--border-strong);
  }

  /* Inside the panel the top margin is the wrapper's job, not the <p>'s. */
  .msg--assistant .msg__disclosure-wrap .msg__disclosure {
    margin-block-start: calc(var(--space-1) / 2);
  }

  .msg--assistant .msg__disclosure {
    max-inline-size: var(--prose-measure);
    margin-block-start: var(--space-1);
    color: var(--fg-secondary);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    overflow-wrap: break-word;
  }

  /* Two or more disclosures are separate claims about one figure, and running
     them together is what made the reported wall: five lines of grey under a
     one-line answer, with no edge between "used listings only" and "no market
     was named". One claim per row, each hanging off its own marker, so the
     reader looking for "used or new?" finds their line instead of reading the
     set. Still the same quiet type — this is a remark under an answer, not a
     report, and it must not out-weigh the number above it. */
  .msg--assistant .msg__disclosure--list {
    display: flex;
    flex-direction: column;
    gap: calc(var(--space-1) / 2);
  }

  .msg--assistant .msg__disclosure-item {
    display: block;
    padding-inline-start: var(--space-3);
    position: relative;
  }

  /* A marker drawn rather than typed, so it is never selected with the text
     and never read aloud as a word. */
  .msg--assistant .msg__disclosure-item::before {
    content: '';
    position: absolute;
    inset-inline-start: var(--space-1);
    /* Optically centred on the first line's x-height rather than its box. */
    inset-block-start: calc(var(--lh-sm) * 0.5em);
    inline-size: 3px;
    block-size: 3px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.55;
  }

  /* The document rules an answer needs. `.msg__prose` does not carry app.css's
     `.prose` class, so they live here; kept to what an answer actually
     contains — no theme, just a document.

     `--prose-flow` is a rem, so the rhythm between blocks grows with the four
     text scales. Headings take more space above than the flow gives and none
     of their own below, which is the asymmetry that makes a heading belong to
     what follows it rather than float between two paragraphs. */
  .msg__prose > * + * {
    margin-block-start: var(--prose-flow);
  }

  .msg__prose h1,
  .msg__prose h2,
  .msg__prose h3,
  .msg__prose h4 {
    margin-block-start: calc(var(--prose-flow) * 1.75);
    color: var(--fg-primary);
    font-weight: var(--weight-semibold);
  }

  .msg__prose h1 { font-size: var(--text-xl);     line-height: var(--lh-xl); }
  .msg__prose h2 { font-size: var(--text-lg);     line-height: var(--lh-lg); }
  .msg__prose h3,
  .msg__prose h4 { font-size: var(--text-answer); line-height: var(--lh-answer); }

  .msg__prose > :first-child {
    margin-block-start: 0;
  }

  /* Indents that belong to the type rather than to the chrome: derived from
     the flow gap, so a list's markers keep their relationship to the text at
     every scale instead of crowding it at XL. */
  .msg__prose ul,
  .msg__prose ol {
    padding-inline-start: calc(var(--prose-flow) * 1.25);
  }

  .msg__prose li + li {
    margin-block-start: calc(var(--prose-flow) / 2);
  }

  .msg__prose li::marker {
    color: var(--fg-faint);
  }

  /* Emphasis at the house weight. A UA `bold` is 700, which inside a 16px
     answer reads as a shout rather than as stress — and the number an answer
     wants emphasised is usually the whole reason it was asked. */
  .msg__prose strong,
  .msg__prose b {
    font-weight: var(--weight-semibold);
  }

  /* The quote rule is 2px on purpose: it is a mark in the reading column, not
     a card frame, so it takes `--border-default` and not `--artifact-line`. */
  .msg__prose blockquote {
    padding-inline-start: var(--prose-flow);
    border-inline-start: 2px solid var(--border-default);
    color: var(--fg-secondary);
  }

  .msg__prose code {
    padding: 0.1em 0.3em;
    background-color: var(--bg-sunken);
    border-radius: var(--radius-control);
    color: var(--fg-secondary);
  }

  /* Same frame as every answer card — a code block quoted inside an answer and
     a SQL card attached under it are the same kind of object, so they may not
     wear two different borders. */
  .msg__prose pre {
    padding: var(--space-4);
    background-color: var(--bg-sunken);
    border: 1px solid var(--artifact-line);
    border-radius: var(--artifact-radius);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }

  .msg__prose pre code {
    padding: 0;
    background: none;
    color: inherit;
    font-size: inherit;
  }

  .msg__prose a {
    color: var(--link);
    text-decoration: underline;
    text-underline-offset: 0.18em;
    text-decoration-thickness: 1px;
  }

  .msg__prose a:hover {
    color: var(--link-hover);
  }

  /* A table written into an answer is the one thing in the prose flow that can
     be wider than the measure, and the page body is never allowed a sideways
     scrollbar (`.chat__scroll` sets `overflow-x: hidden`, which would CLIP a
     wide table rather than let anyone reach it). `display: block` is what makes
     the table its own scroll container without a wrapper element — the prose is
     a text node today, so there is no renderer here to wrap one in. The cost is
     WebKit dropping the table role from a `display: block` table; the trade is
     deliberate, because unreachable columns are worse than a lost role, and it
     disappears the day the renderer can emit `.table-wrap` itself. */
  .msg__prose table {
    display: block;
    inline-size: fit-content;
    max-inline-size: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
  }

  .msg__prose th,
  .msg__prose td {
    padding: var(--space-2) var(--space-3);
    border-block-end: 1px solid var(--artifact-line);
    text-align: start;
  }

  /* A citation marker written inline in the prose, e.g. `[3]`. Quiet evidence:
     a small tinted marker on the baseline, not a raised accent glyph — the
     answer is what is being read, and the marker is only how to check it. */
  .msg__prose sup {
    padding-inline: 0.28em;
    border-radius: var(--radius-control);
    background-color: var(--accent-subtle);
    color: var(--accent-text);
    font-size: 0.78em;
    font-variant-numeric: tabular-nums;
    /* Nudged off the baseline rather than superscripted, so a marker cannot
       add leading to the line it sits in. */
    vertical-align: 0.12em;
  }

  /* ---- Streaming caret ------------------------------------------------ */

  /* One hairline column, exactly a text cursor. It is 1px because a caret is
     a rule, not a box — no token covers a hairline and none should. */
  .msg__caret {
    display: inline-block;
    inline-size: 1px;
    block-size: 1em;
    margin-inline-start: 0.12em;
    vertical-align: text-bottom;
    background-color: var(--accent);
    animation: agp-caret-blink 1.1s steps(1, end) infinite;
  }

  /* ---- Error ---------------------------------------------------------- */

  .msg__error {
    max-inline-size: var(--prose-measure);
    margin-block-start: var(--answer-gap);
    padding: var(--space-3) var(--space-4);
    background-color: var(--danger-subtle);
    border: 1px solid var(--danger-border);
    border-radius: var(--artifact-radius);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  /* Which of the seven `RefusalCode`s this is, in the dealer's own words --
     a fixed retryable-vs-permanent question was previously answerable only
     by reading the sentence below it. Quieter than the message itself: the
     category orients, the message is what actually happened. */
  .msg__error-category {
    margin-block-end: var(--space-1);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }

  .msg__error-message {
    color: var(--danger-text);
    font-weight: var(--weight-semibold);
  }

  /* What to do next is the useful half, so it is ink, not alarm colour. */
  .msg__error-remedy {
    margin-block-start: var(--space-1);
    color: var(--fg-secondary);
  }

  .msg__error-reasons {
    margin-block-start: var(--space-2);
    padding-inline-start: var(--space-4);
    list-style: disc;
    color: var(--fg-muted);
    font-size: var(--text-xs);
    /* 12/16 is fine for one line of chrome and cramped for a wrapped reason. */
    line-height: var(--lh-sm);
  }

  .msg__error-reasons li::marker {
    color: var(--danger);
  }

  /* ---- Attached blocks ------------------------------------------------
     These wrappers are always in the markup and usually empty. Spacing hangs
     off the children rather than the wrapper, so an empty one collapses to
     nothing instead of leaving a hole under the prose.

     One gap — `--answer-gap` — sits above every card, above the clarify chips
     and above the sources. Cards in an answer are punctuation in the prose
     flow, so the distance from the paragraph above to the first card is the
     same as from card to card and from the last card to the evidence. */

  .msg__artifacts > *,
  .msg__clarify > * {
    margin-block-start: var(--answer-gap);
  }

  /* ---- Chrome: marks and actions -------------------------------------- */

  .msg__chrome {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
    margin-block-start: var(--space-3);
    padding-block-start: var(--space-1);
    border-block-start: none;
  }

  .msg__marks,
  .msg__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-inline-size: 0;
  }

  .msg__actions {
    margin-inline-start: auto;
  }

  /* Always visible, never loud. Hiding verification controls behind hover is
     exactly the wrong instinct on the screen whose argument is "check me". */
  .msg__action {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-block-size: var(--control-h-sm);
    padding-inline: var(--space-2);
    border-radius: var(--radius-control);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-medium);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
  }

  /* The four universal actions are icon-only squares, Claude's bar shape;
     the tooltip carries the word and aria-label the sentence. */
  .msg__action--icon {
    inline-size: var(--control-h-sm);
    padding-inline: 0;
    justify-content: center;
  }

  .msg__action:hover {
    background-color: var(--bg-hover);
    color: var(--fg-primary);
  }

  .msg__action:active {
    background-color: var(--bg-active);
  }

  .msg__action[aria-pressed="true"] {
    background-color: var(--accent-subtle);
    color: var(--accent-text);
  }

  /* The two governance doors. One word each, the sentence in the tooltip and
     in `aria-label` — four icon squares followed by "Show the SQL behind this"
     was a row with a tail. The quiet border is what keeps them *doors*: they
     read as available without competing with the answer, and they are never
     folded into a menu. */
  .msg__action--door {
    border: 1px solid var(--border-subtle);
  }

  .msg__action--door:hover {
    border-color: var(--border-default);
  }

  /* What separates a verdict from an exhibit. Hidden from readers — the
     accessible names already say which is which. */
  .msg__actions-sep {
    flex: 0 0 auto;
    inline-size: 1px;
    block-size: var(--space-4);
    background-color: var(--border-subtle);
  }

  /* An answer with no run behind it hides both doors (and chat.js unhides them
     when the plan compiles), so the divider follows them — a rule separating
     nothing from nothing is a stray mark, which is the sort of thing this
     whole pass is about. */
  .msg__actions:has([data-action="show-plan"][hidden]) .msg__actions-sep {
    display: none;
  }

  .msg__action-label {
    white-space: nowrap;
  }

  /* The reasons a thumb-down offers to refine into — see the comment beside
     the markup in `_message_assistant.html`. A second row under the action
     bar, not a popover: it is optional and quiet, the same register as the
     chip strip a clarification uses. */
  .msg__feedback-reasons {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-block-start: var(--space-2);
    padding-block-start: var(--space-2);
    border-block-start: 1px solid var(--border-subtle);
  }

  .msg__feedback-reasons-label {
    color: var(--fg-muted);
    font-size: var(--text-xs);
  }

  /* "Something other than the routine plan is behind this door" — the planner
     wrote a fresh one, worth a look before assuming it's the same as usual.
     Never colours the whole button: that quiet border is what keeps a
     governance door from competing with the answer above it, see the comment
     on `.msg__action--door`. */
  .msg__action-badge {
    inline-size: 6px;
    block-size: 6px;
    border-radius: var(--radius-pill);
    background-color: var(--accent);
  }

  /* ---- Chat-specific badge modifiers ----------------------------------
     Extended only through the custom properties components.css exposes; none
     of the badge's own box properties are touched. */

  .badge--grounded {
    --badge-bg: var(--accent-subtle);
    --badge-fg: var(--accent-text);
    --badge-border: var(--accent-border);
  }

  /* The grounding critic downgrades the claim in place rather than removing
     it — an answer that was checked and found thin is not the same as one
     that was never checked, and the badge has to be able to say so. */
  .badge--grounded[data-verdict="warn"] {
    --badge-bg: var(--warning-subtle);
    --badge-fg: var(--warning-text);
    --badge-border: var(--warning-border);
  }

  .badge--grounded[data-verdict="fail"] {
    --badge-bg: var(--danger-subtle);
    --badge-fg: var(--danger-text);
    --badge-border: var(--danger-border);
  }

  .badge--overseer {
    --badge-bg: transparent;
    --badge-fg: var(--fg-muted);
    --badge-border: var(--artifact-line);
  }

  /* Dashed, to rhyme with the incognito composer: the same idea, "this is not
     being kept", expressed the same way in both places. */
  .badge--temporary {
    --badge-bg: transparent;
    --badge-fg: var(--fg-muted);
    --badge-border: var(--border-strong);
    border-style: dashed;
  }

  /* `.badge` is a flex box with a gap, so a slot that has not been filled yet
     is still a flex item and still buys a 4px gap — "Grounded" would sit with
     a stray space after it until the count arrives. */
  .msg__marks .badge__count:empty,
  .msg__marks .badge__text:empty {
    display: none;
  }

  /* ======================================================================
     Thinking trace
     ----------------------------------------------------------------------
     The signature element. Closed, it is one scannable line. Open, it is a
     stage timeline on a rail with monospace tabular durations, so the numbers
     form a column the eye can compare down.

     Colour carries exactly one meaning in here: teal for a deterministic step
     that finished, amber for the one step that called a model, red for a step
     that failed. `is-slow` is deliberately *not* coloured — see below.
     ====================================================================== */

  /* Closed, the trace is one quiet line in the answer's own flow: no fill, no
     frame, its text starting on the same edge as the prose below it. Open, it
     becomes the card the timeline needs. It stays a real `<details>` either
     way — the argument for the answer is never hidden, only quiet.

     The frame is declared as a TRANSPARENT hairline rather than left out, so
     opening the disclosure cannot shift the row by a pixel. */
  .trace {
    display: inline-block;
    max-inline-size: 100%;
    margin-block-end: var(--space-2);
    background-color: transparent;
    border: none;
    border-radius: var(--radius-pill);
    transition: all var(--dur-fast) var(--ease-out);
  }

  .trace[open] {
    display: block;
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
  }

  .trace__summary {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 24px;
    padding: 2px var(--space-2);
    border-radius: var(--radius-pill);
    background-color: transparent;
    border: 1px solid transparent;
    cursor: pointer;
    transition: all var(--dur-fast) var(--ease-out);
  }

  .trace[open] .trace__summary {
    display: flex;
    border: none;
    border-radius: 0;
    border-block-end: 1px solid var(--border-subtle);
    padding: var(--space-2) var(--space-3);
    background-color: var(--bg-sunken);
  }

  .trace:not([open]) .trace__summary:hover {
    background-color: var(--bg-hover);
    border-color: var(--border-subtle);
  }

  .trace:not([open]) .trace__summary:hover .trace__headline {
    color: var(--fg-primary);
  }

  /* Disclosure caret, drawn from the shared mask so it themes for free. */
  .trace__summary::after {
    content: "";
    flex: 0 0 auto;
    inline-size: var(--icon-sm);
    block-size: var(--icon-sm);
    margin-inline-start: var(--space-1);
    background-color: var(--fg-faint);
    mask-image: var(--mask-chevron-down);
    mask-size: 100% 100%;
    -webkit-mask-image: var(--mask-chevron-down);
    -webkit-mask-size: 100% 100%;
    transition: transform var(--dur-fast) var(--ease-out);
  }

  .trace[open] .trace__summary::after {
    transform: rotate(180deg);
  }

  .trace[open] .trace__summary {
    border-block-end: 1px solid var(--border-subtle);
  }

  /* Route tone. Two deterministic routes share one colour on purpose: the
     distinction a reader needs is deterministic vs not, and the label already
     says which of the two it was. Amber is the planner and only the planner —
     it is the single non-deterministic path in the pipeline.

     Sentence case, too. The template writes "Compiled" and this rule used to
     shout it back as COMPILED at 12px with 0.48px of tracking — enterprise-
     table furniture standing at the top of a reading surface. Tracked
     capitals are for the data surfaces, where a column heading has to be told
     apart from the data under it; above an answer there is no column. */
  .badge--route {
    --badge-bg: var(--bg-sunken);
    --badge-fg: var(--fg-muted);
    --badge-border: var(--artifact-line);
  }

  /* Tone is the badge modifier's business; holding its width against a long
     headline is the trace's. */
  .trace__route {
    flex: 0 0 auto;
  }

  .badge--route[data-route="cache_hit"],
  .badge--route[data-route="shape_compiled"] {
    --badge-bg: var(--accent-subtle);
    --badge-fg: var(--accent-text);
    --badge-border: var(--accent-border);
  }

  .badge--route[data-route="planner"] {
    --badge-bg: var(--warning-subtle);
    --badge-fg: var(--warning-text);
    --badge-border: var(--warning-border);
  }

  .badge--route[data-route="clarify"] {
    --badge-bg: var(--info-subtle);
    --badge-fg: var(--info-text);
    --badge-border: var(--info-border);
  }

  .badge--route[data-route="refused"] {
    --badge-bg: var(--danger-subtle);
    --badge-fg: var(--danger-text);
    --badge-border: var(--danger-border);
  }

  /* The clone skeleton and any run whose route is not decided yet carry an
     empty attribute; an empty badge is a 20px grey stub, so it is withheld
     until the route is known. */
  .badge--route[data-route=""] {
    display: none;
  }

  .trace__headline {
    flex: 1 1 auto;
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  /* The headline already ends in the elapsed time, so showing the total twice
     on a wide line is noise. It is held back until the headline is narrow
     enough to be clipped, where it becomes the only surviving figure. */
  .trace__total {
    display: none;
    flex: 0 0 auto;
    color: var(--fg-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
  }

  .trace__panel {
    padding: var(--space-4);
  }

  .trace__list {
    display: grid;
  }

  /* No row gap: the rail must be continuous, so the breathing room is padding
     inside each row rather than space between them. */
  .trace__row {
    position: relative;
    display: grid;
    grid-template-columns: var(--size-node) auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--space-3);
    padding-block: var(--space-2);
    /* Clips the live underline to the row. */
    overflow: hidden;
  }

  /* The rail. Drawn on the row rather than the list so the first and last
     segments can stop at their node instead of running past it. */
  .trace__row::before {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: calc((var(--size-node) - 1px) / 2);
    inline-size: 1px;
    background-color: var(--artifact-line);
  }

  .trace__row:first-child::before {
    inset-block-start: calc(var(--space-2) + var(--lh-sm) / 2);
  }

  /* `inset-block: 0` plus a height is not over-constrained for an absolutely
     positioned box: the start edge wins and the end inset is dropped, so this
     is a segment that stops dead at the last node. */
  .trace__row:last-child::before {
    block-size: calc(var(--space-2) + var(--lh-sm) / 2);
  }

  .trace__row:first-child:last-child::before {
    display: none;
  }

  .trace__mark {
    inline-size: var(--size-node);
    block-size: var(--size-node);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background-color: var(--bg-surface);
    /* Sits above the rail it interrupts. */
    position: relative;
    z-index: var(--z-base);
  }

  .trace__row.is-done .trace__mark {
    background-color: var(--accent);
    border-color: var(--accent);
  }

  /* The open stage: an accent ring, still hollow, because it has not landed. */
  .trace__row:not(.is-done) .trace__mark {
    border-color: var(--accent);
  }

  .trace__row[data-status="failed"] .trace__mark,
  .trace__row[data-status="error"] .trace__mark {
    background-color: var(--danger);
    border-color: var(--danger);
  }

  .trace__row[data-status="skipped"] .trace__mark {
    background-color: var(--bg-inset);
    border-color: var(--border-default);
  }

  /* The one stage that was not deterministic. `trace__model-note` below the
     list points at it in words — "it is the amber row above" — so this rule
     and that sentence have to stay in agreement. */
  .trace__row[data-model-call="1"] .trace__mark {
    background-color: var(--warning);
    border-color: var(--warning);
  }

  .trace__row[data-model-call="1"] .trace__label {
    color: var(--warning-text);
  }

  .trace__label {
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  /* Truncated rather than wrapped: a timeline is read down the durations, and
     a row that grows to two lines breaks that column. The full text is
     restored on its own line at the handheld breakpoint. */
  .trace__detail {
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  /* Monospace and tabular so the millisecond figures form a true column.
     `min-inline-size` stops that column jittering as digits arrive. */
  .trace__ms {
    justify-self: end;
    min-inline-size: var(--space-16);
    color: var(--fg-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    font-variant-numeric: tabular-nums;
    text-align: end;
  }

  /* A duration whose stage finished faster than the dwell floor carries a
     title explaining the difference. The dotted rule is the affordance that
     says there is something to hover. */
  .trace__ms[title] {
    text-decoration: underline dotted;
    text-decoration-color: var(--border-strong);
    text-underline-offset: 0.2em;
    cursor: help;
  }

  /* `is-slow` says "this took a while", and that is all it says. It gets
     weight and a chip, never amber and never red: a slow deterministic step
     is not a warning, and spending the warning colour here would blunt the
     one thing amber means on this screen. */
  .trace__row.is-slow .trace__ms {
    padding-inline: var(--space-1);
    background-color: var(--bg-sunken);
    border-radius: var(--radius-control);
    color: var(--fg-primary);
    font-weight: var(--weight-medium);
  }

  .trace__row.is-slow .trace__label {
    color: var(--fg-primary);
  }

  /* ---- Live stage ------------------------------------------------------
     A 1px underline that travels the row. No spinner, no dots, nothing that
     implies progress toward a known end. */

  /* Two selectors, one bar. The second draws it across the summary when the
     trace is collapsed, because a closed trace still has to say that a stage
     is open; `:has()` is the only way to reach up from the live row, and
     where it is unsupported the rule never matches and all that is lost is
     the hint. On the row the bar is `::after` (`::before` is the rail); on
     the summary the roles swap, because there `::after` is the chevron. */
  .trace__row:not(.is-done)::after,
  .trace:not([open]):has(.trace__row:not(.is-done)) .trace__summary::before {
    content: "";
    position: absolute;
    inset-block-end: 0;
    inset-inline-start: 0;
    inline-size: 26%;
    block-size: 1px;
    background-color: var(--accent);
    animation: agp-trace-scan 1.8s var(--ease-in-out) infinite;
  }

  /* `translateX` is physical, so the bar would travel backwards in a
     right-to-left locale. */
  [dir="rtl"] .trace__row:not(.is-done)::after,
  [dir="rtl"] .trace:not([open]):has(.trace__row:not(.is-done)) .trace__summary::before {
    animation-direction: reverse;
  }

  .trace__note,
  .trace__model-note {
    max-inline-size: var(--prose-measure);
    margin-block-start: var(--space-4);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  .trace__note {
    color: var(--fg-faint);
  }

  .trace__model-note {
    padding-inline-start: var(--space-3);
    border-inline-start: 2px solid var(--warning-border);
    color: var(--warning-text);
  }

  /* ======================================================================
     Composer
     ====================================================================== */

  .composer-shell {
    /* The dashed frame is always here and normally transparent, so switching
       incognito on changes colour only — nothing on screen moves, and the
       inner radius stays concentric with the outer one. */
    padding: var(--space-2);
    border: 1px dashed transparent;
    border-radius: var(--radius-shell);
    transition: background-color var(--dur-base) var(--ease-out),
                border-color var(--dur-base) var(--ease-out);
  }

  /* The composer is a SURFACE, not a bare control: the textarea inside it
     has no edge of its own, so this frame is doing the same job an answer
     card's frame does and takes the same hairline. It used to take
     `--border-default` — a card and the composer, twelve pixels apart, drawn
     in two different greys, on the one screen where they are always seen
     together. */
  .composer {
    position: relative;
    display: block;
    padding: var(--space-2) var(--space-3);
    background-color: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--glass-border);
    border-radius: 16px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05), var(--shadow-sm);
    transition: all var(--dur-fast) var(--ease-out);
  }

  .composer:hover {
    border-color: var(--border-strong);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.08), var(--shadow-sm);
  }

  .composer:focus-within {
    border-color: var(--accent);
    box-shadow: 0 8px 30px rgba(16, 185, 129, 0.14), 0 0 0 1px var(--accent);
  }

  /* reset.css is explicit that a component may recolour the focus ring but
     never remove it. This does not remove it — it relocates it to the box the
     user perceives as the control, which is the composer, not the bare
     textarea inside it. */
  .composer:has(.composer__input:focus-visible) {
    outline: var(--focus-ring-width) solid var(--border-focus);
    outline-offset: var(--focus-ring-offset);
  }

  .composer__input:focus-visible {
    outline: none;
  }

  .composer__field {
    min-inline-size: 0;
  }

  .composer__input {
    inline-size: 100%;
    display: block;
    min-block-size: var(--control-h-lg);
    max-block-size: var(--composer-max-h);
    padding: var(--space-2);
    background: none;
    border: 0;
    color: var(--fg-primary);
    font-family: var(--font-sans);
    font-size: var(--text-md);
    line-height: var(--lh-md);
    resize: none;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    /* Grows with its content where supported; chat.js autosizes elsewhere. */
    field-sizing: content;
  }

  /* A run in flight. `chat.js` writes `aria-busy` on the box for the length of
     a run and no sheet painted it, so the composer looked identical whether an
     answer was arriving or not — while the Send button beside it was disabled
     and the placeholder was still inviting a question. This is not a disabled
     box: you may keep typing the next question, and that is deliberate. It is
     the caret that stops claiming to be the live one, in the same quiet ink
     the placeholder uses. */
  .composer__input[aria-busy="true"] {
    color: var(--fg-secondary);
    caret-color: var(--fg-faint);
  }

  .composer__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-1) 0;
  }

  .composer__tools {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    min-inline-size: 0;
  }

  .composer__hint {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.6875rem;
    color: var(--fg-faint);
    margin-inline-start: var(--space-1);
  }

  .composer__kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 1px 4px;
    font-family: var(--font-mono);
    font-size: 0.65rem;
    border-radius: 3px;
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    color: var(--fg-muted);
  }

  .composer__tool {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-block-size: var(--control-h-sm);
    padding-inline: var(--space-2);
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    color: var(--fg-muted);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    font-weight: var(--weight-medium);
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
  }

  .composer__tool:hover {
    background-color: var(--bg-hover);
    color: var(--fg-secondary);
  }

  .composer__tool:active {
    background-color: var(--bg-active);
  }

  .composer__tool[aria-expanded="true"] {
    background-color: var(--bg-active);
    color: var(--fg-primary);
  }

  /* Icon-only squares, the same shape `.msg__action--icon` already uses in the
     action bar above: the word each tool wore is `sr-only` in the markup and
     repeated in `aria-label`, and `data-tip` (components.css) puts it back on
     screen on hover and on keyboard focus. Incognito is not in this family —
     it sits in the topbar and keeps its word. */
  .composer__tool--icon {
    position: relative;
    inline-size: var(--control-h-sm);
    padding-inline: 0;
    justify-content: center;
  }

  /* Answer settings, only when they no longer say "Auto" / grounded / the
     workspace default model. A silent route change is the one thing this
     product otherwise never does — see `paintSettingsIndicator` in chat.js. */
  .composer__tool-dot {
    position: absolute;
    inset-block-start: 5px;
    inset-inline-end: 5px;
    inline-size: 6px;
    block-size: 6px;
    border-radius: var(--radius-pill);
    background-color: var(--accent);
    border: 1px solid var(--bg-surface);
  }

  /* Dictation. The pressed state borrows the danger family on purpose — a
     live microphone is the one composer tool whose "on" state deserves alarm
     colour — and the pulse stops under reduced motion, where the tinted
     button alone carries the message. */
  .composer__tool--voice[aria-pressed="true"] {
    background-color: var(--danger-subtle);
    border-color: var(--danger);
    color: var(--danger-text);
  }

  .composer__tool--voice[aria-pressed="true"] .icon {
    animation: agp-voice-pulse 1.4s var(--ease-out) infinite;
  }

  @keyframes agp-voice-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
  }

  @media (prefers-reduced-motion: reduce) {
    .composer__tool--voice[aria-pressed="true"] .icon {
      animation: none;
    }
  }

  .composer__tool-label {
    white-space: nowrap;
  }

  .composer__tool-state {
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }

  /* ---- Incognito ------------------------------------------------------
     A mode that changes whether what you type is recorded cannot be a subtle
     tint. Three things move together and none of them is optional: the button
     inverts to a solid chip, the whole shell takes a dashed frame on a sunken
     ground, and the "temporary chat" line appears above it (chat.js unhides
     it — `[hidden]` is `!important` in reset.css and CSS cannot reach it).

     The inversion is drawn in ink, not in hue: no colour in this palette is
     free — teal is the brand, amber is non-determinism, red is failure, blue
     is a link. Ink-on-paper reversed is the loudest thing left, it survives
     both themes, and it is what real incognito modes look like. */

  .composer__tool--incognito .icon__slash {
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
  }

  .composer__tool--incognito[aria-pressed="true"] .icon__slash {
    opacity: 1;
  }

  .composer__tool--incognito[aria-pressed="true"] {
    background-color: var(--fg-primary);
    border-color: var(--fg-primary);
    color: var(--bg-surface);
    font-weight: var(--weight-semibold);
  }

  .composer__tool--incognito[aria-pressed="true"]:hover {
    background-color: var(--fg-secondary);
    border-color: var(--fg-secondary);
    color: var(--bg-surface);
  }

  .composer__tool--incognito[aria-pressed="true"] .composer__tool-state {
    color: inherit;
  }

  /* Two hooks, because the mode has two origins: `data-incognito` is the
     server's opening state on `.chat`, `aria-pressed` is the live toggle. */
  .chat[data-incognito="1"] .composer-shell,
  .composer-shell:has(.composer__tool--incognito[aria-pressed="true"]) {
    background-color: var(--bg-inset);
    border-color: var(--border-strong);
  }

  .chat[data-incognito="1"] .composer,
  .composer-shell:has(.composer__tool--incognito[aria-pressed="true"]) .composer {
    border-color: var(--border-strong);
  }

  .composer__temporary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-block-end: var(--space-2);
    padding: var(--space-2) var(--space-3);
    background-color: var(--bg-surface);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-control);
    color: var(--fg-secondary);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    font-weight: var(--weight-medium);
  }

  .composer__temporary-mark {
    flex: 0 0 auto;
    inline-size: var(--size-dot);
    block-size: var(--size-dot);
    border-radius: var(--radius-pill);
    background-color: var(--fg-secondary);
  }

  /* ---- A pending slot -------------------------------------------------
     "Replace {dealer}, then press Enter." Raised by chat.js when a chip loads
     a templated prompt into the composer, dropped when the braces go or the
     turn is sent. Quiet on purpose: an instruction, not an error, so it
     borrows no status colour — only the slot itself is inked up, in the mono
     face, because it is a literal string the person has to overwrite. */
  .composer__pending {
    margin-block-end: var(--space-2);
    padding-inline: var(--space-2);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  .composer__pending-slot {
    color: var(--fg-primary);
    font-family: var(--font-mono);
    font-weight: var(--weight-medium);
  }

  /* ---- Send cluster ---------------------------------------------------- */

  .composer__send {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-inline-start: auto;
    min-inline-size: 0;
  }

  /* Stopping is not destructive — the run finishes and is recorded either way
     (see the button's own title) — so it is an outline control, not a red one. */
  .composer__stop {
    display: inline-flex;
    align-items: center;
    min-block-size: var(--control-h-md);
    padding-inline: var(--space-3);
    background-color: var(--bg-surface);
    border: 1px solid var(--artifact-line);
    border-radius: var(--radius-control);
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    font-weight: var(--weight-medium);
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
  }

  .composer__stop:hover {
    background-color: var(--bg-hover);
    border-color: var(--border-strong);
  }

  .composer__stop-label {
    white-space: nowrap;
  }

  .composer__submit {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    inline-size: 34px;
    block-size: 34px;
    background: linear-gradient(135deg, #059669 0%, #10b981 100%);
    border-radius: var(--radius-pill);
    color: #ffffff;
    box-shadow: 0 2px 8px rgba(16, 185, 129, 0.35);
    transition: all var(--dur-fast) var(--ease-out);
    border: none;
    cursor: pointer;
  }

  .composer__submit:hover:not(:disabled) {
    background: linear-gradient(135deg, #047857 0%, #059669 100%);
    box-shadow: 0 4px 14px rgba(16, 185, 129, 0.55);
    transform: scale(1.06);
  }

  .composer__submit:active:not(:disabled) {
    transform: scale(0.96);
  }

  .composer__submit:disabled {
    background: var(--bg-inset);
    color: var(--fg-faint);
    box-shadow: none;
    cursor: not-allowed;
    transform: none;
  }

  /* ======================================================================
     Answer settings popover
     ----------------------------------------------------------------------
     components.css owns `.popover`'s chrome; only the things a settings sheet
     needs that a menu does not are set here. It is not a native `[popover]`,
     so it is positioned against the composer.
     ====================================================================== */

  .popover--settings {
    position: absolute;
    inset-block-end: calc(100% + var(--space-2));
    inset-inline-start: 0;
    /* Falls back to the 22rem cap `.popover` already sets, and shrinks with
       the composer on a narrow screen without a second magic number. */
    inline-size: 100%;
    /* `.popover`'s default padding is menu padding — a settings sheet is prose
       and controls, and needs a real margin. */
    padding: var(--space-4);
  }

  .popover__group + .popover__group {
    margin-block-start: var(--space-4);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--border-subtle);
  }

  .popover__legend {
    display: block;
    margin-block-end: var(--space-2);
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }

  /* `--lh-xs` pairs with `--text-xs` for single-line chrome; these notes wrap
     to two and three lines, where 12/16 closes up. */
  .popover__note {
    margin-block-start: var(--space-2);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  /* In components.css `.radio` is the <input>; in this template it is the
     <label> wrapping one. The box, ring and radius that belong to the input
     are unwound here — the layer, not specificity, is what makes this win. */
  .popover--settings .radio,
  .popover--settings .switch {
    display: grid;
    grid-template-columns: var(--icon-md) minmax(0, 1fr);
    column-gap: var(--space-2);
    align-items: start;
    inline-size: auto;
    block-size: auto;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    appearance: none;
    cursor: pointer;
  }

  .popover--settings .radio + .radio {
    margin-block-start: var(--space-3);
  }

  /* The inputs themselves carry no component class, so rather than restate
     the checkbox drawing, they stay native and are tinted with `accent-color`.
     A native control is the most robust thing available: no pseudo-element on
     a replaced element, correct indeterminate and forced-colors behaviour,
     and the platform's own hit target. The offset matches components.css's
     `.choice > .radio`, so these line up with the rest of the product. */
  .popover--settings .radio > input[type="radio"],
  .popover--settings .switch__input {
    grid-column: 1;
    grid-row: 1;
    inline-size: var(--icon-md);
    block-size: var(--icon-md);
    margin: 0;
    margin-block-start: calc((var(--lh-base) - var(--icon-md)) / 2);
    accent-color: var(--accent);
    cursor: pointer;
  }

  /* components.css hides `.switch__input` because its switch is drawn on a
     `__track`/`__thumb` pair. This markup ships neither, so the input is
     restored to a real, visible control. */
  .popover--settings .switch__input {
    position: static;
    opacity: 1;
    pointer-events: auto;
  }

  .popover--settings .radio__label,
  .popover--settings .switch__label {
    grid-column: 2;
    color: var(--fg-primary);
    font-size: var(--text-base);
    line-height: var(--lh-base);
    font-weight: var(--weight-medium);
  }

  .popover--settings .radio__note {
    grid-column: 2;
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  /* `.field__control` is the template's name for the select; `.select` in
     components.css is the canonical control and this mirrors it rather than
     leaving a raw UA dropdown inside a styled sheet. */
  .popover--settings .field__control {
    inline-size: 100%;
    block-size: var(--control-h-md);
    padding-inline: var(--space-3) var(--space-8);
    background-color: var(--bg-surface);
    background-image: var(--icon-chevron);
    background-repeat: no-repeat;
    background-position: right var(--space-2) center;
    background-size: var(--icon-md) var(--icon-md);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-control);
    color: var(--fg-primary);
    font-size: var(--text-base);
    appearance: none;
    cursor: pointer;
  }

  [dir="rtl"] .popover--settings .field__control {
    background-position: left var(--space-2) center;
  }

  .popover--settings .field__control:focus {
    border-color: var(--accent);
  }

  /* ======================================================================
     Clarify · chips · assumed context
     ====================================================================== */

  /* Asking is a first-class outcome, not an error, so the panel is tinted in
     the accent rather than in a semantic warning colour. */
  .clarify {
    padding: var(--space-4);
    background-color: var(--accent-subtle);
    border: 1px solid var(--accent-border);
    border-radius: var(--radius-card);
  }

  .clarify__question {
    color: var(--fg-primary);
    font-size: var(--text-md);
    line-height: var(--lh-md);
    font-weight: var(--weight-medium);
  }

  .clarify .assumed {
    margin-block-end: var(--space-2);
  }

  .clarify__note {
    margin-block-start: var(--space-3);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  .chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: var(--space-3);
  }

  .chips__cell {
    display: flex;
    min-inline-size: 0;
  }

  /* `.chip` is never nowrap here: a slot value can be a long trim name, and
     at 390px a nowrap chip would push past the column. */
  .chips .chip {
    white-space: normal;
    padding-block: var(--space-1);
    text-align: start;
  }

  .assumed {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-2);
    min-inline-size: 0;
    color: var(--fg-muted);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  /* "Assuming", not ASSUMING. This label sits in the reading column, one line
     above the answer, and it is a word in a sentence about that answer — the
     tracked capitals made it read as the heading of a form. */
  .assumed__label {
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-semibold);
  }

  .assumed__line {
    min-inline-size: 0;
    color: var(--fg-secondary);
    font-weight: var(--weight-medium);
  }

  /* Reads as prose, behaves as a control — the same treatment app.css gives
     `.link-button`, restated because the template does not carry that class. */
  .assumed__change {
    color: var(--link);
    font: inherit;
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--link) 35%, transparent);
    text-underline-offset: 0.18em;
    border-radius: var(--radius-control);
    transition: color var(--dur-fast) var(--ease-out),
                text-decoration-color var(--dur-fast) var(--ease-out);
  }

  .assumed__change:hover {
    color: var(--link-hover);
    text-decoration-color: currentColor;
  }

  /* ======================================================================
     Citations
     ----------------------------------------------------------------------
     Every source carries its own as-of, because listings, transactions and
     VIO refresh on different cadences. The dates are the column the eye reads
     down, so they are tabular and end-aligned.

     Read as evidence, not as UI: a hairline, a lowercase label and rows of
     quiet text. Nothing in here is capitalised, boxed or chipped. Someone
     reading the answer should be able to skip the whole block, and someone
     checking the answer should find it exactly where the answer ends.
     ====================================================================== */

  .citations {
    margin-block-start: var(--answer-gap);
    padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--artifact-line);
  }

  .citations__head,
  .followups__head {
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }

  /* Declared after the shared `.citations__head, .followups__head` rule above
     rather than by editing it: the follow-ups half of that pair is not this
     region's to retune, and one later rule of equal specificity is all the
     override needs. A sources label is not a section heading — shouting it in
     tracked capitals is exactly the furniture this block should not be. */
  .citations__head {
    color: var(--fg-muted);
    font-weight: var(--weight-regular);
    letter-spacing: normal;
    text-transform: none;
  }

  .citations__list {
    display: grid;
    gap: var(--space-1);
    margin-block-start: var(--space-2);
    max-block-size: 9.5rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-inline-end: var(--space-2);
  }

  .citations__list::-webkit-scrollbar {
    inline-size: 4px;
  }

  .citations__list::-webkit-scrollbar-thumb {
    background-color: var(--border-default);
    border-radius: var(--radius-pill);
  }

  .citation {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: baseline;
    gap: var(--space-2);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-control);
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
  }

  .citation__ref {
    color: var(--accent-text);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    font-weight: var(--weight-medium);
  }

  .citation__source {
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--fg-secondary);
  }

  /* The kind is a fact about the row, not a label for a group of them, so it
     is set as plain text. Capitals here made every source row look like a
     table header. */
  .citation__kind {
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
  }

  .citation__asof {
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-variant-numeric: tabular-nums;
    text-align: end;
    white-space: nowrap;
  }

  /* Two sources disagreeing about freshness is a real warning about the
     answer, and it is said in words on the line rather than in a tooltip. */
  .citations__conflict {
    max-inline-size: var(--prose-measure);
    margin-block-start: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background-color: var(--warning-subtle);
    border: 1px solid var(--warning-border);
    border-radius: var(--artifact-radius);
    color: var(--warning-text);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  /* ======================================================================
     Suggested follow-ups
     ====================================================================== */

  /* Sits outside `.msg__body`, under the chrome, so it carries its own gap. */
  .followups {
    margin-block-start: var(--space-4);
    border-inline-start: 2px solid transparent;
    padding-inline-start: var(--space-3);
  }

  /* When this strip is a refusal's "closest things this can answer" (the one
     case `.followups__head` speaks, above), a quiet accent ties it to the
     SAME category the error box above it just named -- `chat.js::showError`
     sets `data-refusal-reason` on the article, this reads it. A permanent
     product boundary (out of scope / not permitted / never wired up) and a
     retryable one (thin sample, stale load, timed out) read differently at a
     glance, without repeating the category label a second time. */
  article[data-refusal-reason="out_of_scope"] .followups,
  article[data-refusal-reason="not_permitted"] .followups,
  article[data-refusal-reason="capability_missing"] .followups {
    border-inline-start-color: var(--danger-border);
  }

  article[data-refusal-reason="insufficient_sample"] .followups,
  article[data-refusal-reason="stale_data"] .followups,
  article[data-refusal-reason="adapter_timeout"] .followups,
  article[data-refusal-reason="no_data"] .followups {
    border-inline-start-color: var(--info-border);
  }

  /* The strip has no visible label — "ASK NEXT" in caps was the loudest thing
     on the answer and Claude labels nothing there, so the name lives in
     `aria-label` and this slot ships `hidden`. It speaks for exactly one case,
     a refusal's "closest things this can answer", and then as a sentence: the
     caps recipe it inherits from `.citations__head` above is undone here
     rather than edited there, because that rule is the citations block's. */
  .followups__head {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-block-end: var(--space-2);
  }

  .followups__head::before {
    content: "";
    display: inline-block;
    inline-size: 6px;
    block-size: 6px;
    border-radius: 50%;
    background: var(--accent);
  }

  .followups__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: 0;
  }

  .followups__cell {
    display: flex;
    min-inline-size: 0;
  }

  .followups__item {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-block-size: 34px;
    padding: var(--space-1) var(--space-3);
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-xs);
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    font-weight: var(--weight-medium);
    text-align: start;
    cursor: pointer;
    transition: all var(--dur-fast) var(--ease-out);
  }

  .followups__item::after {
    content: "↗";
    font-size: var(--text-xs);
    opacity: 0.45;
    transition: transform var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
  }

  /* A chip whose prompt still has a hole in it does not send — it loads the
     composer. Dashed, borrowing the incognito composer's own vocabulary for
     "this is not finished yet", so the difference is visible before the press
     and not only afterwards in a hint. */
  .followups__item[data-fills] {
    border-style: dashed;
  }

  .followups__item:hover {
    background: linear-gradient(135deg, rgba(16, 185, 129, 0.09) 0%, rgba(99, 102, 241, 0.06) 100%);
    border-color: var(--accent-border);
    color: var(--fg-primary);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(16, 185, 129, 0.12);
  }

  .followups__item:hover::after {
    opacity: 1;
    transform: translate(1px, -1px);
  }

  .followups__item:active {
    background-color: var(--bg-active);
    transform: translateY(0);
  }

  .followups__label {
    min-inline-size: 0;
  }

  /* ======================================================================
     Artifacts
     ----------------------------------------------------------------------
     ONE border language for every card an answer carries: `--artifact-line`
     for the hairline, `--artifact-radius` for the corner, and no exceptions
     except the one that has to be an exception — the dashed amber frame on
     the unknown-kind fallback, whose whole job is to look unlike a card.

     And ONE frame per card. A card inside a card is two hairlines 12px apart,
     which is how a clean answer starts looking like a form: the SQL card's
     statements are separated by a rule instead of boxed, and the unknown
     card's payload is delineated by its own ground rather than a second
     border. Anything wider than the card scrolls inside the card
     (`.artifact__scroll`, `.artifact__canvas`, `.artifact__payload`) — the
     page body is never allowed a sideways scrollbar.
     ====================================================================== */

  .artifact {
    background-color: var(--bg-surface);
    border: 1px solid var(--artifact-line);
    border-radius: var(--artifact-radius);
    /* Keeps a wide table's own scroller inside the rounded corners. */
    overflow: hidden;
  }

  .artifact__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-block-end: 1px solid var(--artifact-line);
  }

  .artifact__title {
    flex: 1 1 auto;
    min-inline-size: 0;
    color: var(--fg-primary);
    font-size: var(--text-base);
    line-height: var(--lh-base);
    font-weight: var(--weight-semibold);
  }

  .artifact__count {
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .artifact__asof {
    margin-inline-start: auto;
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  /* An as-of that disagrees with another source on the same answer is a
     genuine freshness warning, which is what amber is for. */
  .artifact__asof--conflict {
    color: var(--warning-text);
    font-weight: var(--weight-medium);
  }

  .artifact__asof--conflict::before {
    content: "";
    display: inline-block;
    inline-size: var(--size-dot);
    block-size: var(--size-dot);
    margin-inline-end: var(--space-1);
    border-radius: var(--radius-pill);
    background-color: var(--warning);
  }

  .artifact__note {
    max-inline-size: var(--prose-measure);
    padding: var(--space-3) var(--space-4);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  .artifact__problem {
    max-inline-size: var(--prose-measure);
    padding: var(--space-2) var(--space-4) var(--space-3);
    color: var(--warning-text);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  /* ---- Chart ----------------------------------------------------------- */

  /* The chart card is its own container so the reserve below can be stated as
     a share of the card's real width instead of a guess. */
  .artifact--chart {
    container-type: inline-size;
  }

  /* The canvas's inset is a MARGIN, not padding, and that is the whole fix.
     charts.js measures `el.clientWidth` — which padding is inside of — and
     sizes a viewBox `W × clamp(140, W * 0.42, 360)`; the SVG below then lays
     out at 100% of the CONTENT box, which padding is outside of. So the
     drawing was always a slightly different shape from the box that had been
     measured, and the reserve could not be right at more than one width: a
     700px card reserved 294 and drew 313, a 300px card was 29px out (the
     140 floor), a 1000px card left 40px of dead space (the 360 ceiling).

     As a margin, the box charts.js measures IS the box the SVG fills. The
     reserve is then simply the height charts.js will choose — the same clamp,
     over the same width — with no ratio arithmetic in between, and the answer
     does not move at ANY width. The chart is also drawn at 1:1 instead of
     being scaled down by the padding, so its axis type renders at the size it
     was asked for. The inset itself is unchanged to the eye: the canvas has
     no ground of its own, so a margin and a padding look identical here.

     `--canvas-pad` is declared on the canvas's own bare rule below so the
     handheld block can tighten it by source order; three of charts.js's four
     DEFAULTS cross the boundary here by hand (`aspect` as 42cqi, `minHeight`
     and `maxHeight` as the clamp's ends) and test_polish_adversarial.py
     re-derives all of them from both files.

     Two declarations, not one: where container units are not understood the
     second is dropped and charts.js's own floor still holds. */
  .artifact--chart .artifact__canvas {
    margin: var(--canvas-pad);
    min-block-size: 140px;
    min-block-size: clamp(140px, calc(42cqi - 2 * 0.42 * var(--canvas-pad)), 360px);
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }

  /* The inset, on the bare class so the 640px block can override it — a
     `.artifact--chart .artifact__canvas` value there would lose on
     specificity however late it were written. */
  .artifact__canvas {
    --canvas-pad: var(--space-4);
  }

  .artifact__canvas > svg {
    inline-size: 100%;
    block-size: auto;
    overflow: visible;
  }

  /* The numbers behind the chart, carried as a real table because an SVG is
     an image to a screen reader. reset.css's `.sr-only` does the hiding — but
     it cannot make this box 1px on its own: a table's auto layout floors at
     its min-content width and ignores the `inline-size` it is given, so a
     twelve-column result would stay a wide absolutely-positioned box able to
     extend the scrollable area of its containing block. Fixed layout is what
     actually holds it to 1px. */
  .artifact__data {
    table-layout: fixed;
    max-inline-size: 1px;
  }

  /* ---- Table ----------------------------------------------------------
     The one place on this screen where sideways scrolling is allowed, and it
     is contained here so the page body never gains a horizontal scrollbar.
     The scroller is focusable (`tabindex="0"` in the template) because a
     table that scrolls sideways is unreachable from a keyboard otherwise. */

  .artifact__scroll-wrap {
    position: relative;
  }

  .artifact__scroll {
    max-inline-size: 100%;
    /* Caps a thousand-row result at something a page can hold; the sticky
       header components.css puts on `.table thead th` needs a scrolling
       ancestor to stick to, and this is it. */
    max-block-size: calc(var(--space-16) * 6);
    overflow: auto;
    overscroll-behavior: contain;
  }

  /* Edge fades: a hint that there is more table off to that side, since a wide
     result has no other cue on a phone with no visible scrollbar. Fixed over
     the scroller's edges (siblings of it, not inside it, so they never scroll
     away with the content); `tables.js` toggles `.is-visible` from the real
     scroll position, so a table that already fits shows neither. */
  .artifact__fade {
    position: absolute;
    inset-block: 0;
    inline-size: var(--space-8);
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
    z-index: var(--z-base);
  }

  .artifact__fade.is-visible {
    opacity: 1;
  }

  .artifact__fade--start {
    inset-inline-start: 0;
    background: linear-gradient(to right, var(--bg-surface), transparent);
  }

  .artifact__fade--end {
    inset-inline-end: 0;
    background: linear-gradient(to left, var(--bg-surface), transparent);
  }

  /* Drawn inside, because `.artifact`'s `overflow: hidden` would clip a ring
     sitting outside the scroller's edge. */
  .artifact__scroll:focus-visible {
    outline-offset: calc(var(--focus-ring-offset) * -1);
  }

  /* A result cell is a value, not prose: it scrolls sideways with the rest of
     the table rather than wrapping and destroying the row rhythm. That is the
     whole reason `.artifact__scroll` exists. */
  .artifact--table .table tbody td,
  .artifact--table .table thead th {
    white-space: nowrap;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
  }

  /* The one thing a table inside an ANSWER does not inherit from the data
     surfaces: their column headings. DEALER · UNITS · AVG PRICE in tracked
     12px capitals is the furniture of a spreadsheet, and this table is three
     lines of evidence in the middle of a paragraph. The layer order does the
     rest — `chat` comes after `components`, so this needs no extra weight.
     Uppercase headings stay where they earn their keep: /operations, the
     activity feed, every real data surface. */
  .artifact--table .table thead th {
    letter-spacing: normal;
    text-transform: none;
    background-color: var(--bg-surface);
    color: var(--fg-muted);
    font-weight: var(--weight-semibold);
  }

  /* ---- Table preview -----------------------------------------------------
     The card's own copy of the first few rows, plain and unsortable —
     `enhanceTable` never runs on it. It exists so a long result reads as a
     glance rather than a scroll, with the full interactive table one click
     away in `dialog--table`. */
  .artifact__preview {
    padding: var(--space-2) var(--space-3) 0;
    max-block-size: 380px;
    overflow: auto;
  }

  .artifact__preview .table {
    max-inline-size: 100%;
  }

  .artifact__preview .table tbody td,
  .artifact__preview .table thead th {
    white-space: nowrap;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
  }

  .artifact__preview .table thead th {
    letter-spacing: normal;
    text-transform: none;
  }

  .artifact__expand {
    display: inline-flex;
    margin: var(--space-2) var(--space-4) var(--space-3);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-control);
    color: var(--accent-text);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    transition: background-color var(--dur-fast) var(--ease-out);
  }

  .artifact__expand:hover {
    background-color: var(--bg-hover);
  }

  /* A wide result earns the same extra room `dialog--plan`/`dialog--sql`
     take for their own wide content — a sideways-scrolling table forced into
     prose width would be worse than the one it replaces. */
  .dialog--table {
    inline-size: min(100% - var(--space-6) * 2, calc(var(--chat-measure) + var(--space-16) * 2));
  }

  /* ---- Chart group --------------------------------------------------------
     One answer's per-vehicle charts ("Market ageing - <vehicle>", one after
     another) folded into a single closed disclosure — see
     `groupChartArtifacts` in surfaces.js. Closed by default: the whole point
     is that a dozen identical-looking cards start as one line. */
  .artifact-group {
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--artifact-radius);
    box-shadow: var(--shadow-xs);
    overflow: hidden;
    transition: all var(--dur-fast) var(--ease-out);
  }

  .artifact-group[open] {
    border-color: var(--border-default);
    padding-block-end: var(--space-2);
  }

  .artifact-group__summary {
    list-style: none;
    cursor: pointer;
    padding: var(--space-2) var(--space-3);
    color: var(--fg-secondary);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    display: flex;
    align-items: center;
    background-color: var(--bg-surface);
    transition: background-color var(--dur-fast) var(--ease-out);
  }

  .artifact-group__summary:hover {
    background-color: var(--bg-hover);
    color: var(--fg-primary);
  }

  .artifact-group__summary::-webkit-details-marker {
    display: none;
  }

  .artifact-group__summary::before {
    content: "▸";
    display: inline-block;
    margin-inline-end: var(--space-2);
    color: var(--fg-muted);
    transition: transform var(--dur-fast) var(--ease-out);
  }

  .artifact-group[open] > .artifact-group__summary::before {
    transform: rotate(90deg);
  }

  .artifact-group > .artifact {
    margin: var(--space-2) var(--space-3);
  }

  .artifact-group > .artifact:first-of-type {
    margin-block-start: var(--space-1);
  }

  /* ---- Card ------------------------------------------------------------
     `.stat` is a shared block; only the grid that holds these and the two
     fields a metric card adds — unit and withheld/unavailable flag — are set
     here. */

  /* Tiles in one card, so the columns are told apart by space alone — a
     wider gap across than down is what makes a row of them read as a row. */
  .artifact--card .stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--space-16) * 2), 1fr));
    gap: var(--space-2) var(--space-5);
    padding: var(--space-3) var(--space-4);
  }

  /* Not a card. `.stat` is shell.css's dashboard tile — a real card, with a
     hairline, a white ground and 16px of padding — and the answer cards
     borrow the class name from the same markup. Left alone, every metric
     inside the card drew a second frame 1px from the first: a card in a card,
     with a doubled 2px line as the only thing between them. The tiles are
     separated by the grid's own gap and nothing else, so this block states
     the three box properties it is undoing rather than inheriting them. */
  .artifact--card .stat,
  .artifact--stats .stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-inline-size: 0;
    padding: 0;
    border: 0;
    background: none;
  }

  .artifact--card .stat__label {
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-medium);
  }

  /* Rendered verbatim from the projector — the UI never re-formats a number
     that has already been formatted against its metric's unit. */
  .artifact--card .stat__value {
    color: var(--fg-primary);
    font-size: var(--text-xl);
    line-height: var(--lh-xl);
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
  }

  .artifact--card .stat__unit {
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
  }

  /* The metric card is the one card that states its as-of UNDER the numbers
     rather than beside the title, so its as-of is a child of the card and not
     of the padded head — and without this it sits flush against the card's own
     border, the one place on this screen where text touched a hairline. */
  .artifact--card > .artifact__asof {
    padding: 0 var(--space-4) var(--space-4);
  }

  .artifact--card > .artifact__asof:empty {
    display: none;
  }

  /* `withheld` and `unavailable` are not zeroes and must never be mistaken
     for one, so the value drops to muted and the reason is stated. */
  .artifact--card .stat--problem .stat__value {
    color: var(--fg-faint);
    font-weight: var(--weight-regular);
  }

  /* Sentence case, like everything else inside an answer: the producer writes
     "withheld", and WITHHELD in tracked capitals is the loudest thing on a
     card whose whole point is that a number is missing quietly. */
  .artifact--card .stat__flag {
    align-self: start;
    padding-inline: var(--space-1);
    background-color: var(--warning-subtle);
    border-radius: var(--radius-control);
    color: var(--warning-text);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-medium);
  }

  /* ---- SQL ------------------------------------------------------------- */

  /* Full-bleed sections inside the card's own frame, not a stack of boxes
     inside a box: the statements are separated by one hairline in the shared
     border language and the artifact frame is the only frame. */
  .sqlset {
    display: grid;
    gap: 0;
    padding: 0;
  }

  /* When the metric layer reported no statements the list renders empty and
     `.artifact__note` carries the explanation instead. Without this the empty
     grid still contributes its own padding — an unexplained band above a
     sentence whose entire job is to explain. */
  .artifact--sql .sqlset:not(:has(.sqlset__item)) {
    display: none;
  }

  .sqlset__item + .sqlset__item {
    border-block-start: 1px solid var(--artifact-line);
  }

  .sqlset__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-2);
    padding: var(--space-2) var(--space-3);
    background-color: var(--bg-sunken);
    border-block-end: 1px solid var(--artifact-line);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
  }

  .sqlset__step {
    padding: 0;
    background: none;
    color: var(--accent-text);
    font-family: var(--font-mono);
  }

  .sqlset__tool {
    color: var(--fg-primary);
    font-weight: var(--weight-medium);
  }

  .sqlset__rows,
  .sqlset__ms {
    color: var(--fg-muted);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
  }

  .sqlset__copy {
    margin-inline-start: auto;
    padding-inline: var(--space-2);
    min-block-size: var(--control-h-sm);
    border-radius: var(--radius-control);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
  }

  .sqlset__copy:hover {
    background-color: var(--bg-active);
    color: var(--fg-primary);
  }

  /* `.code` is the template's block for a preformatted listing and is not
     declared by any shared stylesheet, so the chat's copies are set here
     rather than left as bare UA <pre>. */
  .sqlset__item .code,
  .plan__raw .code {
    padding: var(--space-3);
    background-color: var(--bg-sunken);
    color: var(--fg-secondary);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    tab-size: 2;
  }

  /* An empty note is still a padded band under the statements. Nothing to say
     means nothing drawn; `:empty` re-draws it the moment chat.js fills it. */
  .artifact--sql .artifact__note:empty {
    display: none;
  }

  .sqlset__calls {
    padding: 0 var(--space-4) var(--space-4);
  }

  .sqlset__calls > summary {
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  .sqlset__calls > summary:hover {
    color: var(--fg-primary);
  }

  .sqlset__calls-list {
    display: grid;
    gap: var(--space-1);
    margin-block-start: var(--space-2);
    color: var(--fg-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  /* ======================================================================
     Plan preview
     ----------------------------------------------------------------------
     A plan is data — cached, logged, validated, replayed, and never
     executable — which is exactly why it can be shown. It is set as a
     specification document: facts, then waves, then output, budget and scope.
     ====================================================================== */

  .plan {
    display: grid;
    gap: var(--space-6);
    min-inline-size: 0;
  }

  .plan__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
  }

  /* Same reason as `.badge--route[data-route=""]`: the origin badge is filled
     from `/api/runs/<id>/plan` and is an empty grey stub until it is. */
  .plan__head .badge[data-origin=""] {
    display: none;
  }

  .plan__facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--space-16) * 2), 1fr));
    gap: var(--space-3);
    inline-size: 100%;
    margin-block-start: var(--space-3);
    padding-block-start: var(--space-3);
    border-block-start: 1px solid var(--border-subtle);
  }

  .plan__fact dt {
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }

  .plan__fact dd {
    margin-block-start: var(--space-1);
    color: var(--fg-primary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    overflow-wrap: break-word;
  }

  .plan__fact dd code {
    color: var(--fg-secondary);
    font-family: var(--font-mono);
  }

  .plan__section {
    min-inline-size: 0;
  }

  .plan__section-title {
    margin-block-end: var(--space-3);
    padding-block-end: var(--space-2);
    border-block-end: 1px solid var(--border-subtle);
    color: var(--fg-primary);
    font-size: var(--text-base);
    line-height: var(--lh-base);
    font-weight: var(--weight-semibold);
  }

  /* Appended with no separating space in the markup, so the gap is this. */
  .plan__count {
    margin-inline-start: var(--space-2);
    color: var(--fg-faint);
    font-weight: var(--weight-regular);
    font-variant-numeric: tabular-nums;
  }

  .plan__count:empty {
    margin-inline-start: 0;
  }

  /* The fan-out is the shape of the execution: everything in one wave ran in
     a single `execute_batch`, so waves are the outer list. */
  .plan__waves {
    display: grid;
    gap: var(--space-4);
  }

  /* Grid items default to `min-width: auto`, which a long parameter value in
     a step below would happily blow past. */
  .plan__wave {
    min-inline-size: 0;
  }

  .plan__wave-title {
    margin-block-end: var(--space-2);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }

  .plan__steps {
    display: grid;
    gap: var(--space-2);
  }

  .plan__step {
    padding: var(--space-3);
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-control);
  }

  .plan__step-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-2);
    min-inline-size: 0;
  }

  .plan__step-id {
    color: var(--accent-text);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
  }

  .plan__step-tool {
    color: var(--fg-primary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    font-weight: var(--weight-medium);
  }

  .plan__step-needs {
    color: var(--fg-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  /* Only drawn when the step actually depends on something. */
  .plan__step-needs:not(:empty)::before {
    content: "";
    display: inline-block;
    inline-size: var(--icon-xs);
    block-size: var(--icon-xs);
    margin-inline-end: var(--space-1);
    vertical-align: -0.1em;
    background-color: currentColor;
    mask-image: var(--mask-arrow-right);
    mask-size: 100% 100%;
    -webkit-mask-image: var(--mask-arrow-right);
    -webkit-mask-size: 100% 100%;
  }

  .plan__kv {
    display: grid;
    gap: var(--space-1);
    min-inline-size: 0;
  }

  .plan__kv-row {
    display: grid;
    grid-template-columns: minmax(var(--space-16), auto) minmax(0, 1fr);
    gap: var(--space-3);
    align-items: baseline;
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  .plan__kv-row dt {
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  .plan__kv-row dd {
    min-inline-size: 0;
    color: var(--fg-primary);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    overflow-wrap: break-word;
  }

  .plan__params {
    margin-block-start: var(--space-2);
    padding-block-start: var(--space-2);
    border-block-start: 1px dashed var(--border-subtle);
  }

  /* Four short columns; fixed layout keeps them even and keeps a long label
     from starving the others. */
  .plan__projection {
    inline-size: 100%;
    table-layout: fixed;
  }

  .plan__narrative {
    margin-block-start: var(--space-3);
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  .plan__note,
  .plan__empty {
    margin-block-start: var(--space-3);
    max-inline-size: var(--measure);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  .plan__attempts {
    display: grid;
    gap: var(--space-3);
  }

  /* A plan the validator rejected before the planner wrote a second one. It is
     kept quiet — the verdict badge carries the colour — because the point is
     that it is visible at all, not that it is alarming. */
  .plan__attempt {
    padding: var(--space-3);
    background-color: var(--bg-sunken);
    border-inline-start: 2px solid var(--danger-border);
    border-radius: var(--radius-control);
  }

  .plan__attempt-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
  }

  .plan__attempt-origin {
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  .plan__attempt-time {
    margin-inline-start: auto;
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-variant-numeric: tabular-nums;
  }

  .plan__attempt-reasons {
    margin-block-start: var(--space-2);
    padding-inline-start: var(--space-4);
    list-style: disc;
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
  }

  .plan__attempt-reasons li::marker {
    color: var(--danger);
  }

  .plan__raw > summary {
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  .plan__raw > summary:hover {
    color: var(--fg-primary);
  }

  .plan__raw .code {
    margin-block: var(--space-2);
    max-block-size: calc(var(--space-16) * 5);
    overflow: auto;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-control);
  }

  /* ======================================================================
     Dialogs
     ----------------------------------------------------------------------
     Real <dialog> elements opened with `showModal()`: the scrim, the focus
     trap and the Escape key are the browser's. Only the box is ours.
     ====================================================================== */

  .dialog {
    position: fixed;
    inset: 0;
    margin: auto;
    /* Sized off the transcript measure so a plan reads at the same rhythm as
       the answer it explains. */
    inline-size: min(100% - var(--space-6) * 2, var(--chat-measure));
    max-block-size: 85dvh;
    display: none;
    flex-direction: column;
    background-color: var(--bg-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
  }

  .dialog[open] {
    display: flex;
    animation: agp-rise-in var(--dur-base) var(--ease-out);
  }

  /* `::backdrop` inherits from the root, where the tokens live, so this
     resolves in both themes without a second declaration. */
  .dialog::backdrop {
    background-color: var(--bg-overlay);
  }

  /* Both of these carry content wider than prose — a four-column projection
     and a JSON document in one, monospace SQL listings in the other — so both
     take the extra room rather than forcing a sideways scroll on every line. */
  .dialog--plan,
  .dialog--sql {
    inline-size: min(100% - var(--space-6) * 2, calc(var(--chat-measure) + var(--space-16) * 2));
  }

  .dialog__head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex: 0 0 auto;
    padding: var(--space-4) var(--space-5);
    border-block-end: 1px solid var(--border-subtle);
  }

  .dialog__title {
    flex: 1 1 auto;
    min-inline-size: 0;
    color: var(--fg-primary);
    font-size: var(--text-md);
    line-height: var(--lh-md);
    font-weight: var(--weight-semibold);
  }

  .dialog__close {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    inline-size: var(--control-h-sm);
    block-size: var(--control-h-sm);
    border-radius: var(--radius-control);
    color: var(--fg-muted);
    font-size: var(--text-xl);
    line-height: 1;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
  }

  .dialog__close:hover {
    background-color: var(--bg-hover);
    color: var(--fg-primary);
  }

  /* The only thing in the dialog that scrolls. */
  .dialog__body {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    overflow: auto;
    overscroll-behavior: contain;
    padding: var(--space-5);
  }

  /* A sticky header inside a dialog has to match the dialog's ground, not the
     surface's — identical in light, two distinct greys in dark. */
  .dialog__body .table thead th {
    background-color: var(--bg-raised);
  }

  /* Inside a dialog the SQL artifact is the whole content, so it drops its
     card chrome rather than framing a frame. */
  .dialog__body > .artifact {
    background: none;
    border: 0;
    border-radius: 0;
  }

  .dialog__body > .artifact > .artifact__head {
    padding-inline: 0;
    padding-block-start: 0;
  }

  .dialog__body > .artifact > .sqlset,
  .dialog__body > .artifact > .artifact__note,
  .dialog__body > .artifact > .sqlset__calls {
    padding-inline: 0;
  }

  /* ======================================================================
     Responsive
     ----------------------------------------------------------------------
     The measure caps the column long before 1440, so the wide breakpoints
     only manage gutters. 640 is where the real work happens.
     ====================================================================== */

  @media (max-width: 900px) {
    .chat {
      --chat-gutter: var(--space-5);
    }
  }

  @media (max-width: 640px) {
    .chat {
      --chat-gutter: var(--space-4);
    }

    .chat__scroll {
      padding-block: var(--space-4) var(--space-6);
    }

    .empty {
      padding-block: var(--space-6) var(--space-4);
    }

    .empty__greeting {
      font-size: var(--text-xl);
      line-height: var(--lh-xl);
    }

    /* The headline is clipped at this width, so the total becomes the only
       readable figure and takes its place. */
    .trace__total {
      display: block;
    }

    /* Detail drops to its own line rather than being reduced to an ellipsis;
       the durations stay in their column on the line above. Placed by hand,
       because auto-placement around an explicitly positioned item depends on
       source order in a way that is not worth reasoning about twice. */
    .trace__row {
      grid-template-columns: var(--size-node) minmax(0, 1fr) auto;
      row-gap: var(--space-1);
      /* Two rows now, so the mark rides the first line, not the middle. */
      align-items: start;
    }

    .trace__mark {
      grid-area: 1 / 1;
      margin-block-start: calc((var(--lh-sm) - var(--size-node)) / 2);
    }

    .trace__label { grid-area: 1 / 2; white-space: normal; }
    .trace__ms    { grid-area: 1 / 3; }

    .trace__detail {
      grid-area: 2 / 2 / auto / -1;
      white-space: normal;
      overflow: visible;
    }

    /* Most stages report no detail; an empty second row would still cost a
       row gap. */
    .trace__detail:empty {
      display: none;
    }

    .trace__panel {
      padding: var(--space-3);
    }

    /* The composer tools are icon-only at every width now (`.sr-only` on the
       label, `.composer__tool--icon` on the button), so the narrow viewport no
       longer has its own opinion about their text — and neither does it need
       to hide an "Enter to send" caption that no longer exists. */

    /* A phone column is already narrower than the quote measure, so the cap
       stops earning its keep and only the padding needs tightening. */
    .msg--user .msg__body {
      max-inline-size: 100%;
      padding: var(--space-2) var(--space-3);
    }

    /* Ref and source on the first line; kind and date share the second. */
    .citation {
      grid-template-columns: auto minmax(0, 1fr) auto;
      row-gap: 0;
    }

    .citation__ref    { grid-area: 1 / 1; }
    .citation__source { grid-area: 1 / 2 / auto / -1; }
    .citation__kind   { grid-area: 2 / 2; }
    .citation__asof   { grid-area: 2 / 3; }

    /* The reserve is a share of the card's own width (see `42cqi` above), so
       it has already narrowed by the time this breakpoint applies — only the
       inset needs tightening here, and the reserve follows it because it is
       stated in terms of this token. Re-pinning a flat height would put the
       jolt straight back on the smallest screen. */
    .artifact__canvas {
      --canvas-pad: var(--space-3);
    }

    .artifact__head {
      padding: var(--space-3);
    }

    .artifact__asof {
      margin-inline-start: 0;
    }

    .dialog,
    .dialog--plan,
    .dialog--sql {
      inline-size: min(100% - var(--space-4) * 2, var(--chat-measure));
      max-block-size: 90dvh;
    }

    .dialog__head {
      padding: var(--space-3) var(--space-4);
    }

    .dialog__body {
      padding: var(--space-4);
    }

    .plan__kv-row {
      grid-template-columns: minmax(0, 1fr);
      gap: 0;
    }
  }

  /* Hover-revealed affordances are a pointer idea; nothing on this screen
     depends on one, and the SQL copy button needs a resting state on touch. */
  @media (hover: none) {
    .sqlset__copy {
      background-color: var(--bg-active);
      color: var(--fg-secondary);
    }
  }

  /* ======================================================================
     Reduced motion
     ----------------------------------------------------------------------
     reset.css already collapses every duration to 1ms globally, which is the
     right default but the wrong outcome for two things here: an animation
     that is *carrying state* must not be left frozen on an arbitrary frame.
     Both resolve to a still, correct, legible state instead.
     ====================================================================== */

  @media (prefers-reduced-motion: reduce) {
    /* The caret stops blinking and simply stays drawn — it still marks where
       the next character will land. */
    .msg__caret {
      animation: none;
      opacity: 1;
    }

    .chat__connection-mark {
      animation: none;
    }

    /* The travelling underline becomes a full-width static rule: the live
       stage is still the only row wearing one, so no information is lost. */
    .trace__row:not(.is-done)::after,
    .trace:not([open]):has(.trace__row:not(.is-done)) .trace__summary::before {
      animation: none;
      inline-size: 100%;
      transform: none;
    }
  }

  /* ======================================================================
     Forced colours
     ----------------------------------------------------------------------
     Backgrounds are dropped in this mode, so anything that carried meaning
     purely as a fill has to regain a border to keep saying it.
     ====================================================================== */

  @media (forced-colors: active) {
    /* The stage marks say done / open / model call / failed entirely through
       their fill; collapsing all four to one system colour would erase the
       timeline's only distinction, which is the documented case for opting a
       specific element out. */
    .trace__mark {
      forced-color-adjust: none;
    }

    /* These read as buttons only because of their fill, which this mode drops. */
    .composer__submit,
    .composer__tool--incognito[aria-pressed="true"],
    .starter,
    .followups__item {
      border: 1px solid currentColor;
    }

    /* Hairlines drawn as backgrounds need a system colour to survive. */
    .msg__caret,
    .trace__row:not(.is-done)::after,
    .trace:not([open]):has(.trace__row:not(.is-done)) .trace__summary::before {
      background-color: CanvasText;
    }
  }

  /* ── Text values in a metric card ──────────────────────────────────────
     A stat tile is sized for a number. A place name is not a number: at
     display type "Tampa-St. Petersburg-Clearwater, FL" wraps to four lines and
     drags the whole row's height with it, which is how a clean card starts
     looking broken. Text-format cells keep the tile but drop to body type and
     take the width they need — the projector already tells us which they are.
     ------------------------------------------------------------------- */
  .stats .stat[data-format="text"] {
    grid-column: span 2;
  }
  .stats .stat[data-format="text"] .stat__value {
    font-size: var(--text-md);
    line-height: var(--lh-md);
    font-weight: var(--weight-medium);
    font-variant-numeric: normal;
    text-wrap: balance;
  }
  .stats .stat[data-format="date"] .stat__value,
  .stats .stat[data-format="datetime"] .stat__value {
    font-size: var(--text-lg);
    line-height: var(--lh-lg);
  }

  @media (max-width: 640px) {
    .stats .stat[data-format="text"] { grid-column: span 1; }
  }
}

@layer chat {
  /* The incognito toggle in the topbar: same component, one positional hook.
     The gap keeps it clear of whatever page_actions may add later. */
  .topbar__incognito { margin-inline-start: var(--space-2); }
}

@layer chat {
  /* ── The all-chats index (`/?all=1`) ────────────────────────────────────
     A management page, not a transcript: rows are links with the same
     hover-revealed delete the sidebar wears (the button class is shell.css's,
     so the two affordances can never drift apart). */
  .chats-index { display: block; }
  .chats-index__search {
    display: flex; align-items: center; gap: var(--space-2);
    margin-block-end: var(--space-4);
  }
  .chats-index__field { flex: 1 1 20rem; }
  .chats-index__list {
    list-style: none; margin: 0; padding: 0;
    display: grid; gap: 2px;
  }
  .chats-index__row { position: relative; display: flex; align-items: center; }
  .chats-index__link {
    flex: 1; min-width: 0;
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
    padding: var(--space-3); padding-right: 40px;
    border: 1px solid var(--border-subtle); border-radius: var(--radius-control);
    background: var(--bg-surface); color: inherit; text-decoration: none;
  }
  .chats-index__link:hover { background: var(--bg-hover); border-color: var(--border-strong); }
  .chats-index__title {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--fg-primary); font-size: var(--text-base); font-weight: var(--weight-medium);
  }
  .chats-index__when { flex: none; color: var(--fg-faint); font-size: var(--text-xs); }
  .chats-index__remove {
    position: absolute; right: var(--space-3); top: 50%; transform: translateY(-50%);
    display: flex;
  }
  .chats-index__row:hover .nav__thread-del,
  .chats-index__row:focus-within .nav__thread-del {
    opacity: 1; pointer-events: auto;
  }
}

@layer chat {
  /* Settings > Voice: the person turned the button off; Settings > Reading:
     answers may take the serif face. Both are attribute-driven from <html>. */
  :root[data-voice="off"] .composer__tool--voice { display: none !important; }
  .msg__prose { font-family: var(--font-prose, inherit); }
}

/* ===========================================================================
   Answer cards — the governed block vocabulary beyond chart/table/card/sql:
   `stats` (KPI grid with reported deltas), `ranked` (scored bars), `notice`
   (a limit said out loud) and the unknown-kind fallback. One dispatch draws
   them (`surfaces.js` streaming, `chat/_blocks.html` server-side); these
   rules are the whole of their styling, on tokens only, so both themes come
   from tokens.css and nothing here animates — motion-off changes nothing
   because there is nothing to switch off.
   =========================================================================== */
@layer chat {

  /* ---- stats: label / value / reported delta --------------------------- */

  .artifact--stats {
    position: relative;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    background: var(--bg-surface);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    margin-block-end: var(--space-3);
  }

  .artifact--stats .artifact__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-5);
    border-bottom: 1px solid var(--border-subtle);
    background: var(--bg-surface);
  }

  .artifact--stats .artifact__title {
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--fg-primary);
    letter-spacing: -0.01em;
  }

  .artifact--stats .artifact__count {
    font-size: 0.6875rem;
    font-weight: var(--weight-semibold);
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    color: var(--fg-muted);
  }

  .artifact--stats .stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--space-16) * 2), 1fr));
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
  }

  .artifact--stats .stat {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: var(--space-3) var(--space-4);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    transition: all var(--dur-fast) var(--ease-out);
  }

  /* Single stat inside an artifact card: avoid nested box-within-a-box */
  .artifact--stats .stats:has(> :only-child) {
    padding: var(--space-4) var(--space-5) var(--space-5);
  }

  .artifact--stats .stats > :only-child {
    background: transparent;
    border: none;
    padding: 0;
    box-shadow: none;
  }

  .artifact--stats .stat:hover {
    border-color: var(--accent-border);
    transform: translateY(-1px);
    box-shadow: var(--shadow-xs);
  }

  .artifact--stats .stats > :only-child:hover {
    transform: none;
    box-shadow: none;
  }

  .artifact--stats .stat__label {
    color: var(--fg-muted);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }

  /* Icon and label on one row, the value on its own below -- restrained */
  .stat__heading {
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

  .stat__icon {
    flex: none;
    color: var(--accent);
  }

  /* Large, bold, executive monospace numeric styling */
  .artifact--stats .stat__value {
    color: var(--fg-primary);
    font-size: 2.125rem;
    line-height: 1.15;
    font-weight: 700;
    font-family: var(--font-numeric, var(--font-mono));
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.025em;
    margin-top: var(--space-2);
  }

  .artifact--stats .stat[data-tone="success"] .stat__value { color: var(--success-text); }
  .artifact--stats .stat[data-tone="warning"] .stat__value { color: var(--warning-text); }
  .artifact--stats .stat[data-tone="danger"]  .stat__value { color: var(--danger-text); }

  /* The delta's direction and its sentiment are separate facts: "up" is good
     for gross and bad for days-on-lot, so the glyph follows data-dir and the
     colour follows data-sentiment — never one inferred from the other. */
  .stat__delta {
    align-self: start;
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-1);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-medium);
    font-variant-numeric: tabular-nums;
    color: var(--fg-muted);
  }

  /* Relative to the delta's own type, not a fixed 9px: the glyph is part of
     the number it qualifies, and a px arrow beside 25px type at scale XL
     reads as a speck. */
  .stat__delta::before {
    font-size: 0.72em;
  }

  .stat__delta[data-dir="up"]::before { content: "\25B2"; }   /* ▲ */
  .stat__delta[data-dir="down"]::before { content: "\25BC"; } /* ▼ */
  .stat__delta[data-dir="flat"]::before { content: "\25A0"; } /* ■ */

  .stat__delta[data-sentiment="good"] { color: var(--success-text); }
  .stat__delta[data-sentiment="bad"] { color: var(--danger-text); }
  .stat__delta[data-sentiment="neutral"] { color: var(--fg-muted); }

  /* ---- ranked: scored bars in server order ------------------------------ */

  /* The ordinal is a CSS counter, so the numbering can never disagree with
     the order the projector ranked the rows in. */
  .artifact--ranked .ranked {
    counter-reset: rank;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
  }

  .ranked__item {
    counter-increment: rank;
    display: grid;
    grid-template-columns: var(--space-6) minmax(0, 1fr);
    column-gap: var(--space-3);
    row-gap: 4px;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    transition: all var(--dur-fast) var(--ease-out);
  }

  .ranked__item:hover {
    background-color: var(--bg-hover);
    border-color: var(--border-strong);
    transform: translateY(-1px);
    box-shadow: var(--shadow-xs);
  }

  .ranked__item::before {
    content: counter(rank);
    grid-column: 1;
    grid-row: 1 / span 3;
    padding-block-start: 2px;
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-variant-numeric: tabular-nums;
    font-weight: var(--weight-bold);
    text-align: end;
  }

  .ranked__head {
    grid-column: 2;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    min-inline-size: 0;
  }

  .ranked__label {
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--fg-primary);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
  }

  .ranked__score {
    flex: none;
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    font-family: var(--font-numeric, var(--font-mono));
    font-variant-numeric: tabular-nums;
  }

  .ranked__item[data-tone="danger"] .ranked__score {
    color: var(--danger-text);
  }

  .ranked__item[data-tone="warning"] .ranked__score {
    color: var(--warning-text);
  }

  .ranked__item[data-tone="success"] .ranked__score {
    color: var(--success-text);
  }

  /* Bar indicator */
  .ranked__bar {
    grid-column: 2;
    display: block;
    block-size: 5px;
    border-radius: var(--radius-pill);
    background-color: var(--bg-sunken);
    overflow: hidden;
    margin-block: 2px;
  }

  .ranked__fill {
    display: block;
    block-size: 100%;
    border-radius: var(--radius-pill);
    background-color: var(--accent);
  }

  .ranked__item[data-tone="warning"] .ranked__fill { background-color: var(--warning); }
  .ranked__item[data-tone="danger"] .ranked__fill { background-color: var(--danger); }
  .ranked__item[data-tone="success"] .ranked__fill { background-color: var(--success); }

  .ranked__detail {
    grid-column: 2;
    color: var(--fg-muted);
    font-size: 0.8125rem;
    line-height: var(--lh-sm);
    font-weight: var(--weight-normal);
  }

  /* ---- notice: a limit, said out loud ----------------------------------- */

  /* The artifact frame yields to the callout: the tone border is the frame. */
  .artifact--notice {
    border: 0;
    background-color: transparent;
    overflow: visible;
  }

  .notice {
    border: 1px solid var(--artifact-line);
    border-inline-start-width: 4px;
    border-radius: var(--radius-lg);
    padding: var(--space-4) var(--space-5);
    background-color: var(--bg-surface);
    box-shadow: var(--shadow-sm);
    transition: box-shadow var(--dur-fast) var(--ease-out);
  }

  .notice[data-tone="info"] {
    border-color: var(--info-border);
    border-inline-start-color: var(--info);
    background: linear-gradient(135deg, var(--info-subtle) 0%, var(--bg-surface) 100%);
  }

  .notice[data-tone="success"] {
    border-color: var(--success-border);
    border-inline-start-color: var(--success);
    background: linear-gradient(135deg, var(--success-subtle) 0%, var(--bg-surface) 100%);
  }

  .notice[data-tone="warning"] {
    border-color: var(--warning-border);
    border-inline-start-color: var(--warning);
    background: linear-gradient(135deg, var(--warning-subtle) 0%, var(--bg-surface) 100%);
  }

  .notice[data-tone="danger"] {
    border-color: var(--danger-border);
    border-inline-start-color: var(--danger);
    background: linear-gradient(135deg, var(--danger-subtle) 0%, var(--bg-surface) 100%);
  }

  .notice__title {
    color: var(--fg-primary);
    font-size: var(--text-base);
    line-height: var(--lh-base);
    font-weight: var(--weight-semibold);
  }

  .notice[data-tone="info"] .notice__title { color: var(--info-text); }
  .notice[data-tone="success"] .notice__title { color: var(--success-text); }
  .notice[data-tone="warning"] .notice__title { color: var(--warning-text); }
  .notice[data-tone="danger"] .notice__title { color: var(--danger-text); }

  .notice__body {
    margin-block-start: var(--space-1);
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    max-inline-size: var(--prose-measure);
  }

  .notice__items {
    margin-block-start: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
  }

  .notice__item {
    position: relative;
    padding-inline-start: var(--space-4);
    color: var(--fg-primary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
  }

  .notice__item::before {
    content: "\2022"; /* • */
    position: absolute;
    inset-inline-start: var(--space-1);
    color: var(--fg-faint);
  }

  /* ---- the unknown-kind fallback: shown as data, never executed --------- */

  /* Dashed and amber on purpose — visibly not a normal card, visibly not an
     error page. A kind nobody registered must be reportable at a glance. */
  .artifact--unknown {
    border: 1px dashed var(--warning-border);
    background-color: var(--warning-subtle);
  }

  .artifact--unknown .artifact__head {
    border-block-end-color: var(--warning-border);
  }

  .artifact__kind {
    flex: none;
    padding: 1px var(--space-2);
    border-radius: var(--radius-control);
    background-color: var(--bg-surface);
    border: 1px solid var(--warning-border);
    color: var(--warning-text);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
  }

  .artifact__invariant {
    padding: var(--space-3) var(--space-4) 0;
    color: var(--fg-secondary);
    font-size: var(--text-sm);
    line-height: var(--lh-sm);
    max-inline-size: var(--prose-measure);
  }

  /* The payload scrolls inside its own box; the page never scrolls sideways
     because a pack invented a wide kind. Its own ground is what separates it
     from the amber card around it — a second hairline inside the dashed frame
     was one border too many. */
  .artifact__payload {
    margin: var(--space-3) var(--space-4) var(--space-4);
    padding: var(--space-3);
    border-radius: var(--radius-control);
    background-color: var(--bg-surface);
    color: var(--fg-secondary);
    font-size: var(--text-xs);
    line-height: var(--lh-sm);
    max-block-size: calc(var(--space-16) * 4);
    overflow: auto;
    overscroll-behavior: contain;
    white-space: pre;
  }

  /* ---- design-system specimens ------------------------------------------ */

  .design-cards {
    display: grid;
    gap: var(--space-5);
    max-inline-size: 44rem;
  }

  .design-specimen__kind {
    margin-block-end: var(--space-2);
    color: var(--fg-faint);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
  }

  .design-specimen [data-slot="cards"] {
    display: grid;
    gap: var(--space-3);
  }

  /* ---- Composer Quick Intent Chips ------------------------------------- */
  .composer__intent-chips {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    overflow-x: auto;
    padding-block-end: var(--space-2);
    margin-block-end: var(--space-1);
    scrollbar-width: none;
  }
  .composer__intent-chips::-webkit-scrollbar { display: none; }

  .intent-chip {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    padding: var(--space-1) var(--space-3);
    color: var(--fg-secondary);
    font-size: var(--text-xs);
    line-height: var(--lh-xs);
    font-weight: var(--weight-medium);
    cursor: pointer;
    transition: all var(--dur-fast) var(--ease-out);
    box-shadow: var(--shadow-xs);
  }

  .intent-chip:hover {
    background-color: var(--accent-subtle);
    color: var(--accent-text);
    border-color: var(--accent-border);
    transform: translateY(-1px);
    box-shadow: var(--shadow-sm);
  }

  .intent-chip[data-category="price"]:hover {
    border-color: var(--domain-price);
    color: var(--domain-price);
    background-color: color-mix(in srgb, var(--domain-price) 12%, transparent);
  }
  .intent-chip[data-category="supply"]:hover {
    border-color: var(--domain-supply);
    color: var(--domain-supply);
    background-color: color-mix(in srgb, var(--domain-supply) 12%, transparent);
  }
  .intent-chip[data-category="mileage"]:hover {
    border-color: var(--domain-mileage);
    color: var(--domain-mileage);
    background-color: color-mix(in srgb, var(--domain-mileage) 12%, transparent);
  }
  .intent-chip[data-category="aging"]:hover {
    border-color: var(--domain-aging);
    color: var(--domain-aging);
    background-color: color-mix(in srgb, var(--domain-aging) 12%, transparent);
  }
  .intent-chip[data-category="sold"]:hover {
    border-color: var(--domain-sold);
    color: var(--domain-sold);
    background-color: color-mix(in srgb, var(--domain-sold) 12%, transparent);
  }
  .intent-chip[data-category="markets"]:hover {
    border-color: var(--domain-market);
    color: var(--domain-market);
    background-color: color-mix(in srgb, var(--domain-market) 12%, transparent);
  }
}

