*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }

/* Nerd Font for Claude's ASCII/box-drawing logo and other glyphs.
   Files served by the container at /fonts/. Without these @font-face
   rules the browser silently falls through to Menlo, which lacks the
   Nerd Font private-use codepoints — Claude's icon then renders as tofu. */
@font-face {
  font-family: 'JetBrains Mono Nerd Font';
  src: url('/fonts/JetBrainsMonoNerdFont-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono Nerd Font';
  src: url('/fonts/JetBrainsMonoNerdFont-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #0d0d0d;
  --surface: #1a1a1a;
  --surface-2: #242424;
  --border: #2a2a2a;
  --text: #e8e8e8;
  --muted: #888;
  --accent: #4caf82;
  --danger: #ff6b6b;
  --sidebar-w: 280px;
  /* Shared height for all pane headers (sidebar, file tree, file viewer,
     chat). Keeps the four banded rows visually aligned when all four are
     open side-by-side. Bumped on mobile to clear the floating chrome
     buttons (☰ / 📁 / 💬). */
  --pane-header-h: 52px;
  /* Shared size for all chrome buttons (sidebar +/‹, back-to-home ☰,
     files 📁, chat 💬, chat-close ×) so the chrome reads as one set. */
  --chrome-btn-size: 32px;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
}

/* Project-wide scrollbar style. One source of truth so every scrollable
   surface (chat, transcript, xterm viewport, file tree, code viewer,
   plan/test/arch artifact body, autocomplete dropdown, modal scrollers
   — anything that grows past its container) renders the same thin 8px
   semi-transparent thumb against a transparent track. Per-element
   overrides are no longer needed; keep them harmless if present.
   Firefox / Edge use `scrollbar-*` properties; Chromium / WebKit reads
   the `::-webkit-scrollbar*` pseudos. Both branches present together. */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 145, .35) transparent;
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(120, 130, 145, .35);
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover { background: rgba(150, 160, 175, .55); }

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

input, textarea { user-select: text; -webkit-user-select: text; }

/* Re-enable text selection inside readable content panes (chat history,
   conversation transcript, inline Claude cards). Four things must align:
   user-select:text, -webkit-user-select:text (Safari/iOS), -webkit-touch-
   callout:default (iOS long-press Copy menu), and touch-action:auto
   (override body's 'manipulation' which suppresses long-press selection).
   !important is required because the body-level user-select:none
   cascades into descendants on some browsers (Chrome desktop in particular
   inherits the value rather than only applying it to the body element),
   and our containers need to forcefully reclaim selectability. */
#chat-messages, #chat-messages *,
#chat-empty,
.claude-card .cc-q, .claude-card .cc-a,
.claude-card .cc-a * {
  user-select: text !important;
  -webkit-user-select: text !important;
  -webkit-touch-callout: default !important;
  touch-action: auto;
  cursor: text;
}

button { font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }

/* ── layout ───────────────────────────────────────────────────── */

#app {
  display: flex;
  height: 100dvh;
}

#sidebar {
  width: var(--sidebar-w);
  min-width: var(--sidebar-w);
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--border);
  /* Match the body background (--bg) so the sidebar reads as part of
     the page chrome rather than a lifted surface — the surface tier
     was rendering as a visibly lighter band on some displays. */
  background: var(--bg);
}

#sidebar header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px calc(16px + var(--safe-right)) 8px calc(16px + var(--safe-left));
  padding-top: calc(8px + var(--safe-top));
  min-height: calc(var(--pane-header-h) + var(--safe-top));
  box-sizing: border-box;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  position: sticky;
  top: 0;
  backdrop-filter: blur(20px);
}

#sidebar h1 { font-size: 1rem; font-weight: 600; color: var(--accent); letter-spacing: .05em; flex: 1; }

#btn-spawn, #btn-collapse {
  background: var(--border);
  color: var(--text);
  border: none;
  border-radius: 8px;
  width: var(--chrome-btn-size); height: var(--chrome-btn-size);
  font-size: .95rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform .08s, background .15s;
}
#btn-spawn { background: var(--accent); color: #000; font-size: 1.2rem; font-weight: 600; }
#btn-spawn:active, #btn-collapse:active { transform: scale(.92); }

/* Lucide-style inline SVG icons in the chrome cluster (btn-files,
   btn-plan, btn-arch, btn-test, btn-chat). Centered inside the 32px
   button, drawn at 18px with currentColor stroke so the existing
   button color/hover/active rules carry through. The buttons used to
   be emoji-based — emoji ignored color/sharpness and varied across
   OSes; SVG gives one consistent line-weight + crisp render. */
.chrome-icon-svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  pointer-events: none;
  display: block;
}
/* Active state: tint the icon to the same accent the button bg uses
   for `.active`, so the cluster reads as a coherent state group. */
#btn-files.active .chrome-icon-svg,
#btn-chat.active .chrome-icon-svg,
.artifact-toggle.active .chrome-icon-svg {
  color: #aac8ff;
}

/* "Back to home / sessions" toggle. Matches the look-and-feel of #btn-files
   and #btn-chat (rounded square, blurred dark glass, subtle white border)
   so the three top-chrome buttons feel like one cluster. Sized to fit
   within the 52px shared pane header with breathing room above + below. */
#btn-expand {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: calc(env(safe-area-inset-left, 0px) + 10px);
  z-index: 50;
  width: var(--chrome-btn-size); height: var(--chrome-btn-size);
  padding: 0;
  border-radius: 8px;
  background: rgba(40, 40, 40, .85);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, .08);
  font-size: .95rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: transform .08s;
}
#btn-expand:active { transform: scale(.92); }

#session-list {
  list-style: none;
  overflow-y: auto;
  flex: 1;
  padding: 8px 0;
}

.session-item {
  padding: 14px 16px;
  cursor: pointer;
  border-left: 3px solid transparent;
  transition: background .1s;
  -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) { .session-item:hover { background: rgba(255,255,255,.03); } }
.session-item:active { background: rgba(255,255,255,.06); }
.session-item.active { border-left-color: var(--accent); background: rgba(76,175,130,.08); }

.session-name { display: block; font-size: .9rem; font-weight: 500; }
.session-cwd  { display: block; font-size: .75rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Recent context summary (what's being worked on now). */
.session-summary {
  font-size: .75rem;
  color: var(--muted);
  line-height: 1.2;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  word-break: break-word;
  margin-top: 2px;
}

/* Shared session badge & card styling */
.shared-badge {
  display: inline-block;
  background: rgba(80, 140, 200, .25);
  color: #8cc5ff;
  font-size: .6rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 4px;
  margin-left: 6px;
  vertical-align: middle;
  line-height: 1.4;
}
.session-item.shared {
  border-left: 3px solid rgba(80, 140, 200, .5);
}

.empty { padding: 16px; color: var(--muted); font-size: .85rem; }

/* Delete (×) button on a card. Hidden by default. Revealed on hover for
   pointer devices, and when the list has .show-delete for touch devices
   (toggled by a long-press in app.js). */
.session-item { position: relative; }

/* Status indicator dot: colored by how recent the activity is. */
.session-status {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #555;
  box-shadow: 0 0 4px rgba(0,0,0,.4);
  z-index: 1;
}
.session-status-active { background: #4cd964; box-shadow: 0 0 6px rgba(76, 217, 100, .6); }
.session-status-recent { background: #5ac8fa; box-shadow: 0 0 6px rgba(90, 200, 250, .5); }
.session-status-stale  { background: #ffcc00; box-shadow: 0 0 6px rgba(255, 204, 0, .5); }
.session-status-idle   { background: #555; }

.session-delete {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 107, 107, 0.20);
  color: var(--text);
  font-size: 1rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  opacity: 0;
  pointer-events: none;
  transform: scale(.7);
  transition: opacity .16s, transform .18s cubic-bezier(.2,.8,.2,1),
              width .18s cubic-bezier(.2,.8,.2,1), height .18s cubic-bezier(.2,.8,.2,1),
              background .15s, box-shadow .18s;
  z-index: 3;
}
.session-delete:hover { background: var(--danger); color: #fff; }
.session-delete:active { transform: scale(.92); background: var(--danger); color: #fff; }
@media (hover: hover) {
  .session-item:hover .session-delete { opacity: 1; pointer-events: auto; transform: scale(1); }
}

/* Long-press reveal: larger, bolder, with a soft red glow and a pop-in. */
.session-item.show-delete .session-delete {
  opacity: 1;
  pointer-events: auto;
  width: 40px;
  height: 40px;
  background: linear-gradient(180deg, #ff6b6b 0%, #e24a4a 100%);
  color: #fff;
  font-size: 1.45rem;
  font-weight: 300;
  box-shadow: 0 6px 16px rgba(255, 75, 75, .38),
              0 0 0 1px rgba(255, 255, 255, .10) inset,
              0 1px 0 rgba(255, 255, 255, .18) inset;
  animation: delete-pop .26s cubic-bezier(.34, 1.56, .64, 1) both;
}
.session-item.show-delete .session-delete:active {
  transform: scale(.92);
  box-shadow: 0 2px 8px rgba(255, 75, 75, .5),
              0 0 0 1px rgba(255, 255, 255, .12) inset;
}

@keyframes delete-pop {
  0%   { transform: scale(.4); opacity: 0; }
  60%  { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

/* Share (↗) button — same reveal pattern as delete, accent-colored, top-left. */
.session-share {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: rgba(76, 175, 130, 0.22);
  color: var(--text);
  font-size: 1rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  opacity: 0;
  pointer-events: none;
  transform: scale(.7);
  transition: opacity .16s, transform .18s cubic-bezier(.2,.8,.2,1),
              width .18s cubic-bezier(.2,.8,.2,1), height .18s cubic-bezier(.2,.8,.2,1),
              background .15s, box-shadow .18s;
  z-index: 3;
}
.session-share:hover { background: var(--accent); color: #000; }
.session-share:active { transform: scale(.92); background: var(--accent); color: #000; }
@media (hover: hover) {
  .session-item:hover .session-share { opacity: 1; pointer-events: auto; transform: scale(1); }
}
.session-item.show-delete .session-share {
  opacity: 1;
  pointer-events: auto;
  width: 40px;
  height: 40px;
  background: linear-gradient(180deg, #4caf82 0%, #2f8c63 100%);
  color: #04210f;
  font-size: 1.25rem;
  font-weight: 600;
  box-shadow: 0 6px 16px rgba(76, 175, 130, .38),
              0 0 0 1px rgba(255, 255, 255, .10) inset,
              0 1px 0 rgba(255, 255, 255, .18) inset;
  animation: delete-pop .22s cubic-bezier(.34, 1.56, .64, 1) both;
}
.session-item.show-delete .session-share:active {
  transform: scale(.92);
  box-shadow: 0 2px 8px rgba(76, 175, 130, .5),
              0 0 0 1px rgba(255, 255, 255, .12) inset;
}

/* Open-in-VS-Code button — same reveal pattern, blue accent, top-center. */
.session-vscode {
  position: absolute;
  top: 8px;
  left: calc(50% - 14px);
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: rgba(80, 140, 200, 0.22);
  color: var(--text);
  font-family: ui-monospace, monospace;
  font-size: .85rem;
  font-weight: 600;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  opacity: 0;
  pointer-events: none;
  transform: scale(.7);
  transition: opacity .16s, transform .18s cubic-bezier(.2,.8,.2,1),
              width .18s cubic-bezier(.2,.8,.2,1), height .18s cubic-bezier(.2,.8,.2,1),
              left .18s cubic-bezier(.2,.8,.2,1),
              background .15s, box-shadow .18s;
  z-index: 3;
}
.session-vscode:hover { background: #4a8ec7; color: #fff; }
.session-vscode:active { transform: scale(.92); background: #4a8ec7; color: #fff; }
@media (hover: hover) {
  .session-item:hover .session-vscode { opacity: 1; pointer-events: auto; transform: scale(1); }
}
.session-item.show-delete .session-vscode {
  opacity: 1;
  pointer-events: auto;
  width: 40px;
  height: 40px;
  left: calc(50% - 20px);
  background: linear-gradient(180deg, #5a99cf 0%, #3475ad 100%);
  color: #fff;
  font-size: .85rem;
  font-weight: 700;
  box-shadow: 0 6px 16px rgba(74, 142, 199, .38),
              0 0 0 1px rgba(255, 255, 255, .10) inset,
              0 1px 0 rgba(255, 255, 255, .18) inset;
  animation: delete-pop .22s cubic-bezier(.34, 1.56, .64, 1) both;
}
.session-item.show-delete .session-vscode:active {
  transform: scale(.94);
  box-shadow: 0 2px 8px rgba(74, 142, 199, .5),
              0 0 0 1px rgba(255, 255, 255, .12) inset;
}

/* Share-link cards in the sidebar */
.session-item.shared .session-title { color: #8cc5ff; }

/* Toast (used by "Link copied" feedback). */
.toast {
  position: fixed;
  bottom: calc(var(--safe-bottom) + 28px);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(20, 20, 20, .92);
  color: #fff;
  padding: 10px 18px;
  border-radius: 22px;
  font-size: .88rem;
  z-index: 300;
  border: 1px solid rgba(255, 255, 255, .08);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .45);
  animation: toast-in .2s cubic-bezier(.2, .8, .2, 1);
}
.toast.hide { opacity: 0; transition: opacity .25s; }
@keyframes toast-in {
  from { transform: translate(-50%, 14px); opacity: 0; }
  to   { transform: translate(-50%, 0);    opacity: 1; }
}

/* ── terminal pane ────────────────────────────────────────────── */

#terminal-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: #000;
  position: relative;        /* anchor for #myco-waiting + other overlays */
}

#no-session {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  color: var(--muted);
  font-size: .9rem;
  /* Use the whole viewport breadth so the oversized slogan can
     scale without colliding with the chrome buttons at the top. */
  padding: 0 24px;
  text-align: center;
}
/* Large, subtle brand slogan. Scales with viewport so it dominates
   the empty home page without ever being LOUD. Hairline weight,
   low-opacity foreground, slight letter-spacing for poise. Falls
   back to a static size on tiny viewports via clamp(). */
#no-session .ns-slogan {
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: clamp(2.4rem, 9vw, 7rem);
  font-weight: 200;
  letter-spacing: 0.02em;
  line-height: 1.05;
  color: rgba(255, 255, 255, .055);
  /* Slight green wash to echo the accent — but kept barely-visible
     so it reads as texture, not a banner. */
  background: linear-gradient(135deg,
    rgba(76, 175, 130, .065) 0%,
    rgba(255, 255, 255, .055) 50%,
    rgba(76, 175, 130, .055) 100%);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  user-select: none;
  pointer-events: none;
  max-width: min(1200px, 100%);
}
#no-session .ns-hint {
  font-size: .9rem;
  color: var(--muted);
  opacity: .55;
  letter-spacing: 0.02em;
}
/* On mobile the chrome cluster overlays this area; tighten the
   slogan so it doesn't push the hint off the bottom of the viewport. */
@media (max-width: 900px) {
  #no-session .ns-slogan {
    font-size: clamp(2rem, 11vw, 4rem);
  }
  /* On mobile the sidebar IS the home page (full-screen). Hide the
     terminal-pane slogan since it sits behind the sidebar; the
     sidebar-slogan below picks up that role under the session cards. */
  #no-session .ns-slogan { display: none; }
}

/* Sidebar slogan — only visible on mobile, sits as background
   wallpaper below the session cards. Same low-contrast aesthetic
   as the main-pane slogan but sized for the narrow sidebar column.
   Absolutely positioned so it doesn't fight the existing scroll
   layout (session-list flex:1) — it lives BEHIND the list at the
   bottom of the sidebar. When the list is short, the slogan shows
   through; when the list overflows + scrolls past it, the list
   simply paints over. */
#sidebar-slogan {
  display: none;                  /* desktop: hidden */
  user-select: none;
  pointer-events: none;
}
@media (max-width: 900px) {
  #sidebar-slogan {
    display: block;
    position: absolute;
    /* Pin near the bottom, above the status bar. The status bar
       sits at the bottom of #sidebar (last flex child) so the
       slogan needs ~60px breathing room. */
    bottom: calc(env(safe-area-inset-bottom, 0px) + 56px);
    left: 0;
    right: 0;
    padding: 0 16px;
    text-align: center;
    font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif;
    font-size: clamp(1.6rem, 9vw, 3rem);
    font-weight: 200;
    letter-spacing: 0.02em;
    line-height: 1.05;
    color: rgba(255, 255, 255, .08);
    background: linear-gradient(135deg,
      rgba(76, 175, 130, .085) 0%,
      rgba(255, 255, 255, .075) 50%,
      rgba(76, 175, 130, .075) 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
    z-index: 0;                   /* behind session cards */
  }
  /* Sidebar already is position:relative on mobile? Make sure so
     the absolute slogan anchors to the sidebar, not the page. */
  #sidebar { position: relative; }
  /* Session-list paints over the slogan when long. */
  #session-list { position: relative; z-index: 1; background: transparent; }
}

/* ── claude "waiting" indicator — minimalist three-dot typing animation.
   No text, no pill background — just three small dots fading in/out at the
   bottom of the session pane. As unobtrusive as possible while still
   signaling activity. */
#myco-waiting {
  position: absolute;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
  transform: translateX(-50%);
  z-index: 35;
  display: flex;
  gap: 5px;
  padding: 0;
  pointer-events: none;
  animation: conn-fade-in .25s cubic-bezier(.22, .9, .3, 1);
}
.mw-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .55);
  /* Soft halo so dots stay readable over both terminal black and the
     conv pane gradient, without needing a pill background. */
  box-shadow: 0 0 4px rgba(0, 0, 0, .5);
  animation: mw-pulse 1.3s infinite ease-in-out;
}
.mw-dot:nth-child(2) { animation-delay: .15s; }
.mw-dot:nth-child(3) { animation-delay: .30s; }
@keyframes mw-pulse {
  0%, 70%, 100% { opacity: .2;  transform: translateY(0);    }
  35%           { opacity: 1.0; transform: translateY(-2px); }
}

/* ── connection-state floating card ───────────────────────────────────────
   Sits centered over the terminal area while the WebSocket is establishing
   or reconnecting. Replaces the old inline `[connecting...]` text in xterm. */
#conn-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 30;
}
.conn-pill {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 22px 30px 22px 26px;
  border-radius: 22px;
  background:
    linear-gradient(135deg, rgba(34, 42, 60, .85), rgba(20, 24, 34, .92)),
    radial-gradient(circle at 30% 20%, rgba(120, 160, 240, .08), transparent 60%);
  border: 1px solid rgba(140, 180, 255, .28);
  color: #e6efff;
  font: 15px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  letter-spacing: .01em;
  backdrop-filter: blur(24px) saturate(180%);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  box-shadow:
    0 18px 48px rgba(0, 0, 0, .55),
    0 0 32px rgba(120, 160, 240, .12),
    inset 0 1px 0 rgba(255, 255, 255, .07);
  pointer-events: auto;
  animation: conn-fade-in .28s cubic-bezier(.22, .9, .3, 1);
  min-width: 240px;
}
.conn-labels { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.conn-text {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: .015em;
  line-height: 1.1;
}
.conn-sub {
  font-size: 12px;
  color: rgba(220, 230, 255, .55);
  letter-spacing: .02em;
  line-height: 1.2;
}
.conn-pill.error {
  border-color: rgba(255, 130, 130, .45);
  color: #ffe2e2;
  box-shadow:
    0 18px 48px rgba(0, 0, 0, .55),
    0 0 32px rgba(255, 130, 130, .15),
    inset 0 1px 0 rgba(255, 255, 255, .07);
}
.conn-pill.error .conn-sub { color: rgba(255, 220, 220, .60); }
.conn-pill.error .conn-spinner {
  border-color: rgba(255, 130, 130, .25);
  border-top-color: rgba(255, 180, 180, .95);
}

/* Spinner ring — reads as "loading" more clearly than a pulsing dot. */
.conn-spinner {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid rgba(140, 180, 255, .22);
  border-top-color: rgba(170, 200, 255, .95);
  animation: conn-spin .9s linear infinite;
  flex-shrink: 0;
  box-shadow: 0 0 14px rgba(140, 180, 255, .15);
}
@keyframes conn-spin {
  to { transform: rotate(360deg); }
}
@keyframes conn-fade-in {
  from { opacity: 0; transform: translateY(-6px) scale(.96); }
  to   { opacity: 1; transform: translateY(0)    scale(1);   }
}

@media (max-width: 900px) {
  .conn-pill { min-width: 220px; padding: 18px 24px 18px 20px; gap: 14px; }
  .conn-spinner { width: 24px; height: 24px; border-width: 2.5px; }
  .conn-text { font-size: 15px; }
  .conn-sub  { font-size: 11.5px; }
}

#terminal {
  flex: 1;
  min-height: 0;
  padding: 4px;
  overflow: hidden;
}

/* xterm sizes its own canvas; only force width so it fills the wrapper.
   Don't force height — xterm needs to control viewport height for scrolling. */
/* xterm + FitAddon set the canvas to exact pixel multiples of cell width.
   Forcing width:100% here stretches the canvas and misaligns box-drawing
   glyphs (rounded corners, lines, etc.), so don't override it. */

/* Touch scroll is driven from JS (setupTouchScroll in app.js): we hijack
   touchmove and call term.scrollLines() so the WebGL canvas and the scroll
   position stay perfectly in sync. Disable native pan to avoid double-scrolling. */
/* JS scroll handler owns scrolling on both desktop and mobile (native scroll
   doesn't reach .xterm-viewport because .xterm-screen overlays it — known
   xterm.js#594, no upstream fix). */
#terminal .xterm-viewport {
  touch-action: none !important;
  overflow-y: auto !important;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: auto;
  /* Match the thin scrollbar used by #chat-messages / #conv-messages so
     all three scrollers share the same affordance. Default xterm.js
     ships with the browser's default scrollbar (thick, OS-styled) which
     looked white against the dark terminal. */
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 145, .35) transparent;
}
#terminal .xterm-viewport::-webkit-scrollbar { width: 8px; }
#terminal .xterm-viewport::-webkit-scrollbar-track { background: transparent; }
#terminal .xterm-viewport::-webkit-scrollbar-thumb {
  background: rgba(120, 130, 145, .35);
  border-radius: 4px;
}
#terminal .xterm-viewport::-webkit-scrollbar-thumb:hover { background: rgba(150, 160, 175, .55); }

/* ── keyboard ─────────────────────────────────────────────────── */

.keyboard-bar {
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 6px calc(6px + env(safe-area-inset-right, 0px)) calc(6px + env(safe-area-inset-bottom, 0px)) calc(6px + env(safe-area-inset-left, 0px));
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  align-items: center;
  overflow-x: hidden;
}

/* On a desktop (hover-capable mouse/trackpad with a fine pointer) the user
   has a real keyboard — hide the on-screen keyboard bar entirely. Phones
   and touch tablets keep it. */
@media (hover: hover) and (pointer: fine) {
  #keyboard-bar { display: none !important; }
}

.kbd-toggle {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: .9rem;
  white-space: nowrap;
  min-height: 38px;
  flex-shrink: 0;
}

.kbd-grid {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  flex: 1;
  min-width: 0;
}

.kbd-key {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 4px;
  font-size: .85rem;
  font-family: ui-monospace, SFMono-Regular, monospace;
  min-height: 38px;
  min-width: 0;
  flex: 1;
  transition: transform .08s, background .12s;
}

.kbd-key:active { background: #3a3a3a; transform: scale(.94); }
.kbd-key.wide   { flex: 1; }

.kbd-native-input {
  flex: 1;
  min-width: 0;
  background: #2e2e2e;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 1rem;
  min-height: 38px;
}

.kbd-send {
  background: var(--accent);
  color: #000;
  border: none;
  border-radius: 8px;
  padding: 8px 16px;
  font-size: .9rem;
  font-weight: 600;
  min-height: 38px;
  flex-shrink: 0;
  cursor: pointer;
  transition: transform .08s;
}
.kbd-send:active { transform: scale(.94); }

/* ── spawn modal ──────────────────────────────────────────────── */

#spawn-modal {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

#spawn-dialog {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 24px;
  width: min(380px, 90vw);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

#spawn-dialog h2 { font-size: 1rem; }

#spawn-dialog label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: .85rem;
  color: var(--muted);
}

#spawn-cwd {
  background: #2e2e2e;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 1rem;
  min-height: 44px;
}

#spawn-mode-label {
  /* Overrides the default column layout from #spawn-dialog label so the
     checkbox sits inline with its caption rather than stacked. */
  flex-direction: row !important;
  align-items: center;
  gap: 8px !important;
  cursor: pointer;
  padding: 6px 0;
  user-select: none;
}
#spawn-mode-label span {
  font-size: 0.85rem;
}
#spawn-mode-pty,
#spawn-mode-agent {
  width: 16px;
  height: 16px;
  cursor: pointer;
  flex-shrink: 0;
}

#spawn-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

#spawn-cancel, #spawn-ok {
  border: none;
  border-radius: 8px;
  padding: 12px 20px;
  font-size: 1rem;
  min-height: 44px;
  transition: transform .08s;
}
#spawn-cancel:active, #spawn-ok:active { transform: scale(.96); }

#spawn-cancel { background: var(--border); color: var(--text); }
#spawn-ok     { background: var(--accent); color: #000; font-weight: 600; }

#spawn-workspace { font-size: .8rem; color: var(--muted); }
#spawn-workspace code { color: var(--text); background: #2e2e2e; padding: 2px 6px; border-radius: 4px; font-family: monospace; }

#spawn-suggestions { display: flex; flex-wrap: wrap; gap: 6px; max-height: 120px; overflow-y: auto; }

.dir-chip {
  background: #2e2e2e; color: var(--text); border: 1px solid var(--border);
  border-radius: 12px; padding: 4px 10px; font-size: .8rem; cursor: pointer;
}
.dir-chip:hover { border-color: var(--accent); }

#spawn-error { color: var(--danger); font-size: .85rem; padding: 8px; background: rgba(255,107,107,.1); border-radius: 4px; border: 1px solid rgba(255,107,107,.3); }

/* ── login modal ──────────────────────────────────────────────── */

#login-modal {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.7);
  display: flex; align-items: center; justify-content: center;
  z-index: 200;
  padding: 20px;
}
#login-dialog {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  width: min(360px, 90vw);
  display: flex; flex-direction: column; gap: 12px;
}
#login-dialog h2 { font-size: 1.1rem; color: var(--accent); }
.login-hint { font-size: .85rem; color: var(--muted); }
#login-github {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: #24292e;
  color: #fff;
  border: 1px solid #444c56;
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
  min-height: 44px;
  transition: background-color .12s ease;
}
#login-github:hover { background: #2f363d; }
#login-github:active { transform: scale(.98); }
#login-github svg { display: block; }
/* GitHub OAuth disabled-for-now styling. .disabled-soon mutes the
   button, kills the hover lift, and disables the press transform.
   The struck-through label + the amber "Soon" badge communicate
   "this works but isn't wired up yet — use the PAT form below." */
#login-github.disabled-soon {
  background: #1c2025;
  color: #9aa4b1;
  cursor: not-allowed;
  opacity: .75;
}
#login-github.disabled-soon:hover { background: #1c2025; }
#login-github.disabled-soon:active { transform: none; }
#login-github.disabled-soon svg { opacity: .6; }
#login-github .soon-badge {
  display: inline-block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 10px;
  background: rgba(246, 180, 138, 0.18);
  color: #f6b48a;
  border: 1px solid rgba(246, 180, 138, 0.35);
  margin-left: 2px;
}
.login-fineprint { color: var(--muted); font-size: .8rem; margin: 4px 0 0; line-height: 1.45; }
.login-fineprint a { color: #8ab8ff; }
.login-fineprint code {
  background: var(--border);
  padding: 1px 5px;
  border-radius: 4px;
  font: 11.5px 'JetBrains Mono', monospace;
}

.login-divider {
  display: flex; align-items: center; gap: 10px;
  color: var(--muted); font-size: .75rem;
  margin: 4px 0;
}
.login-divider::before,
.login-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}

#login-pat-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
}

#login-pat {
  background: #2e2e2e;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  font: 13px 'JetBrains Mono', monospace;
  min-height: 40px;
}
#login-pat:focus { outline: none; border-color: var(--accent); }
#login-pat-submit {
  background: var(--accent);
  color: #000;
  border: none;
  border-radius: 8px;
  padding: 10px;
  font-size: .95rem;
  font-weight: 600;
  cursor: pointer;
  min-height: 40px;
}
#login-pat-submit:active { transform: scale(.98); }
#login-pat-submit:disabled { opacity: .55; cursor: progress; }
#login-error {
  color: var(--danger);
  font-size: .85rem;
  background: rgba(255, 100, 100, .08);
  border: 1px solid rgba(255, 100, 100, .25);
  padding: 6px 10px;
  border-radius: 6px;
}

/* ── mobile ───────────────────────────────────────────────────── */

@media (max-width: 900px) {
  /* Mobile UX deltas (2026-05-16):
     - Chrome buttons bump to 40px (Apple HIG min 44pt is the target;
       40px on a 2x device is 80 physical px, well above the practical
       finger threshold), so the five-icon cluster is tappable.
     - Chat-messages overscroll contained — kills the iOS pull-to-
       refresh that would yank the whole pane when the user scrolls
       at the top of a long conversation.
     - Chat input min font-size 16px (Safari ≤14 auto-zooms on focus
       when font is smaller); padding bumped so the tap target inside
       textarea is full-height.
     - Perm-modal options become full-width, taller, with 44px+ tap
       targets and a bigger glyph cluster.
     - Carousel chip row scrolls horizontally instead of wrapping. */
  :root { --chrome-btn-size: 40px; }
  #chat-messages {
    overscroll-behavior-y: contain;
    -webkit-overflow-scrolling: touch;
  }
  #chat-input {
    font-size: 16px;
    line-height: 1.45;
    padding: 8px 10px;
    min-height: 2.4em;
  }
  #chat-send {
    min-width: 56px;
    min-height: 40px;
    font-size: 15px;
  }
  /* Perm-modal: each option is its own card on mobile — full width,
     taller hit area, larger glyph. */
  .perm-modal-opt {
    min-height: 48px !important;
    padding: 12px 14px !important;
    font-size: 15px;
  }
  .perm-modal-opt-num {
    min-width: 28px;
    font-size: 14px;
  }
  /* Carousel chips: scroll horizontally instead of wrap. The flex-
     wrap default piled chips into 3+ rows on narrow widths. */
  .perm-modal-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
  }
  .perm-chip {
    max-width: 120px;
    flex: 0 0 auto;
  }
  /* Token meter — already small; tighten font on narrow phones. */
  .token-meter {
    font-size: 0.66rem;
  }
  /* Horizontal-overflow safety: keep the chat-pane STRICTLY no wider
     than the viewport on mobile. Long URLs, paths, and unbreakable
     tokens in chat text used to push the page wider than the
     screen, painting a horizontal scroll bar at the document level.
     - chatpane / messages cap at 100% with min-width:0 so flex
       children can't expand them.
     - chat-text / agent-card-md / artifact-md wrap aggressively
       on word boundaries OR mid-token if necessary.
     - pre / code blocks scroll INTERNALLY (max-width:100%) so a
       long code line stays inside the message bubble.
     - mermaid SVGs + images cap at 100% of their container. */
  #chatpane, #chat-messages {
    max-width: 100vw;
    min-width: 0;
    overflow-x: hidden;
  }
  #chat-messages > * { max-width: 100%; min-width: 0; }
  .chat-msg .chat-text,
  .agent-card-md,
  .agent-card-body,
  .artifact-item-text.artifact-md,
  .agent-chrome-summary,
  .agent-tool-summary,
  .turn-head-text {
    word-break: break-word;
    overflow-wrap: anywhere;
  }
  .chat-msg .chat-text pre,
  .agent-card-md pre,
  .agent-card-body pre,
  .agent-chrome-pre,
  .agent-tool-result-preview,
  .agent-diff-body,
  .agent-json {
    max-width: 100%;
  }
  .chat-msg .chat-text img,
  .agent-card-md img,
  .conv-mermaid,
  .conv-mermaid svg {
    max-width: 100%;
    height: auto;
  }
  /* Composer card sits at 4px lateral margin to maximize textarea
     width on narrow phones. Composer-actions column stays just
     wide enough for the 80px buttons. */
  #chat-form.composer { margin-left: 4px; margin-right: 4px; }

  /* Composer buttons bump to 44px tall on mobile (Apple HIG finger
     target). Composer is taller naturally because the actions row
     lives below the textarea. Stop hides its Esc kbd hint on
     touch (no Esc key on keyboard). */
  .composer-btn {
    height: 44px;
    padding: 0 16px;
    font-size: 0.92rem;
    min-width: 80px;
  }
  .composer-icon {
    width: 20px;
    height: 20px;
  }
  .composer-btn-kbd { display: none; }
  /* Slightly thicker composer padding on mobile so the larger
     buttons + textarea don't feel cramped against the rounded
     card edge. */
  #chat-form.composer {
    padding: 10px 12px calc(10px + var(--safe-bottom));
    gap: 8px;
  }
  /* Tighten the inter-button gap so the 5-icon cluster (files /
     plan / arch / test / chat) fits comfortably on a 375px screen
     after the chrome-btn-size bump to 40px. 5*40 + 4*6 = 224px,
     leaving room for ☰ on the left without crowding. */
  #btn-files {
    right: calc(env(safe-area-inset-right, 0px) + 8px + var(--chrome-btn-size) + 6px);
  }
  #btn-plan { right: calc(env(safe-area-inset-right, 0px) + 8px + 2 * (var(--chrome-btn-size) + 6px)); }
  #btn-arch { right: calc(env(safe-area-inset-right, 0px) + 8px + 3 * (var(--chrome-btn-size) + 6px)); }
  #btn-test { right: calc(env(safe-area-inset-right, 0px) + 8px + 4 * (var(--chrome-btn-size) + 6px)); }
  #btn-chat, #btn-files, #btn-plan, #btn-arch, #btn-test {
    /* Bigger SVG inside the larger button looks balanced. */
  }
  #btn-chat { right: calc(env(safe-area-inset-right, 0px) + 8px); }
  /* Slightly larger icon on mobile to match the bigger button. */
  .chrome-icon-svg { width: 22px; height: 22px; }

  #sidebar {
    position: fixed;
    inset: 0;
    width: 100%;
    min-width: 0;
    /* Above the floating chrome buttons (z-50) so the full-screen sidebar
       cleanly covers ☰ / 📁 / 💬 instead of letting them leak through. */
    z-index: 60;
    border-right: none;
  }
  #sidebar header h1 { font-size: 1.4rem; }
  #btn-collapse { display: none; }
  /* #btn-spawn keeps the shared chrome-button square shape on mobile too,
     instead of the previous pill — see :root --chrome-btn-size. */

  #session-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-auto-rows: min-content;
    align-content: start;
    column-gap: 10px;
    row-gap: 6px;
    padding: 10px 14px 100px;
  }
  .session-item {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 14px 14px;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    gap: 4px;
    position: relative;
    overflow: hidden;
    transition: transform .12s, background .15s, border-color .15s;
    min-height: 120px;
  }
  .session-item:active {
    transform: scale(.97);
    background: rgba(255,255,255,.04);
  }
  .session-item.active {
    border-color: var(--accent);
    background: rgba(76,175,130,.10);
    border-left: 1px solid var(--accent);
  }
  .session-item.shared {
    border-color: rgba(80, 140, 200, .4);
    border-left: 1px solid rgba(80, 140, 200, .5);
    background: rgba(80, 140, 200, .05);
  }
  .session-item.active::after {
    content: '';
    position: absolute;
    top: 8px; right: 8px;
    width: 6px; height: 6px;
    background: var(--accent);
    border-radius: 50%;
    box-shadow: 0 0 6px var(--accent);
  }
  .session-name {
    font-size: 1rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
  }
  .session-cwd {
    font-size: .75rem;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Prevent iOS auto-linkification (URLs/IPs in description) from
     hijacking taps that should open the session. Scope this to text spans —
     the .session-delete button must remain tappable. */
  .session-item > span { pointer-events: none; }

  /* JS-rendered cards use these classes */
  .session-title {
    font-size: .92rem;
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
    line-height: 1.2;
  }
  .session-status {
    top: 8px;
    right: 8px;
    width: 8px;
    height: 8px;
  }
  .session-summary {
    font-size: .78rem;
    color: #aaa;
    line-height: 1.3;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
  }
  .session-desc {
    font-size: .78rem;
    color: var(--muted);
    line-height: 1.3;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    word-break: break-word;
  }
  .session-meta {
    font-size: .65rem;
    color: var(--muted);
    opacity: .7;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
    margin-top: auto;
  }

  #terminal-pane {
    width: 100%;
    /* Reserve space at the top so the floating ☰ button (44px + 12px gap)
       doesn't overlap the terminal's first row. */
    padding-top: calc(var(--safe-top) + 60px);
  }

  /* (#btn-expand size is unified via --chrome-btn-size; no per-button
     override needed here.) */

  /* Bottom-sheet modal on phones */
  #spawn-modal { align-items: flex-end; padding: 0; }
  #spawn-dialog {
    width: 100%;
    max-width: none;
    border-radius: 18px 18px 0 0;
    padding: 12px 20px calc(20px + var(--safe-bottom));
    animation: sheet-up .28s cubic-bezier(.2,.8,.2,1);
  }
  #spawn-dialog::before {
    content: '';
    display: block;
    width: 36px; height: 4px;
    margin: 0 auto 14px;
    background: var(--border);
    border-radius: 2px;
  }
  #spawn-dialog h2 { font-size: 1.2rem; font-weight: 600; }
}

@keyframes sheet-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* ── status bar ───────────────────────────────────────────────── */

#status-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-top: 1px solid var(--border);
  /* Transparent so the strip blends with whatever surface it docks
     to (sidebar = --surface) instead of forming a lighter band via
     --surface-2. The border-top above is the only visual separator. */
  background: transparent;
  font-size: .7rem;
  color: var(--muted);
  cursor: pointer;
  flex-shrink: 0;
  min-height: 28px;
}

#status-indicator {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #4cd964;
  box-shadow: 0 0 4px rgba(76, 217, 100, .5);
  flex-shrink: 0;
}

#status-indicator.warn { background: #ffcc00; box-shadow: 0 0 4px rgba(255, 204, 0, .5); }
#status-indicator.error { background: var(--danger); box-shadow: 0 0 4px rgba(255, 107, 107, .5); }

#status-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}

/* Status-bar chips — username + build timestamp. Empty in dev / when auth
   is disabled, so :empty hides them entirely. */
#user-stamp,
#build-stamp {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: .65rem;
  color: #555;
  flex-shrink: 0;
}
#user-stamp { color: var(--accent); }
#user-stamp:empty,
#build-stamp:empty { display: none; }

/* ── log panel ────────────────────────────────────────────────── */

#log-panel {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  max-height: 50vh;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: 14px 14px 0 0;
  display: flex;
  flex-direction: column;
  z-index: 100;
  animation: sheet-up .25s cubic-bezier(.2,.8,.2,1);
}

#log-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

#log-panel-header h2 { font-size: .9rem; font-weight: 600; }

#log-panel-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.3rem;
  padding: 4px;
  line-height: 1;
}

#log-entries {
  overflow-y: auto;
  padding: 8px 0;
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: .72rem;
  line-height: 1.5;
}

.log-entry {
  padding: 2px 18px;
  color: var(--muted);
  word-break: break-all;
}

.log-entry.error { color: var(--danger); }
.log-entry.warn { color: #ffcc00; }

.log-ts {
  color: #555;
  margin-right: 8px;
}

/* ── chat pane (collaborator discussion + claude assistant) ────── */

/* Default chat-pane width: half of the available main-pane area (i.e.
   half of viewport minus the session-list sidebar), so the readonly
   transcript on the left and the chat pane on the right open in a
   50/50 split by default. The drag-handle (#chatpane-resize) overrides
   this with a px value persisted in localStorage when the user resizes;
   without a stored value, the calc() applies on each load and
   auto-adjusts to viewport size. When the sidebar is collapsed
   (html.sidebar-collapsed, toggled by setSidebar in app.js), drop the
   --sidebar-w subtraction so the split is half of the FULL viewport. */
:root { --chatpane-w: calc((100vw - var(--sidebar-w)) / 2); }
html.sidebar-collapsed { --chatpane-w: calc(100vw / 2); }

/* Chat layout:
     desktop — a RIGHT sidebar (var(--chatpane-w) wide, resizable via
       the left-edge handle). The terminal / plan / arch / test / files
       view stays visible to the LEFT of it.
     mobile  — fills the whole main pane (full-width).
   In both cases it sits inside #terminal-pane (position:relative) via
   absolute positioning, so the chrome buttons (z:50) stay above and
   are reachable while chat is open. */
/* `hidden` attribute must beat the author display:flex below — without
   this, setChatPane(false) flips the attribute but the CSS rule keeps
   the pane rendered (covering whichever main view is supposedly active,
   especially on mobile where chatpane width is 100% and z-index:30). */
#chatpane[hidden] { display: none !important; }

/* Phase 3 — single centered column. The chatpane fills the entire
   main pane (no longer a right-anchored sidebar) and its contents are
   centered with a comfortable max-width. The empty margins still
   belong to the chatpane (same background), so the pane reads as one
   cohesive surface. Artifact panes (plan/arch/test/files) overlay the
   same area via .artifact-main-view {inset: 0}, so opening an
   artifact tab covers the conversation without geometry math. */
#chatpane.chat-main-view {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  padding-top: calc(env(safe-area-inset-top, 0px) + var(--pane-header-h));
  z-index: 30;
  overflow: hidden;
}
/* Center the conversation column on wide screens; max-width keeps long
   lines readable and the chevron-collapse cards from sprawling. */
@media (min-width: 901px) {
  #chatpane.chat-main-view #chat-messages,
  #chatpane.chat-main-view #chat-form,
  #chatpane.chat-main-view #claude-typing,
  #chatpane.chat-main-view #chat-autocomplete,
  #chatpane.chat-main-view #chat-empty {
    width: 100%;
    max-width: 880px;
    margin-left: auto;
    margin-right: auto;
  }
}

/* Resize handle defaults to hidden — only the side-by-side layout
   below (#terminal-pane.has-artifact #chatpane:not([hidden])) makes
   it visible. In single-pane chat mode there's no resize axis. */
#chatpane-resize { display: none; }

/* Side-by-side layout (desktop only): when an artifact view
   (plan/arch/test) or the files-wrap is open AND chat is also open,
   chat collapses from full-pane to a RIGHT sidebar of width
   var(--chatpane-w). The artifact view's right edge stops at
   var(--chatpane-w) instead of 0 so both panes are visible at once.
   The .chat-main-view centering (max-width: 880px) is also relaxed
   so chat content fills the narrow sidebar without awkward gutters. */
@media (min-width: 901px) {
  #terminal-pane.has-artifact #chatpane.chat-main-view:not([hidden]) {
    left: auto;
    right: 0;
    width: var(--chatpane-w);
    border-left: 1px solid var(--border);
  }
  #terminal-pane.has-artifact #chatpane.chat-main-view #chat-messages,
  #terminal-pane.has-artifact #chatpane.chat-main-view #chat-form,
  #terminal-pane.has-artifact #chatpane.chat-main-view #claude-typing,
  #terminal-pane.has-artifact #chatpane.chat-main-view #chat-autocomplete,
  #terminal-pane.has-artifact #chatpane.chat-main-view #chat-empty {
    max-width: none;
  }
  #terminal-pane.has-artifact .artifact-main-view,
  #terminal-pane.has-artifact #files-wrap {
    right: var(--chatpane-w);
  }
  #terminal-pane.has-artifact #chatpane:not([hidden]) #chatpane-resize {
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 6px;
    cursor: col-resize;
    background: transparent;
    z-index: 32;
  }
  #terminal-pane.has-artifact #chatpane:not([hidden]) #chatpane-resize:hover,
  #terminal-pane.has-artifact #chatpane:not([hidden]) #chatpane-resize.dragging {
    background: rgba(120, 160, 230, .35);
  }
}

/* Tab bar inside the chatpane header. Holds Discussion / Plan / Arch / Test. */
#chatpane-tabs {
  flex: 1;
  display: flex;
  gap: 2px;
  min-width: 0;
  overflow-x: auto;
}
.chatpane-tab {
  background: transparent;
  color: var(--muted);
  border: none;
  border-radius: 6px;
  padding: 6px 10px;
  font: inherit;
  font-size: .82rem;
  font-weight: 500;
  letter-spacing: .03em;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s, color .12s;
}
.chatpane-tab:hover { background: rgba(255, 255, 255, .06); color: var(--text); }
.chatpane-tab.is-active {
  background: rgba(80, 120, 200, .22);
  color: var(--accent);
}

/* Tab bodies fill the leftover vertical space. Only one is visible at a time. */
.chatpane-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Header strip inside Plan/Arch/Test bodies: title on the left, Refresh on the right. */
.artifact-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  font-size: .78rem;
  color: var(--muted);
}
.artifact-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.artifact-refresh {
  background: rgba(80, 120, 200, .22);
  color: var(--text);
  border: 1px solid rgba(120, 160, 230, .35);
  border-radius: 6px;
  padding: 3px 9px;
  font: inherit;
  font-size: .76rem;
  cursor: pointer;
  transition: background .12s, transform .08s;
}
.artifact-refresh:hover { background: rgba(80, 120, 200, .35); }
.artifact-refresh:active { transform: scale(.95); }
.artifact-refresh:disabled { opacity: .5; cursor: progress; }
/* Inline checkbox for toggling the best-practices template injection
   in the Arch tab. Sits between the title (flex:1) and the refresh
   button, matches the same dark-on-dark mini-control aesthetic. */
.artifact-toggle-bp {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  font-size: .76rem;
  color: var(--muted);
  user-select: none;
}
.artifact-toggle-bp input { accent-color: var(--accent); cursor: pointer; }
.artifact-toggle-bp:hover { color: var(--text); }
/* Best-practices banner injected at the top of the Arch body when the
   toggle is on. Distinct background to signal it's a template, not
   per-project content. */
.bp-banner {
  background: rgba(76, 175, 130, .06);
  border-left: 3px solid rgba(76, 175, 130, .35);
  padding: 8px 14px;
  margin: 0 0 12px 0;
  border-radius: 4px;
}
.bp-banner > :first-child { margin-top: 0; }
.bp-banner > :last-child { margin-bottom: 0; }

/* Scrollable body for the extracted artifact content. */
.artifact-body {
  flex: 1;
  overflow-y: auto;
  padding: 12px 14px;
  font-size: .9rem;
  line-height: 1.45;
}
.artifact-empty {
  color: var(--muted);
  font-size: .85rem;
  text-align: center;
  padding: 24px 8px;
}
.artifact-empty code {
  font-family: ui-monospace, SFMono-Regular, monospace;
  background: var(--surface-2);
  padding: 1px 6px;
  border-radius: 4px;
}
.artifact-updated {
  margin-top: 12px;
  color: var(--muted);
  font-size: .72rem;
  text-align: right;
}

/* Plan-tab merge-proposal callout. Pushed in at the top of
   #artifact-body-plan by _renderMergeProposals after a refresh that
   found candidates. Each group is one row with reason + ids +
   Apply/Dismiss buttons. */
.plan-merge-callout {
  background: rgba(220, 180, 90, .08);
  border: 1px solid rgba(220, 180, 90, .35);
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 12px;
}
.plan-merge-title {
  font-weight: 600;
  font-size: 0.85rem;
  color: rgba(220, 180, 90, .95);
  margin-bottom: 8px;
}
.plan-merge-group {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  gap: 4px 12px;
  padding: 8px 0;
  border-top: 1px dashed rgba(220, 180, 90, .22);
}
.plan-merge-group:first-of-type { border-top: none; padding-top: 0; }
.plan-merge-reason {
  grid-column: 1;
  grid-row: 1;
  font-size: 0.9rem;
}
.plan-merge-ids {
  grid-column: 1;
  grid-row: 2;
  font-size: 0.8rem;
  color: var(--muted);
}
.plan-merge-ids code {
  background: rgba(220, 180, 90, .12);
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 0.75rem;
}
.plan-merge-actions {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-self: center;
}
.plan-merge-apply,
.plan-merge-dismiss {
  font-size: 0.75rem;
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--fg);
}
.plan-merge-apply {
  border-color: rgba(220, 180, 90, .55);
  background: rgba(220, 180, 90, .15);
}
.plan-merge-apply:hover { background: rgba(220, 180, 90, .28); }
.plan-merge-apply:disabled { opacity: .55; cursor: progress; }
.plan-merge-dismiss:hover { background: var(--bg-hover, rgba(255,255,255,.05)); }
.plan-merge-error {
  grid-column: 1 / -1;
  font-size: 0.78rem;
  color: rgba(255, 120, 120, .9);
  margin-top: 4px;
}

/* Plan items are bucketed into named architectural layers (Frontend /
   Backend / Tests / …) that the extractor assigns. Each bucket has a
   small header above its list. Test items don't have layers — they
   render as a flat list (no .artifact-layer-group wrapper). */
.artifact-layer-group {
  margin-bottom: 14px;
}
.artifact-layer-group:last-child { margin-bottom: 0; }
.artifact-layer-name {
  margin: 0 0 6px 0;
  padding: 4px 8px;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(80, 120, 200, .12);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
}

/* Plan/Test todo list with checkboxes. Checking a Plan item dispatches it
   back to the running Claude session via the chat pipeline (a `[run:plan#id]`
   marker on the dispatched text binds the next turn_result back to the item).
   Plan items also have per-item voting + a flat comment thread; once votes
   hit the threshold (2 by default) the server auto-dispatches the item. */
.artifact-items { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
.artifact-items li {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 8px;
  border-radius: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.artifact-items li.is-done { opacity: .55; }
.artifact-items li.is-done .artifact-item-text { text-decoration: line-through; }
.artifact-item-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
/* fr-47: artifact-item-checkbox removed — explicit Close/Reopen
 * button (.artifact-item-close below) replaces the conflated
 * dispatch-or-toggle checkbox. */
.artifact-item-close {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border-soft, rgba(128, 128, 128, 0.3));
  border-radius: 4px;
  padding: 2px 10px;
  font-size: 11px;
  cursor: pointer;
  flex-shrink: 0;
  font-weight: 500;
}
.artifact-item-close:hover {
  color: var(--accent, #4caf50);
  border-color: var(--accent, #4caf50);
}
.is-done .artifact-item-close {
  /* Reopen-state visually distinct: a touch warmer to signal
   * "this item is closed; click to revive". */
  color: var(--accent-warm, #d97706);
}
.is-done .artifact-item-close:hover {
  color: var(--accent-warm, #d97706);
  border-color: var(--accent-warm, #d97706);
}
.artifact-item-text { flex: 1; word-break: break-word; min-width: 0; }
/* Creator line — small muted "filed by @user · <ts>" on its own row
 * BELOW the body text. Doesn\'t compete with body for horizontal
 * space. Hover-title carries the raw addedAt timestamp. */
.artifact-item-by {
  font-size: 11px;
  color: var(--muted);
  font-style: italic;
  margin: 4px 0 2px 0;
  padding-left: 2px;
}
/* Markdown rendered inside a plan/test item — paragraphs, lists,
   headings, code, blockquotes, mermaid all need block-level layout. */
.artifact-item-text.artifact-md > :first-child { margin-top: 0; }
.artifact-item-text.artifact-md > :last-child  { margin-bottom: 0; }
.artifact-item-text.artifact-md p,
.artifact-item-text.artifact-md ul,
.artifact-item-text.artifact-md ol { margin: 6px 0; }
.artifact-item-text.artifact-md ul,
.artifact-item-text.artifact-md ol { padding-left: 22px; }
.artifact-item-text.artifact-md li { margin: 3px 0; }
/* artifact-md pre / pre code: unified with chat/agent code-block
   styles in the .chat-msg .chat-text pre rule earlier in this file.
   Only the inline-code background stays per-surface here so artifact
   bodies pop slightly more than a chat bubble's prose. */
.artifact-item-text.artifact-md code {
  background: rgba(255, 255, 255, .06);
  padding: 1px 5px;
  border-radius: 3px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
}
.artifact-item-text.artifact-md .conv-mermaid {
  margin: 8px 0;
  max-width: 100%;
  overflow-x: auto;
}
.artifact-item-text.artifact-md .conv-mermaid svg { max-width: 100%; height: auto; }

/* id chip + merged-from badge. The chip sits between the checkbox and
   the body text; the badge sits AFTER the text and shows count + the
   list of absorbed ids on hover. */
.artifact-item-id {
  background: rgba(140, 140, 160, .15);
  color: var(--muted);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 0.72rem;
  flex-shrink: 0;
  margin-top: 1px;
  white-space: nowrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* fr-6: id chip is also a deep-link copy affordance (role="button"
   tabindex="0" data-deep-link-id). Make it feel interactive on hover
   + show a brief flash on copy. The .artifact-item-id-copied class
   is toggled by _copyArtifactItemDeepLink for ~1.2s, then removed. */
.artifact-item-id[data-deep-link-id] {
  cursor: copy;
  transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
}
.artifact-item-id[data-deep-link-id]:hover {
  background: rgba(140, 140, 160, .28);
  color: var(--fg);
}
.artifact-item-id[data-deep-link-id]:focus-visible {
  outline: 2px solid rgba(100, 170, 255, .7);
  outline-offset: 1px;
}
.artifact-item-id-copied {
  background: rgba(100, 200, 130, .35) !important;
  color: rgba(220, 255, 230, .95) !important;
  box-shadow: 0 0 0 1px rgba(100, 200, 130, .55), 0 0 10px rgba(100, 200, 130, .35);
}

/* fr-6: deep-link highlight pulse when the user arrives at a plan
   item via `#<id>` in the URL. Brief outline + soft glow so the
   target item is immediately findable in a long list. Auto-removed
   after ~2.4s — see _scrollToArtifactItem. */
.artifact-items > li.artifact-item-deep-link-focused {
  position: relative;
  animation: artifact-item-deep-link-pulse 1.6s ease-out 2;
}
@keyframes artifact-item-deep-link-pulse {
  0%   { box-shadow: 0 0 0 2px rgba(100, 170, 255, .8), 0 0 24px rgba(100, 170, 255, .55); }
  100% { box-shadow: 0 0 0 1px rgba(100, 170, 255, .25), 0 0 12px rgba(100, 170, 255, .15); }
}
.artifact-item-merged {
  background: rgba(220, 180, 90, .15);
  color: rgba(220, 180, 90, .95);
  border: 1px solid rgba(220, 180, 90, .35);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 0.72rem;
  flex-shrink: 0;
  align-self: center;
  cursor: help;
  white-space: nowrap;
}

/* Actions row sits BELOW the [checkbox + id-chip + text] row in each
   plan/test item, so a long body text doesn't compete with the
   vote/comment/merged-badge/delete cluster for horizontal width. */
.artifact-item-actions {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-top: 4px;
  margin-left: 28px;   /* indent under the checkbox so it visually attaches to the item */
  flex-wrap: wrap;
}
.artifact-item-actions .artifact-item-delete {
  margin-left: auto;  /* push delete to the right edge */
}

/* fr-46: edit affordances. Pencil button on item header + on each
 * comment; trash button on each comment. All small / muted by default,
 * a touch brighter on hover to confirm they're interactive. Hidden for
 * viewers via conditional render in app.js (no need for CSS to hide). */
.artifact-item-edit,
.artifact-comment-edit,
.artifact-comment-delete {
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 4px;
  width: 22px;
  height: 22px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.artifact-item-edit:hover,
.artifact-comment-edit:hover {
  color: var(--accent, #4caf50);
  border-color: var(--border-soft, rgba(128, 128, 128, 0.3));
}
.artifact-comment-delete:hover {
  color: #d32f2f;
  border-color: var(--border-soft, rgba(128, 128, 128, 0.3));
}
.artifact-comment-actions {
  display: inline-flex;
  gap: 4px;
  margin-left: 6px;
  vertical-align: middle;
}
.artifact-item-edited,
.comment-edited {
  font-size: 11px;
  color: var(--muted);
  font-style: italic;
  margin: 0 4px;
}
/* Inline edit textareas — match the surrounding font so the editor
 * doesn't visually jar against the markdown preview it replaces. */
.artifact-item-edit-textarea,
.artifact-comment-edit-textarea {
  width: 100%;
  font-family: inherit;
  font-size: inherit;
  padding: 6px 8px;
  border: 1px solid var(--border-soft, rgba(128, 128, 128, 0.4));
  border-radius: 4px;
  background: var(--bg-input, transparent);
  color: inherit;
  box-sizing: border-box;
}
.artifact-item-edit-actions,
.artifact-comment-edit-actions {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}
.artifact-item-edit-actions button,
.artifact-comment-edit-actions button {
  padding: 4px 10px;
  font-size: 12px;
  cursor: pointer;
}

/* fr-48: persistent queue chip strip — pinned at the top of the
 * chat pane. (The redundant ⊤ Queue per-item button was removed
 * after the unified-dispatch refactor — ▶ Run now POSTs /queue/add.) */
.runqueue-strip {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 10px;
  background: var(--bg-soft, rgba(128, 128, 128, 0.08));
  border-bottom: 1px solid var(--border-soft, rgba(128, 128, 128, 0.2));
  font-size: 12px;
}
.runqueue-strip[hidden] { display: none; }
.runqueue-label { font-weight: bold; color: var(--muted); margin-right: 4px; }
.runqueue-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 12px;
  background: var(--bg-input, rgba(128, 128, 128, 0.15));
  border: 1px solid var(--border-soft, rgba(128, 128, 128, 0.25));
  cursor: pointer;
  font-family: var(--mono, monospace);
  font-size: 11px;
}
.runqueue-chip:hover { border-color: var(--accent, #4caf50); }
.runqueue-chip-running { background: rgba(76, 175, 80, 0.15); border-color: rgba(76, 175, 80, 0.5); }
.runqueue-chip-success { opacity: 0.6; }
.runqueue-chip-failed  { background: rgba(211, 47, 47, 0.15); border-color: rgba(211, 47, 47, 0.5); }
.runqueue-chip-cancelled { opacity: 0.5; text-decoration: line-through; }
.runqueue-paused {
  color: #d32f2f;
  font-weight: bold;
  margin-left: 6px;
}
.runqueue-cancel,
.runqueue-resume,
.runqueue-clear {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border-soft, rgba(128, 128, 128, 0.3));
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 11px;
  cursor: pointer;
}
.runqueue-cancel { padding: 0 4px; }
.runqueue-resume:hover { color: var(--accent, #4caf50); border-color: var(--accent, #4caf50); }
.runqueue-clear:hover  { color: #d32f2f; border-color: #d32f2f; }

.artifact-item-delete {
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 4px;
  width: 22px;
  height: 22px;
  padding: 0;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: .55;
  transition: background .12s, opacity .12s, color .12s;
}
.artifact-item-delete:hover {
  background: rgba(248, 81, 73, .18);
  border-color: rgba(248, 81, 73, .4);
  color: #f85149;
  opacity: 1;
}

.artifact-vote,
.artifact-comment-toggle {
  background: rgba(80, 120, 200, .15);
  color: var(--text);
  border: 1px solid rgba(120, 160, 230, .25);
  border-radius: 6px;
  padding: 2px 8px;
  font: inherit;
  font-size: .76rem;
  cursor: pointer;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: background .12s, transform .08s, border-color .12s;
}
.artifact-vote:hover,
.artifact-comment-toggle:hover { background: rgba(80, 120, 200, .28); }
.artifact-vote.is-voted {
  background: rgba(76, 175, 130, .25);
  border-color: rgba(76, 175, 130, .55);
}
.artifact-vote .vote-count,
.artifact-comment-toggle .comment-count {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  min-width: 1ch;
}
.artifact-comments {
  margin-top: 6px;
  padding: 6px 8px;
  background: rgba(0, 0, 0, .18);
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.artifact-comments-list { display: flex; flex-direction: column; gap: 4px; }
.artifact-comment {
  font-size: .82rem;
  padding: 4px 6px;
  border-left: 2px solid rgba(120, 160, 230, .35);
}
.artifact-comment .comment-user { font-weight: 600; color: var(--text); }
.artifact-comment .comment-ts { margin-left: 6px; font-size: .7rem; color: var(--muted); }
.artifact-comment .comment-body { margin-top: 2px; color: var(--text); }
.artifact-comment .comment-body p:first-child { margin-top: 0; }
.artifact-comment .comment-body p:last-child  { margin-bottom: 0; }
.artifact-comment-form { display: flex; gap: 6px; }
.artifact-comment-input {
  flex: 1;
  min-width: 0;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 8px;
  font: inherit;
  font-size: .82rem;
}
.artifact-comment-input:focus { outline: none; border-color: var(--accent); }
.artifact-comment-send {
  background: var(--accent);
  color: #000;
  border: none;
  border-radius: 6px;
  padding: 4px 10px;
  font: inherit;
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
}
.artifact-comment-send:hover { filter: brightness(1.1); }

#chatpane-close {
  background: var(--border);
  color: var(--text);
  border: none;
  border-radius: 8px;
  width: var(--chrome-btn-size); height: var(--chrome-btn-size);
  font-size: 1.05rem;
  line-height: 1;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform .08s, background .15s;
}
#chatpane-close:hover { background: rgba(255, 255, 255, .12); }
#chatpane-close:active { transform: scale(.92); }

/* Empty-state hint — terse + monospace so the chat-pane opens with
   a "ready prompt" feel rather than a paragraph of instructions. */
.chat-empty {
  padding: 10px 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .76rem;
  color: rgba(140, 150, 165, .65);
  text-align: center;
  letter-spacing: 0.02em;
}
.chat-empty code {
  font-family: inherit;
  background: rgba(255, 255, 255, .05);
  color: var(--text);
  padding: 1px 5px;
  border-radius: 2px;
}
.chat-empty .ce-arrow {
  margin: 0 4px;
  color: rgba(140, 150, 165, .45);
}

#chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 6px 4px;
  display: flex;
  flex-direction: column;
  /* Roomier vertical rhythm between messages — was 2px which made
     the list feel like a single dense blob. 10px gives each entry
     visible breathing space without ballooning the scroll height. */
  gap: 10px;
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 145, .35) transparent;
}
#chat-messages::-webkit-scrollbar { width: 8px; }
#chat-messages::-webkit-scrollbar-track { background: transparent; }
#chat-messages::-webkit-scrollbar-thumb {
  background: rgba(120, 130, 145, .35);
  border-radius: 4px;
}
#chat-messages::-webkit-scrollbar-thumb:hover { background: rgba(150, 160, 175, .55); }

/* Meta line reads like a terminal log-line prefix: monospace, muted,
   tight. The visual hierarchy is "timestamp + user header → body";
   the mono + muted treatment keeps the header from competing with
   the message content underneath. */
.chat-msg .chat-meta {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .72rem;
  color: var(--muted);
  margin-bottom: 4px;
  letter-spacing: 0.02em;
}
.chat-msg .chat-user { font-weight: 600; color: var(--text); }
.chat-msg.from-claude .chat-user { color: var(--accent); }
.chat-msg .chat-ts { margin-left: 10px; color: rgba(140, 150, 165, .65); }

.chat-msg .chat-text {
  font-size: .92rem;
  word-wrap: break-word;
  line-height: 1.4;
}
/* Markdown is rendered inside chat-text via renderMd → marked, which wraps
   single-line messages in <p>…</p>. Trim the default paragraph margin so
   single-paragraph bubbles don't get top/bottom whitespace, and tighten
   spacing between paragraphs/lists for multi-paragraph bubbles. */
.chat-msg .chat-text > p:first-child { margin-top: 0; }
.chat-msg .chat-text > p:last-child  { margin-bottom: 0; }
.chat-msg .chat-text p,
.chat-msg .chat-text ul,
.chat-msg .chat-text ol { margin: 6px 0; }
.chat-msg .chat-text ul,
.chat-msg .chat-text ol { padding-left: 22px; }
.chat-msg .chat-text code {
  background: rgba(255, 255, 255, .06);
  padding: 1px 5px;
  border-radius: 2px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .88em;
}
/* Code blocks: ONE flat layer — a single subtle dark slab embedded in
   the chat bubble. The hljs override below kills github-dark's own
   #0d1117 + 1em padding so we don't get a nested 2-layer effect. */
.chat-msg .chat-text pre,
.agent-card-md pre,
.agent-card-body pre,
.artifact-item-text.artifact-md pre {
  background: rgba(0, 0, 0, .28);
  padding: 8px 12px;
  border-radius: 3px;
  border: none;
  overflow-x: auto;
  margin: 8px 0;
  font-size: 0.82em;
  line-height: 1.45;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.chat-msg .chat-text pre code,
.agent-card-md pre code,
.agent-card-body pre code,
.artifact-item-text.artifact-md pre code,
.chat-msg .chat-text pre code.hljs,
.agent-card-md pre code.hljs,
.agent-card-body pre code.hljs,
.artifact-item-text.artifact-md pre code.hljs {
  background: transparent;
  padding: 0;
  display: block;
  color: inherit;
}
/* Full markdown coverage inside chat bubbles — headings, blockquote,
   tables, links, hr, images, mermaid SVG. Keep proportions tight so a
   chat bubble doesn't visually compete with the transcript view. */
.chat-msg .chat-text h1,
.chat-msg .chat-text h2,
.chat-msg .chat-text h3,
.chat-msg .chat-text h4,
.chat-msg .chat-text h5,
.chat-msg .chat-text h6 {
  margin: 0.7em 0 0.3em;
  line-height: 1.25;
  color: #e6edf3;
}
.chat-msg .chat-text h1 { font-size: 1.3em; border-bottom: 1px solid rgba(255,255,255,.08); padding-bottom: 2px; }
.chat-msg .chat-text h2 { font-size: 1.15em; border-bottom: 1px solid rgba(255,255,255,.06); padding-bottom: 2px; }
.chat-msg .chat-text h3 { font-size: 1.05em; }
.chat-msg .chat-text blockquote {
  margin: 6px 0;
  padding: 2px 10px;
  border-left: 3px solid rgba(120,160,220,.4);
  color: rgba(201,209,217,.85);
  background: rgba(255,255,255,.02);
}
.chat-msg .chat-text a { color: #79b8ff; text-decoration: none; }
.chat-msg .chat-text a:hover { text-decoration: underline; }
.chat-msg .chat-text hr {
  border: 0;
  border-top: 1px solid rgba(255,255,255,.08);
  margin: 8px 0;
}
.chat-msg .chat-text img { max-width: 100%; height: auto; border-radius: 4px; }
.chat-msg .chat-text table {
  border-collapse: collapse;
  margin: 6px 0;
  display: block;
  overflow-x: auto;
  font-size: .92em;
}
.chat-msg .chat-text table th,
.chat-msg .chat-text table td {
  border: 1px solid rgba(255,255,255,.1);
  padding: 4px 8px;
}
.chat-msg .chat-text table th { background: rgba(255,255,255,.04); text-align: left; }
/* Mermaid SVG sized to the bubble width. The .conv-mermaid class is
   assigned by renderMermaidInContainer and shared with the transcript
   view; reuse the global svg sizing rule there if it exists. */
.chat-msg .chat-text .conv-mermaid {
  margin: 8px 0;
  background: rgba(255,255,255,.02);
  border-radius: 4px;
  padding: 6px;
  overflow-x: auto;
}
.chat-msg .chat-text .conv-mermaid svg { max-width: 100%; height: auto; }
/* Claude's bubble: subtle green wash + 2px left bar; rounded
   corners stay light (3px) to keep the look "log-pane" rather than
   "iMessage". Padding matches the user bubble exactly. */
.chat-msg.from-claude .chat-text {
  background: rgba(76, 175, 130, .07);
  border-left: 2px solid var(--accent);
  padding: 8px 12px;
  border-radius: 3px;
}
.chat-msg.from-self {
  text-align: right;
}
.chat-msg.from-self .chat-meta {
  justify-content: flex-end;
  text-align: right;
}
/* User's bubble: matching subtle blue wash + 2px right bar. Width
   capped so a long user message doesn't reach the chatpane edge —
   easier to scan the boundary between left-stacked claude bubbles
   and right-stacked user bubbles. */
.chat-msg.from-self .chat-text {
  background: rgba(80, 140, 200, .07);
  border-right: 2px solid #5a99cf;
  padding: 8px 12px;
  border-radius: 3px;
  text-align: left;
  display: inline-block;
  max-width: 85%;
}

/* @<user> chat-only mentions. The card is always badged so the sender +
   other viewers see who the mention was addressed to; .chat-msg-mention-me
   adds an attention-grabbing accent for the recipient specifically. */
.chat-msg.chat-msg-mention .chat-text {
  border-left: 2px solid rgba(220, 180, 90, .6);
  background: rgba(220, 180, 90, .05);
  padding: 6px 10px;
  border-radius: 4px;
}
.chat-msg.chat-msg-mention-me .chat-text {
  border-left: 3px solid rgba(255, 200, 90, .95);
  background: rgba(255, 200, 90, .14);
  box-shadow: 0 0 0 1px rgba(255, 200, 90, .35), 0 0 12px rgba(255, 200, 90, .25);
  animation: chat-mention-me-pulse 1.6s ease-out 2;
}
@keyframes chat-mention-me-pulse {
  0%   { box-shadow: 0 0 0 1px rgba(255, 200, 90, .65), 0 0 24px rgba(255, 200, 90, .55); }
  100% { box-shadow: 0 0 0 1px rgba(255, 200, 90, .35), 0 0 12px rgba(255, 200, 90, .25); }
}

/* @all broadcast mention — addressed to everyone in the session.
   Distinct cool-blue tint so it's recognizably different from a
   personal @<user> ping (warm amber). Combined with chat-msg-mention
   and chat-msg-mention-me (every viewer is a recipient), so override
   the warm amber accents that those classes set above. */
.chat-msg.chat-msg-mention-all .chat-text {
  border-left: 3px solid rgba(100, 170, 255, .9);
  background: rgba(100, 170, 255, .12);
  box-shadow: 0 0 0 1px rgba(100, 170, 255, .35), 0 0 12px rgba(100, 170, 255, .25);
  animation: chat-mention-all-pulse 1.6s ease-out 2;
}
@keyframes chat-mention-all-pulse {
  0%   { box-shadow: 0 0 0 1px rgba(100, 170, 255, .65), 0 0 24px rgba(100, 170, 255, .55); }
  100% { box-shadow: 0 0 0 1px rgba(100, 170, 255, .35), 0 0 12px rgba(100, 170, 255, .25); }
}

/* When the chat pane is collapsed and a mention-to-me has bumped the
   unread counter, swap the #btn-chat badge accent so the user can tell
   "someone addressed me" apart from generic activity. */
#btn-chat.has-mention[data-unread]::after {
  background: rgba(255, 200, 90, .95);
  color: #2a1f00;
}

/* Composer card — single-row: taller textarea on the left, action
   buttons (Stop on top, Send on bottom) stacked vertically on the
   right. The textarea grows in height naturally to ~3 lines so the
   action column has the room it needs. Send always visible; Stop
   only while claude is running (.composer-running). */
#chat-form.composer {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 8px;
  padding: 8px 10px calc(8px + var(--safe-bottom));
  border-top: 1px solid transparent;
  position: relative;
  flex: 0 0 auto;
  margin: 4px 8px calc(4px + var(--safe-bottom));
  background: var(--surface-2, #1a1f2a);
  border-radius: 12px;
  box-shadow: 0 0 0 1px var(--border, rgba(255, 255, 255, .08));
  transition: box-shadow .15s ease;
}
#chat-form.composer:focus-within {
  box-shadow:
    0 0 0 1px rgba(76, 175, 130, .55),
    0 4px 16px rgba(0, 0, 0, .25);
}
#chat-form.composer.composer-running {
  box-shadow:
    0 0 0 1px rgba(138, 180, 248, .35),
    0 4px 16px rgba(0, 0, 0, .25);
}

/* Slash-command + @-mention dropdown — anchored above the composer
   card. Nested INSIDE #chat-form so position:absolute resolves to
   the form's own bounding box; `bottom: calc(100% + 4px)` floats
   the dropdown right above the card's top edge. */
#chat-autocomplete {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 4px);
  max-height: 220px;
  overflow-y: auto;
  background: rgba(28, 32, 42, .96);
  border: 1px solid rgba(120, 160, 230, .35);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .55);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  z-index: 50;
  padding: 4px 0;
  font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
#chat-autocomplete[hidden] { display: none; }
#chat-autocomplete .ac-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 12px;
  cursor: pointer;
  color: var(--text);
}
#chat-autocomplete .ac-item:hover,
#chat-autocomplete .ac-item.active {
  background: rgba(80, 120, 200, .25);
}
#chat-autocomplete .ac-name {
  font-family: 'JetBrains Mono', monospace;
  font-size: 12.5px;
  color: #aac8ff;
  flex-shrink: 0;
}
#chat-autocomplete .ac-desc {
  font-size: 12px;
  color: rgba(255, 255, 255, .55);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}
#chat-autocomplete .ac-empty {
  padding: 8px 12px;
  font-size: 12px;
  color: rgba(255, 255, 255, .35);
  font-style: italic;
}

/* Context chips above the textarea — one pill per detected @-mention
   in the input value, with an × to remove. Sits inside the composer
   card so the focus-within glow includes them. */
.composer-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px 4px 0;
  margin: 0;
}
.composer-chips[hidden] { display: none; }
.composer-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px 4px 2px 6px;
  background: rgba(120, 160, 230, .14);
  border: 1px solid rgba(120, 160, 230, .35);
  border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  line-height: 1.3;
  color: #aac8ff;
  user-select: none;
}
.composer-chip-at { opacity: 0.7; }
.composer-chip-name { color: #c9d1d9; }
.composer-chip-x {
  background: transparent;
  border: 0;
  color: rgba(255, 255, 255, .5);
  cursor: pointer;
  padding: 0 4px;
  margin-left: 2px;
  font-size: 0.95rem;
  line-height: 1;
  border-radius: 4px;
  transition: background .12s ease, color .12s ease;
}
.composer-chip-x:hover {
  background: rgba(255, 255, 255, .12);
  color: var(--fg, #c9d1d9);
}

/* Textarea inside the composer card — borderless + transparent so
   the card's own border/glow is the sole visual edge. Taller min-
   height (3 lines) so the vertically-stacked Send + Stop column
   on the right has room without forcing a tiny single-line input. */
#chat-input {
  flex: 1 1 auto;
  background: transparent;
  color: var(--text);
  border: 0;
  border-radius: 6px;
  padding: 6px 8px;
  font: inherit;
  line-height: 1.45;
  min-width: 0;
  resize: none;
  overflow-y: auto;
  min-height: 4.4em;     /* ~3 lines at line-height 1.45 + padding */
  max-height: 220px;
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 145, .35) transparent;
}
#chat-input::-webkit-scrollbar { width: 6px; }
#chat-input::-webkit-scrollbar-track { background: transparent; }
#chat-input::-webkit-scrollbar-thumb { background: rgba(120, 130, 145, .35); border-radius: 4px; }
#chat-input:focus { outline: none; }
#chat-input:disabled { opacity: .5; cursor: not-allowed; }
#chat-input::placeholder { color: rgba(255, 255, 255, .28); }

/* Composer actions column — vertical stack on the right of the
   textarea. Stop on top (when visible), Send on bottom. The column
   matches the textarea's height via align-self: stretch on the
   form's row, so both buttons share the vertical space. */
.composer-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-end;
  gap: 6px;
  flex: 0 0 auto;
  padding: 0;
  min-width: 90px;
}
.composer-btn {
  height: 34px;
  padding: 0 12px;
  border: 0;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.84rem;
  font-weight: 500;
  transition: background .12s ease, color .12s ease, transform .08s ease;
}
.composer-btn:hover { background: rgba(255, 255, 255, .07); }
.composer-btn:active { transform: scale(.97); }
.composer-btn:disabled { opacity: .35; cursor: not-allowed; }
.composer-icon { width: 16px; height: 16px; display: block; flex: 0 0 auto; }
.composer-btn-label { line-height: 1; }
.composer-btn-kbd {
  font: inherit;
  font-size: 0.66rem;
  padding: 0 4px;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 2px;
  background: rgba(0, 0, 0, .28);
  color: rgba(255, 255, 255, .65);
  opacity: .85;
  line-height: 1.4;
}

/* Send: filled accent disc, dark icon + label. */
.composer-btn-send {
  background: var(--accent, #4caf82);
  color: #08120d;
}
.composer-btn-send:hover {
  background: #5fbe93;
  color: #08120d;
}

/* Mic: voice input toggle. Currently SHIPPED AS DISABLED (button
   carries the disabled attribute in HTML) — visual treatment makes
   that obvious: 40% opacity, dashed border, strikethrough across
   the WHOLE button (icon + label), not-allowed cursor on hover,
   no hover-state lift. Tooltip explains "Voice input — in
   progress · coming soon". Hidden via the `hidden` HTML attribute
   until JS confirms SpeechRecognition is supported, then revealed
   but still disabled. */
.composer-btn-mic {
  background: rgba(255, 255, 255, .03);
  color: rgba(255, 255, 255, .45);
  border: 1px dashed rgba(255, 255, 255, .18);
  opacity: 0.45;
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  text-decoration-color: rgba(255, 255, 255, .55);
  cursor: not-allowed;
  position: relative;
}
.composer-btn-mic .composer-icon {
  opacity: 0.7;
}
.composer-btn-mic:hover {
  /* No hover lift — disabled state should feel inert. */
  background: rgba(255, 255, 255, .03);
  border-color: rgba(255, 255, 255, .18);
  color: rgba(255, 255, 255, .45);
}
.composer-btn-mic:disabled,
.composer-btn-mic[disabled],
.composer-btn-mic[aria-disabled="true"] {
  pointer-events: auto;        /* allow tooltip on hover */
  cursor: not-allowed;
}
/* (Retired) Tiny "soon" badge on the mic button. The strikethrough
   + dashed border + disabled state already read as WIP — the extra
   chip cluttered the composer. */
.composer-btn-mic:hover {
  background: rgba(255, 255, 255, .10);
  border-color: rgba(255, 255, 255, .25);
}
.composer-btn-mic.chat-mic-recording {
  background: rgba(255, 80, 80, .18);
  color: #ff8080;
  border-color: rgba(255, 80, 80, .55);
  animation: chat-mic-pulse 1.4s ease-in-out infinite;
}
.composer-btn-mic.chat-mic-recording:hover {
  background: rgba(255, 80, 80, .28);
  color: #ffb0b0;
}
@keyframes chat-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 80, 80, .35); }
  50%      { box-shadow: 0 0 0 6px rgba(255, 80, 80, 0);   }
}
.composer-btn-mic[hidden] { display: none !important; }

/* Stop: red tinted button next to Send. Visible only when claude
   is running (.composer-running). */
.composer-btn-stop {
  background: rgba(255, 130, 130, .14);
  color: #ff8a8a;
  border: 1px solid rgba(255, 130, 130, .35);
  display: none;
}
.composer-btn-stop:hover {
  background: rgba(255, 130, 130, .24);
  color: #ffb0b0;
  border-color: rgba(255, 130, 130, .55);
}
/* bug-19: composer-blocked styling — read-only viewer typed a
 * message that would be denied (no @mention, no allowed slash).
 * The Send button is also disabled via the disabled attr; this
 * class adds a subtle visual hint on the form itself so the user
 * sees "this won't go through" before they reach for Send. */
#chat-form.composer.composer-blocked {
  border-color: rgba(220, 80, 80, 0.45);
}
#chat-form.composer.composer-blocked #chat-send {
  opacity: 0.4;
  cursor: not-allowed;
}
#chat-form.composer.composer-running .composer-btn-stop {
  display: inline-flex;
}

/* (Retired) .composer-btn-stop + .composer-running swap rules —
   Stop now lives in #claude-typing (the status strip above the
   composer). Send stays always-visible in the composer so users
   can queue messages while claude is running. */

/* Floating toggle on the terminal pane (always available when a session
   is open). Position it top-right so it doesn't collide with btn-expand.
   Sized to fit within the 52px shared pane header. */
#btn-chat {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  right: calc(env(safe-area-inset-right, 0px) + 10px);
  z-index: 50;
  width: var(--chrome-btn-size); height: var(--chrome-btn-size);
  padding: 0;
  border-radius: 8px;
  background: rgba(40, 40, 40, .85);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, .08);
  font-size: .95rem;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
#btn-chat:active { transform: scale(.92); }
#btn-chat.active { background: rgba(80, 120, 200, .35); }

/* Unread badge on the chat toggle when the pane is collapsed and new
   claude content has arrived. Counter ("1".."99") comes from
   data-unread; pure CSS render keeps the button itself a plain
   single-glyph icon. Pulses briefly on first appearance so the user
   notices a new turn arrived (e.g. plan generated post-wizard). */
#btn-chat[data-unread]::after {
  content: attr(data-unread);
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 9px;
  background: #f85149;
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  box-shadow: 0 0 0 2px rgba(40, 40, 40, .9);
  animation: btn-chat-pulse 1.4s ease-out 2;
}
@keyframes btn-chat-pulse {
  0%   { transform: scale(1);   box-shadow: 0 0 0 2px rgba(40, 40, 40, .9), 0 0 0 0 rgba(248, 81, 73, .55); }
  60%  { transform: scale(1.18); box-shadow: 0 0 0 2px rgba(40, 40, 40, .9), 0 0 0 8px rgba(248, 81, 73, 0); }
  100% { transform: scale(1);   box-shadow: 0 0 0 2px rgba(40, 40, 40, .9), 0 0 0 0 rgba(248, 81, 73, 0); }
}

/* Files toggle sits left of #btn-chat in the same fixed cluster. */
#btn-files {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  /* 10 + chrome-btn-size + 8 gap from right edge — desktop value; mobile bumps in @media */
  right: calc(env(safe-area-inset-right, 0px) + 10px + var(--chrome-btn-size) + 8px);
  z-index: 50;
  width: var(--chrome-btn-size); height: var(--chrome-btn-size);
  padding: 0;
  border-radius: 8px;
  background: rgba(40, 40, 40, .85);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, .08);
  font-size: .9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
#btn-files:active { transform: scale(.92); }
#btn-files.active { background: rgba(80, 120, 200, .35); }

/* Plan / Arch / Test toggles. Promoted from in-chatpane tabs to top-level
   chrome buttons sitting alongside files / chat. Each opens its
   own main-pane view (.artifact-main-view) and is mutually-exclusive with
   files and the other artifact views. */
#btn-plan, #btn-arch, #btn-test {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  z-index: 50;
  width: var(--chrome-btn-size); height: var(--chrome-btn-size);
  padding: 0;
  border-radius: 8px;
  background: rgba(40, 40, 40, .85);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, .08);
  font-size: .95rem;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
/* slot 2 / 3 / 4 from the right (chat=0, files=1, then plan/arch/test). */
#btn-plan { right: calc(env(safe-area-inset-right, 0px) + 10px + 2 * (var(--chrome-btn-size) + 8px)); }
#btn-arch { right: calc(env(safe-area-inset-right, 0px) + 10px + 3 * (var(--chrome-btn-size) + 8px)); }
#btn-test { right: calc(env(safe-area-inset-right, 0px) + 10px + 4 * (var(--chrome-btn-size) + 8px)); }
#btn-plan:active, #btn-arch:active, #btn-test:active { transform: scale(.92); }
#btn-plan.active, #btn-arch.active, #btn-test.active { background: rgba(80, 120, 200, .35); }

/* Main-pane artifact view container. Fills the terminal-pane like
   #files-wrap does, with a small header strip on top and a scrollable
   body underneath. */
.artifact-main-view {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  /* Mobile reserves room at the top for the floating chrome
     cluster (5 buttons, 40-44px tall) so scroll content doesn't
     hide under them. Desktop drops the reservation in the rule
     below so the artifact pane takes the entire vertical extent
     of the main area. */
  padding-top: calc(env(safe-area-inset-top, 0px) + var(--pane-header-h));
  box-sizing: border-box;
  /* Sit above the chatpane (z-index:30) so opening Plan/Arch/Test
     actually covers the conversation. Without this the chatpane
     still painted on top and the chrome-icon click looked dead. */
  z-index: 31;
}
@media (min-width: 901px) {
  /* Desktop: artifact takes the entire main-pane vertical space.
     Chrome cluster overlays the top-right; the artifact-header's
     padding-right below keeps the title + refresh button out from
     under the floating buttons. */
  .artifact-main-view {
    padding-top: env(safe-area-inset-top, 0px);
  }
  .artifact-main-view .artifact-header {
    /* Clear the chrome cluster: 5 buttons × 32px + 4 gaps × 8px
       + 10px right inset = ~210px. Round up with a safety margin
       so the title doesn't clip behind the buttons. */
    padding-right: 232px;
  }
}
.artifact-main-view .artifact-header {
  /* Match the chrome cluster's vertical band exactly: the cluster
     sits at top:10px and is --chrome-btn-size tall (32px desktop /
     40px mobile), so its band is ~52px / 60px. We size the header
     to --pane-header-h (52px/56px) and vertical-center its content
     so the title baseline lines up with the floating buttons. */
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--pane-header-h);
  height: var(--pane-header-h);
  padding: 0 16px;
  font-size: .92rem;
  box-sizing: border-box;
  flex: 0 0 auto;
}
.artifact-main-view .artifact-body {
  flex: 1;
  padding: 16px;
  font-size: 1rem;
  line-height: 1.5;
}
/* Phase 3 — center the artifact column the same way the chatpane is
   centered, so opening Plan / Arch / Test feels visually continuous
   with the conversation underneath. The whole artifact-main-view
   surface (background) still fills the main pane edge-to-edge; only
   the header + body content are constrained to the same 880px lane. */
@media (min-width: 901px) {
  .artifact-main-view .artifact-header,
  .artifact-main-view .artifact-body {
    width: 100%;
    max-width: 880px;
    margin-left: auto;
    margin-right: auto;
    box-sizing: border-box;
  }
}

/* ── per-session file explorer (code-review polish) ──────────────────────── */

#files-wrap {
  position: absolute;
  inset: 0;
  display: flex;
  min-height: 0;
  overflow: hidden;
  background: #0d1117;
  /* Same overlay treatment as .artifact-main-view — sits above the
     chatpane (z-index:30) so the file tree + viewer actually
     replaces the conversation visually. */
  z-index: 31;
}

/* ── tree pane ──────────────────────────────────────────────────────────── */
#files-tree-pane {
  /* Tightened 280 → 220 so the file viewer gets more horizontal
     space by default. The collapse button on the tree header
     (◀) drops the pane to a thin strip when the user wants the
     viewer to take everything. */
  width: 220px;
  min-width: 180px;
  border-right: 1px solid rgba(255, 255, 255, .06);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: #0a0e14;
  transition: width .15s ease;
  /* Match the thin scrollbar used by #chat-messages, #conv-messages,
     and the xterm viewport so the file explorer doesn't stand out
     with the browser's default OS-styled scrollbar. */
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 145, .35) transparent;
}
#files-tree-pane::-webkit-scrollbar { width: 8px; }
/* Collapsed-tree state: tree pane drops to a thin strip, just wide
   enough to show the ▶ expand button. The viewer pane fills the
   rest of the wrap. Triggered by the ◀ button in files-header
   (handled by _toggleFilesTreeCollapsed). */
#files-wrap.files-tree-collapsed #files-tree-pane {
  width: 36px;
  min-width: 36px;
  overflow: hidden;
}
#files-wrap.files-tree-collapsed #files-tree-pane #files-tree,
#files-wrap.files-tree-collapsed #files-tree-pane #files-tree-msg,
#files-wrap.files-tree-collapsed #files-tree-pane #files-tree-back,
#files-wrap.files-tree-collapsed #files-tree-pane #files-crumb {
  display: none;
}
#files-wrap.files-tree-collapsed #files-tree-pane #files-header {
  padding: 8px 4px;
  justify-content: center;
}
#files-tree-collapse {
  background: transparent;
  border: 0;
  color: var(--muted, #8b949e);
  padding: 2px 6px;
  cursor: pointer;
  font-size: 0.8rem;
  border-radius: 3px;
  margin-left: auto;
  flex: 0 0 auto;
}
#files-tree-collapse:hover { background: rgba(255, 255, 255, .06); color: var(--text); }

/* Inline file editor — fills the files-view-body with a monospace
   textarea. The Save / Cancel buttons live in the files-view-header
   (toggled visible in renderViewerHeader when v.editing). */
.files-edit-textarea {
  width: 100%;
  height: 100%;
  min-height: 100%;
  background: #0d1117;
  color: var(--text);
  border: 0;
  outline: none;
  padding: 12px 16px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  resize: none;
  box-sizing: border-box;
  white-space: pre;
  tab-size: 2;
}
/* fr-50: CodeMirror 6 editor host. Sized to fill the files-view-body
   pane exactly like the textarea fallback. CM6 injects its own
   .cm-editor + .cm-scroller; we just make the wrapper full-bleed so
   gutters/lines have room. The oneDark theme handles internal colors. */
.files-edit-cm {
  width: 100%;
  height: 100%;
  min-height: 100%;
  background: #282c34;       /* matches oneDark; avoids flash before CM mounts */
  box-sizing: border-box;
}
.files-edit-cm .cm-editor {
  height: 100%;
  font-size: 13px;
}
.files-edit-cm .cm-scroller {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  line-height: 1.5;
}
/* fr-50: file-conflict modal. Centered card with three big choice
   buttons. Mirrors the spawn-modal visual language so it doesn't feel
   like a stray paradigm. */
#file-conflict-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
}
#file-conflict-dialog {
  background: #161b22;
  color: var(--text);
  padding: 20px 24px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  max-width: 480px;
  width: calc(100% - 32px);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
#file-conflict-dialog h2 {
  margin: 0 0 10px;
  font-size: 1.05rem;
  color: #f6b48a;             /* warning amber */
}
#file-conflict-body {
  margin: 0 0 18px;
  font-size: 0.9rem;
  line-height: 1.5;
}
#file-conflict-body code {
  background: rgba(255, 255, 255, 0.08);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 0.85rem;
}
#file-conflict-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}
#file-conflict-actions button {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: var(--text);
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 0.85rem;
  cursor: pointer;
}
#file-conflict-actions button:hover {
  background: rgba(255, 255, 255, 0.05);
}
#file-conflict-force {
  border-color: rgba(246, 180, 138, 0.5) !important;
  color: #f6b48a !important;
}
#file-conflict-reload {
  border-color: rgba(120, 200, 255, 0.5) !important;
  color: #78c8ff !important;
}
#files-edit,
#files-edit-save,
#files-edit-cancel {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, .12);
  color: var(--text);
  padding: 4px 10px;
  border-radius: 4px;
  font-size: 0.78rem;
  cursor: pointer;
  margin-left: 6px;
}
#files-edit:hover, #files-edit-save:hover, #files-edit-cancel:hover {
  background: rgba(255, 255, 255, .06);
}
#files-edit-save {
  background: rgba(76, 175, 130, .14);
  border-color: rgba(76, 175, 130, .35);
  color: #6fdc97;
}
#files-edit-save:hover {
  background: rgba(76, 175, 130, .24);
}
#files-edit-save:disabled { opacity: .5; cursor: wait; }
#files-tree-pane::-webkit-scrollbar-track { background: transparent; }
#files-tree-pane::-webkit-scrollbar-thumb {
  background: rgba(120, 130, 145, .35);
  border-radius: 4px;
}
#files-tree-pane::-webkit-scrollbar-thumb:hover { background: rgba(150, 160, 175, .55); }
/* Both pane headers (tree + viewer) share the same min-height so the split
   reads as one banded row, and reserve enough horizontal space for the
   floating chrome buttons (☰ / 📁 / 💬) that overlay the top edge. */
#files-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  min-height: var(--pane-header-h);
  box-sizing: border-box;
  border-bottom: 1px solid rgba(255, 255, 255, .06);
  font: 12px 'JetBrains Mono', monospace;
  color: var(--text);
  background: rgba(255, 255, 255, .02);
  position: sticky;
  top: 0;
  z-index: 2;
}
#files-tree-back {
  background: transparent;
  border: 0;
  color: var(--text);
  font-size: 1.1rem;
  cursor: pointer;
  padding: 0 4px;
  border-radius: 4px;
}
#files-tree-back:hover { background: rgba(255, 255, 255, .06); }
#files-crumb { flex: 1; overflow-wrap: anywhere; opacity: .8; font-size: 11px; }

#files-tree {
  list-style: none;
  margin: 0;
  padding: 4px 0;
  flex: 1;
}
#files-tree li {
  padding: 5px 12px 5px 8px;
  font: 12.5px 'JetBrains Mono', monospace;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: flex;
  align-items: center;
  gap: 8px;
  border-left: 2px solid transparent;
  transition: background-color .08s, border-color .08s;
}
#files-tree li:hover { background: rgba(80, 120, 200, .08); border-left-color: rgba(80, 120, 200, .35); }
#files-tree li.heavy { opacity: .5; }

/* fr-9 polish: Lucide-style SVG icons match the main app's chrome
   cluster. .ft-ic is the SVG container; the actual <svg.ft-svg>
   lives inside. currentColor lets per-extension CSS tint the
   stroke without forking the SVG markup. */
#files-tree li .ft-ic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: rgba(255, 255, 255, .55);
}
#files-tree li .ft-ic .ft-svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
}
/* Folder = warm yellow (matches the chrome-cluster Files icon's
   intent of "workspace files"). Symlink = blue-link tone. Other
   (FIFO/socket/etc.) = muted neutral. */
#files-tree li .ft-ic.kind-dir       { color: #ffc56b; }
#files-tree li .ft-ic.kind-symlink   { color: #8ad; }
#files-tree li .ft-ic.kind-other     { color: rgba(255,255,255,.3); }
/* Per-extension file-icon tints. Same palette as before; just
   applied to SVG stroke now instead of text background. */
#files-tree li .ft-ic.ext-js   { color: #f7df1e; }
#files-tree li .ft-ic.ext-ts   { color: #6cb1ff; }
#files-tree li .ft-ic.ext-json { color: #c4c4c4; }
#files-tree li .ft-ic.ext-md   { color: #aac8ff; }
#files-tree li .ft-ic.ext-css  { color: #79c0ff; }
#files-tree li .ft-ic.ext-html { color: #ffaa7a; }
#files-tree li .ft-ic.ext-sh   { color: #9cdc90; }
#files-tree li .ft-ic.ext-py   { color: #6cb1ff; }
#files-tree li .ft-ic.ext-go   { color: #5fdcef; }
#files-tree li .ft-ic.ext-rs   { color: #ffaa7a; }
#files-tree li .ft-ic.ext-yml  { color: #ff9090; }
#files-tree li .ft-ic.ext-default { color: rgba(255, 255, 255, .5); }

/* fr-9: git status badge — 1-letter chip next to the name. Colors
   match conventional VCS UI (Modified=green, Added=bright-green,
   Deleted=red, Renamed=blue, Unmerged=orange, Untracked=muted,
   Ignored=dim). */
#files-tree li .ft-git-status {
  display: inline-block;
  width: 14px;
  text-align: center;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
  border-radius: 3px;
  padding: 1px 0;
  flex-shrink: 0;
}
#files-tree li .ft-git-M { background: rgba(76, 175, 130, .18); color: #6fd29a; }
#files-tree li .ft-git-A { background: rgba(120, 220, 130, .20); color: #9be59f; }
#files-tree li .ft-git-D { background: rgba(220, 120, 120, .20); color: #ff9a9a; }
#files-tree li .ft-git-R { background: rgba(120, 170, 255, .20); color: #98c0ff; }
#files-tree li .ft-git-C { background: rgba(120, 170, 255, .20); color: #98c0ff; }
#files-tree li .ft-git-U { background: rgba(240, 180, 80, .25); color: #ffb86b; }
#files-tree li .ft-git-\? { background: rgba(140, 140, 140, .18); color: #b0b0b0; }
#files-tree li .ft-git-\! { background: transparent; color: rgba(255,255,255,.25); }

/* fr-9: per-row download button. Margin-left:auto pushes it to the
   right edge. Now uses a Lucide SVG (download arrow) instead of a
   text glyph. Hidden until row hover on desktop; always-visible at
   .55 opacity on touch where there's no hover state. */
#files-tree li .ft-download {
  margin-left: auto;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, .4);
  cursor: pointer;
  padding: 6px;                   /* big enough hit target without bloating row height */
  border-radius: 4px;
  flex-shrink: 0;
  opacity: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: opacity .12s, background .12s, color .12s;
}
#files-tree li .ft-download .ft-svg { width: 14px; height: 14px; }
#files-tree li:hover .ft-download { opacity: 1; }
#files-tree li .ft-download:hover {
  background: rgba(120, 170, 255, .14);
  color: #98c0ff;
}
#files-tree li .ft-download:active {
  background: rgba(120, 170, 255, .22);
}
/* Touch devices: no hover. Keep the button always-visible at .55
   opacity. Also enlarge for thumb-friendly hit target. */
@media (hover: none) {
  #files-tree li .ft-download {
    opacity: 0.55;
    padding: 10px;                /* 14px icon + 20px padding = 34px tap target */
  }
  #files-tree li .ft-download .ft-svg { width: 16px; height: 16px; }
}

/* fr-9 polish: header back/collapse buttons use SVG icons too.
   Sized to match the chrome-cluster buttons + larger tap targets
   on touch. */
#files-header button .ft-svg,
#files-view-header button .ft-svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
}
#files-header button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
}
@media (hover: none) {
  /* 44px Apple HIG-recommended tap target on touch devices. */
  #files-header button {
    padding: 12px;
    min-width: 44px;
    min-height: 44px;
  }
  #files-header button .ft-svg { width: 18px; height: 18px; }
}

/* Mobile usability bumps for the tree itself:
   - Row min-height of 40-44px so taps don't miss
   - Slightly larger font + more horizontal padding
   - Active-tap feedback via :active background */
@media (hover: none) {
  #files-tree li {
    min-height: 44px;
    padding: 10px 12px;
    font-size: 14px;
    gap: 10px;
  }
  #files-tree li .ft-ic { width: 20px; height: 20px; }
  #files-tree li .ft-ic .ft-svg { width: 18px; height: 18px; }
  #files-tree li:active {
    background: rgba(80, 120, 200, .14);
  }
}

#files-tree-msg {
  padding: 12px;
  font: 12px 'JetBrains Mono', monospace;
  color: #f88;
}

/* ── view pane ──────────────────────────────────────────────────────────── */
#files-view-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  background: #0d1117;
  position: relative;       /* anchor for the floating action bar */
}
#files-view-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 92px 8px 12px;        /* right padding clears #btn-files + #btn-chat (32+8+32+10+10) */
  min-height: var(--pane-header-h);  /* shared with sidebar / file list / chat */
  box-sizing: border-box;
  border-bottom: 1px solid rgba(255, 255, 255, .06);
  font: 12px 'JetBrains Mono', monospace;
  background: rgba(255, 255, 255, .02);
  flex-wrap: nowrap;                 /* directory + filename stay on one line */
  flex-shrink: 0;
  overflow: hidden;                  /* crumbs shrink with ellipsis instead of wrapping */
}
#files-view-header button {
  background: transparent;
  border: 0;
  color: var(--text);
  cursor: pointer;
  font-size: .95rem;
  padding: 3px 8px;
  border-radius: 6px;
  transition: background-color .08s;
  flex-shrink: 0;
}
#files-view-header button:hover { background: rgba(255, 255, 255, .08); }
#files-view-header .header-spacer { flex: 1; }
#files-save { background: rgba(80, 160, 110, .25) !important; font-weight: 600; }
#files-save:hover { background: rgba(80, 160, 110, .40) !important; }
#files-cancel { color: #f88 !important; }
#files-view-back { font-size: 1.2rem !important; }

/* Single-line breadcrumb: directory crumbs are flex-shrinkable with ellipsis;
   the filename (.crumb.last) and separators are flex-shrink:0 so the
   filename always stays fully readable, and earlier path segments truncate
   first if the header is narrow. */
#files-view-crumbs {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: nowrap;
  flex: 1;
  min-width: 0;
  overflow: hidden;
}
#files-view-crumbs .crumb {
  color: rgba(255,255,255,.55);
  cursor: pointer;
  padding: 1px 4px;
  border-radius: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 1;
  min-width: 0;
}
#files-view-crumbs .crumb:hover { background: rgba(255,255,255,.06); color: var(--text); }
#files-view-crumbs .crumb-sep { color: rgba(255,255,255,.25); padding: 0 1px; flex-shrink: 0; }
#files-view-crumbs .crumb.last {
  color: var(--text);
  cursor: default;
  flex-shrink: 0;          /* filename never truncates */
}
#files-view-crumbs .crumb.last:hover { background: transparent; }

#files-view-dirty { color: #fb8; padding: 0 4px; }

/* ── selection action bar (floating, anchored near selection) ───────────── */
#files-action-bar {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 6px 5px 10px;
  background: rgba(20, 24, 34, .96);
  border: 1px solid rgba(120, 160, 230, .45);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .55), 0 0 0 1px rgba(0, 0, 0, .3);
  font: 12px 'JetBrains Mono', monospace;
  flex-wrap: nowrap;
  position: absolute;
  z-index: 50;
  /* JS sets top/left; default off-screen until positioned. */
  top: -9999px;
  left: -9999px;
  max-width: calc(100% - 16px);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  pointer-events: auto;
}
#files-action-label { color: #aac8ff; flex-shrink: 0; font-size: 10.5px; padding-right: 4px; border-right: 1px solid rgba(120, 160, 230, .20); margin-right: 2px; white-space: nowrap; }
.files-action-btn {
  background: rgba(80, 120, 200, .22);
  border: 1px solid rgba(120, 160, 230, .25);
  color: #d8e6ff;
  cursor: pointer;
  font: 11px 'JetBrains Mono', monospace;
  padding: 4px 8px;
  border-radius: 5px;
  transition: background-color .08s, transform .04s;
  white-space: nowrap;
}
.files-action-btn:hover { background: rgba(80, 120, 200, .45); }
.files-action-btn:active { transform: scale(.95); }
/* ── inline comment editor (Add comment) ───────────────────────────────────
   Renders as a faux code line in the viewer at the comment's destination,
   with the language's comment prefix as a non-editable label and an input
   the user types into. Looks like a code line being drafted in place. */
.inline-comment-editor {
  display: flex;
  align-items: stretch;
  font: 13px 'JetBrains Mono', monospace;
  background: rgba(180, 130, 255, .08);
  border-top: 1px solid rgba(180, 130, 255, .35);
  border-bottom: 1px solid rgba(180, 130, 255, .35);
  border-left: 4px solid rgba(180, 130, 255, .55);
  line-height: 1.5;
  margin: 0;
}
.inline-comment-editor .ce-gutter {
  flex-shrink: 0;
  padding: 8px 10px 8px 8px;
  color: rgba(180, 130, 255, .70);
  user-select: none;
  text-align: right;
  min-width: 42px;
  background: rgba(180, 130, 255, .05);
  font-weight: 700;
}
.inline-comment-editor .ce-prefix-wrap {
  flex: 1 1 auto;
  display: flex;
  align-items: baseline;
  padding: 6px 8px;
  background: transparent;
  min-width: 0;
  overflow: hidden;
}
.inline-comment-editor .ce-prefix,
.inline-comment-editor .ce-suffix {
  color: rgba(160, 220, 160, .85);
  font: inherit;
  white-space: pre;
  user-select: none;
  flex-shrink: 0;
}
.inline-comment-editor .ce-usertag {
  color: rgba(180, 200, 255, .9);
  font: inherit;
  font-weight: 600;
}
.inline-comment-editor .ce-input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: 0;
  outline: none;
  color: rgba(180, 230, 180, 1);
  font: inherit;
  padding: 0;
  margin: 0;
  caret-color: #fff;
  -webkit-user-select: text;
  user-select: text;
  /* Multi-line via <textarea>: no resize handle, auto-grow handled in JS */
  resize: none;
  overflow: hidden;
  line-height: 1.5;
  min-height: 1.5em;
  white-space: pre-wrap;
  word-break: break-word;
  vertical-align: top;
}
.inline-comment-editor .ce-input::placeholder {
  color: rgba(255, 255, 255, .25);
  font-style: italic;
}
.inline-comment-editor .ce-hint {
  flex-shrink: 0;
  align-self: center;
  color: rgba(255, 255, 255, .30);
  font: 10.5px 'JetBrains Mono', monospace;
  padding: 0 8px;
  white-space: nowrap;
  user-select: none;
}
.inline-comment-editor .ce-actions {
  flex-shrink: 0;
  display: flex;
  align-items: stretch;
  border-left: 1px solid rgba(180, 130, 255, .15);
}
.inline-comment-editor .ce-actions button {
  background: transparent;
  border: 0;
  color: rgba(255, 255, 255, .60);
  cursor: pointer;
  font-size: 14px;
  padding: 4px 12px;
  font-weight: 700;
}
.inline-comment-editor .ce-actions .ce-save:hover {
  color: #9cdc90;
  background: rgba(150, 220, 140, .10);
}
.inline-comment-editor .ce-actions .ce-cancel:hover {
  color: #f88;
  background: rgba(255, 128, 128, .08);
}
.inline-comment-editor.busy { opacity: .55; pointer-events: none; }

@media (max-width: 900px) {
  /* Match the code-chunk size on mobile so the editor reads as a code line.
     Tradeoff: iOS Safari may auto-zoom on focus (it triggers below 16px) —
     we accept that in exchange for visual consistency with the surrounding
     code, which is what makes the inline editor feel right. */
  .inline-comment-editor { font-size: 12.5px; }
  .inline-comment-editor .ce-actions button { padding: 6px 14px; font-size: 14px; }
  /* Mobile keyboards have no Cmd/Ctrl — hide the desktop hint. */
  .inline-comment-editor .ce-hint { display: none; }
}
#files-action-clear {
  background: transparent;
  border: 0;
  color: rgba(255,255,255,.5);
  cursor: pointer;
  font-size: 14px;
  padding: 0 6px;
  margin-left: auto;
}
#files-action-clear:hover { color: #f88; }

/* ── code viewer body (chunked rendering) ───────────────────────────────── */
#files-view-body {
  flex: 1;
  overflow: auto;
  background: #0d1117;
  -webkit-overflow-scrolling: touch;
  /* Same thin scrollbar as #chat-messages / #conv-messages / xterm /
     #files-tree-pane so all scrollers share one visual language. */
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 145, .35) transparent;
}
#files-view-body::-webkit-scrollbar { width: 8px; height: 8px; }
#files-view-body::-webkit-scrollbar-track { background: transparent; }
#files-view-body::-webkit-scrollbar-thumb {
  background: rgba(120, 130, 145, .35);
  border-radius: 4px;
}
#files-view-body::-webkit-scrollbar-thumb:hover { background: rgba(150, 160, 175, .55); }
.code-chunk {
  display: flex;
  font: 13px 'JetBrains Mono', 'SF Mono', Menlo, monospace;
  line-height: 1.5;
  margin: 0;
  background: #0d1117;
  color: #c9d1d9;
  white-space: pre;
}
.code-chunk.wrap { white-space: pre-wrap; word-break: break-all; }
.code-chunk .ln-gutter {
  flex-shrink: 0;
  padding: 8px 10px 8px 8px;
  color: rgba(255, 255, 255, .25);
  user-select: none;
  text-align: right;
  border-right: 1px solid rgba(255, 255, 255, .04);
  background: rgba(255, 255, 255, .015);
  min-width: 42px;
}
.code-chunk .ln-gutter span { display: block; padding: 0 1px; }
.code-chunk .code-content {
  flex: 1;
  padding: 8px 12px;
  overflow-x: auto;
  margin: 0;
  background: transparent;
  color: inherit;
  /* Re-enable selection: body has user-select:none for app chrome, but the
     viewer is for code review — long-press / drag to select must work. */
  user-select: text;
  -webkit-user-select: text;
  -webkit-touch-callout: default;
  cursor: text;
}
.code-chunk .code-content code {
  user-select: text;
  -webkit-user-select: text;
}
.code-chunk .code-content code { font: inherit; background: transparent; padding: 0; color: inherit; }
.code-chunk ::selection { background: rgba(80, 120, 200, .40); }
#files-view-body .code-chunk:not(:first-child) { border-top: 1px solid rgba(255, 255, 255, .04); }

/* ── Rendered markdown view (file viewer for .md/.markdown) ─────────────── */
.md-rendered {
  padding: 18px 28px 32px;
  max-width: 920px;
  margin: 0 auto;
  color: #c9d1d9;
  font: 14.5px/1.65 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  user-select: text;
  -webkit-user-select: text;
  cursor: text;
}
.md-rendered h1, .md-rendered h2, .md-rendered h3,
.md-rendered h4, .md-rendered h5, .md-rendered h6 {
  margin: 1.6em 0 0.6em;
  line-height: 1.25;
  color: #e6edf3;
}
.md-rendered h1 { font-size: 1.8em; border-bottom: 1px solid rgba(255,255,255,.08); padding-bottom: .3em; }
.md-rendered h2 { font-size: 1.45em; border-bottom: 1px solid rgba(255,255,255,.06); padding-bottom: .25em; }
.md-rendered h3 { font-size: 1.2em; }
.md-rendered h4 { font-size: 1.05em; }
.md-rendered p { margin: 0 0 0.9em; }
.md-rendered ul, .md-rendered ol { margin: 0 0 0.9em; padding-left: 1.6em; }
.md-rendered li { margin: 0.15em 0; }
.md-rendered blockquote {
  margin: 0.6em 0;
  padding: 0.3em 1em;
  border-left: 3px solid rgba(120,160,220,.4);
  color: rgba(201,209,217,.85);
  background: rgba(255,255,255,.02);
}
.md-rendered a { color: #79b8ff; text-decoration: none; }
.md-rendered a:hover { text-decoration: underline; }
.md-rendered code {
  font: 13px 'JetBrains Mono', 'SF Mono', Menlo, monospace;
  background: rgba(255,255,255,.06);
  padding: 1px 5px;
  border-radius: 4px;
  color: #ffd58a;
}
.md-rendered pre {
  background: #0d1117;
  border: 1px solid rgba(255,255,255,.06);
  border-radius: 6px;
  padding: 12px 14px;
  overflow-x: auto;
  margin: 0.6em 0 1em;
}
.md-rendered pre code {
  background: transparent;
  padding: 0;
  color: inherit;
  font-size: 12.5px;
  line-height: 1.55;
}
.md-rendered table {
  border-collapse: collapse;
  margin: 0.6em 0 1em;
  display: block;
  overflow-x: auto;
}
.md-rendered table th, .md-rendered table td {
  border: 1px solid rgba(255,255,255,.1);
  padding: 6px 12px;
}
.md-rendered table th { background: rgba(255,255,255,.04); text-align: left; }
.md-rendered img { max-width: 100%; height: auto; border-radius: 4px; }
.md-rendered hr { border: 0; border-top: 1px solid rgba(255,255,255,.08); margin: 1.4em 0; }
.md-rendered .conv-mermaid { margin: 0.6em 0 1em; }
.md-rendered .conv-mermaid svg { max-width: 100%; height: auto; }

@media (max-width: 720px) {
  .md-rendered { padding: 14px 16px 24px; font-size: 14px; }
  .md-rendered pre { padding: 10px 12px; }
}

/* ── Claude inline card ─────────────────────────────────────────────────── */
.claude-card {
  margin: 0;
  background: linear-gradient(180deg, rgba(180, 130, 255, .06), rgba(80, 120, 200, .03));
  border-top: 2px solid rgba(180, 130, 255, .35);
  border-bottom: 2px solid rgba(180, 130, 255, .35);
  border-left: 4px solid rgba(180, 130, 255, .55);
  font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.claude-card .cc-anchor {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  background: rgba(180, 130, 255, .10);
  font: 11px 'JetBrains Mono', monospace;
  color: #d8c0ff;
  border-bottom: 1px solid rgba(180, 130, 255, .15);
}
.claude-card .cc-anchor .cc-spacer { flex: 1; }
.claude-card .cc-anchor button {
  background: transparent;
  border: 0;
  color: rgba(255,255,255,.55);
  cursor: pointer;
  font-size: 13px;
  padding: 0 6px;
  border-radius: 4px;
}
.claude-card .cc-anchor button:hover { background: rgba(255,255,255,.08); color: var(--text); }
.claude-card .cc-q {
  padding: 8px 12px 4px 12px;
  color: rgba(255,255,255,.85);
  font: 12.5px 'JetBrains Mono', monospace;
  border-bottom: 1px solid rgba(180, 130, 255, .12);
  user-select: text;
  -webkit-user-select: text;
  -webkit-touch-callout: default;
}
.claude-card .cc-q::before { content: '› '; opacity: .5; }
.claude-card .cc-a {
  padding: 10px 14px;
  color: #c9d1d9;
  line-height: 1.55;
  overflow-x: auto;
  user-select: text;
  -webkit-user-select: text;
  -webkit-touch-callout: default;
}
.claude-card .cc-a p { margin: 0 0 .6em 0; }
.claude-card .cc-a p:last-child { margin-bottom: 0; }
.claude-card .cc-a pre {
  background: rgba(0,0,0,.35);
  border: 1px solid rgba(255,255,255,.05);
  border-radius: 4px;
  padding: 8px 10px;
  margin: .5em 0;
  font: 12.5px 'JetBrains Mono', monospace;
  overflow-x: auto;
}
.claude-card .cc-a code { font: 12.5px 'JetBrains Mono', monospace; background: rgba(255,255,255,.06); padding: 1px 4px; border-radius: 3px; }
.claude-card .cc-a pre code { background: transparent; padding: 0; }
.claude-card .cc-a ul, .claude-card .cc-a ol { margin: .3em 0 .6em 0; padding-left: 1.4em; }
.claude-card.pending .cc-a { color: rgba(255,255,255,.55); font-style: italic; }
.claude-card.pending .cc-a::before {
  content: '';
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 8px;
  border-radius: 50%;
  background: rgba(180, 130, 255, .60);
  animation: cc-pulse 1.2s infinite;
}
@keyframes cc-pulse {
  0%, 100% { opacity: 0.3; transform: scale(.8); }
  50%      { opacity: 1.0; transform: scale(1.2); }
}
.claude-card.collapsed .cc-q,
.claude-card.collapsed .cc-a { display: none; }
.claude-card.error { border-left-color: #f88; }
.claude-card.error .cc-anchor { background: rgba(255, 100, 100, .10); color: #ffd0d0; }

#files-view-msg {
  padding: 12px;
  font: 12px 'JetBrains Mono', monospace;
  color: #fc8;
}

@media (max-width: 900px) {
  #files-wrap { flex-direction: column; }
  #files-tree-pane {
    width: 100%;
    min-width: 0;
    border-right: 0;
    border-bottom: 1px solid rgba(255, 255, 255, .06);
    /* Mobile: tree-pane and viewer-pane are mutually exclusive (opening
       a file hides the tree per openFileInViewer in app.js), so the
       tree should fill the entire available area when shown — not
       capped at 40vh leaving 60% empty space. flex:1 + min-height:0
       lets it grow within the column-flex parent. */
    flex: 1;
    min-height: 0;
  }
  /* Bump the shared header height + chrome-button size on mobile (single
     variable cascade — no per-button overrides needed). */
  :root {
    --pane-header-h: 56px;
    --chrome-btn-size: 36px;
  }
  /* On mobile, ☰ (left) and 📁 + 💬 (right) overlay whichever pane is
     at the top. Reserve room on both sides of both file-pane headers. */
  #files-header,
  #files-view-header {
    padding-left: 54px;     /* clear #btn-expand (10 + 36 + ~8 gap) */
    padding-right: 100px;   /* clear #btn-files + #btn-chat (10 + 36 + 8 + 36 + 10) */
  }
  .code-chunk { font-size: 12.5px; }
  #files-action-bar { padding: 5px 6px; gap: 3px; max-width: calc(100vw - 16px); }
  .files-action-btn { padding: 6px 8px; font-size: 11.5px; }
  #files-action-label { font-size: 10px; padding-right: 3px; }
  /* (No popover input mode anymore — Add comment opens an inline editor in
     the code body. The popover stays small and selection-anchored.) */
  #files-view-header { padding: 6px 8px; gap: 4px; }
  .claude-card .cc-q { font-size: 12px; }
  .claude-card .cc-a { font-size: 13px; }
}

/* (Mobile chatpane overrides removed — chatpane is now a main-pane
   view governed by .chat-main-view above. The old @media block forced
   it to position:fixed; z-index:60 which sat ON TOP of the chrome
   buttons and made them unreachable while chat was open.) */

/* Phase 9 step 3 removed the JSONL transcript viewer (#conversation-wrap +
   #readonly-banner + #conv-messages/.conv-* rules). All session views live
   in the chat pane now — agent events stream as cards inside #chat-messages. */
#readonly-banner .ro-label {
  font-weight: 600;
  color: #7ab2ff;
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: 10.5px;
  background: rgba(122, 178, 255, .12);
  padding: 2px 8px;
  border-radius: 4px;
}
#readonly-banner .ro-owner { color: rgba(201, 209, 217, .65); font-style: italic; }
#readonly-banner .ro-owner:not(:empty)::before { content: "owned by "; opacity: .6; }

#conv-messages {
  /* The single scroller for the conversation pane. flex:1 so it fills
     #conversation-wrap below the read-only banner; overflow-y:auto so
     long transcripts get a scrollbar; min-height:0 so flex doesn't
     stretch us past the parent's height (without it, content grows
     beyond the viewport with no scroll). scrollConvToBottom() targets
     this element to keep the latest message in view. */
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  font-family: 'JetBrains Mono Nerd Font', 'JetBrains Mono', Menlo, monospace;
  font-size: 13px;
  line-height: 1.45;
  color: #c9d1d9;
  padding: 8px 8px 40px;
  max-width: none;
  margin: 0;
  user-select: text;
  /* Thin scrollbar to match the chat pane — see #chat-messages above. */
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 130, 145, .35) transparent;
  -webkit-user-select: text;
}
#conv-messages::-webkit-scrollbar { width: 8px; }
#conv-messages::-webkit-scrollbar-track { background: transparent; }
#conv-messages::-webkit-scrollbar-thumb {
  background: rgba(120, 130, 145, .35);
  border-radius: 4px;
}
#conv-messages::-webkit-scrollbar-thumb:hover { background: rgba(150, 160, 175, .55); }

/* Cap the readable column in the readonly viewer so long lines don't
   sprawl across an ultra-wide pane. The OUTER scroller (#conv-messages)
   stays full-width — only the inner content stack is centered and
   capped. Mobile keeps the full width (the breakpoint matches the rest
   of the site's mobile rules). */
#conv-content {
  max-width: 50vw;
  margin: 0 auto;
}
@media (max-width: 900px) {
  #conv-content { max-width: none; margin: 0; }
}

/* ── turn divider ── */
.conv-turn {
  margin-top: 5px;
  padding-top: 5px;
  border-top: 1px solid #21262d;
}
.conv-turn:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* ── base message ── */
.conv-msg {
  padding: 0;
  margin: 0;
  border: none;
  background: none;
}

/* ── user messages: cyan prompt ── */
.conv-msg-user {
  color: #58d5e0;
  font-weight: 600;
  padding: 2px 0;
}

/* Put the ❯ prefix INSIDE the first child block (the <p> from renderMd)
   instead of on .conv-text directly. ::before content is inline; if it sits
   on .conv-text but a block-level <p> follows, the <p> drops to a new line
   and the message looks like "❯ \n message". Putting the pseudo-element on
   the first <p> (or whichever block is first) means ❯ flows inline with
   the paragraph's text — same line. */
.conv-msg-user .conv-text > :first-child::before {
  content: '\276F  ';
  color: #58d5e0;
  letter-spacing: 0.15em;
}

/* ── assistant messages: green-accented block ──
   The transcript interleaves Claude's prose with tool_use + tool_result
   blocks (which can dominate the page visually). Stronger styling here
   makes the actual ANSWER the eye lands on, and the tool blocks
   (dimmed below) read as footnotes around it. */
.conv-msg-assistant {
  margin: 6px 0;
}

.conv-msg-assistant .conv-text {
  color: #e6edf3;
  background: rgba(63, 185, 80, 0.04);
  border-left: 4px solid rgba(63, 185, 80, 0.55);
  border-radius: 0 4px 4px 0;
  padding: 8px 12px 8px 16px;
  margin-top: 2px;
  font-size: 1.02em;
  line-height: 1.6;
}

.conv-msg-assistant .conv-text strong { color: #f0f6fc; }
.conv-msg-assistant .conv-text a { font-weight: 500; }

/* ── title divider ── */
.conv-msg-title {
  text-align: center;
  padding: 14px 0 6px;
  font-size: 0.8em;
  color: #484f58;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ── New roles surfaced by the expanded parser ── */

/* Mode-boundary pill (entered/exited plan or auto mode). Single line,
   centered, muted background — visually subordinate to chat. */
.conv-msg-mode {
  display: flex;
  justify-content: center;
  margin: 6px 0;
}
.conv-mode-pill {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 0.78em;
  color: #8b949e;
  background: #161b22;
  border: 1px solid #30363d;
}
.conv-msg-mode-plan-mode .conv-mode-pill { color: #d2a8ff; border-color: #553a8b; }
.conv-msg-mode-auto-mode .conv-mode-pill { color: #79c0ff; border-color: #2c5282; }

/* Thinking blocks — collapsible reasoning. Dimmed by default so they
   stay subordinate to the final assistant text. */
.conv-msg-thinking {
  margin: 4px 0;
  font-size: 0.9em;
}
.conv-thinking {
  border-left: 2px solid #30363d;
  padding: 2px 0 2px 8px;
  margin: 4px 0;
}
.conv-thinking > summary {
  color: #6e7681;
  cursor: pointer;
  user-select: none;
  font-style: italic;
}
.conv-thinking[open] > summary { color: #8b949e; }
.conv-thinking-body {
  padding: 6px 0 2px;
  color: #8b949e;
  white-space: pre-wrap;
}

/* Framework / API errors — red callout so failures don't blend in. */
.conv-msg-error {
  margin: 6px 0;
  padding: 8px 12px;
  background: rgba(248, 81, 73, 0.08);
  border-left: 3px solid #f85149;
  border-radius: 4px;
}
.conv-error-head {
  color: #ffa198;
  font-weight: 600;
  font-size: 0.85em;
  text-transform: lowercase;
}
.conv-error-body {
  color: #f0f6fc;
  margin-top: 4px;
  white-space: pre-wrap;
}

/* Muted system events — permission updates, queued slash commands. */
.conv-msg-permission,
.conv-msg-queued {
  margin: 4px 0;
  padding: 2px 0;
  font-size: 0.85em;
  color: #8b949e;
  font-style: italic;
}

/* Structured status chips inside #claude-typing's label. */
.claude-typing-primary { font-weight: 500; }
.claude-typing-chip {
  color: #8b949e;
  font-size: 0.92em;
  margin-left: 2px;
}
.claude-typing-tokens { color: #79c0ff; }
.claude-typing-interrupt { color: #d2a8ff; }
.claude-typing-effort { color: #f0883e; }

/* ── markdown inside .conv-text ── */
.conv-text {
  word-break: break-word;
  color: inherit;
  line-height: 1.6;
}

.conv-text p {
  margin: 0.5em 0;
}

.conv-text p:first-child { margin-top: 0; }
.conv-text p:last-child { margin-bottom: 0; }

.conv-text strong {
  color: #f0f6fc;
  font-weight: 600;
}

.conv-text em {
  font-style: italic;
  color: #d2a8ff;
}

.conv-text a {
  color: #58a6ff;
  text-decoration: none;
}

.conv-text a:hover {
  text-decoration: underline;
}

.conv-text code {
  background: rgba(255,255,255,0.1);
  color: #f0f6fc;
  padding: 2px 6px;
  border-radius: 4px;
  font-family: inherit;
  font-size: 0.9em;
}

.conv-text pre {
  background: #161b22;
  border: 1px solid #30363d;
  border-radius: 6px;
  padding: 12px 16px;
  margin: 0.6em 0;
  overflow-x: auto;
  white-space: pre;
}

.conv-text pre code {
  background: none;
  color: inherit;
  padding: 0;
  font-size: 0.88em;
}

.conv-text h1, .conv-text h2, .conv-text h3,
.conv-text h4, .conv-text h5, .conv-text h6 {
  color: #f0f6fc;
  margin: 0.8em 0 0.3em;
  font-weight: 600;
}

.conv-text h1 { font-size: 1.3em; }
.conv-text h2 { font-size: 1.15em; border-bottom: 1px solid #21262d; padding-bottom: 4px; }
.conv-text h3 { font-size: 1.05em; }

.conv-text ul, .conv-text ol {
  margin: 0.3em 0;
  padding-left: 1.5em;
}

.conv-text li {
  margin: 0.15em 0;
}

.conv-text blockquote {
  border-left: 3px solid #3fb950;
  margin: 0.5em 0;
  padding: 0.3em 1em;
  color: #8b949e;
}

.conv-text hr {
  border: none;
  border-top: 1px solid #30363d;
  margin: 0.8em 0;
}

.conv-text table {
  border-collapse: collapse;
  margin: 0.5em 0;
}

.conv-text th, .conv-text td {
  border: 1px solid #30363d;
  padding: 6px 12px;
  text-align: left;
}

.conv-text th {
  background: #161b22;
  font-weight: 600;
  color: #f0f6fc;
}

.conv-text img {
  max-width: 100%;
  border-radius: 6px;
}

/* ── mermaid diagrams ── */
.conv-mermaid {
  margin: 0.6em 0;
  padding: 16px;
  background: #161b22;
  border: 1px solid #30363d;
  border-radius: 8px;
  overflow-x: auto;
}

.conv-mermaid svg {
  max-width: 100%;
  height: auto;
}

/* ── tool calls: terminal command blocks ──
   Deliberately quiet so they don't compete with the assistant's prose
   above. Click to expand the body when you actually need to inspect a
   call. */
.conv-tool-call {
  margin: 1px 0;
  position: relative; /* anchor for the tool-name decorator */
  opacity: 0.72;
  transition: opacity 0.12s;
}
.conv-tool-call:hover,
.conv-tool-call[open] { opacity: 1; }

.conv-tool-call summary {
  cursor: pointer;
  font-family: inherit;
  font-size: 0.82em;
  color: #6e7681;
  padding: 3px 10px;
  padding-right: 68px; /* reserve space for top-right tool-name chip */
  background: rgba(255,255,255,0.025);
  border-radius: 3px;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 6px;
}

.conv-tool-call summary::marker { content: ''; }

.conv-tool-call summary::before {
  content: '\25B8';
  font-size: 0.7em;
  transition: transform 0.15s;
  color: #484f58;
}

.conv-tool-call[open] summary::before {
  transform: rotate(90deg);
}

.conv-tool-call summary:hover {
  background: rgba(255,255,255,0.06);
}

/* Tool name floats as a small chip in the top-right corner of the pane,
   freeing the summary line to use the full width for the call summary. */
.conv-tool-name {
  position: absolute;
  top: 0;
  right: 0;
  color: #e0a050;
  font-weight: 600;
  font-size: 0.7em;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 8px;
  background: rgba(224, 160, 80, 0.12);
  border-radius: 0 4px 0 4px;
  pointer-events: none;
  line-height: 1.4;
}

.conv-tool-summary {
  color: #8b949e;
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}

.conv-tool-body {
  margin: 2px 0 2px 0;
  padding: 6px 0 6px 14px;
  background: none;
  font-family: inherit;
  font-size: 0.85em;
  word-break: break-word;
  color: #8b949e;
  max-height: 400px;
  overflow-y: auto;
  border-left: 2px solid #30363d;
  /* Preserve newlines in the body so multi-question / multi-line tool
     summaries (AskUserQuestion, multi-step Bash heredocs) render
     readably. textContent assigns newlines verbatim; default
     white-space: normal collapses them into single spaces. */
  white-space: pre-wrap;
}
/* Rendered-markdown layout inside tool output: trim default block margins,
   give code blocks the same look as the chat-text variant. The body's
   own indent (padding-left:14px) is preserved by these overrides. */
.conv-tool-body > p:first-child { margin-top: 0; }
.conv-tool-body > p:last-child  { margin-bottom: 0; }
.conv-tool-body p,
.conv-tool-body ul,
.conv-tool-body ol { margin: 6px 0; }
.conv-tool-body ul,
.conv-tool-body ol { padding-left: 22px; }
.conv-tool-body code {
  background: rgba(255, 255, 255, .06);
  padding: 1px 5px;
  border-radius: 3px;
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: .92em;
}
.conv-tool-body pre {
  background: rgba(0, 0, 0, .25);
  padding: 8px 10px;
  border-radius: 6px;
  overflow-x: auto;
  margin: 6px 0;
  white-space: pre;
}
.conv-tool-body pre code { background: transparent; padding: 0; }

/* "… N earlier messages hidden" banner shown when the transcript exceeds
   TRANSCRIPT_RENDER_CAP. Sits at the very top of the conv-content, muted
   so it doesn't compete with real content. */
.conv-truncation-note {
  padding: 6px 10px;
  margin: 4px 0 8px 0;
  font-size: .78rem;
  color: var(--muted);
  text-align: center;
  font-style: italic;
  border-bottom: 1px dashed var(--border);
}

/* ── tool results ── */
.conv-msg-result {
  margin: 2px 0;
  opacity: 0.72;
  transition: opacity 0.12s;
}
.conv-msg-result:hover { opacity: 1; }

.conv-tool-result summary {
  cursor: pointer;
  font-family: inherit;
  font-size: 0.82em;
  color: #6e7681;
  list-style: none;
  padding: 3px 0;
  background: none;
}

.conv-tool-result summary::marker { content: ''; }

.conv-result-prefix {
  color: #484f58;
}

.conv-result-first-line {
  color: #8b949e;
}

.conv-result-error {
  color: #f85149;
}

/* ── Claude typing indicator inside the chat pane ──
   Floats just above #chat-form (which is position:relative) — same
   pattern #chat-autocomplete uses. position:absolute means the
   indicator is OUT OF FLEX FLOW, so its show/hide toggle never resizes
   the #chat-messages slot. (Previous design used a fixed-height flex
   sibling, which still reflowed by ±30px on every hidden↔visible
   transition; mobile users saw the chat content "jump up and down"
   every time a turn started or ended.)
   The label text overflows with ellipsis instead of wrapping. The
   indicator carries only the live spinner text — no "Claude is
   working…" fallback chatter — so when claude is idle the strip is
   invisible (visibility:hidden, layout unchanged). */
.claude-typing {
  /* Declared in HTML as a direct child of #chatpane (sibling of
     #chat-messages and #chat-form). Always rendered as a 30px tall
     flex item so the chat scroll region's height is constant — the
     spinner's hidden↔visible toggle, glyph cycles (✻→✦→✺…), and
     status-line text updates never reflow the chat content above it.
     When idle, visibility:hidden hides the dots+label while the slot
     stays reserved (see .claude-typing[hidden] below). Earlier this
     was an absolute overlay floating above #chat-form which still
     left enough visual ambiguity (overlapping chat content, rapid
     glyph repaints) for users to perceive movement. */
  flex: 0 0 22px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 14px;
  color: #8b949e;
  font-size: 0.78rem;
  /* Composer card below has its own visual boundary now; the dashed
     border above the status row was redundant chrome. */
  border-top: 0;
  background: transparent;
  overflow: hidden;
  white-space: nowrap;
  /* Tell the browser this strip is layout/paint-independent — glyph
     swaps and text changes inside don't propagate reflow to siblings. */
  contain: layout style paint;
}
.claude-typing-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Stop button: visible only while claude is running. The wrapping
   strip already hides itself when idle (visibility:hidden), so we
   don't need a separate hide rule — when the strip is invisible, so
   is the button. The kbd hint mirrors the Esc-to-interrupt binding. */
/* (Retired) #claude-stop moved from the status strip into the
   composer's actions row as a proper .composer-btn-stop sitting
   next to Send. The taller two-row composer gives both buttons
   room to breathe without crowding the slim status strip. */
/* Token meter sits at the right edge of the status row via its
   own margin-left:auto rule — no extra rule needed here. */
.claude-typing[hidden] {
  /* The global !important display:none rule on the `hidden` attribute
     at the top of this file would otherwise collapse the slot on
     every spinner toggle, undoing the permanent-slot scheme — hence
     the mirroring !important here. visibility hides the visuals; the
     30px slot stays reserved. */
  display: flex !important;
  visibility: hidden;
}
/* Distinct visual states (research item #7):
   - thinking: default muted gray + accent dots (claude is just chewing)
   - running: blue tint, brighter dots (a tool is in-flight)
   - awaiting: amber, attention-getting (claude is blocked on the user)
   - done: muted accent, no dots animation (grace before retiring)
   - error: red tint, persistent until cleared
   Each class targets the strip and the dots inside, so the
   indicator is readable from peripheral vision. */
.claude-typing-running { color: #8ab4f8; }
.claude-typing-running .claude-typing-dots span { background: #8ab4f8; }
.claude-typing-awaiting {
  color: #ffb86b;
  /* Slight pulse on the strip background to draw the eye — user must
     act, this isn't just claude chugging. */
  background: rgba(255, 184, 107, .06);
}
.claude-typing-awaiting .claude-typing-dots span { background: #ffb86b; }
.claude-typing-done { color: var(--accent, #4caf82); }
.claude-typing-done .claude-typing-dots { visibility: hidden; }
.claude-typing-error { color: #ff8c69; }
.claude-typing-error .claude-typing-dots span { background: #ff8c69; }
/* (Retired) Stop hide-rules — Stop now lives in the composer and
   is toggled via #chat-form.composer-running, which is set true
   only for thinking / running / awaiting states (see
   _renderClaudeTyping in app.js). */
.claude-typing-dots {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.claude-typing-dots span {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #58a6ff;
  opacity: 0.3;
  animation: claude-typing-pulse 1.2s infinite ease-in-out;
}
.claude-typing-dots span:nth-child(2) { animation-delay: 0.18s; }
.claude-typing-dots span:nth-child(3) { animation-delay: 0.36s; }
@keyframes claude-typing-pulse {
  0%, 60%, 100% { opacity: 0.3; transform: scale(0.9); }
  30%           { opacity: 1;   transform: scale(1.1); }
}

/* ── Inline menu picker inside a chat message ──
   When a chat message carries menu options (meta.kind === 'menu'),
   the whole message is one unified card: the lead/question is the
   card header, the option buttons are flat rows inside the same
   bordered container, and a hint footer sits below. The
   `chat-msg-menu` class is added by renderChatMessage in app.js. */
/* Menu chat row — no border, no background fill. The perm-modal
   popup is the action surface; the chat row is just history + a
   "↗ open in popup" deferred hint (yellow, set by .chat-menu-deferred
   below). Inline content keeps the chat-msg defaults so menu rows
   sit visually flat next to regular chat bubbles. */
.chat-msg.chat-msg-menu {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
}
.chat-msg.chat-msg-menu:not(.chat-msg-menu-collapsed) {
  background: transparent;
  border: none;
}
/* Active tool permission menus ("Allow ExitPlanMode?", "Allow Bash?",
   …) hide from the chat list entirely — the perm_request agent-event
   already breadcrumbs them inside the chrome batch (one row "perm
   asked · Bash"), and the modal popup is the answer surface. Once
   resolved, .chat-msg-menu-collapsed wins this rule's not() filter
   and the row reappears as "✓ Picked [N] label" history. AskUser-
   Question menus (no target.tool) stay visible because the question
   text is human-readable content worth keeping in the chat. */
.chat-msg.chat-msg-menu.chat-msg-menu-perm:not(.chat-msg-menu-collapsed) {
  display: none;
}
/* Active menu row is click-to-reopen-modal — the cursor + subtle
   hover background tell the user this row is interactive. The old
   '↗ Awaiting answer — open in popup' hint line was retired in
   favour of clicking the row itself. */
.chat-msg.chat-msg-menu.chat-msg-menu-active {
  cursor: pointer;
}
.chat-msg.chat-msg-menu.chat-msg-menu-active:hover {
  background: rgba(255, 255, 255, .02);
}
/* Menu chat rows now inherit the .from-claude .chat-text bubble
   (green wash, 2px accent left bar, rounded corners, 8×12 padding)
   — claude-authored content is rendered consistently across plain
   chat rows + menu rows + agent-card-md bodies. The base
   .chat-msg.chat-msg-menu rule above zeros the OUTER row framing;
   .from-claude .chat-text supplies the INNER bubble. */
.chat-msg.chat-msg-menu .chat-text {
  margin: 0 0 4px 0;
}
/* Compact resolved-line that REPLACES the option-buttons block once
   the menu is answered. Same selector also handles the "↗ open in
   popup" deferred state on still-active menus. */
/* Resolved-menu rendering folded into a single inline row in
   renderChatMessage: "<question>: ✓ Picked [N] label". This block
   only styles the few cases that still render as their own line —
   ↪ Superseded / (no longer active) — plus the inline question +
   answer spans used by the merged row. The question span is muted,
   the answer span (with ✓) is the base color. */
.chat-menu-resolved-inline {
  display: inline;
  font-size: 0.92rem;
  color: var(--muted, #8b949e);
}
.chat-menu-resolved-q {
  color: var(--muted, #8b949e);
}
.chat-menu-resolved-a {
  color: var(--fg, #c9d1d9);
}
.chat-menu-resolved.chat-menu-deferred {
  color: #f0b440;
  cursor: pointer;
  user-select: none;
}
.chat-menu-resolved.chat-menu-deferred:hover {
  text-decoration: underline;
}

/* Collapsed menu card — applied to .chat-msg.chat-msg-menu once the
   menu has been answered or superseded. The lead text + question are
   dropped from the bubble (question is preserved as a tooltip on hover),
   so the whole entry shrinks to "user · timestamp · ✓ Picked [N] label".
   This keeps the chat history from being dominated by tall picker cards
   long after the decision was made. */
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed {
  /* Resolved menus are already flat — no extra border tweaks needed
     since the base .chat-msg-menu rule above zeroed everything. */
  padding: 0;
}
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-menu-resolved {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
/* Resolved menu rows (permission asks + AskUserQuestion answers
   alike): the "Allow Bash: ... : ✓ Picked [N] label" line is
   procedural chrome, not user content. Italic + smaller +
   muted so it recedes visually while staying readable. */
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-text,
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-menu-resolved-inline,
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-menu-resolved-q,
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-menu-resolved-a {
  font-size: 0.82rem;
  font-style: italic;
  color: var(--muted, #8b949e);
}
/* Keep the ✓ Picked answer slightly brighter than the question so
   the eye still locks onto the decision. Italic + muted base, but
   answer ticks back up to readable. */
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-menu-resolved-a {
  color: rgba(201, 209, 217, .82);
}
/* Resolved-menu question body — kept visible above the ✓ Picked line
   so the user can see what was asked. Inherits the base chat-text
   font/size/color so the resolved menu reads as a normal chat
   message; the blockquote loses its left-border bar to match. The
   single visual cue that this is a "resolved menu" is the green
   ✓ Picked line below, not the question's framing. */
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-text-resolved {
  margin-bottom: 2px;
}
.chat-msg.chat-msg-menu.chat-msg-menu-collapsed .chat-text-resolved blockquote {
  margin: 0;
  padding: 0;
  border: none;
  color: inherit;
}

/* ── waiting spinner ── */
.conv-waiting {
  text-align: center;
  padding: 60px 20px;
  color: #484f58;
  font-size: 0.85rem;
}

.conv-waiting::before {
  content: '';
  display: inline-block;
  width: 20px;
  height: 20px;
  border: 2px solid #30363d;
  border-top-color: #3fb950;
  border-radius: 50%;
  animation: conv-spin 0.8s linear infinite;
  margin-right: 8px;
  vertical-align: middle;
}

@keyframes conv-spin {
  to { transform: rotate(360deg); }
}

/* ── agent-mode session pane (phase 3) ───────────────────────────────
   Replaces the xterm.js terminal for sessions spawned with
   mode='agent'. Each SDK event renders as its own card so the
   conversation timeline is scannable. The chat-pane menu cards
   (phase 2) handle interactive flows — permission cards here are
   one-line breadcrumbs pointing to the chat.                          */
.agent-log {
  flex: 1;
  overflow: auto;
  padding: 16px;
  background: var(--bg-elev, #0d1117);
  color: var(--fg, #c9d1d9);
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 0.88rem;
  line-height: 1.5;
}
/* Quiet single-line rows. No card chrome — borders, backgrounds, and
   nested boxes were making the timeline read like a stack of dialogs.
   Each event is just text now: timestamp · kind · summary. The body
   slides in beneath as an indented continuation when expanded; only
   embedded code/pre blocks keep their own subtle background so a
   stack trace or tool result stays visually distinct from prose. */
.agent-card {
  margin: 0;
  padding: 2px 0;
  background: transparent;
  border: none;
  border-radius: 0;
}
.agent-card-head {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-size: 0.78rem;
  color: var(--muted, #8b949e);
  cursor: pointer;
  user-select: none;
}
.agent-card-head:hover { color: var(--fg, #c9d1d9); }
/* Chevron pinned to the right edge of the head. */
.agent-card .agent-card-head::after {
  content: '▸';
  margin-left: auto;
  color: var(--muted, #8b949e);
  font-size: 0.68rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  opacity: 0.6;
  transition: opacity 0.12s ease;
}
.agent-card.agent-card-expanded .agent-card-head::after { content: '▾'; opacity: 0.9; }
/* Inline one-liner summary in the head — visible whether the card is
   collapsed or expanded. Truncates with ellipsis instead of wrapping. */
.agent-card-summary {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 0.78rem;
}
.agent-tool-summary.agent-card-summary {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: transparent;
  padding: 0;
}
/* The body is hidden when the card is collapsed. */
.agent-card.agent-card-collapsed .agent-card-body { display: none; }
/* Expanded body — indent under the head so the structural relationship
   is visual, not framing. No background, no border. */
.agent-card.agent-card-expanded .agent-card-body {
  padding: 4px 0 4px 14px;
  border-left: 2px solid rgba(255, 255, 255, .06);
  margin-top: 2px;
}
/* Assistant text (claude's prose) wears the exact same from-claude
   bubble as the chat-msg path — same wash, same 2px accent bar,
   same 3px corner radius, same padding. Single source of truth
   for "claude is talking" styling. Tool input / tool result blobs
   keep the muted default frame from the rule above. */
.agent-card.agent-card-expanded .agent-card-md {
  background: rgba(76, 175, 130, .07);
  border: none;
  border-left: 2px solid var(--accent);
  border-radius: 3px;
  padding: 8px 12px;
  margin-top: 4px;
  font-size: 0.92rem;
  line-height: 1.55;
}

/* 2026-05-17 user-report (mobile): "the message is displayed as
   part of the chrome batch". On mobile/narrow viewport, the
   assistant_text card was visually merging with the chrome batches
   above/below it because there was no whole-card framing — only
   the inner body had the green wash. Add explicit margins +
   a subtle background to the WHOLE card so it stands out as a
   distinct "claude reply" bubble, never blendable with chrome.
   The .agent-card-claude head kind also gets bolder + a chip
   look so the "claude" label is unmistakable. */
.agent-card.agent-card-assistant_text {
  margin: 14px 0 !important;
  padding: 10px 14px;
  background: rgba(76, 175, 130, .04);
  border-radius: 8px;
  border-left: 3px solid var(--accent);
}
.agent-card.agent-card-assistant_text .agent-card-head {
  margin-bottom: 4px;
  opacity: 1;
}
.agent-card.agent-card-assistant_text .agent-card-claude {
  background: rgba(76, 175, 130, .14);
  color: rgba(76, 175, 130, 1);
  padding: 2px 8px;
  border-radius: 12px;
  font-weight: 600;
  font-size: 0.74rem;
  letter-spacing: 0.02em;
}
.agent-card.agent-card-assistant_text .agent-card-md {
  background: transparent;
  border-left: none;
  padding: 0;
}
@media (max-width: 900px) {
  .agent-card.agent-card-assistant_text {
    margin: 16px 0 !important;
    padding: 12px 14px;
  }
}
/* tool_use input + tool_result preview — single subtle background so
   structured payloads stay visually distinct from surrounding prose. */
.agent-card-tool-input {
  background: rgba(255, 255, 255, .03);
  padding: 6px 8px;
  border-radius: 3px;
  overflow-x: auto;
  font-size: 0.76rem;
  margin: 4px 0;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 400px;
  overflow-y: auto;
}
.agent-card-ts {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--muted, #8b949e);
  font-size: 0.72rem;
}
.agent-card-kind {
  font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Drop the chip pill — colour alone is enough to distinguish kinds. */
  padding: 0;
  border-radius: 0;
  background: transparent;
}
.agent-card-claude { color: rgba(120, 200, 140, .95); }
.agent-card-tool   { color: rgba(140, 180, 220, .95); }
.agent-card-result { color: rgba(180, 200, 160, .85); }
.agent-card-error  { color: rgba(255, 130, 130, .95); }
.agent-card-done   { color: rgba(120, 200, 140, 1); }
.agent-card-turn   { color: rgba(220, 180, 90, .95); }
.agent-card-perm   { color: rgba(220, 180, 90, .95); }
.agent-mute        { color: var(--muted, #8b949e); }
.agent-card-body {
  font-size: 0.85rem;
  color: var(--fg, #c9d1d9);
}
.agent-card-md p:first-child { margin-top: 0; }
.agent-card-md p:last-child  { margin-bottom: 0; }
/* Comfortable per-paragraph + per-list breathing room so claude's
   markdown doesn't read as one wall of text. The default browser
   margins are usually 16px; we pick 10px which keeps the card from
   ballooning while still being clearly separated. */
.agent-card-md p,
.agent-card-md ul,
.agent-card-md ol {
  margin: 10px 0;
}
.agent-card-md ul,
.agent-card-md ol {
  padding-left: 24px;
}
.agent-card-md li { margin: 4px 0; }
.agent-card-md h1, .agent-card-md h2, .agent-card-md h3,
.agent-card-md h4, .agent-card-md h5, .agent-card-md h6 {
  margin: 14px 0 6px 0;
  font-weight: 600;
}
.agent-card-md blockquote {
  margin: 8px 0;
  padding-left: 10px;
  border-left: 2px solid rgba(255, 255, 255, .12);
  color: var(--muted, #8b949e);
}
/* Note: pre / pre code rules for .agent-card-md and .agent-card-body
   live in the unified block earlier in this file (around the
   .chat-msg .chat-text pre rule) — one flat layer across every
   chat/agent surface. Keep this comment as a breadcrumb so future
   edits don't reintroduce a per-surface fork. */
.agent-card-md code,
.agent-tool-summary,
.agent-card-body code {
  background: rgba(255, 255, 255, .05);
  padding: 1px 5px;
  border-radius: 3px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.84rem;
}
.agent-prompt {
  font-style: italic;
  color: var(--muted, #8b949e);
}
/* Phase 1 retired the per-card <details> elements (.agent-card-collapse).
   The whole card body is now toggled via the head's click handler and
   the .agent-card-expanded / .agent-card-collapsed classes above. */

/* (Retired — #agent-status-strip was merged into #claude-typing.
   The unified indicator's existing dots animation + label slot
   carry both 'agent is busy' (chrome) and 'claude is writing'
   (assistant_text) signals now.) */

/* Phase 9 step 3 — read-only banner sits at the top of the chatpane
   for guest / share-token viewers. Compact single row; sticky to the
   chatpane's top edge so it stays visible as the user scrolls the
   chat-messages list underneath. The chat input stays enabled because
   guests can still post discussion replies + /td /fr /bug; the
   server's handleChatMessage is what gates claude-routing. */
.chatpane-readonly-banner {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
  align-items: baseline;
  padding: 8px 14px;
  background: rgba(210, 153, 34, .12);
  border-bottom: 1px solid rgba(210, 153, 34, .35);
  font-size: 0.82rem;
  color: var(--fg, #c9d1d9);
  position: sticky;
  top: 0;
  z-index: 6;
}
.chatpane-readonly-banner .ro-label {
  font-weight: 600;
  color: #f0b440;
}
.chatpane-readonly-banner .ro-owner {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 600;
}
.chatpane-readonly-banner .ro-hint {
  color: var(--muted, #8b949e);
  font-size: 0.76rem;
}

/* Count badge for combined consecutive cards. Subtle "× 3" appended
   after the kind chip so a long run of reattaches / iter starts / etc.
   reads as one row. */
.agent-card-count {
  font-size: 0.72rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 0 4px;
}

/* Chrome batch — consecutive low-info events (turn/iter/hook/perm/etc.)
   collapse into one compact indicator. Head is a single muted row; the
   body (when expanded) lists every individual event on its own
   timestamped line so investigation is still one click away. */
.agent-card-chrome {
  opacity: 0.78;
}
.agent-card-chrome .agent-card-head {
  font-size: 0.78rem;
}
.agent-chrome-kind-head {
  letter-spacing: 0.04em;
}
/* Leading ▸ glyph on chrome batch heads — visual indicator that
   this row aggregates N child events. ::after on .agent-card-head
   adds the expand/collapse chevron at the END; this one anchors
   the START so the row reads "▸ × N <latest event>". */
.agent-chrome-glyph {
  flex: 0 0 auto;
  font-size: 0.78rem;
  letter-spacing: 0;
  opacity: 0.8;
}
.agent-card-expanded .agent-chrome-glyph {
  /* Rotated glyph on expand looks redundant with the trailing
     chevron — keep the leading one static so it remains the "this
     is a chrome batch" identifier regardless of expansion. */
}
/* Outcome chip on the chrome batch head — replaces the standalone
   turn-footer row. Shows "✓ done · 6.4s · 6 in / 150 out · 54.7k
   cached" once the turn's result event lands. Tinted by outcome. */
.agent-chrome-outcome {
  flex: 0 0 auto;
  margin-left: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  padding: 1px 6px;
  border-radius: 3px;
  white-space: nowrap;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.agent-chrome-outcome-ok {
  color: rgba(76, 175, 130, .9);
  background: rgba(76, 175, 130, .08);
}
.agent-chrome-outcome-warn {
  color: #ffb86b;
  background: rgba(255, 184, 107, .10);
}
.agent-chrome-outcome-glyph { font-weight: 600; }
.agent-chrome-outcome-tok,
.agent-chrome-outcome-dur,
.agent-chrome-outcome-cache {
  font-variant-numeric: tabular-nums;
}
@media (max-width: 900px) {
  /* Drop the cache portion on narrow screens — the rest of the
     chip (✓ + duration + in↓/out↑) carries the actionable info.
     Full breakdown still in the title= tooltip on long-press. */
  .agent-chrome-outcome-optional { display: none; }
  .agent-chrome-outcome { max-width: 180px; font-size: 0.66rem; padding: 1px 4px; }
}
/* The legacy .turn-footer rule still exists for any old DOM the
   user has cached, but new turns won't emit one. Hide if present. */
.turn-footer { display: none; }
.agent-chrome-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 0 2px;
}
/* Collapsible JSON tree — rendered for tool_use input + tool_result
   content that parses as JSON. Each object/array uses <details>
   with the bracket counts on the summary; primitives get color
   classes. Default open at depth 0-1, closed deeper. */
.agent-json {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.76rem;
  line-height: 1.5;
  background: rgba(0, 0, 0, .22);
  border-radius: 4px;
  padding: 6px 10px;
  max-height: 360px;
  overflow: auto;
  color: var(--fg, #c9d1d9);
}
.agent-json-error { border-left: 2px solid rgba(255, 130, 130, .6); }
.agent-json .json-block { margin: 0; }
.agent-json .json-summary {
  cursor: pointer;
  user-select: none;
  list-style: none;
  display: inline;
}
.agent-json .json-summary::before {
  content: '▸';
  display: inline-block;
  width: 12px;
  opacity: 0.5;
  font-size: 0.7em;
  transform: translateY(-1px);
}
.agent-json details[open] > .json-summary::before { content: '▾'; }
.agent-json summary::-webkit-details-marker { display: none; }
.agent-json .json-item {
  margin-left: 14px;
  padding: 1px 0;
}
.agent-json .json-key { color: #8ab4f8; }
.agent-json .json-idx { color: rgba(255, 255, 255, .35); font-size: 0.92em; }
.agent-json .json-colon { color: rgba(255, 255, 255, .45); margin-right: 2px; }
.agent-json .json-str { color: #b5f4a5; word-break: break-word; }
.agent-json .json-num { color: #ffb86b; }
.agent-json .json-bool { color: #c792ea; }
.agent-json .json-null { color: rgba(255, 255, 255, .45); font-style: italic; }
.agent-json .json-bracket { color: rgba(255, 255, 255, .55); }
.agent-json .json-str-multiline {
  display: block;
  margin: 2px 0 2px 14px;
  padding: 6px 10px;
  background: rgba(0, 0, 0, .25);
  border-radius: 3px;
  font-size: 0.96em;
  color: #b5f4a5;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow: auto;
}

/* Edit / Write / MultiEdit tool calls render as a styled diff
   inside the chrome-batch expanded body. Unified format, monospace,
   with -/+ glyphs and bg tints. Long diffs are scrollable via
   max-height on the body so the chat-pane stays compact. */
.agent-diff {
  margin: 4px 0;
  border-radius: 4px;
  overflow: hidden;
  background: rgba(0, 0, 0, .18);
  border: 1px solid rgba(255, 255, 255, .06);
}
.agent-diff-head {
  display: flex;
  gap: 8px;
  align-items: baseline;
  padding: 4px 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  color: var(--muted, #8b949e);
  background: rgba(255, 255, 255, .03);
  border-bottom: 1px solid rgba(255, 255, 255, .05);
}
.agent-diff-path {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg, #c9d1d9);
}
.agent-diff-stat {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
.agent-diff-body {
  margin: 0;
  padding: 4px 0;
  max-height: 360px;
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.76rem;
  line-height: 1.45;
  background: transparent;
}
.agent-diff-add,
.agent-diff-del,
.agent-diff-ctx {
  padding: 0 10px;
  white-space: pre-wrap;
  word-break: break-word;
}
.agent-diff-add {
  background: rgba(80, 200, 120, .10);
  color: #6fdc97;
}
.agent-diff-del {
  background: rgba(255, 130, 130, .10);
  color: #ff8a8a;
}
.agent-diff-ctx { color: var(--muted, #8b949e); opacity: 0.7; }
.agent-diff-sep {
  height: 1px;
  margin: 6px 0;
  background: rgba(255, 255, 255, .06);
}

/* Plan-item ▶ Run button + status chip. The Run button sends a
   chat message scoped to the item; the chip surfaces the last
   run's outcome (running / success / error) so the user can scan
   plan progress at a glance. */
.artifact-item-run {
  background: rgba(76, 175, 130, .14);
  color: #6fdc97;
  border: 1px solid rgba(76, 175, 130, .35);
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 0.74rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
.artifact-item-run:hover {
  background: rgba(76, 175, 130, .24);
  border-color: rgba(76, 175, 130, .55);
  color: #9fe6b8;
}
.artifact-item-run:active { transform: scale(.96); }
.artifact-item-run:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: rgba(255, 255, 255, .04);
  color: rgba(255, 255, 255, .45);
  border-color: rgba(255, 255, 255, .12);
}
.artifact-item-run:disabled:hover {
  background: rgba(255, 255, 255, .04);
  border-color: rgba(255, 255, 255, .12);
  color: rgba(255, 255, 255, .45);
}
.artifact-item-run-status {
  padding: 1px 6px;
  border-radius: 3px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  margin-right: 4px;
  white-space: nowrap;
}
/* Dependency chip — shown when a plan item carries dependsOn:[ids].
   Amber when any prereq is unmet (blocked), muted-green when all
   are done (ready). The chip itself is informational; the gate is
   on the ▶ Run button. */
.artifact-item-deps {
  padding: 1px 6px;
  border-radius: 3px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  margin-right: 4px;
  white-space: nowrap;
  cursor: help;
}
.artifact-item-deps-blocked {
  background: rgba(240, 180, 64, .14);
  color: #f0b440;
}
.artifact-item-deps-ok {
  background: rgba(76, 175, 130, .10);
  color: rgba(76, 175, 130, .82);
}
.artifact-run-running {
  background: rgba(138, 180, 248, .14);
  color: #8ab4f8;
  animation: artifact-run-pulse 1.5s ease-in-out infinite;
}
.artifact-run-success {
  background: rgba(76, 175, 130, .14);
  color: #6fdc97;
}
.artifact-run-error {
  background: rgba(255, 130, 130, .14);
  color: #ff8a8a;
}
@keyframes artifact-run-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .5; }
}

/* Task-burst grouping — consecutive turn-groups within ~5min of
   each other share a left bar so the eye reads them as ONE task.
   Tags assigned by _groupTaskBursts: task-burst-start / -mid /
   -end. Standalone turn-groups (no adjacent neighbor) stay
   unmarked. Tracks the same "shared bar" visual idiom as the Q&A
   clustering (resolved AskUserQuestion runs). */
.turn-group.task-burst-start,
.turn-group.task-burst-mid,
.turn-group.task-burst-end {
  position: relative;
  margin-left: 6px;
  padding-left: 10px;
}
.turn-group.task-burst-start::before,
.turn-group.task-burst-mid::before,
.turn-group.task-burst-end::before {
  content: '';
  position: absolute;
  left: -2px;
  top: 0;
  bottom: 0;
  width: 3px;
  background: rgba(76, 175, 130, .35);
}
.turn-group.task-burst-start::before { border-top-left-radius: 2px; }
.turn-group.task-burst-end::before { border-bottom-left-radius: 2px; }
/* Tighten vertical spacing inside the burst so the turns visually
   belong together (vs the larger gap between bursts). */
.turn-group.task-burst-mid,
.turn-group.task-burst-end {
  margin-top: 2px;
}
.turn-group.task-burst-start { margin-top: 14px; }

/* Date separator — "Today" / "Yesterday" / "May 15" divider slotted
   between top-level chat rows when the UTC date changes. Helps long
   multi-day sessions read as discrete chunks of work. */
.date-sep {
  position: relative;
  text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  color: var(--muted, #8b949e);
  margin: 16px 8px 8px;
  opacity: 0.7;
  user-select: none;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.date-sep::before,
.date-sep::after {
  content: '';
  position: absolute;
  top: 50%;
  width: calc(50% - 40px);
  height: 1px;
  background: rgba(255, 255, 255, .06);
}
.date-sep::before { left: 0; }
.date-sep::after { right: 0; }

/* Presence cluster — small avatar chips at the top of the chatpane
   showing who's currently attached. Hidden when only one user is
   present (just self — no need for a cluster of one). Each chip is
   a colored circle keyed by a deterministic hue from the login, so
   the same user always gets the same color across sessions. */
.chatpane-presence {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  padding: 6px 12px 4px;
  background: rgba(255, 255, 255, .015);
  border-bottom: 1px solid rgba(255, 255, 255, .06);
  font-size: 0.7rem;
}
.chatpane-presence[hidden] { display: none; }
.presence-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: hsl(var(--presence-hue, 200deg) 38% 38%);
  color: #fff;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  font-weight: 600;
  user-select: none;
  cursor: help;
  position: relative;
  border: 2px solid transparent;
  transition: transform .12s ease, box-shadow .12s ease;
}
.presence-chip:hover { transform: scale(1.08); }
/* Owner: small accent ring + crown-ish dot at top */
.presence-chip-owner {
  border-color: rgba(76, 175, 130, .55);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35);
}
.presence-chip-owner::after {
  content: '';
  position: absolute;
  top: -2px;
  right: -1px;
  width: 6px;
  height: 6px;
  background: var(--accent, #4caf82);
  border-radius: 50%;
  border: 1px solid var(--bg, #0d1117);
}
.presence-chip-guest { opacity: 0.85; }
.presence-chip-me {
  border-color: rgba(170, 200, 255, .65);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35);
}
.presence-initial { line-height: 1; }

@media (max-width: 900px) {
  .chatpane-presence { padding: 8px 12px 6px; }
  .presence-chip { width: 26px; height: 26px; font-size: 0.82rem; }
}

/* Turn groups — each user prompt → tool activity → claude reply →
   turn footer bundles into one collapsible unit. Default state:
   collapsed on phone (≤900px), expanded on desktop. Click the head
   to toggle. The whole head row is the tap target. */
.turn-group {
  margin: 8px 0;
  border-radius: 6px;
  background: rgba(255, 255, 255, .015);
  transition: background .12s ease;
}
.turn-group.turn-collapsed { background: rgba(255, 255, 255, .01); }
.turn-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 8px 10px;
  cursor: pointer;
  user-select: none;
  border-radius: 6px;
  color: var(--text);
  font-size: 0.86rem;
  line-height: 1.4;
  border: 1px solid transparent;
  /* Sticky while the body is expanded — keeps the user oriented
     when scrolling through a long claude reply inside the turn. */
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--surface, #161821);
}
.turn-head:hover { background: rgba(255, 255, 255, .04); }
.turn-collapsed .turn-head { background: transparent; }
.turn-collapsed .turn-head:hover { background: rgba(255, 255, 255, .03); }
.turn-chevron {
  flex: 0 0 auto;
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .15s ease;
  opacity: 0.6;
}
.turn-expanded .turn-chevron {
  transform: rotate(90deg);
  opacity: 0.8;
}
.turn-head-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}
.turn-head-ts {
  flex: 0 0 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  color: var(--muted, #8b949e);
  opacity: 0.7;
}
.turn-head-outcome {
  flex: 0 0 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  padding: 1px 6px;
  border-radius: 3px;
  background: rgba(76, 175, 130, .10);
  color: rgba(76, 175, 130, .85);
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.turn-head-outcome-warn {
  background: rgba(255, 184, 107, .12);
  color: #ffb86b;
}
.turn-head-outcome[hidden] { display: none; }
.turn-body { padding: 2px 10px 10px; }
.turn-collapsed .turn-body { display: none; }

@media (max-width: 900px) {
  .turn-head {
    min-height: 44px;        /* finger-friendly tap target */
    padding: 10px 12px;
    font-size: 0.9rem;
  }
  .turn-head-outcome { font-size: 0.66rem; }
  .turn-head-ts { font-size: 0.7rem; }
}

/* Q&A clustering: when claude fires consecutive AskUserQuestion
   prompts (wizard-style), the resolved rows collapse to one-liners
   like "Daemon model: ✓ Picked [2] Built-in --daemon flag". Three
   or four of these in a row used to read as N disconnected events
   — the clustering wraps them in a shared left bar + tight stack so
   the eye groups them as ONE Q&A run. JS tags qa-run-start/-mid/
   -end via _clusterAnsweredQuestions(). Tool-permission menus
   (.chat-msg-menu-perm) are excluded because they're hidden by
   default and live in the modal carousel anyway. */
.qa-run-start,
.qa-run-mid,
.qa-run-end {
  border-left: 2px solid rgba(76, 175, 130, .30);
  padding-left: 12px;
  margin-left: 4px;
  background: rgba(76, 175, 130, .025);
}
.qa-run-start {
  margin-top: 10px;
  padding-top: 6px;
  border-top-left-radius: 3px;
}
.qa-run-mid {
  margin-top: 0;
  padding-top: 2px;
  padding-bottom: 2px;
}
.qa-run-end {
  margin-top: 0;
  padding-top: 2px;
  padding-bottom: 6px;
  border-bottom-left-radius: 3px;
  margin-bottom: 8px;
}
/* Q&A run badge — a tiny "Q&A" tag pinned to the top-left of the
   first row in a run, so the user has a one-word affordance for
   "these belong together". Drawn via ::before so we don't have to
   inject DOM. The 'content: "Q&A"' is intentionally short — long
   labels fight with the actual question text. */
.qa-run-start::before {
  content: 'Q&A';
  display: inline-block;
  margin: -2px 6px 2px -2px;
  padding: 1px 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: rgba(76, 175, 130, .85);
  background: rgba(76, 175, 130, .10);
  border-radius: 2px;
  vertical-align: 1px;
}

/* Lazy-load older history (2026-05-16): keeps the chat pane crisp on
   long sessions. Cards beyond CHAT_VISIBLE_LIMIT (50) get .chat-msg-
   archived; the load-older button sits at the top of #chat-messages
   so the user can reveal CHAT_LOAD_OLDER_BATCH (25) more at a time.
   Full DOM is preserved — nothing is destroyed, just hidden. */
.chat-msg-archived { display: none !important; }
.chat-load-older {
  display: block;
  width: 100%;
  margin: 0 0 8px;
  padding: 6px 10px;
  background: rgba(255, 255, 255, .03);
  color: var(--muted, #8b949e);
  border: 1px dashed rgba(255, 255, 255, .14);
  border-radius: 4px;
  cursor: pointer;
  font: inherit;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  text-align: center;
  user-select: none;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.chat-load-older:hover {
  background: rgba(255, 255, 255, .07);
  color: var(--fg, #c9d1d9);
  border-color: rgba(255, 255, 255, .25);
}
.chat-load-older:active { transform: scale(.99); }

/* Token meter — inline span on the right edge of the status row
   (#claude-typing). Zed-style context-window fill + cumulative
   cost. Color tint shifts at 60% (warn) and 80% (alarm) so a
   swelling context is visible without reading numbers. Hidden until
   the first turn_result arrives. */
.token-meter {
  flex: 0 0 auto;
  margin-left: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  line-height: 1.4;
  color: var(--muted, #8b949e);
  opacity: 0.72;
  user-select: text;
  cursor: help;
}
.token-meter.token-meter-warn { color: #d4b16a; opacity: 0.9; }
.token-meter.token-meter-alarm {
  color: #ff8c69;
  opacity: 1;
  font-weight: 500;
}
.token-meter[hidden] { display: none !important; }

/* Per-turn telemetry footer — Aider-style one-line summary right
   after each turn (duration · tokens · cost · turns). Sits flat in
   #chat-messages between cards, muted enough to be skimmable noise
   for the user who doesn't care, scannable for the user who does. */
.turn-footer {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--muted, #8b949e);
  padding: 2px 4px 6px;
  opacity: 0.78;
  user-select: text;
}
.turn-footer-glyph {
  flex: 0 0 auto;
  color: var(--accent, #4caf82);
  font-weight: 600;
}
.turn-footer-glyph.turn-footer-warn { color: #ffb86b; }
.turn-footer-ts { flex: 0 0 auto; opacity: 0.7; }
.turn-footer-stats {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Chrome-row: a single event inside the expanded chrome batch.
   .agent-chrome-row is the outer wrap (head + optional details);
   .agent-chrome-row-head is the always-visible single line.
   .agent-chrome-row-details holds the click-to-expand payload. */
.agent-chrome-row {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--muted, #8b949e);
}
.agent-chrome-row-head {
  display: flex;
  gap: 8px;
  align-items: baseline;
}
.agent-chrome-row-expandable .agent-chrome-row-head {
  cursor: pointer;
  user-select: none;
}
.agent-chrome-row-expandable .agent-chrome-row-head:hover {
  color: var(--fg, #c9d1d9);
}
/* Chevron only when this row has expandable details. */
.agent-chrome-row-expandable .agent-chrome-row-head::after {
  content: '▸';
  margin-left: auto;
  opacity: 0.55;
  font-size: 0.66rem;
  transition: opacity .12s ease;
}
.agent-chrome-row-expanded .agent-chrome-row-head::after {
  content: '▾';
  opacity: 0.85;
}
.agent-chrome-row .agent-card-ts {
  flex: 0 0 auto;
  opacity: 0.75;
}
.agent-chrome-row .agent-chrome-kind {
  flex: 0 0 auto;
  min-width: 110px;
}
.agent-chrome-row .agent-chrome-summary {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Details block — full payload, hidden when collapsed. Indents
   under the head's timestamp + kind columns so the visual
   hierarchy is clear. */
.agent-chrome-row-collapsed .agent-chrome-row-details {
  display: none;
}
.agent-chrome-row-expanded .agent-chrome-row-details {
  margin: 4px 0 8px 18px;
  padding-left: 12px;
  border-left: 1px solid rgba(255, 255, 255, .06);
}
.agent-chrome-pre {
  background: rgba(0, 0, 0, .25);
  padding: 6px 8px;
  border-radius: 3px;
  overflow-x: auto;
  overflow-y: auto;
  max-height: 320px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.76rem;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--fg, #c9d1d9);
  margin: 2px 0;
}
.agent-chrome-pre-error {
  border-left: 2px solid rgba(255, 130, 130, .65);
}
.agent-chrome-kv {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--fg, #c9d1d9);
  padding: 2px 0;
}
.agent-chrome-result {
  margin-top: 6px;
  padding: 6px 10px;
  border-left: 2px solid rgba(76, 175, 130, .45);
  background: rgba(76, 175, 130, .04);
  border-radius: 3px;
  font-size: 0.86rem;
  line-height: 1.5;
}

/* Phase 2 — agent cards live inside #chat-messages now (single timeline).
   Override the standalone .agent-card margins so the chat-messages flex
   gap controls spacing between every row, whether it's a chat bubble or
   a tool call. The card's own padding/border/background stay intact. */
#chat-messages .agent-card,
.chat-msg-agent {
  margin: 0;
}
/* Hover is a subtle text-colour brighten via .agent-card-head:hover —
   no background fill so the timeline doesn't strobe when the cursor
   moves down a long event list. */

/* ─── Permission / AskUserQuestion modal ──────────────────────────────
   Phase 1.5: pop a centered modal whenever the chat-pane menu queue is
   non-empty. The user can't accidentally scroll past an open
   canUseTool callback. Multiple parallel pendings (e.g., subagent +
   parent agent both asking) navigate with prev/next; every click
   carries the menu's hash so the server's _pendingPermissions Map
   routes the resolve to the correct promise. */
.perm-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.perm-modal[hidden] { display: none; }
.perm-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  cursor: pointer;       /* click-outside-to-defer */
}
.perm-modal-box {
  position: relative;
  background: #161b22;
  border: 1px solid #30363d;
  border-left: 4px solid #f0b440;
  border-radius: 10px;
  width: min(540px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  overflow: auto;
  padding: 16px 18px 14px;
  box-shadow:
    0 0 0 1px rgba(210, 153, 34, .25),
    0 12px 40px -8px rgba(0, 0, 0, 0.6);
  animation: perm-modal-in 0.16s ease-out;
}
@keyframes perm-modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .perm-modal-box { animation: none; }
}
/* Carousel chip row — sits ABOVE the head, one chip per pending
   permission/question in the parallel-canUseTool queue. Active chip
   is highlighted; clicking jumps the modal to that pending. Hidden
   when there's only one pending (the queue carousel is overhead the
   user doesn't need to see in that case). */
.perm-modal-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 0 10px;
  padding: 4px 0;
  border-bottom: 1px dashed rgba(255, 255, 255, .08);
}
.perm-modal-chips[hidden] { display: none; }
.perm-chip {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  background: rgba(255, 255, 255, .04);
  color: var(--muted, #8b949e);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 3px;
  padding: 2px 8px;
  cursor: pointer;
  white-space: nowrap;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background .12s ease, border-color .12s ease;
}
.perm-chip:hover {
  background: rgba(255, 255, 255, .07);
  border-color: rgba(255, 255, 255, .14);
}
.perm-chip-active {
  background: rgba(240, 180, 64, .18);
  border-color: rgba(240, 180, 64, .55);
  color: #f0b440;
}

.perm-modal-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid #21262d;
}
.perm-modal-title {
  flex: 1;
  font-weight: 600;
  color: #f0b440;
  font-size: 0.92rem;
}
.perm-modal-pager {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  color: var(--muted, #8b949e);
  padding: 0 4px;
}
.perm-modal-nav,
.perm-modal-close {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted, #8b949e);
  font: inherit;
  padding: 2px 8px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.perm-modal-nav:hover,
.perm-modal-close:hover {
  background: rgba(255, 255, 255, 0.05);
  color: var(--fg, #c9d1d9);
}
.perm-modal-close { font-size: 1.1rem; line-height: 1; }
.perm-modal-meta {
  font-size: 0.78rem;
  color: var(--muted, #8b949e);
  margin-bottom: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.perm-modal-meta code {
  background: rgba(255, 255, 255, .06);
  padding: 1px 5px;
  border-radius: 3px;
}
.perm-modal-question {
  color: var(--fg, #c9d1d9);
  font-size: 0.95rem;
  line-height: 1.4;
  margin-bottom: 12px;
  white-space: pre-wrap;
  word-break: break-word;
}
.perm-modal-opts {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 10px;
}
.perm-modal-opt {
  background: transparent;
  color: var(--fg, #c9d1d9);
  border: 1px solid #30363d;
  border-radius: 6px;
  padding: 9px 12px;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
  font-size: 0.9rem;
}
.perm-modal-opt:hover:not(:disabled) {
  background: rgba(88, 166, 255, 0.10);
  border-color: rgba(88, 166, 255, 0.45);
}
.perm-modal-opt:disabled { opacity: 0.45; cursor: default; }
.perm-modal-opt-num {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--muted, #8b949e);
  margin-right: 8px;
}
.perm-modal-opt-desc {
  display: block;
  font-size: 0.78rem;
  color: var(--muted, #8b949e);
  margin-top: 3px;
}
.perm-modal-hint {
  font-size: 0.72rem;
  color: var(--muted, #8b949e);
  text-align: center;
  padding-top: 8px;
  border-top: 1px solid #21262d;
}
.perm-modal-hint kbd {
  background: rgba(255, 255, 255, .06);
  border: 1px solid #30363d;
  border-radius: 3px;
  padding: 0 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
}
.agent-tool-result-preview {
  margin: 0;
  max-height: 250px;
  overflow: auto;
}
