/* ============================================================
   The Assistant — the nav-level conversation surface (/chat).

   Two halves:
     .asst-*         the page shell (rail, thread column, dock)
     .asst-card*     the interactive cards the agent draws, built
                     entirely by assistant_card_controller.js

   Everything reuses the Chart Room tokens and the shared .card /
   .seg / .pill / .tk-tile primitives, so dark mode needs almost
   nothing here: the tokens flip in design.css. The exceptions are
   the two surfaces this file invents (the area wash under a line
   and the composer's raised dock).

   The chat bubbles, ledger and whispers are the copilot's own
   .cop-* classes, shared verbatim — this page is a second surface
   on the same assistant, so it must never drift into a second
   dialect of it. Only the sizes that assume a 380px sidebar are
   re-stated for a 820px column.
   ============================================================ */

.asst {
  --asst-rail-w: 254px;
  /* The conversation column. It carries BOTH the prose and the charts, because
     the charts are the answer — so it is sized for a chart (a 12-period axis
     needs room) and the prose sits on its own 56ch measure inside it. */
  --asst-col: 780px;
  /* The artifacts drawer is an INDEX, not a second stage: a list of rows with a
     sparkline each. It never needs the 726px a chart deck needed, and taking
     less means the conversation barely moves when it opens. */
  --asst-arts-w: clamp(280px, calc(100vw - 900px), 360px);
  /* Shut, the drawer becomes a strip you can still see and click. It is the
     same pane at a different width, never a second thing that replaced it. */
  --asst-arts-strip: 46px;
  --asst-motion: cubic-bezier(.32, .72, 0, 1);
  position: relative;
  display: flex; flex-direction: column;
  height: 100dvh; overflow: hidden;
  background: var(--bg);
}

/* The page owns the viewport, so the bar is a flex row, not a sticky one. */
.asst .topbar { position: static; flex: 0 0 48px; }

.asst-body { flex: 1 1 auto; display: flex; min-height: 0; }

/* ---- Thread rail ---------------------------------------------------------- */
.asst-rail {
  flex: 0 0 var(--asst-rail-w);
  /* min-width:0, or a flex item's auto minimum lets its widest thread title
     push the column past its own basis — the rail was laying out at 358px and
     taking 100px off the reading line for a name nobody can read anyway (the
     titles ellipsis). */
  position: relative;
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  overflow: hidden;
  background: var(--surface); border-right: 1px solid var(--rule);
  transition: flex-basis .38s var(--asst-motion);
}
/* ---- Folding the rail ------------------------------------------------------
   The box narrows, but its CONTENT does not: `.asst-rail-inner` is pinned at
   the open width and the rail clips it. Letting the list reflow from 254px to
   46px would re-wrap every thread title on every frame — a column of text
   thrashing sideways, which is what a "collapsing sidebar" usually looks like
   and why it usually looks cheap.

   So there are three movements, and they are deliberately not the same length:
   the box takes 380ms (it is the layout, and the reading column has to follow
   it), the content leaves in 150ms (an exit should be softer and quicker than
   an enter — the reader's attention is going the other way), and the strip
   arrives at 260ms once the space is nearly there. */
.asst-rail-inner {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  width: var(--asst-rail-w);
  transition: opacity .22s ease .06s, translate .22s var(--asst-motion) .06s;
}
.asst-rail.folded { flex-basis: var(--asst-arts-strip); }
.asst-rail.folded .asst-rail-inner {
  opacity: 0; translate: -10px 0; pointer-events: none;
  transition-duration: .15s; transition-delay: 0s;
}
/* The strip is laid over the content rather than replacing it, so neither has
   to wait for the other to finish. */
.asst-rail-open {
  position: absolute; inset: 0 0 auto 0; z-index: 1;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  width: var(--asst-arts-strip); padding: 24px 0;
  color: var(--ink-3);
  opacity: 0; translate: -6px 0; pointer-events: none;
  transition: opacity .26s ease, translate .26s var(--asst-motion), color .12s ease;
}
.asst-rail.folded .asst-rail-open {
  opacity: 1; translate: 0 0; pointer-events: auto;
  transition-delay: .12s;
}
.asst-rail-open:hover { color: var(--ink); }
.asst-rail-open span {
  writing-mode: vertical-rl;
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-4);
}

.asst-rail-top { display: flex; align-items: center; gap: 8px; padding: 12px 12px 10px; border-bottom: 1px solid var(--rule); }
.asst-new { flex: 1 1 auto; min-width: 0; justify-content: center; gap: 7px; }
/* 24px of ink, 40px of target — the pseudo-element takes the difference. */
.asst-rail-fold {
  position: relative; flex: 0 0 auto;
  display: grid; place-items: center; width: 24px; height: 24px;
  border-radius: 6px; color: var(--ink-4);
  transition: background .12s ease, color .12s ease, scale .15s ease-out;
}
.asst-rail-fold::before { content: ""; position: absolute; inset: -8px; }
.asst-rail-fold:hover { background: var(--surface-3); color: var(--ink-2); }
.asst-rail-fold:active { scale: 0.96; }

.asst-rail-scroll {
  flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
  padding: 10px 8px; scrollbar-width: thin;
}
.asst-rail-label {
  margin: 2px 6px 8px;
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-4);
}
.asst-rail-empty { margin: 0 6px; font-size: 12.5px; line-height: 1.5; color: var(--ink-4); }
.asst-rail-list { display: flex; flex-direction: column; gap: 1px; }

.asst-rail-item {
  position: relative; z-index: 0;
  display: block; width: 100%; text-align: left;
  padding: 8px 10px 8px 8px; border-radius: 7px;
  border-left: 2px solid transparent; color: var(--ink-2);
}
/* Hover tint crossfades on the compositor, like every other list in the app. */
.asst-rail-item::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: var(--hi-bg, var(--hover));
  opacity: 0; transition: opacity .12s ease; pointer-events: none; z-index: -1;
}
.asst-rail-item:hover::after { opacity: 1; }
.asst-rail-item.current {
  color: var(--ink); border-left-color: var(--accent);
  border-radius: 0 7px 7px 0; --hi-bg: var(--surface-2);
}
.asst-rail-item.current::after { opacity: 1; }
.asst-rail-title {
  display: block; font-size: 12.5px; font-weight: 500; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.asst-rail-time {
  display: block; margin-top: 3px;
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4);
}

/* The footer used to link away to the briefing — off the surface the reader had
   just opened. The space now filters the list instead. */
.asst-rail-filter {
  flex: 0 0 auto; display: flex; align-items: center; gap: 7px;
  margin: 10px 12px 2px; padding: 0 9px; height: 28px;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius);
  color: var(--ink-4);
}
.asst-rail-filter:focus-within { border-color: var(--accent-edge); background: var(--surface); }
.asst-rail-filter input {
  flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent;
  font-size: 12.5px; color: var(--ink);
}
.asst-rail-filter input::placeholder { color: var(--ink-4); }

.asst-rail-list { list-style: none; margin: 0; padding: 0; }
.asst-rail-row { position: relative; display: flex; align-items: center; }
.asst-rail-row[hidden] { display: none; }
.asst-rail-row .asst-rail-item { flex: 1 1 auto; min-width: 0; padding-right: 46px; }

/* Rename and delete appear on hover or keyboard focus — never a permanent pair
   of icons shouting on every row. */
.asst-rail-tools {
  position: absolute; right: 4px; display: flex; gap: 1px;
  opacity: 0; transition: opacity .12s ease;
}
.asst-rail-row:hover .asst-rail-tools,
.asst-rail-row:focus-within .asst-rail-tools { opacity: 1; }
.asst-rail-tool {
  display: grid; place-items: center; width: 22px; height: 22px;
  border-radius: 5px; color: var(--ink-4);
}
.asst-rail-tool:hover { background: var(--surface-3); color: var(--ink); }
.asst-rail-tool:last-child:hover { background: var(--bad-soft); color: var(--bad-ink); }

/* ---- Thread column -------------------------------------------------------- */
.asst-main {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column;
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
}
.asst-thread {
  width: 100%; max-width: var(--asst-col); margin: 0 auto;
  padding: 26px 28px 0;
  display: flex; flex-direction: column; gap: 16px;
}
.asst-thread:empty { padding: 0; }

/* A 780px column can carry a longer line than a 380px sidebar. */
.asst-thread .cop-bubble { max-width: 76%; font-size: 14px; }
.asst-thread .cop-md h1 { font-size: 17px; }
.asst-thread .cop-md h2 { font-size: 15.5px; }
.asst-thread .cop-md h3 { font-size: 14.5px; }

/* ---- The reply, and the charts it is about --------------------------------- */
/* The page keeps the sidebar's DOM — one bubble element per text block, the
   same node the live stream writes into — and restyles it as PROSE. A reply
   with a chart under it is a short written answer, not a chat message, and a
   tinted rounded box around it reads as small talk.

   The user's own line keeps its bubble: it is the only thing on the page that
   someone typed, and that is worth marking.

   ONE centred column carries the whole conversation, charts included. The
   prose keeps a 56ch measure inside it and the cards take the full width, the
   way a figure runs wider than the paragraph that introduces it. */
.asst.reading .asst-thread { padding: 22px 26px 0; gap: 0; }
.asst.reading .asst-dock { padding-left: 26px; padding-right: 26px; }
/* A short thread HUGS the composer instead of hanging from the top of a tall
   viewport with a field of nothing between the answer and the box you reply in.
   The turns sit at the bottom and grow upward until they fill the column, then
   scroll normally — so the newest words stay put next to the cursor rather than
   marching down the page as they stream.
   `margin-top: auto` on the child, never `justify-content: flex-end` on the
   scroller: the latter makes the top of an overflowing list unreachable. */
.asst.reading .asst-main:has(.asst-hero[hidden]) .asst-thread { margin-top: auto; }
.asst.reading .asst-main:has(.asst-hero[hidden]) .asst-dock { margin-top: 0; }
/* The composer is centred on the conversation's own measure by the base
   `.asst-composer` rule, hero or no hero — there is no second column for it
   to choose between any more. */
.asst.reading .cop-assistant { margin: 0 0 14px; }
.asst.reading .cop-user { margin: 0 0 20px; }
.asst.reading .cop-assistant .cop-bubble {
  max-width: 56ch; padding: 0; border: 0; background: none; border-radius: 0;
  font-size: 15.5px; line-height: 1.7; color: var(--ink-2); text-wrap: pretty;
}
.asst.reading .cop-assistant .cop-md > :last-child { margin-bottom: 0; }
/* The chart is the answer, so it gets the room and the air. Wider than the
   prose above it on purpose: a figure that lines up exactly with the text
   column reads as another paragraph. */
.asst.reading .asst-card-mount { margin: 4px 0 22px; }

/* ---- Hero (empty thread) --------------------------------------------------- */
.asst-hero {
  width: 100%; max-width: 720px; margin: 0 auto;
  padding: 11vh 28px 40px; text-align: center;
}
.asst-hero[hidden] { display: none; }
.asst-hero .cop-empty-mark { margin: 0 auto 14px; }
.asst-hero-title {
  margin: 0 0 10px;
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(27px, 3.4vw, 38px); letter-spacing: -.02em; line-height: 1.12;
  color: var(--ink); text-wrap: balance;
}
.asst-hero-sub {
  margin: 0 auto; max-width: 48ch;
  font-size: 15px; line-height: 1.5; color: var(--ink-3); text-wrap: pretty;
}
.asst-hero-chips {
  margin-top: 26px;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px;
}
.asst-chip {
  position: relative; z-index: 0;
  border: 1px solid var(--rule-2); background: var(--surface);
  border-radius: 999px; padding: 8px 15px;
  font-size: 12.5px; color: var(--ink-2); box-shadow: var(--shadow-1);
  transition: border-color .12s ease, color .12s ease, transform .1s ease;
}
.asst-chip:hover { border-color: var(--rule-3); color: var(--ink); }
.asst-chip:active { transform: scale(.98); }

/* ---- Composer dock -------------------------------------------------------- */
/* Sticky inside the scroller, over a fade so the thread dissolves into it
   rather than sliding under a hard edge. margin-top:auto keeps it at the
   bottom of a short thread instead of floating mid-page. */
.asst-dock {
  position: sticky; bottom: 0; z-index: 2;
  margin-top: auto; padding: 14px 28px 18px;
  background: linear-gradient(to bottom, transparent, var(--bg) 34%);
  /* Rides with the thread when the canvas folds — the composer must stay under
     the words it answers. */
  transition: padding-left .34s var(--asst-motion);
}
.asst-composer, .asst-dock-note { width: 100%; max-width: var(--asst-col); margin: 0 auto; }
.asst-composer {
  background: var(--surface); border: 1px solid var(--rule-2);
  border-radius: 16px; box-shadow: var(--shadow-pop);
  padding: 4px 8px 8px 8px;
  transition: border-color .14s ease;
}
.asst-composer:focus-within { border-color: var(--accent-edge); }
.asst-composer-row { display: flex; align-items: flex-end; gap: 6px; }
.asst-tray { padding: 6px 2px 0; }

.asst-composer textarea {
  flex: 1 1 auto; resize: none; border: 0; outline: none; background: transparent;
  font-family: var(--sans); font-size: 14.5px; line-height: 1.5; color: var(--ink);
  padding: 10px 2px; max-height: 140px; min-height: 40px;
}
.asst-composer textarea::placeholder { color: var(--ink-4); }
.asst-composer textarea:disabled { opacity: .6; }

.asst-attach, .asst-send {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: 11px;
  transition: background .12s ease, color .12s ease, transform .1s ease;
}
.asst-attach { color: var(--ink-4); }
.asst-attach:hover { background: var(--hover); color: var(--ink-2); }
.asst-send { background: var(--accent); color: #fff; }
.asst-send:hover { background: var(--accent-ink); }
.asst-send:active { transform: scale(.94); }
/* Send becomes Stop while a turn runs — one slot, one meaning at a time. */
.asst-stop { display: none; background: var(--surface-2); color: var(--ink-2); border: 1px solid var(--rule-2); }
.asst-stop:hover { border-color: var(--rule-3); color: var(--ink); }
.asst.busy .asst-send { display: none; }
.asst-send:disabled { opacity: .5; cursor: default; pointer-events: none; }
.asst.busy .asst-stop { display: grid; }
/* A rail item that cannot be honoured must not look like it can. */
.asst-rail-item[disabled], .asst-new[disabled] { opacity: .5; cursor: default; }
.asst-rail-item[disabled]::after { opacity: 0 !important; }

.asst-dock-note {
  margin: 8px auto 0; padding: 0 2px;
  font-size: 11px; color: var(--ink-4);
}

/* The drop scrim covers the page; .asst is the drag target the controller
   toggles (the sidebar and welcome surfaces have their own selectors). */
.asst.dragging .cop-drop { opacity: 1; transition-duration: .12s; }

/* ============================================================
   THE RUN SPINE — what the agent did, in order

   The sidebar folds tool activity into a pill (.cop-ledger) because it has
   380px. This column has room for the actual sequence, so it draws one: a
   hairline rail with a dot per step. Same motion vocabulary as the ledger
   (cop-pulse, cop-scan) — a second dialect of "working" would be a lie about
   there being two systems.

   Proportion is the whole design. A give-up is GREY and exactly the size of
   every other row: the reader lost nothing if the answer still landed, and a
   red banner that says otherwise is a false alarm they will learn to ignore.
   ============================================================ */
.asst-spine { position: relative; margin: 0 0 20px; padding-left: 26px; }
.asst-spine::before {
  content: ""; position: absolute; left: 3px; top: 9px; bottom: 9px;
  width: 1px; background: var(--rule-2);
}
.asst-spine-head { display: flex; align-items: center; gap: 9px; }
.asst-spine-dot {
  position: absolute; left: 0; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok); outline: 3px solid var(--surface);
}
.asst-spine.running .asst-spine-dot { background: var(--accent); animation: cop-pulse 1s ease-in-out infinite; }
.asst-spine-count {
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-4);
  white-space: nowrap;
}
.asst-spine.running .asst-spine-count { color: var(--ink-3); }
.asst-spine-toggle {
  position: relative;
  flex: 0 0 auto;
  border: 1px dashed var(--rule-3); border-radius: 999px; padding: 4px 8px;
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
  transition: color .12s ease, border-color .12s ease;
}
/* The pill is small on purpose — it is a footnote's control, not a button. Its
   HIT AREA is not: a 23px-tall target is a miss on a phone. Same idiom as the
   attachment chip's close. */
.asst-spine-toggle::before { content: ""; position: absolute; inset: -11px -6px; }
.asst-spine-toggle:hover { color: var(--ink-2); border-color: var(--ink-5); }
/* An indeterminate bar, never a percentage: the turn is open-ended and a
   filling bar promises an end it cannot see. */
.asst-spine-bar {
  flex: 1 1 auto; max-width: 120px; height: 3px; border-radius: 2px;
  background: var(--rule-2); overflow: hidden; position: relative;
}
.asst-spine-bar > i {
  position: absolute; inset: 0; width: 40%; border-radius: 2px;
  background: var(--accent); animation: cop-scan 1.1s cubic-bezier(.4, 0, .6, 1) infinite;
}
.asst-spine-stop {
  flex: 0 0 auto;
  border: 1px solid var(--rule-2); border-radius: 999px; padding: 5px 9px;
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3);
}
.asst-spine-stop:hover { border-color: var(--ink-5); color: var(--ink); }

/* Interruptible open/close, the .cop-ledger idiom: a grid row that grows from
   0fr to 1fr, so a click mid-animation reverses instead of queueing. */
.asst-spine-log { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .26s var(--asst-motion); }
.asst-spine.open .asst-spine-log { grid-template-rows: 1fr; }
.asst-spine-log > .asst-spine-rows { overflow: hidden; }
.asst-spine-rows { display: flex; flex-direction: column; gap: 1px; padding-top: 6px; }

.asst-step { position: relative; padding: 5px 0; }
.asst-step-dot {
  position: absolute; left: -26px; top: 10px; margin-left: 1px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--rule-3); outline: 3px solid var(--surface);
}
.asst-step.running .asst-step-dot {
  width: 7px; height: 7px; margin-left: 0;
  background: var(--accent); animation: cop-pulse 1s ease-in-out infinite;
}
.asst-step.gave_up .asst-step-dot { background: var(--ink-5); }
.asst-step-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 7px; font-size: 12.5px; line-height: 1.5; }
.asst-step-label {
  font-family: var(--mono); font-size: 9.5px; font-weight: 500; line-height: 1.5;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
}
.asst-step-detail { color: var(--ink-3); }
.asst-step.gave_up .asst-step-detail, .asst-step.gave_up .asst-step-label { color: var(--ink-4); }
.asst-step-flag {
  display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
  font-family: var(--mono); font-size: 8.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
}
/* The reason, in the tool's own words. "Failed" tells nobody anything;
   "no rows — period 'week', dataset uses 'iso_week'" is a thing to go fix. */
.asst-step-tries {
  margin-top: 4px; padding-left: 9px; border-left: 1px solid var(--rule-2);
  font-family: var(--mono); font-size: 11px; font-weight: 500; line-height: 1.6;
  color: var(--ink-4);
}
.asst-step.gave_up .asst-step-tries { color: var(--ink-5); }
.asst-step-drew { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 6px; }

/* ============================================================
   CITATIONS — a claim that points at part of a card

   .cop-cited (assistant.css) already draws the dashed accent underline for web
   citations; an artifact citation is the same gesture, so it reuses it verbatim
   and adds only the inline name. The badge is NOT added to .cop-cited itself —
   that class also underlines every web citation in the sidebar.

   The card lives in this same column, so the tag is a JUMP: pointing at the
   claim lights the part of the chart it is about, pressing it takes you there.
   This is where the view spends its one accent moment; the drawer's chrome
   stays neutral on purpose.
   ============================================================ */
.asst-cite { font-weight: 600; color: var(--ink); cursor: pointer; }
.asst-cite-tag, .asst-cite-chip {
  font-family: var(--mono); font-weight: 500; letter-spacing: .08em;
  color: var(--accent-ink); background: var(--accent-soft);
}
.asst-cite-tag {
  margin-left: 5px; padding: 2px 4px; border-radius: 3px;
  font-size: 8.5px; line-height: 1; vertical-align: 1px; white-space: nowrap;
}
/* An artifact that has not finished drawing is not a target yet: neutral, and
   nothing happens when you point at it. */
.asst-cite.drawing { font-weight: inherit; color: inherit; text-decoration: none; cursor: default; }
.asst-cite.drawing .asst-cite-tag { color: var(--ink-4); background: var(--surface-2); }
/* The step's own "A1 · net collected" chip — the same target as the claim,
   offered at the moment the artifact appears. */
.asst-cite-chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--accent-edge); border-radius: 999px; padding: 4px 8px;
  font-size: 9px; line-height: 1; text-transform: uppercase; letter-spacing: .1em;
}
.asst-cite-chip:hover { border-color: var(--accent); }

/* ============================================================
   CARDS
   ============================================================ */
.asst-card-mount { position: relative; display: block; max-width: 100%; min-width: 0; }
/* HOLD THE SPACE. A mount is an empty div until its controller connects, and
   with importmap that is a network fetch away — so the thread painted, then
   three cards grew from nothing to ~390px and pushed the answer down the page.
   `:empty` stops applying the moment the card is drawn, so nothing has to
   remember to clear it. The numbers are the drawn heights: a chart is a fixed
   192px plot plus its head, legend, axis and footer. */
.asst-card-mount:empty { min-height: 132px; }
.asst-card-mount[data-kind="chart"]:empty { min-height: 388px; }
.asst-card-mount[data-kind="breakdown"]:empty { min-height: 240px; }
.asst-card-mount[data-kind="table"]:empty { min-height: 240px; }
.asst-card-mount[data-kind="metrics"]:empty { min-height: 196px; }
/* Two ways a card gets pointed at from elsewhere on the page, and they say
   different things. `peek` is a drawer row under the cursor — quiet, it only
   answers "which one is that". `found` is the end of a jump: the reader has
   just been moved, and the ring is what tells them where they landed. */
.asst-card-mount::after {
  content: ""; position: absolute; inset: -3px; pointer-events: none;
  border: 1.5px solid transparent; border-radius: calc(var(--radius-lg) + 2px);
  transition: border-color .18s ease;
}
.asst-card-mount.peek::after { border-color: var(--accent-edge); }
.asst-card-mount.found::after { border-color: var(--accent); animation: asst-found 1.6s ease-out; }
@keyframes asst-found {
  0%   { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
  70%  { border-color: var(--accent); box-shadow: 0 0 0 4px transparent; }
  100% { border-color: transparent; box-shadow: 0 0 0 4px transparent; }
}
/* A turn that answered purely with a card has no bubble — it must not leave a
   row's worth of gap behind. */
.cop-msg.cop-empty-row { display: none; }
.asst-card { border-radius: var(--radius-lg); max-width: 100%; min-width: 0; }
.asst-card-head { align-items: flex-start; gap: 12px; flex-wrap: wrap; min-width: 0; }
.asst-card-controls { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.asst-card-note {
  margin-left: auto;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
}
.asst-card-body { display: flex; flex-direction: column; gap: 12px; }
.asst-seg .si { padding: 0 9px; }

/* ---- Legend --------------------------------------------------------------- */
.asst-legend { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.asst-legend-item {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--ink-2);
  transition: color .12s ease;
}
.asst-legend-item.off { color: var(--ink-5); }
.asst-legend-item:hover { color: var(--ink); }
.asst-legend-swatch {
  width: 14px; height: 0; flex: 0 0 auto;
  border-top-width: 2px; border-top-color: var(--chart);
}
.asst-legend-hint {
  margin-left: auto;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
}

/* ---- Plot ----------------------------------------------------------------- */
.asst-plot-frame { display: flex; gap: 8px; }
/* 50px, not 34: a "$400,000" tick is 8 characters of tabular mono and the
   labels are right-aligned into this column, so the old width pushed the
   leading "$4" out into the card's padding. The same number lives in
   assistant_card_controller.js as AXIS_W — x labels are positioned as a
   percentage across gutter + plot, so the two must move together. */
.asst-plot-axis { flex: 0 0 50px; position: relative; height: 192px; }
.asst-plot { flex: 1 1 auto; min-width: 0; position: relative; border-radius: 4px; }
.asst-plot:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.asst-plot-svg { display: block; width: 100%; height: 192px; }
.asst-plot-capture { cursor: crosshair; }
.asst-plot-x { position: relative; height: 14px; }

.asst-axis-label {
  position: absolute; white-space: nowrap;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  color: var(--ink-4); font-variant-numeric: tabular-nums;
}
.asst-plot-axis .asst-axis-label { right: 0; }
.asst-plot-x .asst-axis-label { transform: translateX(-50%); }

/* ---- Region callout ------------------------------------------------------- */
/* What the highlight says, in words. It appears only while a claim is pointing
   at this card, so it never competes with the hover readout — and it takes no
   pointer events, or it would steal the crosshair from the capture rect. */
.asst-callout {
  position: absolute; top: 6px; z-index: 2; pointer-events: none;
  display: flex; align-items: baseline; gap: 8px; white-space: nowrap;
  padding: 7px 10px; border-radius: 8px;
  background: var(--surface); border: 1px solid var(--accent-edge);
  box-shadow: var(--shadow-pop);
  animation: asst-callout-in .26s cubic-bezier(.2, 0, 0, 1);
}
.asst-callout.pill {
  padding: 4px 8px; border-radius: 999px; box-shadow: none;
  background: var(--accent-soft);
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink);
}
.asst-callout-figure {
  font-family: var(--mono); font-size: 15px; font-weight: 500;
  color: var(--accent-ink); font-variant-numeric: tabular-nums;
}
.asst-callout-note { font-size: 11.5px; color: var(--ink-3); }
/* Enter motion only — no fill mode — so the resting state is the correct one
   even on a card that was off-screen while the timeline never ticked. */
@keyframes asst-callout-in { from { opacity: 0; translate: 0 7px; } }

/* ---- Hover readout -------------------------------------------------------- */
.asst-tip {
  position: absolute; top: 6px; pointer-events: none; z-index: 3;
  min-width: 140px; padding: 8px 10px;
  background: var(--surface); border: 1px solid var(--rule-2);
  border-radius: 8px; box-shadow: var(--shadow-pop);
}
.asst-tip-period {
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
}
.asst-tip-row { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
.asst-tip-swatch { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; }
.asst-tip-swatch.dashed { border-radius: 0; height: 0; width: 10px; border-top: 2px dashed currentColor;
                          background: none !important; color: inherit; }
.asst-tip-row.muted .asst-tip-label, .asst-tip-row.muted .asst-tip-value { color: var(--ink-4); font-weight: 400; }
.asst-tip-label { flex: 1 1 auto; min-width: 0; font-size: 11.5px; color: var(--ink-3);
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asst-tip-value {
  font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.asst-tip-delta { margin-top: 4px; font-size: 11px; color: var(--ink-3); }
.asst-tip-delta.up { color: var(--ok-ink); }
.asst-tip-delta.down { color: var(--bad-ink); }

/* ---- Card footer actions --------------------------------------------------- */
.asst-card-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding-top: 11px; border-top: 1px solid var(--rule);
}
.asst-card-act {
  display: inline-flex; align-items: center; height: 26px; padding: 0 10px;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  background: var(--surface);
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3);
  transition: border-color .12s ease, color .12s ease, background .12s ease;
}
.asst-card-act:hover { border-color: var(--rule-3); color: var(--ink); }
.asst-card-act.on {
  border-color: var(--accent-edge); background: var(--accent-soft); color: var(--accent-ink);
}
.asst-card-act[disabled] { opacity: .5; cursor: progress; }
/* Provenance. Every card carries it: source, coverage, how the numbers got
   there, and when this was drawn. */
.asst-card-source {
  margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--ink-4); justify-content: flex-end;
}
.asst-card-source-link { color: var(--ink-3); text-decoration: underline; text-decoration-color: var(--rule-3);
                         text-underline-offset: 2px; }
.asst-card-source-link:hover { color: var(--ink); text-decoration-color: var(--accent); }
.asst-origin, .asst-drawn {
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4);
  padding: 2px 6px; border: 1px solid var(--rule); border-radius: 999px; white-space: nowrap;
}
.asst-drawn { border-color: transparent; padding-left: 0; padding-right: 0; }
.asst-card-act.failed { border-color: var(--bad-edge); color: var(--bad-ink); background: var(--bad-soft); }

/* The keyboard readout for the plot. Present for assistive tech, invisible to
   everyone else — the tooltip is the sighted equivalent. */
.asst-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

.asst-sql {
  margin: 0; padding: 11px 13px; overflow-x: auto;
  background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--radius);
  font-family: var(--mono); font-size: 11px; line-height: 1.6; color: var(--ink-2);
}

/* ---- Breakdown ------------------------------------------------------------- */
.asst-seg-rows { display: flex; flex-direction: column; }
.asst-seg-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 9px 4px; border-bottom: 1px solid var(--rule);
  transition: background .12s ease;
}
.asst-seg-row.static { cursor: default; }
.asst-seg-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 4px; }
.asst-seg-row:not(.static):hover { background: var(--hover); }
.asst-seg-row.current { background: var(--surface-2); }
.asst-seg-label { flex: 0 0 108px; text-align: left; font-size: 12.5px; font-weight: 500; color: var(--ink-2);
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asst-seg-track {
  flex: 1 1 auto; height: 22px; min-width: 40px; position: relative; overflow: hidden;
  background: var(--surface-2); border-radius: 3px;
}
.asst-seg-fill {
  position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px;
  background: var(--chart); transition: filter .12s ease;
}
.asst-seg-fill.tone-ok { background: var(--ok); }
.asst-seg-fill.tone-warn { background: var(--warn); }
.asst-seg-fill.tone-bad { background: var(--bad); }
.asst-seg-row:hover .asst-seg-fill { filter: brightness(.92); }
.asst-seg-value {
  flex: 0 0 56px; text-align: right;
  font-family: var(--mono); font-size: 12px; font-weight: 500; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.asst-seg-delta {
  flex: 0 0 60px; text-align: right;
  font-family: var(--mono); font-size: 11px; font-weight: 500; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.asst-seg-delta.tone-ok { color: var(--ok-ink); }
.asst-seg-delta.tone-warn { color: var(--warn-ink); }
.asst-seg-delta.tone-bad { color: var(--bad-ink); }

.asst-detail {
  margin-top: 14px; border: 1px solid var(--rule-2);
  border-radius: var(--radius); overflow: hidden;
}
.asst-detail-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; background: var(--surface-2); border-bottom: 1px solid var(--rule);
}
.asst-detail-title, .asst-detail-close {
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase;
}
.asst-detail-title { color: var(--ink-3); }
.asst-detail-close { margin-left: auto; color: var(--ink-4); }
.asst-detail-close:hover { color: var(--ink); }
.asst-detail-scroll { overflow-x: auto; max-height: 460px; overflow-y: auto; scrollbar-width: thin; }
.asst-detail-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.asst-detail-table th {
  padding: 0; background: var(--surface); border-bottom: 1px solid var(--rule);
}
.asst-detail-table th.right { text-align: right; }
.asst-detail-table .asst-th {
  width: 100%; padding: 7px 12px; text-align: inherit;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
  transition: color .12s ease;
}
.asst-detail-table .asst-th:hover { color: var(--ink); }
.asst-detail-table .asst-th { display: flex; align-items: center; gap: 5px; justify-content: inherit; }
.asst-detail-table th.right .asst-th { justify-content: flex-end; }
.asst-th-caret { font-size: 9px; line-height: 1; opacity: .9; }
.asst-rag { width: 7px; height: 7px; border-radius: 50%; display: inline-block; margin-right: 6px;
            vertical-align: 1px; background: var(--ink-5); }
.asst-rag.ok { background: var(--ok); }
.asst-rag.warn { background: var(--warn); }
.asst-rag.bad { background: var(--bad); }
.asst-detail-table th.sorted .asst-th { color: var(--ink); }
.asst-detail-table td {
  padding: 8px 12px; border-bottom: 1px solid var(--rule); color: var(--ink-2);
  white-space: nowrap;
}
.asst-detail-table td.right { text-align: right; }
.asst-detail-table td.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.asst-detail-table tr:last-child td { border-bottom: 0; }

/* ---- KPI tiles ------------------------------------------------------------- */
.asst-kpis { display: flex; flex-direction: column; gap: 8px; }
.asst-kpis-title {
  margin: 0;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-4);
}
.asst-kpi-grid {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  border: 1px solid var(--rule); border-radius: var(--radius-lg); overflow: hidden;
}
.asst-kpi-grid .tk-tile { min-height: 118px; padding: 14px 16px 12px; cursor: default; }
.asst-kpi-grid .tk-tile::after { content: none; }
.asst-kpi-grid .tk-val { font-size: 24px; }

/* ============================================================
   THE ARTIFACTS DRAWER — the index of what this session found

   The charts are in the conversation. This is the repository: one row per
   artifact, a sparkline to recognise it by, and the card's own actions
   offered without scrolling back to find it. Pressing a row jumps to the
   live card.

   It arrives SHUT, because the conversation is the surface and a repository
   is something you go and get. Shut, it is a strip on the edge that says how
   much is in it — one pane at two widths, never two things that swap.
   ============================================================ */
.asst-arts {
  position: relative;
  flex: 0 0 var(--asst-arts-w); min-width: 0;
  display: flex; flex-direction: column;
  background: var(--bg); border-left: 1px solid var(--rule);
  transition: flex-basis .34s var(--asst-motion);
  /* The pane is a flex basis, not a min-content box: without this a nowrap
     header blew past the width and `.asst { overflow: hidden }` clipped the
     right edge with no scrollbar. */
  overflow: hidden;
}
.asst-arts[hidden] { display: none; }
.asst-arts.closed { flex-basis: var(--asst-arts-strip); }
.asst-arts.closed .asst-arts-open { display: flex; }
.asst-arts.closed .asst-arts-body, .asst-arts.closed .asst-arts-head { display: none; }
.asst-arts.closed .asst-arts-resize { display: none; }
/* Quiet mid-edge grip on the conversation / artifacts seam. Same language as
   the copilot splitter: findable on hover or drag, not a second accent. */
.asst-arts-resize {
  position: absolute; left: 0; top: 0; bottom: 0; width: 10px; z-index: 3;
  padding: 0; border: 0; background: transparent; cursor: ew-resize;
  touch-action: none; user-select: none;
}
.asst-arts-resize::after {
  content: ""; position: absolute; left: 3px; top: 50%;
  width: 3px; height: 36px; margin-top: -18px; border-radius: 2px;
  background: var(--ink-5); opacity: .75;
  transition: background .14s ease, opacity .14s ease, height .14s ease, margin-top .14s ease;
}
.asst-arts-resize:hover::after,
.asst-arts-resize:focus-visible::after,
.asst.resizing-arts .asst-arts-resize::after {
  background: var(--ink-3); opacity: 1; height: 56px; margin-top: -28px;
}
.asst-arts-resize:focus { outline: none; }
.asst-arts-resize:focus-visible { outline: none; }
.asst.resizing-arts, .asst.resizing-arts * { cursor: ew-resize !important; user-select: none; }
.asst.resizing-arts .asst-arts { transition: none; }
.asst.resizing-arts iframe, .asst.resizing-arts .asst-plot { pointer-events: none; }
/* The thread lifts onto --surface once the drawer takes the fog ground beside
   it, so the two read as two places rather than one washed one. */
.asst.has-arts .asst-main { background: var(--surface); }
.asst.has-arts .asst-dock { background: linear-gradient(to bottom, transparent, var(--surface) 34%); }

.asst-arts-open {
  display: none; flex-direction: column; align-items: center; gap: 10px;
  width: 100%; padding: 24px 0; color: var(--ink-3);
}
.asst-arts-open:hover { color: var(--ink); }
.asst-arts-open span {
  writing-mode: vertical-rl;
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-4);
}
/* A card just landed while the drawer was shut. The chart itself is already on
   screen in the conversation, so this is a note, not an alarm: the strip says
   its new count in accent for a moment and goes quiet again. */
.asst-arts.bumped .asst-arts-open { animation: asst-bump 2s ease-out; }
.asst-arts.bumped .asst-arts-open span { animation: asst-bump-ink 2s ease-out; }
@keyframes asst-bump {
  0%, 60% { background: var(--accent-soft); }
  100% { background: transparent; }
}
@keyframes asst-bump-ink {
  0%, 60% { color: var(--accent-ink); }
  100% { color: var(--ink-4); }
}

.asst-arts-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  height: 40px; padding: 0 14px; white-space: nowrap;
  border-bottom: 1px solid var(--rule);
  min-width: 0; overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-inline: contain; scrollbar-width: thin;
}
.asst-arts-title {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-4);
}
.asst-arts-count {
  flex: 0 0 auto; border-radius: 999px; padding: 3px 7px;
  border: 1px solid var(--rule-2); color: var(--ink-4);
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase;
}
.asst-arts-head .asst-card-act { flex: 0 0 auto; margin-left: auto; }
.asst-arts-close {
  flex: 0 0 auto; display: grid; place-items: center; width: 26px; height: 26px;
  border: 1px solid var(--rule-2); border-radius: var(--radius); color: var(--ink-4);
  /* Stays on the visible right edge while the rest of the header pans, so
     dismissing the pane never depends on noticing the scrollbar. */
  position: sticky; right: 0; z-index: 1; background: var(--bg);
  box-shadow: -10px 0 10px var(--bg);
}
.asst-arts-close:hover { border-color: var(--rule-3); color: var(--ink-2); }

.asst-arts-body {
  flex: 1 1 auto; min-width: 0; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
  padding: 14px;
}
/* Said once, at the top: the drawer's job is easy to misread as "the charts
   are in here". Set on one measure so it reads as a note, not an instruction. */
.asst-arts-note {
  margin: 0 2px 12px; font-size: 11.5px; line-height: 1.5;
  color: var(--ink-4); text-wrap: pretty;
}
.asst-arts-note[hidden] { display: none; }
.asst-arts-list { display: flex; flex-direction: column; gap: 8px; }

/* ---- One row ---------------------------------------------------------------
   A card-shaped row, because that is what it stands for — but deliberately
   inert above the action bar: a title, what it is of, and a sparkline. There
   is nothing here to operate by mistake, and the real chart is one press
   away. */
.asst-art {
  border: 1px solid var(--rule-2); border-radius: var(--radius-lg);
  background: var(--surface); overflow: hidden;
  transition: border-color .14s ease;
}
.asst-art:hover { border-color: var(--rule-3); }
.asst-art.on { border-color: var(--accent-edge); background: var(--accent-soft); }
.asst-art-open {
  display: block; width: 100%; text-align: left; padding: 10px 12px 4px;
}
.asst-art-head { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.asst-art-name {
  flex: 0 0 auto; border-radius: 3px; padding: 2px 4px;
  background: var(--accent-soft); color: var(--accent-ink);
  font-family: var(--mono); font-size: 8.5px; font-weight: 500;
  letter-spacing: .08em; line-height: 1;
}
.asst-art-title {
  flex: 1 1 auto; min-width: 0;
  font-size: 12.5px; font-weight: 600; letter-spacing: -.01em; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.asst-art-meta {
  display: block; margin-top: 3px;
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
}
.asst-art-meta:empty { display: none; }
.asst-art-plot { display: block; width: 100%; height: 44px; margin-top: 6px; }
.asst-art-open:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; border-radius: var(--radius); }

.asst-art-acts {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
  padding: 8px 10px 10px; border-top: 1px solid var(--rule); margin-top: 8px;
}
.asst-art-act {
  display: inline-flex; align-items: center; height: 23px; padding: 0 8px;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  background: var(--surface);
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4);
  transition: border-color .12s ease, color .12s ease;
}
.asst-art-act[hidden] { display: none; }
.asst-art-act:hover { border-color: var(--rule-3); color: var(--ink); }
.asst-art-act.on { border-color: var(--accent-edge); background: var(--accent-soft); color: var(--accent-ink); }

/* ============================================================
   RESPONSIVE

   Two breakpoints, because there are three columns and they are
   not worth the same.

   ≤1330px — the RAIL stops being a column and becomes a
   horizontal strip above the thread. It is the least valuable of
   the three, and 1330 is where the arithmetic runs out: 254 rail
   + 780 conversation + 280 of drawer is 1314, so below that
   something has to give and the rail gives first.

   ≤860px — the DRAWER stops being a column too, and becomes an
   overlay sheet over the conversation. The conversation keeps the
   whole width, which is where the charts are anyway.
   ============================================================ */
@media (max-width: 1330px) {
  /* The rail no longer costs a column, so the drawer stops paying for one. */
  .asst { --asst-arts-w: clamp(280px, calc(100vw - 660px), 360px); }
  /* Grid, not a column flex: the rail spans the top while the thread and the
     drawer stay side by side underneath. A column would stack all three. */
  .asst-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--asst-arts-w);
    grid-template-rows: auto minmax(0, 1fr);
  }
  /* No pane, no column reserved for one — an empty track beside the thread is
     a margin nobody asked for. A SHUT drawer is the same argument at a
     different width: `flex-basis` means nothing to a grid, so it would keep
     its whole track and leave a 46px strip floating in it. */
  .asst:not(.has-arts) .asst-body { grid-template-columns: minmax(0, 1fr); }
  .asst-body:has(.asst-arts.closed) { grid-template-columns: minmax(0, 1fr) var(--asst-arts-strip); }
  .asst-rail { grid-column: 1 / -1; }
  .asst-main, .asst-arts { min-height: 0; }
  .asst-rail {
    flex: 0 0 auto; flex-direction: row; align-items: center; gap: 8px;
    padding: 8px 12px; border-right: 0; border-bottom: 1px solid var(--rule);
  }
  /* A strip cannot fold into a narrower strip — it is already the compact form,
     and it costs a row, not a column. The fold is a desktop gesture only. */
  .asst-rail.folded { flex-basis: auto; }
  .asst-rail.folded .asst-rail-inner { opacity: 1; translate: 0 0; pointer-events: auto; }
  .asst-rail-open, .asst-rail-fold { display: none; }
  .asst-rail-inner {
    flex: 1 1 auto; flex-direction: row; align-items: center; gap: 8px;
    width: auto; min-width: 0;
  }
  .asst-rail-top { padding: 0; border-bottom: 0; }
  .asst-new { width: auto; padding: 0 12px; white-space: nowrap; }
  .asst-rail-scroll {
    flex: 1 1 auto; display: flex; align-items: center; gap: 6px;
    padding: 0; overflow-x: auto; overflow-y: hidden;
  }
  .asst-rail-filter, .asst-rail-tools { display: none; }
  .asst-rail-list { display: flex; flex-direction: row; gap: 6px; }
  .asst-rail-row .asst-rail-item { padding-right: 10px; }
  .asst-rail-item {
    width: auto; max-width: 190px; flex: 0 0 auto;
    padding: 5px 10px; border-left: 0; border-radius: 999px;
    border: 1px solid var(--rule-2);
  }
  .asst-rail-item.current { border-radius: 999px; border-color: var(--accent-edge); }
  .asst-rail-time { display: none; }
}

@media (max-width: 860px) {
  /* One column. The conversation takes the whole width — which is where the
     charts are, so nothing is lost by it. The shut-drawer track has to be
     named again to be taken away: `:has()` borrows its argument's specificity,
     so the rule above outranks a bare `.asst-body` and was still holding a
     46px column open behind a drawer that is no longer in the layout. */
  .asst-body,
  .asst-body:has(.asst-arts.closed) { grid-template-columns: minmax(0, 1fr); }

  .asst-thread { padding: 18px 16px 0; }
  .asst-thread .cop-bubble { max-width: 88%; }
  .asst-hero { padding: 7vh 20px 32px; }
  .asst-dock { padding: 12px 16px 14px; }
  .asst-card-controls { margin-left: 0; width: 100%; }
  .asst-seg-label { flex-basis: 84px; }
  .asst-seg-delta { flex-basis: 48px; }
  /* A placeholder that wraps must not be clipped by the one-row height. */
  .asst-composer textarea { font-size: 14px; min-height: 46px; }
  .asst-card-source { margin-left: 0; width: 100%; }

  .asst.reading .asst-thread { padding: 18px 16px 0; }
  .asst.reading .asst-dock { padding-left: 16px; padding-right: 16px; }
  .asst.reading .asst-composer, .asst.reading .asst-dock-note { max-width: none; }
  .asst.reading .cop-assistant .cop-bubble { font-size: 15px; line-height: 1.68; max-width: none; }

  /* The drawer stops being a column and becomes a SHEET over the conversation.
     A 46px strip is 12% of a phone, spent on an index nobody is reading —
     shut, it is a pill in the corner instead, out of the way of the answer
     and still one tap from the session's findings. */
  .asst-arts {
    position: fixed; top: 48px; right: 0; bottom: 0; z-index: 40;
    width: min(360px, 90vw); flex-basis: auto;
    border-left: 1px solid var(--rule); box-shadow: var(--shadow-pop);
    transition: transform .3s var(--asst-motion);
  }
  /* Shut, it is a handle on the edge — vertically centred, so it can never
     land on the composer or on the thread strip above it however tall either
     grows. 44px of target minimum, like every other control down here. */
  .asst-arts.closed {
    top: 50%; bottom: auto; translate: 0 -50%;
    width: auto; border: 0; box-shadow: none;
  }
  .asst-arts.closed .asst-arts-open {
    flex-direction: row; gap: 7px; padding: 12px 14px; min-height: 44px;
    border-radius: 999px 0 0 999px;
    background: var(--surface); border: 1px solid var(--rule-2); border-right: 0;
    box-shadow: var(--shadow-pop);
  }
  .asst-arts.closed .asst-arts-open span { writing-mode: horizontal-tb; letter-spacing: .1em; }
  /* The sheet is an overlay, not a column — no leftover seam to drag. */
  .asst-arts-resize { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .asst-chip, .asst-send, .asst-card-act, .asst-legend-item,
  .asst-seg-row, .asst-seg-fill, .asst-composer,
  .asst-arts, .asst-art, .asst-art-act, .asst-card-mount, .asst-spine-log,
  .asst-thread, .asst-dock, .asst-arts-resize,
  .asst-rail, .asst-rail-inner, .asst-rail-open, .asst-rail-fold { transition: none; }
  .asst-spine-bar > i, .asst-spine-dot, .asst-step-dot { animation: none; }
  /* The ring still lands — it is the only thing telling the reader where the
     jump put them — it just does not fade. */
  .asst-arts.bumped .asst-arts-open,
  .asst-arts.bumped .asst-arts-open span { animation: none; }
  .asst-card-mount.found::after { animation: none; }
}

/* ============ DARK MODE ============
   Only the surfaces this file invents need a night value; every other
   colour here is a token that already flipped in design.css. */
[data-theme="dark"] body[data-themeable] .asst-composer { background: var(--surface-2); }
[data-theme="dark"] body[data-themeable] .asst-tip,
[data-theme="dark"] body[data-themeable] .asst-card-act { background: var(--surface-2); }

/* ============================================================
   SAVED CARDS (/cards)
   A kept card is an object, not a screenshot: it stores the
   recipe beside the snapshot, so opening one can re-run it.
   ============================================================ */
.card-page { padding: 0 24px 48px; }
.card-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px;
}
.saved-card { display: block; color: inherit; transition: border-color .12s ease, transform .1s ease; }
.saved-card:hover { border-color: var(--rule-3); transform: translateY(-1px); }
.saved-card-title { margin: 0 0 6px; font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
.saved-card-meta {
  margin: 0; font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4);
}
/* Why this card has no Refresh. Quieter than the byline above it, and set on
   one measure so it reads as a note rather than a warning. */
.card-snapshot-note {
  margin-top: 4px; max-width: 62ch; color: var(--ink-4); font-style: italic;
}
@media (max-width: 640px) { .card-page { padding: 0 16px 32px; } }
