/* ==========================================================================
   turnstone — chat.css
   Shared chat-view vocabulary.  Used by the per-node server UI
   (turnstone/ui/static/) and the coordinator page
   (turnstone/console/static/coordinator/).  Tokens come from base.css;
   chat.css layers component classes on top.
   Sizing scale follows the 11/12/13px established elsewhere so
   components slot into existing layouts without dominating them.
   ========================================================================== */

/* ==========================================================================
   Approval shell — shared by inline (per-tool, interactive UI) and
   batch (pinned bottom bar, coordinator).  Same visual vocabulary,
   different layout hook.

   The message primitive (.msg / .msg-body / .msg-actions / .msg-action-btn)
   used by inline approval blocks is defined further down in this file.
   ========================================================================== */
.ts-approval {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Inline variant: an approval block living as a .msg in the message
   stream, one per tool call awaiting approval.  Cyan to match the
   .msg.tool execution-result colour — both are tool-role surfaces,
   distinct from user (amber) and assistant (neutral).  Selector qualifier
   choice (.msg.ts-approval--inline vs bare) is explained at the matching
   rule in ui/static/style.css. */
.msg.ts-approval--inline {
  border-left-color: var(--cyan);
  background: var(--panel-2);
}

/* Batch variant: pinned to the bottom of the chat pane, batches all
   pending approvals across child workstreams (coordinator). */
.ts-approval--batch {
  position: relative;
  padding: 10px 14px;
  background: var(--bg-surface);
  border-top: 1px solid var(--cyan);
  border-left: 3px solid var(--cyan);
  display: none;
}
.ts-approval--batch.visible {
  display: flex;
}

.ts-approval-header {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--cyan);
  margin-bottom: 2px;
}
.ts-approval-tool {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--code-bg);
}
.ts-approval-tool + .ts-approval-tool {
  margin-top: 4px;
}
.ts-approval-tool-name {
  color: var(--cyan);
  font-weight: 600;
}
.ts-approval-tool-cmd {
  color: var(--fg-bright);
  white-space: pre-wrap;
}
.ts-approval-actions {
  display: flex;
  gap: 6px;
  margin-top: 4px;
  flex-wrap: wrap;
}
.ts-approval-btn {
  font: inherit;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 4px 10px;
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--fg);
  cursor: pointer;
  transition:
    background 0.12s,
    border-color 0.12s,
    color 0.12s;
}
.ts-approval-btn:hover {
  background: var(--bg-highlight);
  color: var(--fg-bright);
  border-color: var(--accent-dim);
}
.ts-approval-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.ts-approval-btn--approve {
  border-color: var(--green);
  color: var(--green);
}
.ts-approval-btn--approve:hover {
  background: var(--green-glow);
  color: var(--green);
}
.ts-approval-btn--deny {
  border-color: var(--red);
  color: var(--red);
}
.ts-approval-btn--deny:hover {
  background: var(--red-glow);
  color: var(--red);
}
.ts-approval-btn--always {
  border-color: var(--accent);
  color: var(--accent);
}
.ts-approval-btn--always:hover {
  background: var(--accent-dim);
  color: var(--accent);
}
.ts-approval-btn .ts-approval-key {
  display: inline-block;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 2px;
  padding: 0 4px;
  margin-right: 4px;
  font-size: 10px;
  color: var(--fg-dim);
}
.ts-approval-btn:disabled {
  opacity: 0.5;
  cursor: wait;
}

.ts-approval-feedback {
  font: inherit;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 4px 8px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  width: 100%;
}
.ts-approval-feedback:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-dim);
}
.ts-approval-feedback::placeholder {
  color: var(--fg-dim);
  opacity: 0.7;
}

.ts-approval-badge {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  letter-spacing: 0.04em;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  width: max-content;
}
.ts-approval-badge--approved {
  color: var(--green);
  border: 1px solid var(--green);
}
.ts-approval-badge--denied {
  color: var(--red);
  border: 1px solid var(--red);
}
.ts-approval-badge--error {
  color: var(--red);
  background: var(--red-glow);
  border: 1px solid var(--red);
}

/* Verdict badge (LLM judge) — inline in the approval block on
   interactive UI.  Coordinator doesn't render verdicts today but the
   class is shared so it can opt in without duplication. */
.ts-verdict-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 2px 8px;
  border-left: 3px solid var(--fg-dim);
  border-radius: 2px;
  background: var(--bg-surface);
  width: max-content;
}
.ts-verdict-badge[data-risk="low"] {
  border-left-color: var(--green);
}
.ts-verdict-badge[data-risk="medium"] {
  border-left-color: var(--yellow);
}
.ts-verdict-badge[data-risk="high"] {
  border-left-color: var(--red);
}
.ts-verdict-badge[data-risk="critical"] {
  border-left-color: var(--red);
  background: var(--red-glow);
}
.ts-verdict-risk {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.ts-verdict-rec {
  color: var(--fg-dim);
}
.ts-verdict-conf {
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
}
.ts-verdict-expand {
  background: none;
  border: none;
  color: var(--accent);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
}
.ts-verdict-detail {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
  padding: 4px 8px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-top: 4px;
}

/* Verdict glow — highlight the recommended button while a judge
   verdict is present.  Kept as a utility class so it can attach to
   any .ts-approval button. */
.ts-verdict-glow--approve .ts-approval-btn--approve {
  box-shadow: 0 0 0 2px var(--green-glow);
}
.ts-verdict-glow--deny .ts-approval-btn--deny {
  box-shadow: 0 0 0 2px var(--red-glow);
}
.ts-verdict-glow--review .ts-approval-btn--approve,
.ts-verdict-glow--review .ts-approval-btn--deny {
  box-shadow: 0 0 0 2px var(--yellow-glow);
}

/* ==========================================================================
   Composer — shared form shell.
   Slots: optional attachment chips row, optional paperclip, textarea,
   send button.  Coordinator omits chips + paperclip; interactive
   includes both.  Same wrapper, same textarea + send styling.
   ========================================================================== */
.composer {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 14px;
  /* Recessed wrapper — the textarea inside reads as the prominent
     input surface above this background.  In light mode --bg
     (#f7f7f4) on --bg-surface (#ffffff) gave near-identical contrast
     and made the textarea invisible; the inversion fixes both modes. */
  background: var(--bg);
  border-top: 1px solid var(--border-strong);
}

.composer-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.composer-chips:empty {
  display: none;
}

.composer-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 2px 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--fg);
}
.composer-chip-size {
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
}
.composer-chip-remove {
  background: none;
  border: none;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 0 2px;
  font: inherit;
  font-size: 13px;
  line-height: 1;
}
.composer-chip-remove:hover {
  color: var(--red);
}

.composer-row {
  display: flex;
  align-items: flex-end;
  gap: 6px;
}

.composer-attach {
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--fg-dim);
  cursor: pointer;
  font-size: 14px;
  padding: 4px 8px;
  line-height: 1;
  height: 32px;
  transition:
    background 0.12s,
    color 0.12s,
    border-color 0.12s;
}
.composer-attach:hover {
  background: var(--bg-highlight);
  color: var(--accent);
  border-color: var(--accent-dim);
}
.composer-attach:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.composer-input {
  flex: 1;
  min-height: 32px;
  max-height: 160px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--fg);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  /* resize: none — max-height + auto-resize handlers in app.js /
     coordinator.js cap growth; letting the user drag-resize pushes
     the messages pane off-screen on small viewports. */
  resize: none;
  transition:
    border-color 0.15s,
    box-shadow 0.15s;
}
.composer-input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-dim);
}
.composer-input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.composer-input::placeholder {
  color: var(--fg-dim);
  opacity: 0.7;
}

.composer-send {
  font: inherit;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 16px;
  background: var(--accent);
  color: var(--on-color);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  cursor: pointer;
  height: 32px;
  transition: filter 0.12s;
  letter-spacing: 0.02em;
}
.composer-send:hover {
  filter: brightness(1.08);
}
.composer-send:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
}
.composer-send:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  filter: none;
}
.composer-send--queue {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
}
.composer-send--queue:hover {
  background: var(--accent-dim);
  filter: none;
}

/* Stop button — only rendered when the composer was constructed with
   stopBtn=true.  Distinct red colour signals "interrupt the in-flight
   generation" rather than "send another message". */
.composer-stop {
  background: var(--red, #c94040);
  min-width: 120px;
  text-align: center;
  white-space: nowrap;
  color: var(--on-color);
  border: 1px solid var(--red);
  border-radius: var(--radius-sm);
  padding: 6px 16px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  height: 32px;
}
.composer-stop:focus-visible {
  outline: 2px solid var(--fg-bright, #e8ecf4);
  outline-offset: 2px;
}
[data-theme="light"] .composer-stop {
  color: #fff;
}

/* ============================================================
   Queued-message bubble — optimistic UI when the user sends
   while the worker is still mid-turn (queueWhileBusy=true).
   The bubble dims, gains a dashed border, and carries a
   "queued" badge + dismiss button until the shared queue
   controller observes the busy → idle edge via
   ``queue.onIdleEdge()`` and promotes it to a normal user
   message. The "important" variant is the !!! priority
   prefix path.
   ============================================================ */
.msg-queued {
  opacity: 0.65;
  border-style: dashed;
}
.msg-queued-important {
  opacity: 0.8;
  border-color: var(--yellow, var(--warn));
}
.queued-badge {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg-dim, var(--ink-3));
  margin-right: 4px;
}
.msg-queued-important .queued-badge {
  color: var(--yellow, var(--warn));
}
.queued-dismiss {
  background: none;
  border: none;
  color: var(--fg-dim, var(--ink-3));
  cursor: pointer;
  font-size: 14px;
  padding: 0 4px;
  margin-left: 8px;
  float: right;
  line-height: 1;
}
.queued-dismiss:hover {
  color: var(--red, var(--err));
}

/* ============================================================
   Stacked layout — textarea above, actions row below.
   Used by creation forms (coordinator-create, future dashboard
   migration) where the input is visually dominant and the action
   row pairs an attach / options cluster on the left with a
   primary "Create" / "Start" button on the right.
   ============================================================ */
.composer--stacked {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.composer--stacked .composer-text {
  display: flex;
}
.composer--stacked .composer-input {
  flex: 1;
  min-height: 72px;
  /* Blend the textarea into its enclosing panel in stacked layout —
     the container already provides the bordered frame; doubling the
     border inside reads as chrome.  Hairline bottom border so the
     input zone is still identifiable pre-focus. */
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 6px 8px;
  resize: vertical;
}
.composer--stacked .composer-input:focus-visible {
  box-shadow: none;
  border-bottom-color: var(--accent);
}
.composer--stacked .composer-row {
  align-items: center;
}
/* Space the primary action button away from the left-clustered
   attach + options buttons in the stacked layout.  The send
   button is always the LAST element in the action row; push it
   to the right edge via margin-left:auto. */
.composer--stacked .composer-send {
  margin-left: auto;
}

/* ============================================================
   Options disclosure — toggle button + collapsible panel.
   Rendered when the composer is constructed with opts.options.
   ============================================================ */
.composer-options-btn {
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--fg);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 4px 10px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition:
    background 0.12s,
    border-color 0.12s;
}
.composer-options-btn:hover {
  background: var(--bg-highlight);
  border-color: var(--accent-dim);
}
.composer-options-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.composer-options-btn[aria-expanded="true"] .composer-options-caret {
  /* Flip ▾ to ▴ via transform so the glyph file stays single-char. */
  display: inline-block;
  transform: rotate(180deg);
}
.composer-options-summary {
  color: var(--fg-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
}
.composer-options-panel {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 12px;
  align-items: center;
  padding: 8px;
  /* Most recessed surface — sits below the wrapper (--bg) and
     well below the textarea (--bg-surface) in the depth hierarchy
     so the options zone reads as its own settings tier. */
  background: var(--bg-highlight);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 12px;
}
/* Explicit [hidden] override — the class rule's display:grid has
   equal specificity to the UA [hidden] { display: none } default
   and comes later in the cascade, so a bare [hidden] attribute
   alone doesn't hide the panel.  Override here so toggleOptions()
   works via the standard hidden attribute. */
.composer-options-panel[hidden] {
  display: none;
}
.composer-options-field {
  display: contents;
}
.composer-options-field > label {
  color: var(--fg-dim);
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.05em;
}
.composer-options-control {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 4px 6px;
  width: 100%;
}
.composer-options-control:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-dim);
}
/* Active-state flip on the toggle button itself (mirrors the
   dashboard composer's affordance). */
.composer-options-btn[aria-expanded="true"] {
  color: var(--fg);
  background: var(--bg);
}

/* ==========================================================================
   Header — shared page-header shell.
   Pages that want it add .ts-header to their existing #header wrapper.
   Structure: logo + status region + actions region.  Already matches
   the shape used by ui/static/index.html and console/static/index.html;
   coordinator migrates into the same pattern.
   ========================================================================== */
.ts-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-strong);
  flex-shrink: 0;
  position: relative;
}
.ts-header::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    var(--accent-dim),
    transparent
  );
}
/* Suppress the accent gradient when the header is immediately followed
   by a #tab-bar (server UI) — the tab-bar already carries its own
   border-bottom and the two decorative lines stack visually otherwise. */
.ts-header:has(+ #tab-bar)::after {
  display: none;
}
.ts-header-title {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.02em;
  margin: 0;
}
.ts-header-title-dim {
  color: var(--fg-dim);
  font-weight: 400;
}
.ts-header-status {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 0.03em;
}
.ts-header-status--ok {
  color: var(--green);
}
.ts-header-status--err {
  color: var(--red);
}
.ts-header-spacer {
  flex: 1;
}

/* Back-to-console navigation — shared affordance used by the
   coordinator page header and by the console's redirect banner
   injected into the interactive UI proxy pages at /node/{id}/. */
.ts-header-back-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-dim);
  text-decoration: none;
  padding: 3px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  letter-spacing: 0.02em;
  transition:
    background 0.12s,
    border-color 0.12s,
    color 0.12s;
}
.ts-header-back-link:hover {
  color: var(--accent);
  background: var(--bg-highlight);
  border-color: var(--accent-dim);
}
.ts-header-back-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.ts-header-back-link-arrow {
  font-family: var(--font-mono);
}
.ts-header-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ==========================================================================
   Sidebar — shared right-sidebar shell.
   Currently only used by the coordinator page; extracted so other
   pages can adopt.  Section headings reuse .ui-section-heading from
   ui-base.css.
   ========================================================================== */
.ts-sidebar {
  display: flex;
  flex-direction: column;
  width: clamp(260px, 22vw, 420px);
  border-left: 1px solid var(--border);
  background: var(--bg);
  overflow: hidden;
  flex-shrink: 0;
}
.ts-sidebar-section {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 30%;
  overflow: hidden;
}
.ts-sidebar-section + .ts-sidebar-section {
  border-top: 1px solid var(--border);
}
.ts-sidebar-section-heading {
  margin: 0;
  padding: 6px 10px;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--fg-dim);
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 6px;
}
.ts-sidebar-section-count {
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
}
.ts-sidebar-section-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 4px 4px 8px;
}
.ts-sidebar-empty {
  padding: 8px 10px;
  font-size: 12px;
  color: var(--fg-dim);
  font-style: italic;
}

/* ==========================================================================
   Mobile (<700px) — chat.css components stack vertically.
   ========================================================================== */
@media (max-width: 700px) {
  .msg {
    padding: 6px 10px;
  }
  .msg-body pre {
    margin: 4px -6px;
    border-radius: 0;
  }
  .composer {
    padding: 6px 10px;
  }
  .composer-input {
    min-height: 40px;
    max-height: 120px;
    resize: none;
  }
  .composer-send {
    height: 40px;
    padding: 6px 14px;
  }
  .ts-sidebar {
    width: 100%;
    border-left: none;
    border-top: 1px solid var(--border);
  }
  .ts-approval--batch {
    padding: 8px 10px;
  }
}

/* ==========================================================================
   Reduced motion — disable chat.css transitions.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .msg-actions,
  .msg-action-btn,
  .ts-approval-btn,
  .composer-attach,
  .composer-input,
  .composer-send {
    transition: none;
  }
}

/* ==========================================================================
   Message primitive — chat-style single-column message block.  Semantic
   variant carries via 3px left-border colour (and text colour for
   error/info).  Shared by the coordinator chat view and the per-node
   server chat UI; both render pure .msg classes.

   Variants:
     .msg                 base (assistant / generic)
     .msg.user            user-authored turn (accent left-border)
     .msg.assistant       explicit assistant (same as base, clarity class)
     .msg.reasoning       <thinking> / reasoning trace — dashed left-border
     .msg.tool            tool output / tool response — accent-tinted
     .msg.error           error states — err red
     .msg.info            transient info — think indigo, smaller
     .msg.system          system-injected turns (policy notices, etc.)

   Content sub-elements:
     .msg-meta            author / timestamp / model label (optional);
                          placed above .msg-body in mono 11px --ink-3
     .msg-body            markdown target — styles code, pre, headings,
                          lists, blockquotes, strong/em
     .msg-actions         hover-revealed action buttons (copy / regenerate)

   Streaming indicator: add `data-streaming="true"` to the .msg and a
   blinking caret appears at the end of .msg-body. Avoids per-view
   reinvention.
   ========================================================================== */
.msg {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 8px 12px;
  margin-bottom: 4px;
  background: var(--panel-2);
  border: 1px solid var(--hair);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  font-size: 13px;
  line-height: 1.55;
  word-wrap: break-word;
  overflow-wrap: break-word;
}

/* Variant left-borders */
.msg.user {
  border-left-color: var(--accent);
}
.msg.assistant {
  border-left-color: var(--hair-2);
}
.msg.reasoning {
  border-left-style: dashed;
  border-left-color: var(--ink-4);
  color: var(--ink-3);
  font-style: italic;
}
.msg.tool {
  background: var(--panel);
  /* Cyan for tool-role surfaces — shared with .ts-approval borders so
     user (amber) / assistant (neutral) / tool (cyan) read distinctly. */
  border-left-color: var(--cyan);
  font-family: var(--font-mono);
  font-size: 12px;
}
.msg.error {
  border-left-color: var(--err);
  color: var(--err);
}
.msg.info {
  border-left-color: var(--think);
  color: color-mix(in srgb, var(--think) 70%, var(--ink));
  font-size: 12px;
}
.msg.system {
  border-left-color: var(--ink-4);
  background: var(--panel);
  font-size: 12px;
  color: var(--ink-3);
}

/* Author / timestamp slot — optional label row above the body. Use
   this for "assistant · 10:42:18 · claude-opus-4-7" style headers. */
.msg-meta {
  display: flex;
  gap: 8px;
  margin-bottom: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-3);
}

/* Markdown body — renderer.js writes innerHTML here; trust boundary
   lives there, not in CSS. Keep styles tight so streamed content
   doesn't jitter layout as tokens arrive.

   ``white-space: normal`` is intentional (and explicit so a future edit
   doesn't "simplify" by reaching for ``pre-wrap``).  The markdown
   converter leaves ``\n`` text nodes between block siblings (headings →
   paragraphs → katex-display → ...).  ``pre-wrap`` rendered every one
   of those as visible vertical space, stacking 20-30px gaps between
   every section.  ``<pre>`` blocks have ``white-space: pre`` built in
   so fenced code still preserves formatting; mid-stream partial fences
   before the closing ``\`\`\`` arrives render as collapsed text for a
   frame and then snap back when the next render tick wraps them in
   ``<pre>``.  User-typed messages render through ``.msg-user-text`` (a
   separate path that preserves its own newlines), so this default only
   affects assistant markdown output. */
.msg-body {
  min-width: 0;
  max-width: 100%;
  white-space: normal;
}

/* Streaming caret — append to the end of .msg-body when the message is
   still being streamed. Opt in via `data-streaming="true"` on the .msg. */
.msg[data-streaming="true"] .msg-body::after {
  content: "▍";
  display: inline-block;
  margin-left: 2px;
  color: var(--accent);
  animation: ts-caret 1s steps(2, start) infinite;
}

@keyframes ts-caret {
  to {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .msg[data-streaming="true"] .msg-body::after {
    animation: none;
  }
}

.msg-body > *:first-child {
  margin-top: 0;
}
.msg-body > *:last-child {
  margin-bottom: 0;
}

.msg-body pre {
  margin: 6px 0;
  padding: 8px 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--panel);
  border: 1px solid var(--hair);
  border-radius: var(--r-sm);
  overflow-x: auto;
}

.msg-body code {
  padding: 1px 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--panel);
  border: 1px solid var(--hair-2);
  border-radius: 3px;
  /* Preserve internal whitespace + allow wrapping.  The body's
     ``white-space: normal`` (above) would otherwise collapse multiple
     spaces inside backtick spans; ``<pre>`` blocks rely on the
     user-agent ``pre { white-space: pre }`` rule and don't need this. */
  white-space: pre-wrap;
}

.msg-body pre code {
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
}

/* Tool messages have a --panel background, same colour as the default
   .msg-body pre bg — shift the pre/code inside tool blocks to --panel-2
   so the inline code stands out from the message surface. */
.msg.tool .msg-body pre,
.msg.tool .msg-body code {
  background: var(--panel-2);
}

.msg-body a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.msg-body h1,
.msg-body h2,
.msg-body h3,
.msg-body h4,
.msg-body h5,
.msg-body h6 {
  margin: 10px 0 4px;
  font-weight: 600;
  color: var(--ink);
}

.msg-body h1 {
  font-size: 16px;
}
.msg-body h2 {
  font-size: 14px;
}
.msg-body h3 {
  font-size: 13px;
}
.msg-body h4,
.msg-body h5,
.msg-body h6 {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}

.msg-body p {
  margin: 6px 0;
}

.msg-body ul,
.msg-body ol {
  margin: 4px 0 4px 20px;
  padding: 0;
}
.msg-body li {
  margin: 2px 0;
}

.msg-body strong {
  color: var(--ink);
  font-weight: 600;
}
.msg-body em {
  font-style: italic;
}

.msg-body blockquote {
  margin: 6px 0;
  padding: 4px 10px;
  border-left: 2px solid var(--hair);
  color: var(--ink-3);
}

/* Hover-revealed action row — copy / regenerate / feedback buttons.
   pointer-events toggles alongside opacity so the invisible toolbar
   doesn't intercept clicks in the top-right corner of the message
   (would otherwise break text selection on the last word of a short
   one-line msg). */
.msg-actions {
  position: absolute;
  top: 4px;
  right: 4px;
  display: flex;
  gap: 2px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}

.msg:hover .msg-actions,
.msg:focus-within .msg-actions {
  opacity: 1;
  pointer-events: auto;
}

.msg-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 24px;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-3);
  background: transparent;
  border: none;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color 120ms ease, background 120ms ease;
}

.msg-action-btn:hover {
  background: var(--hair-2);
  color: var(--ink);
}

.msg-action-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Touch-only: keep actions visible; hover-reveal isn't reachable.
   `(pointer: coarse)` narrows the match to true touch devices —
   `(hover: none)` alone fires on stylus and some laptops too. Matches
   the `@media (hover: none) and (pointer: coarse)` convention above.
   Keyboard users are handled by :focus-within on .msg above. */
@media (hover: none) and (pointer: coarse) {
  .msg-actions {
    opacity: 1;
    pointer-events: auto;
  }
  /* WCAG 2.5.5 — 24×24 minimum touch target; we go to 36×36 to match
     the previously-shipped ui/static/style.css override that the merge
     replaced. */
  .msg-action-btn {
    min-width: 36px;
    min-height: 36px;
    padding: 4px 8px;
    font-size: 12px;
  }
}
