/* OpenSquilla Web UI — Chat view styles */

/* ─── Chat Page Layout ─────────────────────────────────────────────────── */

.chat {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  position: relative;
}

.content:has(> .chat) {
  padding-bottom: 0;
}

.chat--dragover {
  outline: 2px dashed var(--accent);
  outline-offset: -4px;
}

/* ─── Chat Topbar Session Controls ─────────────────────────────────────── */

.chat-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  flex-shrink: 0;
}

/* Session chip — now a single dropdown trigger button. The chevron + accent
 * underline on hover signals "click me", and the chip itself opens the
 * session-popover. The copy icon stays as a sibling button (see below).
 */
.chat-session-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-sm);
  font-family: var(--font-mono);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  flex: 0 1 auto;
  min-width: 0;
  max-width: clamp(180px, 34vw, 720px);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  cursor: pointer;
  transition: border-color 140ms ease, background 140ms ease;
  user-select: none;
}

#chat-run-status {
  flex-shrink: 0;
}

.chat-session-chip:hover,
.chat-session-chip:focus-visible {
  border-color: var(--accent);
  outline: none;
}

.chat-session-chip.is-active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--bg));
}

.chat-session-chip-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-dim);
  transition: color 140ms ease, transform 200ms ease;
}
.chat-session-chip:hover .chat-session-chip-caret,
.chat-session-chip.is-active .chat-session-chip-caret {
  color: var(--accent);
}
.chat-session-chip.is-active .chat-session-chip-caret {
  transform: rotate(180deg);
}

.chat-session-chip-key {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
  user-select: text;
  cursor: text;
}

.chat-session-copy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  padding: 0;
  color: var(--text-muted);
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.chat-session-copy-btn:hover,
.chat-session-copy-btn:focus-visible {
  color: var(--accent);
  background: var(--bg-hover);
}

.chat-session-copy-btn svg {
  width: 14px;
  height: 14px;
}

.chat-session-switch-btn {
  flex-shrink: 0;
  font-size: var(--fs-xs);
  color: var(--accent);
  background: none;
  border: none;
  padding: 0 0 0 var(--sp-1);
  cursor: pointer;
  white-space: nowrap;
  font-family: var(--font-sans);
}
.chat-session-switch-btn:hover { color: var(--accent-hover, var(--accent)); }

/* Switch button — subtle pressed state while the popover is open. */
.chat-session-switch-btn.is-active {
  color: var(--accent-hover, var(--accent));
  text-decoration: underline;
}

/* Session popover — custom dark-theme picker that replaces the native
 * <select> dropdown. Body-mounted with position:fixed so it escapes any
 * `overflow:hidden` ancestors. Groups sessions by type, supports inline
 * search filtering, and visually highlights the current session.
 */
.chat-session-popover {
  z-index: 1000;
  background: var(--bg-elevated, var(--bg-surface));
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.40), 0 2px 8px rgba(0, 0, 0, 0.20);
  display: flex;
  flex-direction: column;
  width: 320px;
  max-height: 380px;
  overflow: hidden;
  font-family: var(--font-sans);
  animation: chatSessionPopoverIn 120ms ease-out;
}

@keyframes chatSessionPopoverIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.chat-session-popover-search {
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-inset, var(--bg));
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-size: var(--fs-sm);
  font-family: var(--font-sans);
  outline: none;
  flex-shrink: 0;
}
.chat-session-popover-search::placeholder { color: var(--text-dim); }

.chat-session-popover-list {
  overflow-y: auto;
  padding: var(--sp-1) 0;
  flex: 1 1 auto;
  min-height: 0;
}

.chat-session-popover-group + .chat-session-popover-group {
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
}

.chat-session-popover-group-label {
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: var(--sp-1) var(--sp-3);
  font-weight: 600;
  user-select: none;
}

.chat-session-popover-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  background: none;
  border: none;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-align: left;
  cursor: pointer;
  transition: background 120ms ease;
}
.chat-session-popover-item:hover,
.chat-session-popover-item:focus-visible {
  background: rgba(255, 255, 255, 0.06);
  outline: none;
}
.chat-session-popover-item.is-current { color: var(--accent); }
.chat-session-popover-item.is-current .chat-session-popover-item-key { font-weight: 600; }

.chat-session-popover-item-key {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}

.chat-session-popover-item-tag {
  font-size: var(--fs-xs);
  font-family: var(--font-sans);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: var(--bg-inset, var(--bg));
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.chat-session-popover-item-run {
  font-size: var(--fs-xs);
  font-family: var(--font-sans);
  font-weight: 600;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
  white-space: nowrap;
}

.chat-session-popover-item-run--interrupted,
.chat-session-popover-item-run--failed,
.chat-session-popover-item-run--timeout {
  color: var(--warn);
  background: rgba(232, 168, 76, 0.1);
  border-color: color-mix(in srgb, var(--warn) 35%, var(--border));
}

.chat-session-popover-empty {
  padding: var(--sp-3);
  color: var(--text-dim);
  font-size: var(--fs-sm);
  text-align: center;
}

/* Context warning */
.chat-ctx-warn {
  font-size: var(--fs-xs);
  color: var(--warn);
  background: rgba(232, 168, 76, 0.1);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-md);
  white-space: nowrap;
  flex-shrink: 0;
  max-width: min(260px, 24vw);
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 1199px) {
  .chat-session-chip {
    max-width: clamp(160px, 28vw, 420px);
  }

  .chat-ctx-warn {
    max-width: 180px;
  }
}

@media (max-width: 768px) {
  .topbar-center .chat-label {
    display: none;
  }

  .topbar-center .chat-session-chip {
    flex: 1 1 auto;
    max-width: none;
  }

  .topbar-center .chat-ctx-warn {
    display: none;
  }
}

@media (max-width: 480px) {
  .topbar-center {
    gap: var(--sp-1);
  }

  .topbar-center .chat-session-chip {
    padding-inline: var(--sp-2);
    font-size: var(--fs-xs);
  }

  .topbar-center #chat-run-status {
    padding-inline: var(--sp-2);
  }
}

/* ─── Chat Body + Thread ───────────────────────────────────────────────── */

.chat-body {
  flex: 1;
  display: flex;
  overflow: hidden;
  min-height: 0;
}

.chat-thread {
  flex: 1;
  overflow-y: auto;
  overflow-x: clip;
  overflow-anchor: none;
  padding: var(--sp-4) var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
/* The scroll container stays full-width so its scrollbar sits at the window
   edge; the CONTENT is what's constrained to the reading measure and centered.
   This is the same decisive column the dashboards use and the composer shares,
   without stranding the scrollbar in the middle of the screen. */
.chat-thread > * {
  width: 100%;
  max-width: var(--chat-measure);
  margin-inline: auto;
}
/* The router panel keeps its own comfortable width (still centered). */
.chat-thread > .router-fx {
  max-width: min(100%, 620px);
}

.chat-thread.drag-over {
  outline: 2px dashed var(--accent);
  outline-offset: -4px;
  background: rgba(255, 92, 92, 0.04);
}

.chat-empty {
  color: var(--text-dim);
  text-align: center;
  padding: var(--sp-8) 0;
  font-size: var(--fs-sm);
}

/* ─── Day separator ────────────────────────────────────────────────────── */

.chat-day-sep {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  max-width: var(--chat-measure);
  color: var(--text-dim);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin: var(--sp-2) auto;
  user-select: none;
}
.chat-day-sep::before,
.chat-day-sep::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* ─── Sidebar (tool output detail) ─────────────────────────────────────── */

.chat-sidebar {
  width: 380px;
  max-width: 40%;
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  flex-shrink: 0;
}

.chat-sidebar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  font-weight: 500;
  font-size: var(--fs-sm);
  flex-shrink: 0;
}

.chat-sidebar-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--sp-3);
  font-size: var(--fs-sm);
}

.chat-sidebar-body pre {
  white-space: pre-wrap;
  word-break: break-all;
}

/* ─── Message Bubbles ──────────────────────────────────────────────────── */

/* Each .msg row is full-width; bubble alignment is done via margin on .msg-body. */
.chat-msg,
.msg {
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: var(--sp-1);
  animation: msgFadeIn 150ms ease;
}

@keyframes msgFadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Consecutive same-speaker messages get a tighter gap */
.chat-thread .msg.user + .msg.user,
.chat-thread .msg.assistant + .msg.assistant {
  margin-top: calc(-1 * var(--sp-5) + 6px);
}

/* User — header and body align right */
.chat-msg--user,
.msg.user {
  align-items: flex-end;
}

.chat-msg--user .chat-msg-text,
.msg.user .msg-body {
  /* Low-tint surface, not a saturated-orange fill: keeps orange as signal and
     clears WCAG AA (>7:1 in both themes via color-mix) where #fff-on-#F56600
     measured only 3.10:1. */
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-surface));
  border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--border));
  color: var(--text);
  border-radius: var(--radius-md);
  padding: var(--sp-2) var(--sp-4);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-sm);
  line-height: 1.6;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  text-align: start;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  max-width: 78%;
  min-width: 56px;
  min-height: 32px;
  margin-left: auto;
}

/* Assistant — bubble aligns left naturally */
.chat-msg--assistant,
.msg.assistant {
  align-items: flex-start;
}

.chat-msg--assistant .chat-msg-text,
.msg.assistant .msg-body {
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: var(--sp-2) var(--sp-4);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-sm);
  line-height: 1.7;
  word-break: break-word;
  max-width: 88%;
  min-width: 56px;
  min-height: 32px;
}

/* Tool / system */
.chat-msg--tool,
.msg.tool,
.msg.system {
  align-self: flex-start;
  opacity: 0.85;
}

.msg.subagent {
  align-items: flex-start;
  opacity: 0.92;
}

.msg.subagent .msg-body {
  max-width: 85%;
  min-width: 260px;
}

.chat-subagent-disclosure {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--ok);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.chat-subagent-disclosure-summary {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--fs-sm);
  list-style: none;
  outline: none;
  user-select: none;
  transition: color var(--transition);
}

.chat-subagent-disclosure-summary::-webkit-details-marker { display: none; }
.chat-subagent-disclosure-summary:hover { color: var(--text); }
.chat-subagent-disclosure-summary:focus-visible { color: var(--text); }
.chat-subagent-disclosure-summary::after {
  content: '';
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  margin-left: auto;
  opacity: 0.5;
  transition: transform 180ms ease, opacity var(--transition);
}
.chat-subagent-disclosure[open] > .chat-subagent-disclosure-summary::after {
  transform: rotate(45deg);
  opacity: 0.8;
}

.chat-subagent-disclosure-body {
  margin: 0;
  max-height: 320px;
  overflow: auto;
  border-top: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-muted);
  padding: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  white-space: pre-wrap;
  word-break: break-word;
}

/* Error */
.msg.error .msg-body {
  background: rgba(232, 84, 84, 0.1);
  border: 1px solid var(--danger);
  border-radius: var(--radius-md);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--danger);
}

/* Streaming */
.chat-msg--streaming .chat-msg-text,
.msg.streaming .msg-body {
  border-color: var(--accent);
  position: relative;
}

/* Streaming active mark — delayed bottom dock. The mark appears only after
   short turns have had time to finish, then sits in the bubble's bottom-left
   corner, aligned with the content-left edge (the tool-card / text icon
   column) so it reads as part of that column rather than an orphaned dot.
   It sits centered in the reserved footer band (padding-bottom below), which
   gives the orbiting ring a clear gap from the last tool card above and the
   bubble's bottom edge — earlier the mark was lifted 8px, which pushed the
   ring up into the card and caused a visible overlap. */
.msg.streaming.streaming-active-mark:not(.awaiting-model) .msg-body {
  padding-bottom: calc(var(--sp-2) + 24px);
}

.msg.streaming.streaming-active-mark:not(.awaiting-model) .msg-body::after {
  content: '';
  display: block;
  position: absolute;
  left: var(--sp-4);
  bottom: var(--sp-2);
  width: 16px;
  height: 16px;
  pointer-events: none;
  border-radius: 50%;
  background-color: color-mix(in srgb, var(--bg-surface) 90%, var(--bg) 10%);
  background-image: url('../../img/opensquilla-mark.png');
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px 11px;
  /* The mark stays UPRIGHT and legible — it is a figurative, directional
     squilla, so it must never tumble. The motion lives in the orbiting
     ring below (::before), not in the brand mark itself. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 7%, transparent);
}

/* Orbital sweep — a conic highlight rides a hairline ring around the mark.
   This is the element that animates; circles are built to rotate, the
   shrimp is not. Reuses --accent / --accent-secondary so it auto-themes. */
.msg.streaming.streaming-active-mark:not(.awaiting-model) .msg-body::before {
  content: '';
  display: block;
  position: absolute;
  left: calc(var(--sp-4) - 4px);
  bottom: calc(var(--sp-2) - 4px);
  width: 24px;
  height: 24px;
  pointer-events: none;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    transparent 200deg,
    var(--accent-secondary) 320deg,
    var(--accent) 350deg,
    transparent 360deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
  animation: squilla-activity-spin 1.15s linear infinite;
}

.msg.streaming.awaiting-model::after {
  content: 'waiting for model response...';
  display: inline-flex;
  align-items: center;
  min-height: 20px;
  margin-top: 2px;
  padding: 0 var(--sp-2);
  border-left: 2px solid color-mix(in srgb, var(--accent) 45%, var(--border));
  color: var(--text-muted);
  font-size: var(--fs-xs);
  line-height: 1.4;
  letter-spacing: 0;
  opacity: 0.82;
  animation: awaitingModelPulse 1.6s ease-in-out infinite;
}

@keyframes awaitingModelPulse {
  0%, 100% { opacity: 0.82; }
  50%      { opacity: 0.48; }
}

@keyframes squilla-activity-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  /* Freeze the orbiting ring into a static arc so the indicator is still
     legibly "active" without motion. The mark itself is already static. */
  .msg.streaming.streaming-active-mark:not(.awaiting-model) .msg-body::before {
    animation: none;
    background: conic-gradient(from 0deg,
      var(--accent) 90deg, transparent 90deg);
  }
}

@keyframes blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.2; }
}

/* Interrupted-turn marker. Appended to the .msg-body when a streaming
   turn is aborted (ESC, Stop button, server-side cancel) so the partial
   response is unambiguously labelled in the transcript. The element is a
   plain inline span — copy / export still surfaces only the partial text.
*/
.msg-interrupt-mark {
  display: inline-block;
  margin: 0 0 0 var(--sp-1, 0.5em);
  padding: 0.05em 0.4em;
  font-style: italic;
  font-size: 0.85em;
  font-weight: 500;
  color: var(--text-muted);
  border: 1px solid var(--text-muted);
  border-radius: 3px;
  vertical-align: baseline;
  user-select: none;
  letter-spacing: 0.02em;
}

/* Text segments — inline chronological layout with tool cards */
.msg-text-seg:empty { display: none; }

/* Role label */
.chat-msg-avatar,
.role-label {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0 var(--sp-1);
  line-height: 1;
}
.chat-msg-avatar svg,
.role-label svg { width: 14px; height: 14px; opacity: 0.5; }

.msg.user .role-label { text-align: right; }

.msg-header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.msg-tags {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  margin-right: auto;
}

.cron-tag {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255, 92, 92, 0.12);
  border: 1px solid rgba(255, 92, 92, 0.2);
  color: var(--accent);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Timestamp */
.chat-msg-time,
.msg-time {
  font-size: 10px;
  color: var(--text-dim);
  opacity: 0.6;
  padding: 0 var(--sp-1);
}
.chat-msg--user .chat-msg-time,
.msg.user .msg-time { text-align: right; }

/* ─── Per-turn metadata footer (model + session tokens + combo) ─────── */
.msg-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  font-size: var(--fs-xs);
  /* --text-dim directly (4.98:1 dark / 5.69:1 light) instead of muted@0.75,
     which dropped light theme to ~4.39:1 and forced calc(1/0.75) lift hacks. */
  color: var(--text-dim);
  padding: 0 2px;
  user-select: none;
  /* Cap the row width to the bubble so long model names break cleanly
     instead of forcing the combo chip past the viewport edge. */
  max-width: 100%;
}
.msg-meta__model {
  font-family: var(--font-mono);
  /* Long composite names ("openrouter/auto/...") may exceed the row on
     narrow screens; allow the segment to wrap rather than push siblings. */
  overflow-wrap: anywhere;
  min-width: 0;
}
.msg-meta__tokens {
  font-family: var(--font-mono);
  letter-spacing: 0.01em;
  flex-shrink: 0;
}

/* Msg text — markdown */
.msg .msg-body p,
.chat-msg-text p { margin: 0 0 0.5em; }
.msg .msg-body p:last-child,
.chat-msg-text p:last-child { margin-bottom: 0; }

.msg .msg-body ul,
.msg .msg-body ol,
.chat-msg-text ul,
.chat-msg-text ol { margin: 0.4em 0; padding-left: 1.5em; }

.msg .msg-body li,
.chat-msg-text li { margin: 0.2em 0; }

.msg .msg-body pre,
.chat-msg-text pre {
  margin: 0.5em 0;
  border-radius: var(--radius-md);
  background: var(--bg);
  border: 1px solid var(--border);
  padding: var(--sp-3);
  overflow-x: auto;
  font-size: var(--fs-xs);
}

/* User bubble is now a light tint with normal text — code/pre inherit the
   base surface treatment (was a dark overlay tuned for white-on-orange). */

.msg .msg-body code,
.chat-msg-text code {
  background: var(--bg-hover);
  padding: 1px 4px;
  border-radius: var(--radius-sm);
  font-size: 0.9em;
}

.msg .msg-body blockquote {
  border-left: 3px solid var(--border-focus);
  margin: 0.5em 0;
  padding: var(--sp-1) var(--sp-3);
  color: var(--text-muted);
}

/* In-bubble markdown headings — sized + weighted so the UA fallback can't
   blow them up inside 13px bubbles, and painted as text (orange stays signal,
   not routine body decoration). */
.msg.assistant .msg-body h1,
.msg.assistant .msg-body h2 {
  font-size: var(--fs-md);
  font-weight: 700;
  line-height: 1.3;
  margin: var(--sp-3) 0 var(--sp-1);
  color: var(--text);
}
.msg.assistant .msg-body h3,
.msg.assistant .msg-body h4 {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.02em;
  margin: var(--sp-3) 0 var(--sp-1);
  color: var(--text-muted);
}

/* ─── Thinking / Typing indicator ──────────────────────────────────────── */

.chat-thinking,
.typing-indicator {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: var(--sp-1) 0;
}

.chat-thinking span,
.typing-indicator .dot {
  width: 6px;
  height: 6px;
  background: var(--text-muted);
  border-radius: 50%;
  animation: chat-dot 1.2s infinite ease-in-out;
}

.chat-thinking span:nth-child(2),
.typing-indicator .dot:nth-child(2) { animation-delay: 0.15s; }
.chat-thinking span:nth-child(3),
.typing-indicator .dot:nth-child(3) { animation-delay: 0.3s; }

@keyframes chat-dot {
  0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
  40% { opacity: 1; transform: scale(1); }
}

/* Thinking indicator bubble */
.msg.thinking .msg-body {
  background: var(--bg-surface);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: none;
  animation: msgFadeIn 150ms ease;
}

.thinking-status {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.thinking-elapsed {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  min-width: 11ch;
}

/* ─── Tool Cards (collapsed tool calls) ────────────────────────────────── */

.chat-tool-provider {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  background: var(--bg);
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
/* when provider badge is present, it claims free space; chevron sits right after */
.chat-tools-summary:has(.chat-tool-provider) > .chat-tool-provider { margin-left: auto; }
.chat-tools-summary:has(.chat-tool-provider)::after { margin-left: 0; }

.chat-tool-input {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-family: var(--font-mono);
  padding: var(--sp-2) 0 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Tool collapse group */
.chat-tools-collapse {
  margin-top: var(--sp-2);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition: border-color var(--transition);
}

.chat-tools-summary {
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  outline: none;
  list-style: none;
  transition: color var(--transition), background-color var(--transition);
}
.chat-tools-summary::-webkit-details-marker { display: none; }
.chat-tools-summary:hover { color: var(--text); }
.chat-tools-summary:focus-visible { color: var(--text); }
.chat-tools-summary::after {
  content: '';
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  margin-left: auto;
  opacity: 0.5;
  transition: transform 180ms ease, opacity var(--transition);
}
.chat-tools-collapse[open] > .chat-tools-summary::after {
  transform: rotate(45deg);
  opacity: 0.8;
}
.chat-tools-icon { font-size: 12px; }

.chat-tools-body {
  padding: 0 var(--sp-3) var(--sp-2);
  animation: expandIn 200ms ease;
}
@keyframes expandIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ─── Tool Results ─────────────────────────────────────────────────────── */

.chat-tool-result {
  margin-top: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: var(--fs-xs);
}

.chat-tool-result--error {
  border-color: var(--danger);
}

/* Tool collapse states: running → success → error */
.chat-tools-collapse--running {
  border-left: 3px solid var(--accent);
}
/* Running tool: suppress click-to-expand affordance */
.chat-tools-collapse--running > .chat-tools-summary,
.chat-tools-summary[aria-disabled="true"] { cursor: progress; }
.chat-tools-collapse--running > .chat-tools-summary:hover,
.chat-tools-summary[aria-disabled="true"]:hover { color: var(--text-muted); }
.chat-tools-collapse--running > .chat-tools-summary::after,
.chat-tools-summary[aria-disabled="true"]::after { opacity: 0.2; }

.chat-tools-collapse--success {
  border-left: 3px solid var(--ok);
}

.chat-tools-collapse--error {
  border-left: 3px solid var(--danger);
}

.chat-tools-collapse--unknown {
  border-left: 3px solid var(--warn);
}

.chat-tool-result--warn {
  border-color: var(--warn);
  background: rgba(232, 168, 76, 0.06);
}

.chat-tool-result-preview {
  font-family: var(--font-mono);
  color: var(--text-muted);
  white-space: pre-wrap;
  word-break: break-all;
}

.chat-memory-sources {
  display: grid;
  gap: var(--sp-1);
  margin-top: var(--sp-2);
}

.chat-memory-source {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  color: var(--text-muted);
}

.chat-memory-source-badge {
  flex: 0 0 auto;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  color: var(--text);
  font-size: 10px;
  line-height: 1.5;
}

.chat-memory-source-badge--sessions {
  border-color: var(--accent);
}

.chat-memory-source-citation {
  min-width: 0;
  overflow: hidden;
  font-family: var(--font-mono);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-tool-view-btn {
  margin-top: var(--sp-1);
}

.chat-tool-result-full {
  box-sizing: border-box;
  width: 100%;
  max-width: min(76vw, 1120px);
  max-height: 62vh;
  margin: 0;
  padding: var(--sp-3);
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
}

@media (max-width: 640px) {
  .chat-tool-result-full {
    max-width: calc(100vw - 48px);
    max-height: 64vh;
    padding: var(--sp-2);
  }
}

/* ─── Attachments Preview ──────────────────────────────────────────────── */

.chat-attachments {
  display: flex;
  gap: var(--sp-2);
  box-sizing: border-box;
  width: min(calc(100% - var(--sp-8)), var(--chat-measure));
  margin: 0 auto var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  flex-shrink: 0;
  overflow-x: auto;
  background: var(--bg-surface);
}
.chat-attachments.hidden { display: none; }

.attachment-thumb {
  position: relative;
  flex-shrink: 0;
}

.attachment-thumb img {
  width: 60px;
  height: 60px;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  display: block;
}

.attachment-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--danger);
  color: #fff;
  border: none;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.attachment-name {
  display: block;
  max-width: 60px;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
  text-align: center;
  margin-top: 2px;
}

.attachment-chip {
  position: relative;
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  grid-template-areas:
    "icon name"
    "icon meta";
  align-items: center;
  column-gap: var(--sp-2);
  min-width: 180px;
  max-width: 280px;
  min-height: 54px;
  padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
}

.attachment-chip--busy {
  border-color: color-mix(in srgb, var(--accent) 32%, var(--border));
}

.attachment-chip__icon {
  grid-area: icon;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  font-size: var(--fs-xs);
}

.attachment-chip__spinner {
  width: 16px;
  height: 16px;
}

.attachment-chip__name {
  grid-area: name;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-sm);
  color: var(--text);
}

.attachment-chip__meta {
  grid-area: meta;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-xs);
  color: var(--text-dim);
}

/* Message images */
.chat-msg-images,
.msg-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: var(--sp-2) 0;
}

.chat-msg-img,
.msg-thumb {
  max-width: 280px;
  max-height: 200px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  object-fit: contain;
  background: var(--bg-surface);
  display: block;
}

.msg-file-chip {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  grid-template-areas:
    "icon name"
    "icon meta";
  align-items: center;
  column-gap: var(--sp-2);
  max-width: min(280px, 65vw);
  min-height: 42px;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--text);
  cursor: default;
  text-decoration: none;
}

.msg-file-chip--download {
  cursor: pointer;
}

.msg-file-chip--download:hover,
.msg-file-chip--download:focus-visible {
  background: var(--bg-hover);
  border-color: color-mix(in srgb, var(--accent) 34%, var(--border));
  outline: none;
}

.msg-file-chip--disabled {
  opacity: 0.78;
}

.msg-file-chip__icon {
  grid-area: icon;
  color: var(--text-dim);
  font-size: var(--fs-xs);
}

.msg-file-chip__name,
.msg-file-chip__meta {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.msg-file-chip__name {
  grid-area: name;
  font-size: var(--fs-sm);
}

.msg-file-chip__meta {
  grid-area: meta;
  color: var(--text-dim);
  font-size: var(--fs-xs);
}

.msg-artifacts {
  display: grid;
  gap: 0.5rem;
  margin: var(--sp-2) 0;
}

.msg-artifact-gallery {
  align-items: start;
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 320px));
}

.msg-artifact-files {
  display: grid;
  gap: 0.5rem;
  max-width: min(520px, 100%);
}

.msg-artifact-chip {
  align-items: center;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  cursor: pointer;
  display: grid;
  grid-template-areas:
    "icon name"
    "icon meta";
  grid-template-columns: 38px minmax(0, 1fr);
  min-height: 48px;
  max-width: min(520px, 100%);
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
  text-decoration: none;
  width: 100%;
}

.msg-artifact-chip:hover,
.msg-artifact-chip:focus-visible {
  border-color: var(--accent);
  outline: none;
}

.msg-artifact-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: min(360px, 78vw);
  min-width: min(300px, 78vw);
  padding: var(--sp-2);
  text-align: left;
  text-decoration: none;
  transition: border-color var(--transition), transform var(--transition), background var(--transition);
}

.msg-artifact-card:hover,
.msg-artifact-card:focus-visible {
  background: var(--bg-hover);
  border-color: var(--accent);
  outline: none;
  transform: translateY(-1px);
}

.msg-artifact-card--image {
  overflow: hidden;
}

.msg-artifact-card--audio {
  cursor: default;
  max-width: min(520px, 100%);
  min-width: min(320px, 78vw);
}

.msg-artifact-card--audio:hover,
.msg-artifact-card--audio:focus-visible {
  background: var(--bg-surface);
  border-color: var(--border);
  transform: none;
}

.msg-artifact-audio {
  display: block;
  width: 100%;
}

.msg-artifact-preview {
  aspect-ratio: 16 / 10;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  display: block;
  max-height: 260px;
  object-fit: contain;
  width: 100%;
}

.msg-artifact-preview--empty {
  min-height: 160px;
}

.msg-artifact-card__body {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.msg-artifact-card__name,
.msg-artifact-card__meta {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.msg-artifact-card__name {
  font-size: var(--fs-sm);
  font-weight: 600;
}

.msg-artifact-card__meta {
  color: var(--text-dim);
  font-size: var(--fs-xs);
}

.msg-artifact-card__action {
  align-self: flex-start;
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
  border-radius: var(--radius-sm);
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 600;
  padding: 3px 8px;
}

.msg.user .msg-body.msg-body--has-attachments {
  align-items: flex-end;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: min(360px, 78%);
  padding: 0;
}

.msg.user .msg-body--has-attachments .msg-attachment-text {
  align-self: flex-end;
  background: var(--accent);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  color: #fff;
  line-height: 1.6;
  max-width: min(360px, 100%);
  min-height: 32px;
  min-width: 56px;
  padding: var(--sp-2) var(--sp-4);
  text-align: start;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.msg.user .msg-body--has-attachments .msg-attachments {
  justify-content: flex-end;
  max-width: 100%;
  margin: 0;
  user-select: text;
}

.msg.user .msg-body--has-attachments .msg-thumb {
  width: min(260px, 42vw);
  height: auto;
  max-width: 100%;
  max-height: 160px;
  object-fit: contain;
  background: transparent;
  border-color: color-mix(in srgb, var(--border) 65%, transparent);
  box-shadow: none;
}

/* ─── Pending Queue (Bug 2c / Proposal C) ──────────────────────────────── */

.chat-pending {
  margin: 0 var(--sp-4) var(--sp-2);
  padding: var(--sp-2);
  background: color-mix(in srgb, var(--accent) 4%, var(--bg-surface));
  border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border));
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
}
.chat-pending.hidden { display: none; }

.chat-pending-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}
.chat-pending-label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  cursor: help;
}
.chat-pending-clear {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font-size: var(--fs-xs);
  padding: 0 2px;
}
.chat-pending-clear:hover { text-decoration: underline; }

.chat-pending-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.chat-pending-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 10px;
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-hover));
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
  border-radius: 999px;
  max-width: 240px;
  line-height: 1.2;
  font-size: var(--fs-xs);
}
.chat-pending-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 180px;
}
.chat-pending-attch {
  color: var(--text-muted);
  font-size: 11px;
}
.chat-pending-chip-remove {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0 4px;
}
.chat-pending-chip-remove:hover { color: var(--danger); }

.chat-context-separator {
  --separator-tone: var(--accent);
  width: min(820px, 100%);
  align-self: center;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 8px 0 10px;
  color: color-mix(in srgb, var(--separator-tone) 68%, var(--text-muted));
  font-family: Bahnschrift, Aptos, "Segoe UI Variable", "Segoe UI", sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.2;
  text-transform: uppercase;
}

.chat-context-separator::before,
.chat-context-separator::after {
  content: "";
  height: 1px;
  flex: 1 1 auto;
  min-width: 24px;
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in srgb, var(--separator-tone) 30%, var(--border))
  );
}

.chat-context-separator::after {
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--separator-tone) 30%, var(--border)),
    transparent
  );
}

.chat-context-separator span {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 56ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-context-separator--session {
  margin-top: auto;
}

.chat-context-separator--live span {
  --separator-shimmer-base: color-mix(
    in srgb,
    var(--separator-tone) 62%,
    var(--text-muted)
  );
  --separator-shimmer-glint: color-mix(
    in srgb,
    var(--separator-tone) 28%,
    var(--text)
  );
  color: transparent;
  background: linear-gradient(
    100deg,
    var(--separator-shimmer-base) 0%,
    var(--separator-shimmer-base) 38%,
    var(--separator-shimmer-glint) 50%,
    var(--separator-shimmer-base) 62%,
    var(--separator-shimmer-base) 100%
  );
  background-size: 220% 100%;
  background-position: 140% 0;
  -webkit-background-clip: text;
  background-clip: text;
  animation: contextSeparatorShimmer 2.2s linear infinite;
}

.chat-context-separator--ok { --separator-tone: var(--ok); }
.chat-context-separator--warn { --separator-tone: var(--warn); }
.chat-context-separator--err { --separator-tone: var(--danger); }
.chat-context-separator--history {
  color: var(--text-dim);
  opacity: 0.82;
}

@keyframes contextSeparatorShimmer {
  from { background-position: 140% 0; }
  to { background-position: -140% 0; }
}

@media (max-width: 760px) {
  .chat-context-separator {
    width: 100%;
    gap: 8px;
  }

  .chat-context-separator span {
    max-width: 34ch;
  }
}

.chat-history-scope {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  margin: 0 0 var(--sp-2);
  border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 5%, var(--bg-surface));
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.chat-history-scope--loading {
  border-color: color-mix(in srgb, var(--accent) 28%, var(--border));
}

.chat-history-scope--partial {
  border-color: color-mix(in srgb, var(--warn) 28%, var(--border));
  background: color-mix(in srgb, var(--warn) 7%, var(--bg-surface));
}

.chat-history-scope--compacted {
  border-color: color-mix(in srgb, var(--accent) 24%, var(--border));
}

.chat-history-scope--error {
  border-color: color-mix(in srgb, var(--danger) 32%, var(--border));
  background: color-mix(in srgb, var(--danger) 7%, var(--bg-surface));
}

.chat-history-scope__text {
  min-width: 0;
  font-weight: 600;
}

.chat-history-scope__detail {
  min-width: 0;
  color: var(--text-muted);
  font-size: var(--fs-xs);
}

.chat-history-scope__actions {
  margin-left: auto;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

@media (max-width: 720px) {
  .chat-history-scope {
    align-items: stretch;
    flex-direction: column;
  }

  .chat-history-scope__actions {
    width: 100%;
    margin-left: 0;
  }

  .chat-history-scope__actions .btn {
    width: 100%;
    justify-content: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chat-context-separator {
    animation: none;
    transition: none;
  }

  .chat-context-separator--live span {
    animation: none;
    background: none;
    color: color-mix(in srgb, var(--separator-tone) 68%, var(--text-muted));
  }
}

#chat-btn-stop.hidden { display: none; }

/* ─── Slash Command Menu ───────────────────────────────────────────────── */

.chat-slash {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-sizing: border-box;
  width: min(calc(100% - var(--sp-8)), var(--chat-measure));
  margin: 0 auto var(--sp-2);
  max-height: 200px;
  overflow-y: auto;
  flex-shrink: 0;
  box-shadow: var(--shadow-md);
}

.chat-slash-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  min-width: 0;
  min-height: 42px;
  cursor: pointer;
  font-size: var(--fs-sm);
  transition: background var(--transition);
}

.chat-slash-item:hover,
.chat-slash-item.active,
.chat-slash-item--active {
  background: var(--bg-hover);
}

.chat-slash-cmd {
  font-family: var(--font-mono);
  font-weight: 500;
  flex: 0 0 96px;
  min-width: 0;
  color: var(--accent);
}

.chat-slash-cat {
  font-size: var(--fs-xs);
  color: var(--accent);
  min-width: 60px;
}

.chat-slash-desc {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-muted);
}

@media (max-width: 480px) {
  .chat-slash {
    width: calc(100% - var(--sp-4));
    margin-bottom: 6px;
    max-height: 168px;
  }

  .chat-slash-cmd {
    flex-basis: 84px;
  }

  .chat-slash-desc {
    font-size: var(--fs-xs);
  }
}

/* ─── Per-bubble hover affordances ────────────────────────────────────── */
/*
 * Quick-action row shown on hover/focus over user and assistant bubbles.
 * Anchored to the bubble's outer side gutter (assistant → right gutter,
 * user → left gutter) and aligned with the bubble's bottom edge. The side
 * gutter is whitespace by construction (assistant bubbles cap at 85%,
 * user at 70%), so the toolbar lives next to the bubble rather than in the
 * vertical gap between turns — earlier "below the bubble" placement
 * collided with consecutive same-speaker bubbles whose negative top margin
 * closes that gap, hiding the next turn's text.
 */
.msg.assistant > .msg-body,
.msg.user > .msg-body {
  position: relative;
}
.msg .msg-body > .msg-actions {
  position: absolute;
  bottom: 4px;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  background: var(--bg-elevated, var(--bg-surface));
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.28);
  opacity: 0;
  transition: opacity 120ms ease, transform 120ms ease;
  pointer-events: none;
  z-index: 2;
  white-space: nowrap;
}
.msg.assistant .msg-body > .msg-actions {
  left: 100%;
  margin-left: 8px;
  transform: translateX(-4px);
}
.msg.user .msg-body > .msg-actions {
  right: 100%;
  margin-right: 8px;
  transform: translateX(4px);
}
.msg:hover .msg-actions,
.msg:focus-within .msg-actions {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}
/* Don't show actions while still streaming the response */
.msg.streaming .msg-actions {
  opacity: 0 !important;
  pointer-events: none !important;
}

.msg-action {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  cursor: pointer;
  transition: color 120ms ease, background 120ms ease;
}
.msg-action:hover,
.msg-action:focus-visible {
  background: var(--bg-hover);
  color: var(--accent);
  outline: none;
}
.msg-action svg { display: block; width: 14px; height: 14px; }

/* ─── Chat Composer (toolbar + input bar wrapper) ──────────────────────── */

/* Wraps .chat-toolbar + .chat-input-bar so ResizeObserver can measure both. */
.chat-composer {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
}

/* ─── Composer toolbar trigger + popover ────────────────────────────── */
/*
 * Composer-state controls live inside a popover anchored to a single gear
 * button in the input bar. The trigger glows accent only when at least one
 * control is in a non-default state: bypass on OR router off.
 */
.chat-toolbar-wrap {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.chat-toolbar-trigger {
  position: relative;
  transition: color 140ms ease, transform 140ms ease;
}
.chat-toolbar-trigger:hover { color: var(--accent); }
.chat-toolbar-trigger.is-active { color: var(--accent); }

/* Per-toggle status dots reveal which runtime mode is non-default. */
.chat-toolbar-trigger-dots {
  position: absolute;
  top: 50%;
  right: 3px;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 2px;
  pointer-events: none;
}
.chat-toolbar-trigger-dots i {
  display: block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: transparent;
  transition: background 200ms var(--ease-press, cubic-bezier(.2,.7,.2,1)),
              box-shadow 200ms ease;
}
/* Bypass — danger/warn red-orange tint, the only safety-critical signal. */
.chat-toolbar-trigger.has-dot-bypass .chat-toolbar-trigger-dots i[data-dot="bypass"] {
  background: var(--danger);
  box-shadow: 0 0 4px color-mix(in srgb, var(--danger) 70%, transparent);
}
/* Router off — muted, since "off" is intentional but lower-stakes. */
.chat-toolbar-trigger.has-dot-router .chat-toolbar-trigger-dots i[data-dot="router"] {
  background: var(--text-muted);
}
/* Open state highlights the trigger itself. */
.chat-toolbar-trigger.is-active .chat-toolbar-trigger-dots i {
  background: color-mix(in srgb, currentColor 30%, transparent);
}

.chat-toolbar-popover {
  position: absolute;
  bottom: calc(100% + 10px);
  left: -8px;
  z-index: 1000;
  min-width: 280px;
  padding: var(--sp-2);
  background: var(--bg-elevated, var(--bg-surface));
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.40), 0 2px 8px rgba(0, 0, 0, 0.20);
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease;
}
.chat-toolbar-popover.hidden { display: none; }
.chat-toolbar-popover.is-open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.chat-toolbar-popover-arrow {
  position: absolute;
  bottom: -6px;
  left: 16px;
  width: 10px;
  height: 10px;
  background: var(--bg-elevated, var(--bg-surface));
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  transform: rotate(45deg);
}

.chat-toolbar-popover-inner {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.chat-toolbar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-2);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
}

.chat-toolbar-row:hover {
  background: var(--bg-hover);
}

.chat-toolbar-row-label {
  color: var(--text);
  font-size: var(--fs-sm);
  white-space: nowrap;
}

/* ─── Chat Toolbar (feature toggle pills) ──────────────────────────────── */

.chat-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-4);
  border-top: 1px solid var(--border);
  background: var(--bg);
  flex-shrink: 0;
  font-size: var(--fs-xs);
  min-height: 40px;
}

/* On wrap (narrow viewports) the token widget should detach from `margin-left:auto`
   and flow with the pills instead of pushing alone to a new line. */
@media (max-width: 640px) {
  .chat-toolbar #chat-token-widget {
    flex: 1 1 100%;
    margin-left: 0 !important;
    max-width: 100%;
    min-width: 0;
  }

  .chat-toolbar #chat-token-widget .token-pill {
    width: 100%;
  }
}

/* On phones, hide Export — rarely needed on mobile. The "new chat" (+) button
   stays visible because the session-chip popover is a switcher, not a creator,
   so the toolbar button is the only discoverable way to start a fresh chat. */
@media (max-width: 480px) {
  #chat-btn-export { display: none !important; }

  /* On narrow phones the input was getting squeezed to ~80px. Tighten icon
     targets to the touch minimum (40px instead of 44px) so the input gets
     back enough width to read its own placeholder. */
  .chat-input-bar .btn--icon {
    min-width: 40px;
    min-height: 40px;
    padding: 6px;
  }
  /* Tighten the textarea side-padding so its own placeholder isn't clipped. */
  .chat-textarea {
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
  }

  /* On phone the trigger sits on the left edge of the composer; a normal
     left-anchored popover would push out the right side, and a right-anchored
     one would push out the left. Switch to fixed positioning so the popover
     always sits within viewport margins, anchored above the composer.
     z-index 2500 stacks above .toast-stack (z-index 2000) so user actions
     inside the popover (toggle router, etc) don't trigger toasts that cover
     the popover's own bottom rows. */
  .chat-toolbar-popover {
    position: fixed;
    left: 12px;
    right: 12px;
    bottom: calc(var(--composer-h, 90px) + 10px + env(safe-area-inset-bottom, 0px));
    width: auto;
    min-width: 0;
    max-width: none;
    z-index: 2500;
  }
  /* When the run-modes popover is open, mute the toast stack so transient
     "Squilla Router: ON/OFF" confirmations don't cover the popover content
     the user is still interacting with. The toggle's own visible state is
     the canonical feedback in this moment. */
  body:has(.chat-toolbar-popover.is-open) .toast-stack {
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease;
  }
  /* Hide the desktop arrow notch — it can't reliably point at the trigger
     once we're using viewport-anchored positioning on phone. */
  .chat-toolbar-popover-arrow { display: none; }
}

/* Tiny phones only (iPhone SE 1st-gen, etc) — hide the "+" new-chat button.
   Most modern iPhones are 375–393px so they keep it. New chat is reachable
   from the session chip dropdown either way. */
@media (max-width: 360px) {
  #chat-btn-new { display: none !important; }
}

.chat-pill {
  padding: 2px 10px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--fs-xs);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  user-select: none;
}
.chat-pill:hover:not(:disabled) { border-color: var(--border-focus); color: var(--text); }
.chat-pill.is-active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.chat-pill.chat-pill--danger.is-active {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}
.chat-pill--disabled, .chat-pill:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  text-decoration: line-through;
}

.chat-pill-group {
  display: flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}

.chat-pill-label {
  padding: 2px 8px;
  color: var(--text-muted);
  font-size: var(--fs-xs);
}

.chat-pill-seg {
  padding: 2px 8px;
  border: none;
  background: var(--bg-surface);
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--fs-xs);
  border-left: 1px solid var(--border);
  transition: background 0.15s, color 0.15s;
  user-select: none;
}
.chat-pill-seg:hover { color: var(--text); }
.chat-pill-seg.is-active {
  background: var(--accent);
  color: #fff;
}

/* ─── Toggle Switch (Squilla Router) ──────────────────────────────────────── */

.toggle-switch-wrap {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}

.toggle-switch-wrap .toggle-label {
  white-space: nowrap;
}

/* The <label> that wraps the hidden checkbox + visible track */
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 32px;
  height: 18px;
  flex-shrink: 0;
}

.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
  margin: 0;
  padding: 0;
  border: none;
}

.toggle-track {
  position: absolute;
  inset: 0;
  background: var(--bg-hover);
  border: 1px solid var(--border);
  border-radius: 999px;
  transition: background 0.2s, border-color 0.2s;
}

.toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 12px;
  height: 12px;
  background: var(--text-dim);
  border-radius: 50%;
  transition: transform 0.2s, background 0.2s;
  pointer-events: none;
}

.toggle-switch input:checked + .toggle-track {
  background: var(--accent);
  border-color: var(--accent);
}

.toggle-switch input:checked + .toggle-track .toggle-thumb {
  transform: translateX(14px);
  background: #fff;
}

.toggle-switch input:focus-visible + .toggle-track {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ─── Chat Input Bar ───────────────────────────────────────────────────── */

.chat-input-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4) var(--sp-1);
  border-top: 1px solid var(--border);
  background: var(--bg);
  flex-shrink: 0;
  /* Share the thread's reading axis so input and conversation align. */
  max-width: var(--chat-measure);
  width: 100%;
  margin: 0 auto;
}

@media (max-width: 480px) {
  .chat-attachments {
    width: calc(100% - var(--sp-4));
    margin-bottom: 6px;
    padding-left: var(--sp-2);
    padding-right: var(--sp-2);
  }

  .chat-input-bar {
    gap: 4px;
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
    /* Sit above iOS Safari's bottom toolbar / home-indicator. */
    padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom, 0px));
  }
}

.chat-input-wrap {
  flex: 1;
  min-width: 0;
}

.chat-textarea {
  width: 100%;
  box-sizing: border-box;
  display: block;
  resize: none;
  border: 1px solid var(--border);
  border-radius: 22px;
  background: var(--bg-surface);
  color: var(--text);
  padding: 10px var(--sp-3);
  font-family: inherit;
  font-size: var(--fs-sm);
  line-height: 22px;
  height: 44px;
  min-height: 40px;
  max-height: 150px;
  overflow-y: auto;
  outline: none;
  box-shadow: var(--shadow-md);
  transition: border-color var(--transition);
}

.chat-textarea:focus {
  border-color: var(--accent);
}

.chat-textarea::placeholder {
  color: var(--text-dim);
}

#chat-btn-send {
  transition: transform 120ms ease, box-shadow 0.3s ease;
}
#chat-btn-send:active {
  transform: scale(0.92);
}

#chat-btn-mic.chat-mic-recording {
  color: #b91c1c;
  background: #fee2e2;
  border-color: #fecaca;
  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.14);
}

#chat-btn-mic.chat-mic-recording svg {
  animation: mic-pulse 1.2s ease-in-out infinite;
}

@keyframes mic-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(0.86); }
}

/* Stop button pulse animation during streaming */
#chat-btn-send.danger {
  animation: stop-pulse 1.8s ease-in-out infinite;
}
#chat-btn-send.danger:hover {
  animation: none;
  box-shadow: 0 0 12px rgba(239, 68, 68, 0.5);
}

@keyframes stop-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
  50% { box-shadow: 0 0 8px 3px rgba(239, 68, 68, 0.25); }
}

/* ─── Squilla-Router & Prompt-Cache per-turn chips ────────────────────────── */

.msg-savings-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  flex-wrap: wrap;
  margin-top: var(--sp-2);
}

.savings-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap;
  cursor: default;
  animation: chipIn 200ms ease;
}

@keyframes chipIn {
  from { opacity: 0; transform: scale(0.8); }
  to   { opacity: 1; transform: scale(1); }
}

/* Squilla router tier routing — amber. Token-based: color-mix(var(--warn))
   resolves per theme, so the hand-written [data-theme] twins are gone. */
.savings-chip--tier {
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 32%, transparent);
  color: var(--warn);
}

/* Prompt cache hit — green (same green the dashboards use). */
.savings-chip--cache {
  background: color-mix(in srgb, var(--ok) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--ok) 28%, transparent);
  color: var(--ok);
}

/* ─── Per-turn saved chip (inline in .msg-meta, persistent) ──────────── */
/*
 * Stays attached to each assistant turn that produced a savings event
 * (cache hit OR squilla-router routed). Shows the bucketed savings %
 * and a small flame next to the model+tokens row. Distinct from the
 * COMBO chip: amber palette here, red palette there — at-a-glance
 * separation between "this turn saved tokens" and "this is run #N".
 */

/* Amber (warn) tier; escalates by raising the accent mix, not by swapping
   hand-picked hues or animating. color-mix resolves per theme. */
.msg-meta__saved {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 8px 1px 6px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--border));
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  color: var(--warn);
  font-family: var(--font-mono);
  font-size: 0.95em;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  cursor: default;
  user-select: none;
  transition: border-color 200ms ease, background 200ms ease, color 200ms ease;
}
.msg-meta__saved--high {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
.msg-meta__saved--peak {
  border-color: color-mix(in srgb, var(--accent) 70%, var(--border));
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
}

/* Flame — inherits the chip's token color (currentColor); static. */
.msg-meta__saved-flame {
  display: inline-block;
  flex-shrink: 0;
  width: 1em;
  height: 1em;
  transform-origin: 50% 85%;
}

.msg-meta__saved-label {
  letter-spacing: 0.01em;
}

/* ─── "Saved ~X%" floating label — viewport-centered, headline weight ── */
/*
 * Anchored at viewport center via top:50%/left:50% + the translate(-50%, …)
 * keyframe. Font size uses clamp() with a wide range so it reads as a real
 * celebration on every breakpoint — phone, tablet, desktop, ultrawide. The
 * --peak modifier ramps the size + glow when the savings bucket is ≥65%.
 */

.savings-float {
  position: fixed;
  top: 50%;
  left: 50%;
  font-family: var(--font-display, var(--font-sans));
  /* Min ~15px (phone), max ~43px (ultrawide). Sized at ⅔ of the prior
     headline so the popup doesn't dominate the chat thread. */
  font-size: clamp(0.95rem, 4vw, 2.65rem);
  font-weight: 900;
  color: #fbbf24;
  text-shadow:
    0 0 24px rgba(251, 191, 36, 0.95),
    0 0 56px rgba(251, 146, 36, 0.55),
    0 4px 14px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  z-index: 9999;
  white-space: nowrap;
  letter-spacing: 0.02em;
  text-align: center;
  will-change: transform, opacity;
  transform: translate(-50%, -50%);
  animation: savings-float-up 2.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

.savings-float__main { display: block; }

/* Subtle scope qualifier — the savings figure above is *per-turn*, not
 * cumulative. Hard-coded clamp keeps it legible on phones (where 0.3em of
 * the main would dip below ~7px). Lighter weight + reduced opacity reads
 * as a caption rather than a second headline. */
.savings-float__sub {
  display: block;
  margin-top: 0.45em;
  font-size: clamp(0.62rem, 1.6vw, 1.05rem);
  font-weight: 500;
  letter-spacing: 0.06em;
  opacity: 0.72;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
}

/* Peak savings tier (≥65%) — biggest, hottest. */
.savings-float--peak {
  font-size: clamp(1.2rem, 5.3vw, 3.65rem);
  color: #fde047;
  text-shadow:
    0 0 36px rgba(251, 191, 36, 1),
    0 0 80px rgba(239, 68, 68, 0.55),
    0 6px 18px rgba(0, 0, 0, 0.6);
}

@keyframes savings-float-up {
  0%   { opacity: 0; transform: translate(-50%, calc(-50% + 18px)) scale(0.55); }
  10%  { opacity: 1; transform: translate(-50%, calc(-50% - 10px)) scale(1.20); }
  60%  { opacity: 1; transform: translate(-50%, calc(-50% - 44px)) scale(1.00); }
  100% { opacity: 0; transform: translate(-50%, calc(-50% - 110px)) scale(0.92); }
}

@media (prefers-reduced-motion: reduce) {
  .savings-float {
    animation: savings-float-fade 1.2s linear forwards;
  }
  @keyframes savings-float-fade {
    0%, 80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100%    { opacity: 0; transform: translate(-50%, -50%) scale(1); }
  }
}

/* ─── Per-turn combo (inline with model + tokens in .msg-meta) ────────── */
/*
 * The combo lives inside the per-turn meta footer beneath each assistant
 * bubble — same area that already shows the model name and session tokens.
 * Renders only when the savings streak ≥ 2 at turn-finish time, so each
 * assistant message carries its own snapshot. Three intensity tiers
 * (amber border → hot orange → blaze red) but the flame icon is *always*
 * a saturated red so it reads unambiguously as fire regardless of OS
 * emoji palette.
 */

/* Three intensity tiers expressed as ONE var(--danger) ramped by mix %
   (16 → 22 → 30) — no hand-picked reds, no perpetual flicker, no glow pulse.
   Brief entrance only. color-mix resolves per theme. */
.msg-meta__combo {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border));
  background: color-mix(in srgb, var(--danger) 16%, transparent);
  color: var(--danger);
  font-family: var(--font-mono);
  font-size: 0.95em;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.2;
  white-space: nowrap;
  cursor: default;
  user-select: none;
  animation: msg-combo-in 320ms cubic-bezier(0.34, 1.56, 0.64, 1);
  transition: border-color 200ms ease, background 200ms ease, color 200ms ease;
}
.msg-meta__combo--hot {
  border-color: color-mix(in srgb, var(--danger) 60%, var(--border));
  background: color-mix(in srgb, var(--danger) 22%, transparent);
}
.msg-meta__combo--blaze {
  border-color: color-mix(in srgb, var(--danger) 75%, var(--border));
  background: color-mix(in srgb, var(--danger) 30%, transparent);
}

/* Flame icon — inherits currentColor (the chip's danger token); static. */
.msg-meta__combo-flame {
  display: inline-block;
  flex-shrink: 0;
  width: 1.05em;
  height: 1.05em;
  transform-origin: 50% 85%;
}

.msg-meta__combo-label {
  font-weight: 800;
  letter-spacing: 0.08em;
}

.msg-meta__combo-count {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  opacity: 0.92;
}

@keyframes msg-combo-in {
  0%   { opacity: 0; transform: scale(0.6); }
  60%  { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .msg-meta__combo { animation: none; }
}

.savings-indicator {
  font-size: var(--fs-xs);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: 999px;
  padding: 1px 7px;
  margin-left: var(--sp-2);
  display: inline-flex;
  align-items: center;
  gap: 3px;
  opacity: 0; /* Hidden by default */
  transition: opacity 0.2s ease;
}

.savings-indicator.active {
  opacity: 1;
}


/* ── Clarify form (PR5 meta-skill user_input pause) ───────────────── */

.clarify-form {
  margin-top: var(--sp-3);
  background: var(--bg-surface);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}

.clarify-form-header {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
}

.clarify-form-header::before {
  content: '📝';
}

.clarify-form-intro {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: pre-wrap;
  line-height: 1.5;
}

.clarify-form-fields {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}

.clarify-form-row {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.clarify-form-label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.clarify-form-input {
  font-size: var(--fs-sm);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-base);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  outline: none;
  transition: border-color var(--transition), box-shadow var(--transition);
  font-family: inherit;
}

.clarify-form-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent);
}

.clarify-form-input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

select.clarify-form-input {
  appearance: auto;
  cursor: pointer;
}

.clarify-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
  margin-top: var(--sp-2);
}

.clarify-form-submit,
.clarify-form-cancel {
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-sm);
  font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color var(--transition),
              border-color var(--transition),
              opacity var(--transition);
  font-family: inherit;
}

.clarify-form-submit {
  background: var(--accent);
  color: var(--accent-contrast, white);
  border-color: var(--accent);
}

.clarify-form-submit:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 88%, black);
}

.clarify-form-cancel {
  background: transparent;
  color: var(--text-muted);
  border-color: var(--border);
}

.clarify-form-cancel:hover:not(:disabled) {
  color: var(--text);
  border-color: var(--text-muted);
  background: color-mix(in srgb, var(--text-muted) 8%, transparent);
}

.clarify-form-submit:disabled,
.clarify-form-cancel:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.clarify-form-error {
  font-size: var(--fs-xs);
  color: #c53030;
  margin-top: var(--sp-1);
}

.clarify-form--submitted {
  opacity: 0.6;
  pointer-events: none;
}


/* ──────────────────────────────────────────────────────────────────
 * Auto-prefill transparency block (step (d) of the clarify rebuild).
 *
 * When the prefill scan inferred values from the user's earlier
 * messages, the server attaches confirmed_fields / ambiguous_fields /
 * unknown_mentions to the form payload. The renderer surfaces them at
 * the top of the form so the user knows what was detected and can
 * confirm or override per-field. The styling stays visually quiet —
 * a translucent panel above the form fields rather than a competing
 * primary surface — because these are *system inferences* the user
 * is reviewing, not new prompts to act on.
 * ────────────────────────────────────────────────────────────────── */
.clarify-form-prefill {
  margin: 8px 0 12px;
  padding: 10px 12px;
  border: 1px solid rgba(80, 130, 200, 0.25);
  border-radius: 6px;
  background: rgba(80, 130, 200, 0.05);
  font-size: 0.9em;
}

.clarify-form-prefill-note {
  font-weight: 500;
  margin-bottom: 6px;
  opacity: 0.85;
}

.clarify-form-prefill-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.clarify-form-prefill-list li {
  padding: 2px 0;
  line-height: 1.45;
  font-family: inherit;
}

.clarify-form-prefill-confirmed {
  color: rgba(60, 130, 70, 0.95);
}

.clarify-form-prefill-ambiguous {
  color: rgba(180, 110, 30, 0.95);
}

.clarify-form-prefill-unknown {
  color: rgba(80, 80, 80, 0.85);
  font-style: italic;
}

/* Per-row markers attached below the input itself — the local
 * counterpart of the top-of-form summary so each field shows where
 * its current value came from without forcing the user to scroll. */
.clarify-form-row-marker {
  margin-top: 4px;
  font-size: 0.85em;
  line-height: 1.35;
  opacity: 0.85;
}

.clarify-form-row-marker-confirmed {
  color: rgba(60, 130, 70, 0.9);
}

.clarify-form-row-marker-ambiguous {
  color: rgba(180, 110, 30, 0.9);
}

.clarify-form-input-confirmed {
  /* Visual cue that the input was auto-filled — a subtle left border
   * accent. Users can still edit/clear normally; the cue just
   * disambiguates "system inferred this" from "I typed this". */
  border-left: 3px solid rgba(60, 130, 70, 0.6);
  background-color: rgba(60, 130, 70, 0.04);
}

.clarify-form-dual-channel-hint {
  /* Soft, low-key callout right under the form header that tells the
   * user the main chat input still works while the form is open.
   * Quiet styling on purpose — this is a permission marker, not a
   * call to action. */
  margin: 2px 0 10px;
  padding: 6px 10px;
  font-size: 0.85em;
  line-height: 1.45;
  color: rgba(80, 80, 120, 0.85);
  background: rgba(120, 100, 200, 0.04);
  border-left: 2px solid rgba(120, 100, 200, 0.35);
  border-radius: 3px;
}


/* ──────────────────────────────────────────────────────────────────
 * Clarify-skip transparency card.
 *
 * When a meta author's ``when:`` expression suppresses an otherwise-
 * required user_input step (because the upstream context-extraction
 * already filled the answers), the scheduler attaches a
 * ``clarify_skip_summary`` so the user sees what was inferred
 * without the form being forced back open. The card sits inline in
 * the assistant stream — quieter than the active form, louder than a
 * passive log entry, and always offers an undo path.
 * ────────────────────────────────────────────────────────────────── */
.clarify-skip-summary {
  margin: 8px 0;
  padding: 10px 12px;
  border: 1px dashed rgba(120, 100, 200, 0.35);
  border-radius: 6px;
  background: rgba(120, 100, 200, 0.05);
  font-size: 0.9em;
}

.clarify-skip-summary-header {
  font-weight: 500;
  margin-bottom: 6px;
  color: rgba(100, 80, 180, 0.95);
}

.clarify-skip-summary-note {
  opacity: 0.85;
  margin-bottom: 6px;
}

.clarify-skip-summary-trigger {
  margin-bottom: 6px;
  font-style: italic;
  opacity: 0.8;
}

.clarify-skip-summary-fields-header,
.clarify-skip-summary-inferred-header {
  margin-top: 6px;
  margin-bottom: 4px;
  font-weight: 500;
  opacity: 0.85;
}

.clarify-skip-summary-fields {
  list-style: none;
  padding-left: 12px;
  margin: 0 0 6px;
}

.clarify-skip-summary-fields li {
  padding: 1px 0;
  line-height: 1.4;
}

.clarify-skip-summary-inferred-entry {
  margin: 2px 0;
  padding: 4px 8px;
  border-left: 2px solid rgba(120, 100, 200, 0.35);
  background: rgba(120, 100, 200, 0.03);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85em;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
}

.clarify-skip-summary-inferred-step {
  font-weight: 500;
  opacity: 0.75;
}

.clarify-skip-summary-hint {
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px dotted rgba(120, 100, 200, 0.25);
  opacity: 0.8;
  font-size: 0.85em;
}


/* ──────────────────────────────────────────────────────────────────
 * Router slider — arcade-brutalist whac-a-mole grid that fires when
 * the squilla router lands a tier.
 *
 * The strip is a compact grid of real candidates for the current
 * request kind. A square hammer-selector hops between cells with
 * bouncy easing and locks onto the routed cell — each visited cell
 * does a quick mole-pop, the winner cell additionally fires a
 * particle burst.
 *
 * Aesthetic: hairline mantis-brutalist. Orange is the only colour
 * that signals. Live and history-loaded renders share the same
 * component (history skips the hop animation and settles immediately
 * on the winner cell). ─────────────────────────────────────────── */
.router-fx {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Centered, prominent panel within the reading column. The column (P0-1)
     already keeps it from stranding in a dead band on wide screens, so the
     full AI-model-router grid stays a deliberate centerpiece. */
  margin: var(--sp-3) auto 4px;
  padding: 0;
  user-select: none;
  align-self: center;
  width: 100%;
  max-width: min(100%, 620px);
}

.router-fx-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.44em;
  text-transform: uppercase;
  color: var(--text-muted);
  font-weight: 600;
  padding: 2px 0 0;
}
.router-fx-header .title {
  padding-left: 0.44em; /* compensate for letter-spacing offset */
}
.router-fx-header .glyph {
  color: var(--accent);
  font-size: 12px;
  letter-spacing: 0;
  transition: transform 220ms ease;
}
.router-fx[data-state="playing"] .router-fx-header .glyph {
  animation: router-fx-chev 900ms cubic-bezier(.4,0,.6,1) infinite;
}
.router-fx[data-state="playing"] .router-fx-header .glyph:last-child {
  animation-delay: 450ms;
}
@keyframes router-fx-chev {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(3px); }
}
/* Settled header — keeps a faint accent tint so the strip reads "dial
   locked on a tier" rather than "abandoned." Glyph arrows go translucent
   accent so the "← MODEL ROUTER →" line continues to point at the pick. */
.router-fx[data-state="settled"] .router-fx-header {
  color: color-mix(in srgb, var(--accent) 38%, var(--text-dim));
}
.router-fx[data-state="settled"] .router-fx-header .glyph {
  color: color-mix(in srgb, var(--accent) 70%, transparent);
}

/* The grid surface — circuit-board hairlines with a faint dot field
 * in the gaps. Padded so the absolute-positioned selector can sit
 * inside without overflowing the rounded border. */
.router-fx-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--router-fx-cols, 2), 1fr);
  grid-template-rows: none;
  grid-auto-rows: 34px;
  gap: 4px;
  padding: 8px;
  background:
    radial-gradient(color-mix(in srgb, var(--text) 8%, transparent) 0.7px, transparent 1.2px) 0 0 / 8px 8px,
    var(--bg-surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.router-fx-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--bg) 65%, transparent);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text);
  letter-spacing: 0;
  /* Allow the inner .nm label to shrink/ellipsize inside the grid track —
     a flex container ignores its own text-overflow, so the actual clamp
     lives on .nm below. min-width:0 lets the cell yield to the track. */
  min-width: 0;
  overflow: hidden;
  padding: 0 5px;
  transition:
    transform 220ms cubic-bezier(.34,1.65,.5,1),
    background 240ms ease,
    color 240ms ease,
    border-color 240ms ease,
    box-shadow 240ms ease;
}

/* The label itself owns the truncation. Any model name longer than the
   grid track (e.g. "gemini-3.1-flash-lite") ellipsizes here instead of
   bleeding past the cell edge; the full name stays on the `title` tooltip. */
.router-fx-cell .nm {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Equal prominence: every candidate cell renders identically at rest. Only
 * the routing RESULT (.win, below) is emphasised. */

/* Mole-pop: scale up + tiny lift + accent tint as the hammer arrives. */
@keyframes router-fx-mole-pop {
  0%   { transform: translateY(0)    scale(1);    background: color-mix(in srgb, var(--bg) 65%, transparent); }
  35%  { transform: translateY(-2px) scale(1.16); background: color-mix(in srgb, var(--accent) 14%, var(--bg)); }
  100% { transform: translateY(0)    scale(1);    background: color-mix(in srgb, var(--bg) 65%, transparent); }
}
.router-fx-cell.pinging {
  animation: router-fx-mole-pop 200ms cubic-bezier(.34,1.7,.5,1) both;
  z-index: 3;
}
/* Winner: solid orange border, accent text, with a soft outer halo +
 * inner top-edge highlight + a one-shot scan-line that sweeps top-to-
 * bottom on landing. Reads as the locked pick, not just "selected".
 * A lock dot in the corner pulses to confirm the lock (the only dot in
 * the grid now that idle cells carry none). */
.router-fx-cell.win {
  color: var(--accent);
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 14%, var(--bg)) 0%,
      color-mix(in srgb, var(--accent) 7%, var(--bg)) 100%);
  border-color: var(--accent);
  font-weight: 700;
  font-style: normal;
  transform: translateY(-2px);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent),
    0 8px 22px -8px color-mix(in srgb, var(--accent) 55%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--accent) 40%, transparent);
  z-index: 4;
}
.router-fx-cell.win::after {
  content: '';
  position: absolute;
  top: 3px;
  right: 3px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 1;
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 75%, transparent);
  animation: router-fx-dot-locked 1.4s ease-in-out infinite;
}
/* Scan-line sweep — fires once when .win lands. Sits above the cell
   surface but below text via z-index management. Pointer-events stay
   off so it never interferes with click/copy on the model name. */
.router-fx-cell.win::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--accent) 90%, transparent) 50%,
    transparent 100%);
  pointer-events: none;
  opacity: 0;
  animation: router-fx-scan 720ms cubic-bezier(.4,0,.2,1) 1 forwards;
  z-index: 1;
}
@keyframes router-fx-scan {
  0%   { opacity: 0;    top: 0;    }
  20%  { opacity: 1;    top: 10%;  }
  80%  { opacity: 0.85; top: 90%;  }
  100% { opacity: 0;    top: 100%; }
}
@keyframes router-fx-dot-locked {
  0%, 100% { opacity: 0.9; box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 50%, transparent); }
  50%      { opacity: 1;   box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 85%, transparent); }
}
/* Fallback variant — chosen tier was the safe net, not the routed pick.
   Mirrors the locked-winner treatment but in the danger palette so the
   shape is the same and the colour change carries the signal. */
.router-fx[data-source="fallback"] .router-fx-cell.win {
  color: var(--danger);
  border-color: var(--danger);
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--danger) 14%, var(--bg)) 0%,
      color-mix(in srgb, var(--danger) 7%, var(--bg)) 100%);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--danger) 35%, transparent),
    0 8px 22px -8px color-mix(in srgb, var(--danger) 55%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--danger) 40%, transparent);
}
.router-fx[data-source="fallback"] .router-fx-cell.win::after {
  background: var(--danger);
  box-shadow: 0 0 8px color-mix(in srgb, var(--danger) 75%, transparent);
}
.router-fx[data-source="fallback"] .router-fx-cell.win::before {
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--danger) 90%, transparent) 50%,
    transparent 100%);
}

/* Hammer selector — absolutely positioned, JS sets transform per hop.
 * Each translate uses a bouncy ease for the "thwack" feel. */
.router-fx-selector {
  position: absolute;
  z-index: 2;
  top: 8px;
  left: 8px;
  width: 0;
  height: 0;
  border: 2px solid color-mix(in srgb, var(--accent) 80%, transparent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
  pointer-events: none;
  opacity: 0;
  transform: translate(0, 0) rotate(0deg);
  transition:
    transform 150ms cubic-bezier(.18,1.7,.5,1),
    opacity 180ms ease,
    border-color 220ms ease,
    background 220ms ease,
    box-shadow 220ms ease;
}
.router-fx-selector.visible { opacity: 1; }
.router-fx-selector.lock {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 8%, transparent);
}
.router-fx[data-source="fallback"] .router-fx-selector.lock {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--danger) 22%, transparent),
    inset 0 0 0 1px color-mix(in srgb, var(--danger) 8%, transparent);
}

/* Lock impact — selector overshoots slightly horizontally then snaps. */
@keyframes router-fx-impact {
  0%   { outline: 0 solid transparent; outline-offset: 0; }
  35%  { outline: 2px solid color-mix(in srgb, var(--accent) 70%, transparent); outline-offset: 4px; }
  100% { outline: 0 solid transparent; outline-offset: 0; }
}
.router-fx-selector.lock-impact {
  animation: router-fx-impact 280ms cubic-bezier(.34,1.6,.5,1) both;
}

/* Particle burst — 4 tiny orange squares scatter outward from the
 * winning cell. Pure CSS via pseudo-element layers on a dedicated
 * .router-fx-burst overlay that the JS appends on lock. */
.router-fx-burst {
  position: absolute;
  z-index: 4;
  pointer-events: none;
  width: 0; height: 0;
}
.router-fx-burst i {
  position: absolute;
  width: 4px;
  height: 4px;
  left: -2px; top: -2px;
  background: var(--accent);
  border-radius: 1px;
  opacity: 0;
  animation: router-fx-burst 540ms cubic-bezier(.2,.7,.2,1) forwards;
}
.router-fx-burst i:nth-child(1) { --bx: -22px; --by: -10px; }
.router-fx-burst i:nth-child(2) { --bx:  22px; --by: -10px; }
.router-fx-burst i:nth-child(3) { --bx: -22px; --by:  10px; }
.router-fx-burst i:nth-child(4) { --bx:  22px; --by:  10px; }
.router-fx-burst i:nth-child(5) { --bx:   0;   --by: -18px; width: 3px; height: 3px; }
.router-fx-burst i:nth-child(6) { --bx:   0;   --by:  18px; width: 3px; height: 3px; }
@keyframes router-fx-burst {
  0%   { opacity: 1; transform: translate(0, 0) scale(1); }
  60%  { opacity: 0.7; }
  100% { opacity: 0; transform: translate(var(--bx, 16px), var(--by, 0)) scale(0.4); }
}
.router-fx[data-source="fallback"] .router-fx-burst i {
  background: var(--danger);
}

/* Observe-mode rollout: router classified the turn but did NOT swap
 * the model in. Strip dims and the winner cell wears an "observe"
 * badge so users understand the routed model wasn't the one that
 * actually answered. No animation — the strip lands settled. */
.router-fx[data-observe="true"] {
  opacity: 0.55;
}
.router-fx[data-observe="true"] .router-fx-header::after {
  content: 'observe';
  margin-left: 12px;
  padding: 1px 6px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--text-muted) 22%, transparent);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.router-fx[data-observe="true"] .router-fx-cell.win {
  /* Less commitment than the bright accent — the model wasn't used. */
  background: color-mix(in srgb, var(--text-muted) 8%, var(--bg));
  border-color: color-mix(in srgb, var(--text-muted) 35%, var(--hairline));
  color: var(--text-muted);
  font-weight: 500;
}
.router-fx[data-observe="true"] .router-fx-cell.win::after {
  background: var(--text-muted);
}

/* NOTE: the router grid deliberately does NOT honour prefers-reduced-motion:
 * the "Visual effects" switch is the explicit opt-in, so OS reduce-motion
 * does not suppress the chase. */

/* Mobile: keep the same real-candidate roster, but wrap compactly. */
@media (max-width: 640px) {
  .router-fx-grid {
    grid-template-columns: repeat(var(--router-fx-mobile-cols, var(--router-fx-cols, 2)), 1fr);
    grid-template-rows: none;
    grid-auto-rows: 30px;
    padding: 6px;
    gap: 3px;
  }
  .router-fx-cell { font-size: 10px; padding: 0 5px; }
  .router-fx-header { font-size: 9.5px; letter-spacing: 0.36em; }
}
@media (max-width: 380px) {
  .router-fx-grid {
    grid-template-columns: repeat(var(--router-fx-mobile-cols, 2), 1fr);
    grid-template-rows: none;
    grid-auto-rows: 28px;
  }
  .router-fx-cell { font-size: 9.5px; padding: 0 4px; }
}

/* Once settled, the chase hammer is gone — the .win cell is the marker. */
.router-fx[data-state="settled"] .router-fx-selector { opacity: 0 !important; }

/* History/reopened strips are records, not live routing moments. Keep the
   selected look, but suppress every motion affordance that would read like
   the model is being chosen again after refresh or session reopen. */
.router-fx[data-render-mode="history"],
.router-fx[data-render-mode="history"] *,
.router-fx[data-render-mode="history"] *::before,
.router-fx[data-render-mode="history"] *::after {
  animation: none !important;
  transition: none !important;
}

/* ── Router-fx style variants ────────────────────────────────────────────
 * Groundwork for future frontend experiments. The visible style today is the
 * BASE look above, rendered when no data-variant attribute is present. JS
 * (_buildRouterFxElement) stamps data-variant="<name>" on the .router-fx root
 * only for non-'default' variants, so adding a skin is purely additive here.
 *
 * To add a variant:
 *   .router-fx[data-variant="<name>"] { --accent: …; --danger: …; }
 * Prefer redefining the custom properties the strip already consumes (--accent,
 * --danger, --text*, --bg*, --hairline, --radius-*). Keep variants
 * palette-only unless the router contract changes to support a second visual
 * grammar for real candidates.
 */
