/*
 * Chat module styles — transcript + composer for the desktop center pane.
 *
 * Ported from media/chat.css, reskinned entirely onto the brand tokens in
 * styles/tokens.css (--color-*, --space-*, --radius-*, --font-*, --shadow-*).
 * No --vscode-* anywhere: this is a standalone Tauri webview, not the VSCode
 * host. Dark-first; light mode follows from the token swap on <html>.
 */

/* ============================================================ */
/* Layout: transcript fills the pane and scrolls *behind* the    */
/* floating composer; the latest user prompt bubble pins itself   */
/* at the top edge via position: sticky (see .msg.user).          */
/* ============================================================ */

.chat {
  /* The composer floats over the transcript, so the chat region is a
     positioning context rather than a flex column. */
  position: relative;
  min-height: 0;
  height: 100%;
  background: var(--color-page-bg);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  overflow: hidden;

  /* Composer geometry, shared by the transcript's bottom padding and the
     floating panel so the last message can scroll fully clear of it. */
  --composer-gap: var(--space-4);
  --composer-clearance: 9.5rem;
  /* Soft opacity fade at the bottom scroll edge (around the composer). The top
     edge has no fade so the sticky user prompt bubble stays crisp. */
  --fade-bottom: 5rem;
}

.chat-transcript {
  position: absolute;
  inset: 0;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-6) var(--space-6) var(--composer-clearance);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  scroll-behavior: smooth;

  /* Fade content into transparency at the bottom edge (around the floating
     composer). Content beside the narrower composer stays readable; content
     directly behind it is covered by the opaque panel. No top fade: it would
     also fade the sticky user-prompt bubble, which must stay crisp. */
  -webkit-mask-image: linear-gradient(
    to bottom,
    #000 calc(100% - var(--fade-bottom)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    #000 calc(100% - var(--fade-bottom)),
    transparent 100%
  );
}

/* Designed empty state shown when the transcript holds no messages: a brand
   mark, a one-line welcome, and a few clickable starter chips that pre-fill the
   composer. Rendered by chat/index.js and removed once the first message lands. */
.chat-empty {
  margin: auto;
  max-width: var(--measure-narrow);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
  padding: var(--space-6) var(--space-4);
}

.chat-empty__mark {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-circle);
  background: var(--color-brand);
  box-shadow: var(--glow-violet-sm);
}

.chat-empty__intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}

.chat-empty__eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-faint);
}

.chat-empty__welcome {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-text-primary);
  line-height: var(--leading-snug);
}

.chat-empty__subhead {
  margin: 0;
  max-width: var(--measure-narrow);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  line-height: var(--leading-normal);
}

.chat-empty__starters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}

.chat-empty__starter {
  appearance: none;
  border: var(--border-default);
  background: var(--color-surface-raised);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}

.chat-empty__starter:hover {
  border-color: var(--color-brand);
  color: var(--color-text-primary);
  background: var(--color-surface);
}

.chat-empty__starter:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--color-accent);
  outline-offset: 2px;
}

/* ============================================================ */
/* Messages                                                      */
/* ============================================================ */

/* Each real user prompt opens a `.turn` wrapper that holds the prompt plus the
   assistant reply, tool calls, and inline notices that follow it. It mirrors the
   transcript's own column layout and gap so spacing is unchanged, but it also
   bounds the sticky pin below: the latest prompt stays pinned only while its own
   turn is on screen, then releases as the turn scrolls past. */
.turn {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  /* Containing block for the absolutely-positioned `.turn-sentinel` at its top. */
  position: relative;
}

/* A 1px marker pinned to each turn's top edge. The IntersectionObserver watches
   it to tell a stuck (pinned) prompt from one still in flow. Absolutely
   positioned so it claims no flex slot and adds no gap. */
.turn-sentinel {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}

.msg {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.msg.user {
  align-items: stretch;
}

/* The non-user messages (assistant, notices, errors) sit slightly inset from
   the user prompt card so the prompt reads as the widest element of its turn. */
.msg:not(.user) {
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

/* The user's prompt bubble pins to the top of the transcript and the rest of
   the conversation scrolls up behind it (Cursor-style). The pin is bounded by
   the enclosing `.turn` box: a sticky element can only stay pinned while its
   containing block is in view, so each prompt releases the top edge once its
   turn scrolls past, and the next turn's prompt takes over — only the newest
   stays pinned. The z-index lifts the opaque bubble above the scrolling content
   beneath it. Queued previews and synthetic retry markers aren't the active
   prompt, so they don't pin. */
.msg.user:not(.queued):not(.synthetic) {
  position: sticky;
  top: 0;
  z-index: 3;
}

.msg.user .body {
  white-space: pre-wrap;
  word-break: break-word;
  /* Subtle gray surface (matching Claude Code / the extension) rather than brand
     violet. All four corners use the uniform large radius — a full-width card
     reads wrong with a speech-bubble tail. */
  background: var(--color-surface-raised);
  color: var(--color-text-primary);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  font-weight: 500;
  /* Anchor for the fade overlay that caps the prompt when it's pinned. */
  position: relative;
}

/* A pinned (stuck) prompt is capped to ~6 lines and fades cleanly into the card
   background; an in-flow prompt shows in full (no cap in the base rule). The
   fade is a gradient overlay into the card color, not a see-through mask. */
.msg.user.stuck .body {
  max-height: 9.5rem;
  overflow: hidden;
}
.msg.user.stuck.overflowing .body::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2rem;
  background: linear-gradient(to top, var(--color-surface-raised), transparent);
  pointer-events: none;
}

/* When the prompt is pinned, fade the transcript content immediately below it
   into the app background so messages don't appear to butt up hard against the
   pinned card as they scroll past underneath. The user bubble is the positioned
   containing block (position: sticky) and has no overflow:hidden, so this
   overflows past its bottom edge. */
.msg.user.stuck::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 2rem;
  background: linear-gradient(to bottom, var(--color-page-bg), transparent);
  pointer-events: none;
}

/* Completely hide anything that scrolls ABOVE the pinned prompt: an opaque
   page-background panel extends up from the card to the transcript's clipped
   top edge, so earlier content vanishes behind empty space instead of peeking
   above the bubble. Anchored to the card (bottom: 100%) so it tracks the pin
   regardless of the exact sticky offset; the transcript's overflow clips the
   excess height. */
.msg.user.stuck::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 100vh;
  background: var(--color-page-bg);
  pointer-events: none;
}

.msg.user.queued .body {
  opacity: 0.65;
}

.msg.user.reverted .body {
  opacity: 0.5;
  text-decoration: line-through;
}

.msg.user .synthetic-badge {
  align-self: flex-end;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--color-text-faint);
}

.msg.assistant {
  align-items: stretch;
}

.msg .text-segment {
  line-height: var(--leading-normal);
  color: var(--color-text-primary);
}

/* Model prose reads one step larger than the surrounding chrome so the answer
   itself is the most legible thing in the transcript. Scoped to assistant text
   segments (and the markdown body they carry); finalized streamed deltas render
   into this same node so they inherit it. Tool metadata, code, and controls
   reset explicitly below so only prose grows. */
.msg.assistant .text-segment {
  font-size: var(--text-lg);
}

.msg.assistant .text-segment + .text-segment {
  margin-top: var(--space-2);
}

.msg.assistant.cancelled .text-segment {
  opacity: 0.7;
}

.msg.error .body {
  white-space: pre-wrap;
  word-break: break-word;
  align-self: flex-start;
  max-width: min(85%, var(--measure));
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--status-error) 12%, var(--color-surface));
  border: 1px solid color-mix(in srgb, var(--status-error) 45%, transparent);
  color: var(--color-text-primary);
}

.msg.notice .notice-body {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  font-style: italic;
  text-align: center;
}

/* Notice rendered inline within an assistant turn, interleaved in segment
   order. Graytext like the standalone notice row, left-aligned to flow with
   the surrounding text/tool segments. */
.notice-segment {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  font-style: italic;
  line-height: var(--leading-normal);
}

/* End-of-turn "what I checked" recap card: a calm, never-collapsed summary of
   what the assistant could and couldn't confirm. Status is carried by the glyph
   shape + tone (never color alone), and reads honest rather than alarming — the
   couldn't-confirm tone is the warm caution amber, never the danger red. */
.check-recap {
  margin: var(--space-2) 0 2px;
  padding: var(--space-3);
  border: var(--border-default);
  border-radius: var(--radius-md);
  background: var(--color-surface-raised);
}
.check-recap-head {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-2);
}
.check-recap-item {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  line-height: var(--leading-normal);
}
.check-recap-item + .check-recap-item {
  margin-top: 6px;
}
.check-recap-glyph {
  flex: none;
  font-weight: 600;
}
.check-recap-item[data-status="confirmed"] .check-recap-glyph {
  color: var(--status-live-text);
}
.check-recap-item[data-status="unconfirmed"] .check-recap-glyph {
  color: var(--status-checking-text);
}
.check-recap-text {
  color: var(--color-text-primary);
  white-space: pre-wrap;
  word-break: break-word;
}
/* Spoken-only status word so the recap is legible without the glyph's color. */
.check-recap-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================ */
/* Markdown body                                                 */
/* ============================================================ */

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

.markdown-body p {
  margin: 0 0 var(--space-3);
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--leading-snug);
  margin: var(--space-4) 0 var(--space-2);
}
.markdown-body h1 { font-size: var(--text-xl); }
.markdown-body h2 { font-size: var(--text-lg); }
.markdown-body h3 { font-size: var(--text-base); }
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 { font-size: var(--text-base); }

.markdown-body ul,
.markdown-body ol {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-6);
}
.markdown-body li {
  margin: var(--space-1) 0;
}
.markdown-body li > p {
  margin: 0;
}

.markdown-body blockquote {
  margin: 0 0 var(--space-3);
  padding: var(--space-1) var(--space-4);
  border-left: 3px solid var(--color-brand);
  color: var(--color-text-secondary);
}

.markdown-body hr {
  border: none;
  border-top: var(--border-default);
  margin: var(--space-4) 0;
}

.markdown-body a {
  color: var(--color-link);
  text-decoration: none;
}
.markdown-body a:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
}

/* Clickable file-path references (e.g. `src/foo.ts:42`). Mono like the path it
 * names, brand-violet like other links, with a dotted underline to read as a
 * jump target rather than a web link. */
.file-link {
  color: var(--color-link);
  font-family: var(--font-mono);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: pointer;
}
.file-link:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
}

.markdown-body code {
  font-family: var(--font-mono);
  /* Absolute, not em-relative: inline code stays put when the assistant prose
     it sits in is bumped up a step. */
  font-size: var(--text-sm);
  /* Soft, theme-adaptive chip: a faint tint of the text color over the page
     so inline code reads as gently raised (light-gray on the dark-first
     theme) instead of a hard dark box. */
  background: color-mix(in srgb, var(--color-text-primary) 12%, transparent);
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
}

/* File-path links never wear the code chip — they stay plain links even
   when the model wrapped the path in backticks. */
.markdown-body code.file-code {
  background: transparent;
  padding: 0;
}

.markdown-body pre {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  overflow-x: auto;
  background: var(--color-surface);
  border: var(--border-default);
  border-radius: var(--radius-md);
}

.markdown-body pre > code {
  background: none;
  padding: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.markdown-body .code-block {
  position: relative;
  margin: 0 0 var(--space-3);
}

.markdown-body .code-block .copy-btn {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  opacity: 0;
  transition: opacity 0.12s ease;
  background: var(--color-surface-raised);
  color: var(--color-text-secondary);
  border: var(--border-default);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
}
.markdown-body .code-block:hover .copy-btn,
.markdown-body .code-block .copy-btn:focus-visible {
  opacity: 1;
}

/* Faint mono language caption in the code-block header, opposite the copy
   button. Rendered only when the fence declared a language. */
.markdown-body .code-block .code-lang {
  position: absolute;
  top: var(--space-2);
  left: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--color-text-faint);
  user-select: none;
  pointer-events: none;
}

.markdown-body table {
  border-collapse: collapse;
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
}
.markdown-body th,
.markdown-body td {
  border: var(--border-default);
  padding: var(--space-1) var(--space-3);
  text-align: left;
}
.markdown-body th {
  background: var(--color-surface-raised);
  font-weight: 600;
}

/* ============================================================ */
/* Revert / retry / error-link bars                              */
/* ============================================================ */

.revert-bar,
.retry-bar,
.error-link-bar,
.copy-bar {
  display: flex;
  justify-content: flex-end;
}

.revert-bar {
  opacity: 0;
  transition: opacity 0.12s ease;
}
.msg.user:hover .revert-bar,
.msg.user:focus-within .revert-bar,
.revert-bar:focus-within {
  opacity: 1;
}

/* Hover-revealed per-message copy control, mirroring the revert bar. */
.copy-bar {
  opacity: 0;
  transition: opacity 0.12s ease;
}
.msg.assistant:hover .copy-bar,
.msg.assistant:focus-within .copy-bar,
.copy-bar:focus-within {
  opacity: 1;
}

.revert-btn,
.retry-btn,
.error-link-btn,
.copy-msg-btn {
  background: transparent;
  color: var(--color-text-secondary);
  border: var(--border-default);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.revert-btn:hover:not(:disabled),
.retry-btn:hover:not(:disabled),
.error-link-btn:hover,
.copy-msg-btn:hover {
  background: var(--color-surface-raised);
  color: var(--color-text-primary);
}
.revert-btn:disabled,
.retry-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ============================================================ */
/* Shared status motion                                          */
/* ============================================================ */
/* Soft pulse for live/working status indicators, shared across the chat +
   approvals modules (they bundle into one document). Gated by
   prefers-reduced-motion at each use site so a working dot still reads (by
   shape + color) when motion is off. */
@keyframes pl-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

/* ============================================================ */
/* "Working…" indicator                                          */
/* ============================================================ */
/* A quiet sign-of-life row shown between a turn starting and its first streamed
   frame, so a slow turn never reads as a dead, empty screen. It sits under the
   just-sent prompt and is removed the moment any content (or a tool row) renders
   or the turn ends. De-emphasized to match the graytext/tool aesthetic. */
.thinking {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  color: var(--color-text-secondary);
}
.thinking-dots {
  display: inline-flex;
  gap: 4px;
}
.thinking-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  /* Staggered so the three dots read as a travelling pulse, not a blink. */
  animation: pl-pulse 1.2s ease-in-out infinite;
}
.thinking-dot:nth-child(2) {
  animation-delay: 0.2s;
}
.thinking-dot:nth-child(3) {
  animation-delay: 0.4s;
}
@media (prefers-reduced-motion: reduce) {
  .thinking-dot {
    animation: none;
  }
}
.thinking-label {
  font-size: var(--text-sm);
}

/* ============================================================ */
/* Tool segments (collapsible <details>)                         */
/* ============================================================ */

/* Tool rows read as compact, quiet cards — a hairline border, a faintly raised
   fill, and clipped corners. A left chevron opens the row; the headline is
   muted (never the loud primary ink of a message); a small badge and a status
   dot close out the summary on the right. The whole row must read *quieter*
   than the assistant/user messages around it. */
.tool {
  font-size: var(--text-sm);
  border: var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
  overflow: hidden;
}

/* Repeated tool rows breathe a little more than the default segment gap so a
   run of calls doesn't read as one dense block. Only between sibling tool rows
   (adjacency on the shared margin) — a tool's own expanded detail/diff/badges
   stay tucked under their owning row, untouched. */
.tool + .tool {
  margin-top: var(--space-2);
}

.tool summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px var(--space-2);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  list-style: none;
  font-size: var(--text-sm);
}
.tool summary:hover {
  background: color-mix(in srgb, var(--color-text-primary) 5%, transparent);
}
/* Inset focus ring: the card's `overflow: hidden` would clip an outset outline,
   so draw it inside the summary box. */
.tool summary:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--color-accent);
  outline-offset: -2px;
}
.tool summary::-webkit-details-marker {
  display: none;
}

/* Disclosure chevron: points right when collapsed, rotates down on open. */
.tool .chevron {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  transition: transform 0.15s ease;
}
.tool[open] .chevron {
  transform: rotate(90deg);
}

.tool .name {
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Read/Edit headline parts: a muted verb, the file path (a link for reads),
   and a muted inline detail (the line range). */
.tool .tool-verb {
  font-weight: 400;
  color: var(--color-text-secondary);
}

.tool .tool-file {
  color: var(--color-link);
}

.tool .tool-detail {
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* Edit's one-line change summary ("Added N lines"), dropped to its own row
   under the headline. Empty for every other tool, so it collapses away. */
.tool .subtitle {
  flex-basis: 100%;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
}

.tool .subtitle:empty {
  display: none;
}

.tool .args {
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

.tool .badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  flex: 0 1 auto;
  min-width: 0;
}

.tool .badge {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--text-xs);
  padding: 1px var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
  border: var(--border-default);
  color: var(--color-text-secondary);
  white-space: nowrap;
}

/* Status marker at the right end of the summary. Per DESIGN.md status is never
   carried by color alone, so each state also has a distinct SHAPE that survives
   prefers-reduced-motion: running = a hollow info ring (pulsing), done = a
   filled live/ok circle, error = a filled square in the muted error tone
   (reads as recoverable, never an alarm). Mapping matches the VSCode shell
   (media/chat.css) so the two surfaces stay visually consistent. */
.tool .status {
  flex: none;
  width: 7px;
  height: 7px;
  box-sizing: border-box;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid var(--status-building-dot);
  animation: pl-pulse 1.5s ease-in-out infinite;
}
.tool.done .status {
  border: none;
  background: var(--status-live-dot);
  animation: none;
}
.tool.failed .status {
  border: none;
  border-radius: 2px;
  background: var(--status-error-dot);
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .tool .status {
    animation: none;
  }
  .tool .chevron {
    transition: none;
  }
}

/* Expanded body: flush to the card edges, divided from the summary by a
   hairline rule. Empty/short bodies (common in the web builder, which only
   carries bounded projections) still read as an intentional card footer. */
.tool .detail {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-top: var(--border-default);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--color-text-secondary);
  max-height: 24rem;
  overflow: auto;
}
.tool .detail.error {
  color: var(--status-error);
}
/* A diff body manages its own inset (the diff-header + gutters), so drop the
   text padding — but keep the hairline divider under the summary that every
   other expanded state has. */
.tool .detail.has-diff {
  padding: 0;
  white-space: normal;
}

/* ============================================================ */
/* Inline diff                                                   */
/* ============================================================ */

.diff-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--border-default);
  background: var(--color-surface-raised);
}
.diff-path {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.open-file-btn,
.view-full-btn {
  background: transparent;
  color: var(--color-accent);
  border: var(--border-default);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  white-space: nowrap;
}
.open-file-btn:hover,
.view-full-btn:hover {
  background: var(--color-surface);
  color: var(--color-brand-hover);
}

.diff-view {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.5;
  padding: var(--space-1) 0;
}

.diff-hunk-header {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  background: var(--color-surface-raised);
  padding: var(--space-1) var(--space-3);
  border-top: var(--border-default);
  border-bottom: var(--border-default);
  user-select: none;
}
.diff-hunk-header:first-child {
  border-top: none;
}
.diff-hunk-separator {
  height: var(--space-2);
  border-top: 1px dashed var(--color-border);
  border-bottom: 1px dashed var(--color-border);
  background: var(--color-surface-raised);
}

.diff-line {
  display: flex;
  gap: var(--space-2);
  padding: 0 var(--space-3);
}
.diff-line .diff-gutter {
  flex: 0 0 auto;
  color: var(--color-text-faint);
  user-select: none;
  white-space: pre;
}
.diff-line .diff-text {
  flex: 1 1 auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.diff-line.added {
  background: color-mix(in srgb, var(--status-success) 18%, transparent);
}
.diff-line.removed {
  background: color-mix(in srgb, var(--status-error) 18%, transparent);
}
.diff-empty {
  padding: var(--space-2) var(--space-3);
  color: var(--color-text-faint);
  font-style: italic;
}

/* ============================================================ */
/* Truncation badge                                              */
/* ============================================================ */

.truncation-badge {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 var(--space-3) var(--space-1) var(--space-5);
  padding: var(--space-1) 0;
  color: var(--color-text-faint);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}
.truncation-label {
  flex: 1 1 auto;
}

/* ============================================================ */
/* Attachments footer                                            */
/* ============================================================ */

.attachments-footer {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  align-items: flex-end;
}
.attachment-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
}
.attachment-row.attachment-skipped .attachment-label,
.attachment-row.attachment-error .attachment-label {
  color: var(--color-text-faint);
}
.attachment-open {
  background: transparent;
  color: var(--color-accent);
  border: var(--border-default);
  border-radius: var(--radius-pill);
  padding: 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
}
.attachment-open:hover {
  background: var(--color-surface-raised);
}

/* ============================================================ */
/* "↓ N new" scroll pill                                         */
/* ============================================================ */

.scroll-pill {
  position: absolute;
  left: 50%;
  /* Sit just above the floating composer panel. */
  bottom: calc(var(--composer-clearance) + var(--space-2));
  transform: translateX(-50%);
  z-index: 5;
  background: var(--color-brand);
  color: var(--color-on-brand);
  border: none;
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-md);
}
.scroll-pill[hidden] {
  display: none;
}
.scroll-pill:hover {
  background: var(--color-brand-hover);
}

/* ============================================================ */
/* Composer                                                      */
/*                                                               */
/* An opaque floating panel anchored bottom-center, narrower     */
/* than the transcript so content stays visible to its sides.    */
/* Two rows: a textarea on top, an action row beneath (left      */
/* launcher cluster + context ring, right circular send / Stop). */
/* ============================================================ */

.chat-composer {
  position: absolute;
  left: 50%;
  bottom: var(--composer-gap);
  transform: translateX(-50%);
  z-index: 4;
  width: min(46rem, calc(100% - 2 * var(--space-6)));
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: var(--border-default);
  border-radius: var(--radius-lg);
  /* Opaque so transcript content scrolling behind it is covered. The whole
     panel reads as one lighter-gray raised card; the textarea stays transparent
     to blend into it. */
  background: var(--color-surface-raised);
  box-shadow: var(--shadow-lg);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.chat-composer:focus-within {
  border-color: var(--color-brand);
  box-shadow: var(--shadow-lg), 0 0 0 3px color-mix(in srgb, var(--color-brand) 22%, transparent);
}

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

.composer-input {
  flex: 1 1 auto;
  min-height: 1.5rem;
  max-height: 200px;
  resize: none;
  border: none;
  outline: none;
  background: transparent;
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  padding: var(--space-1) var(--space-2);
}
.composer-input::placeholder {
  color: var(--color-text-faint);
}
.composer-input:disabled {
  opacity: 0.6;
}

/* ── Attached-image chips ──
   Thumbnail previews above the textarea, each with an inline remove control.
   Brand violet, 14px radii per DESIGN.md. The strip hides when nothing is
   staged. Mirrors the VSCode shell's chip styling. */
.attachment-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-1) 0;
}
.attachment-chips[hidden] {
  display: none;
}
.attachment-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 160px;
  padding: 3px 6px 3px 4px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-accent) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-accent) 50%, transparent);
  color: var(--color-text-primary);
}
.attachment-chip-thumb {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
}
.attachment-chip-name {
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--text-sm);
}
.attachment-chip-remove {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  line-height: 1;
  font-size: 14px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
}
.attachment-chip-remove:hover {
  background: var(--color-accent);
  color: var(--color-on-brand);
}

/* Uploaded text-file chips: a separate strip below the image chips, each marked
   with a document glyph. Mirrors the VSCode shell's #uploadChips styling. */
.upload-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-1) 0;
}
.upload-chips[hidden] {
  display: none;
}
.upload-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 200px;
  padding: 3px 6px 3px 8px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-accent) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-accent) 50%, transparent);
  color: var(--color-text-primary);
}
.upload-chip-icon {
  flex: 0 0 auto;
  display: inline-flex;
  color: var(--color-text-secondary);
}
.upload-chip-name {
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--text-sm);
}
.upload-chip-remove {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  line-height: 1;
  font-size: 14px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
}
.upload-chip-remove:hover {
  background: var(--color-accent);
  color: var(--color-on-brand);
}

/* Drag-over affordance: the composer glows in the brand ring while an image is
   being dragged over it. */
.chat-composer.drop-active {
  border-color: var(--color-brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 20%, transparent);
}

/* ── Action row ── */

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

.composer-launchers {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.composer-launch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: var(--border-default);
  border-radius: var(--radius-circle);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: var(--font-mono);
  font-size: var(--text-base);
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.composer-launch:hover:not(:disabled) {
  background: var(--color-surface-raised);
  color: var(--color-text-primary);
  border-color: var(--color-brand);
}
.composer-launch:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Plus menu: a small popover anchored to the plus button, opening upward like
   the Plan/Agent menu since the composer is pinned to the bottom of the view.
   Two rows — upload a file, add @-mention context. */
.composer-plus-wrapper {
  position: relative;
  display: inline-flex;
}
.composer-plus-menu {
  position: absolute;
  bottom: calc(100% + var(--space-2));
  left: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 12rem;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border: var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.composer-plus-menu[hidden] {
  display: none;
}
.composer-plus-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  background: transparent;
  color: var(--color-text-primary);
  border: none;
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1.2;
  cursor: pointer;
}
.composer-plus-item:hover,
.composer-plus-item:focus-visible {
  background: var(--color-surface-sunken, var(--color-surface));
  outline: none;
}
.composer-plus-item-icon {
  flex: 0 0 auto;
  color: var(--color-text-secondary);
}
.composer-plus-item-label {
  flex: 1 1 auto;
}

/* Plan / Agent master switch. Sits in the right action cluster; its menu opens
   *upward* since the composer is anchored to the bottom of the view. Quiet
   chrome: no colored mode dot and no accent fill — current state is conveyed by
   the button label and aria-current on the chosen menu row, so neither mode
   shouts visually. */
.plan-mode-wrapper {
  position: relative;
  display: inline-flex;
}
.plan-mode-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 1.75rem;
  padding: 0 var(--space-3);
  border: var(--border-default);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.plan-mode-button:hover {
  background: var(--color-surface-raised);
  color: var(--color-text-primary);
  border-color: var(--color-border-strong, var(--color-border));
}
.plan-mode-menu {
  position: absolute;
  bottom: calc(100% + var(--space-2));
  left: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 17rem;
  max-width: 22rem;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border: var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.plan-mode-menu[hidden] {
  display: none;
}
.plan-mode-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: 100%;
  text-align: left;
  background: transparent;
  color: var(--color-text-primary);
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}
/* Hover gives transient feedback; the selected row is signalled by aria-current
   (and assistive tech) only — no persistent accent fill, keeping the menu quiet. */
.plan-mode-item:hover,
.plan-mode-item:focus-visible {
  background: var(--color-surface);
  outline: none;
}
.plan-mode-item-row {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
}
.plan-mode-item-desc {
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  white-space: normal;
  line-height: var(--leading-normal);
}

.composer-actions {
  flex: 0 0 auto;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Circular accent send button carrying the up-arrow glyph. */
.composer-send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-circle);
  background: var(--color-brand);
  color: var(--color-on-brand);
  cursor: pointer;
  transition: background 0.12s ease, opacity 0.12s ease;
}
.composer-send:hover:not(:disabled) {
  background: var(--color-brand-hover);
}
.composer-send:disabled {
  opacity: 0.45;
  cursor: default;
}
/* Glyph runs larger than its 22px viewBox attrs while the button keeps its
   fixed 2.5rem circle, so the arrow reads boldly without resizing the control. */
.composer-send-icon {
  display: block;
  width: 1.5rem;
  height: 1.5rem;
}

.composer-send[hidden] {
  display: none;
}

/* While working, the send button swaps its arrow glyph for a stop square in
   place rather than handing off to a separate button. */
.composer-send .composer-stop-glyph {
  display: none;
}
.composer-send.is-busy .composer-send-glyph {
  display: none;
}
.composer-send.is-busy .composer-stop-glyph {
  display: block;
}

.composer-cancel-pending {
  font-size: var(--text-xs);
  font-style: italic;
  color: var(--color-text-faint);
}
.composer-cancel-pending[hidden] {
  display: none;
}

/* ============================================================ */
/* Context-usage meter (composer toolbar)                        */
/*                                                               */
/* Circular ring that fills as the running turn's prompt grows   */
/* toward the model's context window. Hidden until the host says */
/* it's visible. The fill hue steps violet -> amber -> red via   */
/* the data-tier on the wrapper; the reveal band reads brand     */
/* violet (--color-accent), then the warning/error status tokens.*/
/* ============================================================ */

.context-meter {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: var(--border-default);
  border-radius: var(--radius-pill);
  color: var(--color-text-secondary);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  user-select: none;
  cursor: default;
}
.context-meter[hidden] {
  display: none;
}

.context-meter-ring {
  flex: 0 0 auto;
  display: block;
}

.context-meter-track {
  stroke: var(--color-border);
}

.context-meter-fill {
  /* Default tier (violet). data-tier on the wrapper steps the hue up. */
  stroke: var(--color-accent);
  transition: stroke-dashoffset 120ms ease, stroke 120ms ease;
}

.context-meter[data-tier="amber"] .context-meter-fill {
  stroke: var(--status-warning);
}
.context-meter[data-tier="red"] .context-meter-fill {
  stroke: var(--status-error);
}

.context-meter[data-tier="amber"] .context-meter-label {
  color: var(--status-warning);
}
.context-meter[data-tier="red"] .context-meter-label {
  color: var(--status-error);
}

.context-meter-label {
  line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
  .context-meter-fill {
    transition: none;
  }
}

/* ============================================================ */
/* @-file mention menu                                           */
/* ============================================================ */

.anchor-menu {
  position: absolute;
  left: var(--space-4);
  right: var(--space-4);
  bottom: calc(100% - var(--space-2));
  max-height: 16rem;
  overflow-y: auto;
  z-index: 10;
  background: var(--color-surface);
  border: var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-1);
}
.anchor-menu[hidden] {
  display: none;
}

/* The slash and mention menus are appended inside the composer, which is the
   positioned ancestor, so left/right above already pin them to the composer's
   box rather than the viewport. This makes the composer-bound width explicit so
   the slash completion never stretches edge-to-edge. */
.slash-menu,
.mention-menu {
  left: var(--space-4);
  right: var(--space-4);
  width: auto;
}

.anchor-item {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.anchor-item.active,
.anchor-item:hover {
  background: var(--color-surface-raised);
}
.anchor-label {
  font-weight: 600;
  color: var(--color-text-primary);
  font-size: var(--text-sm);
}
.anchor-hint {
  color: var(--color-text-faint);
  font-size: var(--text-xs);
  font-family: var(--font-mono);
}
.anchor-status {
  padding: var(--space-2) var(--space-3);
  color: var(--color-text-faint);
  font-size: var(--text-sm);
  font-style: italic;
}

/* ============================================================ */
/* Keyboard-shortcut cheat-sheet modal (Cmd/Ctrl+/). A backdrop  */
/* dims the pane; a centered raised panel lists the shortcuts.   */
/* The desktop app's first full-overlay dialog — scoped to the   */
/* chat region, above the composer and its menus.                */
/* ============================================================ */

.shortcuts-overlay {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}

.shortcuts-overlay[hidden] {
  display: none;
}

.shortcuts-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 10, 10, 0.55);
  /* A faint blur lifts the panel off the transcript without hiding context. */
  backdrop-filter: blur(2px);
}

.shortcuts-panel {
  position: relative;
  width: min(28rem, 100%);
  max-height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-5) var(--space-4);
  background: var(--color-surface-raised);
  border: var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  color: var(--color-text-primary);
}

.shortcuts-panel:focus {
  outline: none;
}

.shortcuts-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.shortcuts-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--color-text-primary);
}

.shortcuts-close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  appearance: none;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: background 100ms ease, color 100ms ease;
}

.shortcuts-close:hover {
  background: var(--color-page-bg);
  color: var(--color-text-primary);
}

.shortcuts-close:focus-visible {
  outline: var(--focus-ring-width, 2px) solid var(--color-accent);
  outline-offset: 1px;
}

.shortcuts-list {
  margin: 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  align-items: center;
  gap: var(--space-3) var(--space-4);
}

.shortcuts-key-cell {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  justify-self: start;
}

.shortcuts-chip {
  display: inline-flex;
  align-items: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding: 0 0.5rem;
  border: var(--border-default);
  border-radius: var(--radius-pill);
  background: var(--color-page-bg);
  color: var(--color-text-primary);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}

.shortcuts-sep {
  color: var(--color-text-faint);
  font-size: var(--text-xs);
}

.shortcuts-desc-cell {
  margin: 0;
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
}

.shortcuts-footer {
  color: var(--color-text-faint);
  font-size: var(--text-xs);
  text-align: right;
}
