/* Only things Tailwind CDN can't handle */

/* ─── Global refinements ─────────────────────── */
body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

* { transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }

/* ─── Auth overlay ───────────────────────────── */
.auth-card { animation: auth-enter 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
.auth-glow {
  position: absolute; top: -60px; left: 50%; transform: translateX(-50%);
  width: 200px; height: 120px;
  background: radial-gradient(ellipse, hsl(var(--c-primary) / 0.15), transparent 70%);
  pointer-events: none;
}
@keyframes auth-enter {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* ─── Heartbeat pulse ────────────────────────── */
/* The dot only animates when the server pushes a heartbeat; each beat
   restarts the animation via a class swap in app.js. If the heartbeat
   stops, the dot stops — so a stale dot means the server is not alive. */
.status-pulse {
  width: 6px; height: 6px; border-radius: 50%;
  background: hsl(142 71% 45%);
  flex-shrink: 0;
  transition: background 0.2s;
}
.status-pulse.beat {
  animation: heartbeat 1.5s ease-out;
}
@keyframes heartbeat {
  0%   { opacity: 1;    transform: scale(1.25); box-shadow: 0 0 8px hsl(142 71% 45% / 0.6); }
  30%  { opacity: 0.9;  transform: scale(1); }
  100% { opacity: 0.45; transform: scale(1); box-shadow: 0 0 0 hsl(142 71% 45% / 0); }
}
/* Stalled state: dot turns amber/red and stops beating. */
#status-bar.stalled .status-pulse {
  background: hsl(0 70% 55%);
  animation: none;
  opacity: 1;
}
#status-bar.stalled #status-text {
  color: hsl(0 70% 70%);
}

/* Deploying state: amber pulse + text. Set while the engine is
   armed for a restart; client sits with the banner until the
   new container accepts auth again. */
#status-bar.deploying .status-pulse {
  background: hsl(38 90% 55%);
  box-shadow: 0 0 8px hsl(38 90% 55% / 0.55);
  animation: dot-flash 1.4s ease-in-out infinite;
  opacity: 1;
}
#status-bar.deploying #status-text {
  color: hsl(38 90% 72%);
  font-weight: 500;
}

/* Deploy done: green check state, brief (4s) confirmation before
   the bar fades back to hidden. */
#status-bar.deploy-done .status-pulse {
  background: hsl(142 71% 50%);
  box-shadow: 0 0 10px hsl(142 71% 50% / 0.55);
  animation: none;
  /* Swap the dot for a checkmark via mask — simpler than swapping
     the DOM node. Falls back to the green dot on older browsers. */
  width: 10px; height: 10px;
  border-radius: 50%;
  position: relative;
}
#status-bar.deploy-done .status-pulse::after {
  content: "";
  position: absolute;
  left: 2px; top: 1px;
  width: 3px; height: 5px;
  border-right: 1.5px solid white;
  border-bottom: 1.5px solid white;
  transform: rotate(45deg);
}
#status-bar.deploy-done #status-text {
  color: hsl(142 60% 75%);
  font-weight: 500;
}

/* Composer: dimmed signal when the deploy lock is engaged. The lock
   itself is enforced by disabled/pointer-events in JS; this just
   provides a visual cue. */
.input-wrapper.deploy-locked {
  opacity: 0.6;
  pointer-events: none;
}
.input-wrapper.deploy-locked > textarea {
  pointer-events: auto; /* but keep the draft editable */
}

/* ─── Connection dot ──────────────────────────
   Lives inside the chat-header title row, left of the title text.
   Flashes while the socket is alive so the user can tell at a
   glance that the session is healthy. */
.dot-online {
  width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; display: inline-block;
  background: hsl(142 71% 45%);
  box-shadow: 0 0 8px hsl(142 71% 45% / 0.45);
  animation: dot-flash 1.8s ease-in-out infinite;
}
.dot-offline {
  width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; display: inline-block;
  background: hsl(var(--c-muted-fg) / 0.55);
  animation: none;
}
@keyframes dot-flash {
  0%, 100% { opacity: 1;   box-shadow: 0 0 10px hsl(142 71% 45% / 0.55); }
  50%      { opacity: 0.4; box-shadow: 0 0 4px  hsl(142 71% 45% / 0.15); }
}

/* ─── Messages ─────────────────────────────────
   Flat, left-aligned, Slack/terminal-style.

   Agent messages have NO chrome — text lives directly on --c-bg, which is
   what makes the app feel calm. User messages are the ONLY visual marker
   in the stream: a subtle tinted band with a 2px accent left border and a
   small "YOU" label above, bleeding to the left edge of the scroll
   container so they act as clear section dividers between agent replies.

   --stream-gutter is the single horizontal gutter every message shares —
   sender label, body text, tool rows, code blocks, composer all start at
   this x. Change it in one place; the whole layout follows. */
.message-stream {
  /* --stream-pad is the single left/right gutter every main item shares.
     User bands, agent messages, tool groups, and the composer all line
     up at this x from the pane edge so the chat reads as one column. */
  --stream-pad: 28px;
  --stream-max: 820px;
  display: flex; flex-direction: column;
  padding: 20px var(--stream-pad) 12px;
  width: 100%;
  gap: 4px;
}

.msg {
  width: 100%;
  max-width: var(--stream-max);
  padding: 10px 14px;
  box-sizing: border-box;
  word-wrap: break-word; overflow-wrap: break-word;
  animation: msg-in 0.18s ease-out;
  font-size: 13.5px; line-height: 1.55;
  border-radius: 8px;
}
@keyframes msg-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Sender label — only shown for agent. User messages have the tinted
   bg for identification, so a "you" label is redundant chrome. */
.msg-label {
  display: block;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 10.5px;
  font-weight: 500;
  text-transform: lowercase;
  letter-spacing: 0.04em;
  color: hsl(var(--c-muted-fg));
  margin-bottom: 4px;
  opacity: 0.85;
}

/* Agent: no bg, no border, no rounded — text on the canvas. Same padding
   as user messages so text-x is identical. */
.msg.agent {
  background: transparent;
  border: none;
  color: hsl(var(--c-fg));
}

/* User: tinted band. No accent border — the bg alone identifies the
   message as user-authored. Text-x matches agent via identical padding. */
.msg.user {
  background: hsl(var(--c-muted) / 0.55);
  color: hsl(var(--c-fg));
}

.msg.system {
  align-self: center;
  background: hsl(var(--c-msg-system));
  color: hsl(var(--c-muted-fg));
  padding: 5px 14px;
  border-radius: 20px;
  font-size: 12px; letter-spacing: 0.01em;
  max-width: max-content;
}

/* ─── Markdown ────────────────────────────────── */
.msg pre {
  background: hsl(var(--c-code-bg));
  border: 1px solid hsl(var(--c-border));
  border-radius: 8px;
  padding: 12px 14px;
  overflow-x: auto;
  margin: 8px 0;
}
.msg code {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 12.5px;
}
.msg p code {
  background: hsl(var(--c-code-inline));
  padding: 2px 6px; border-radius: 4px;
  font-size: 12px; color: hsl(var(--c-primary));
}
.msg p { margin: 4px 0; }
.msg ul, .msg ol { padding-left: 20px; margin: 4px 0; }
.msg a { color: hsl(var(--c-primary)); text-decoration: none; }
/* File-path links (auto-generated from agent message text). Render as
   mono inline code but clickable — subtle enough not to shout, obvious
   enough that the user knows they can jump into the file viewer. */
.msg a.msg-filepath {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.92em;
  background: hsl(var(--c-code-inline));
  padding: 1px 6px;
  border-radius: 4px;
  border-bottom: 1px dashed hsl(var(--c-primary) / 0.4);
}
.msg a.msg-filepath:hover {
  background: hsl(var(--c-primary) / 0.14);
  border-bottom-color: hsl(var(--c-primary));
  text-decoration: none;
}
.msg a:hover { text-decoration: underline; }
.msg blockquote { border-left: 2px solid hsl(var(--c-primary)); padding-left: 12px; color: hsl(var(--c-muted-fg)); margin: 8px 0; }
.msg table { border-collapse: collapse; margin: 8px 0; font-size: 12.5px; }
.msg th, .msg td { border: 1px solid hsl(var(--c-border)); padding: 6px 10px; }
.msg th { background: hsl(var(--c-muted)); }

/* ─── Tool groups ─────────────────────────────
   Flat inline style: no card, no border. Header aligns with message-text
   x (same stream-pad) so it reads as peer to messages, not sub-content.
   Items indent beneath with a subtle left rule. */
.tool-group {
  width: 100%;
  max-width: var(--stream-max);
  padding: 6px 14px;
  box-sizing: border-box;
  background: transparent;
  border: none;
  animation: msg-in 0.18s ease-out;
}

.tool-group-header {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0;
  cursor: pointer; user-select: none;
  transition: color 0.12s;
  color: hsl(var(--c-fg) / 0.85);
}
.tool-group-header:hover { color: hsl(var(--c-fg)); background: transparent; }

.tool-group-label {
  color: hsl(var(--c-fg) / 0.85);
  font-family: 'DM Sans', -apple-system, 'Segoe UI', sans-serif;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0;
  flex: 1; min-width: 0;
}

.tool-group .tool-chevron {
  color: hsl(var(--c-muted-fg));
  font-size: 10px;
  transition: transform 0.15s;
  flex-shrink: 0;
}
.tool-group:not(.collapsed) .tool-chevron { transform: rotate(90deg); }

.tool-group-body {
  border-top: none;
  border-left: 1px solid hsl(var(--c-border));
  padding: 4px 0 4px 14px;
  margin-left: 3px;
  margin-top: 4px;
}
.tool-group.collapsed .tool-group-body { display: none; }

.tool-item {
  display: flex; align-items: center; gap: 10px;
  padding: 3px 0;
}

.tool-item .tool-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: hsl(var(--c-muted-fg) / 0.7);
  flex-shrink: 0; transition: background 0.2s;
}
.tool-item.active .tool-dot {
  background: hsl(142 71% 45%);
  animation: pulse 1.4s ease-in-out infinite;
}
.tool-item.done .tool-dot {
  background: hsl(var(--c-muted-fg) / 0.7);
  animation: none;
}

/* Tool label: larger text, no ellipsis/clip so long paths/filenames stay
   legible. If it overflows the panel width, let it wrap — truncation is
   worse than a two-line tool item. */
.tool-item .tool-label {
  color: hsl(var(--c-fg) / 0.82);
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 12.5px;
  flex: 1; min-width: 0;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.tool-item .tool-label code {
  background: hsl(var(--c-code-inline));
  padding: 1px 6px; border-radius: 4px;
  font-size: 12px;
}

/* ─── File attachments ────────────────────────── */

/* Image attachment: inline preview, clicks through to full size. Used for
   everything with mime/image/*. The bubble owner's max-width contains it. */
.file-image {
  display: block;
  margin-top: 8px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid hsl(var(--c-border));
  background: hsl(var(--c-muted));
  line-height: 0;
  transition: border-color 0.15s, transform 0.1s;
  max-width: 360px;
}
.file-image img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 400px;
  object-fit: cover;
}
.file-image:hover {
  border-color: hsl(var(--c-primary) / 0.45);
}
.msg.user .file-image {
  margin-left: auto;
}

/* Non-image attachment card: file icon + filename + size + download arrow.
   Same visual on both sent (user) and received (agent) sides, differing
   only in alignment and background tint to match the parent bubble. */
.file-card {
  display: inline-flex; align-items: center; gap: 12px;
  margin-top: 8px;
  padding: 10px 12px 10px 10px;
  border-radius: 10px;
  border: 1px solid hsl(var(--c-border));
  background: hsl(var(--c-card));
  color: hsl(var(--c-fg));
  text-decoration: none;
  transition: border-color 0.15s, background 0.15s, transform 0.08s;
  max-width: 320px;
  min-width: 200px;
}
.file-card:hover {
  border-color: hsl(var(--c-primary) / 0.5);
  background: hsl(var(--c-card));
}
.file-card:active { transform: translateY(0.5px); }

.file-card-icon {
  width: 34px; height: 34px;
  flex-shrink: 0;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: hsl(var(--c-primary) / 0.12);
  color: hsl(var(--c-primary));
}

.file-card-meta {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.file-card-name {
  font-size: 12.5px; font-weight: 500;
  color: hsl(var(--c-fg));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-card-size {
  font-size: 10.5px;
  color: hsl(var(--c-muted-fg));
  letter-spacing: 0.01em;
}
.file-card-download {
  color: hsl(var(--c-muted-fg));
  flex-shrink: 0;
  transition: color 0.15s;
}
.file-card:hover .file-card-download {
  color: hsl(var(--c-primary));
}

/* Sent (user) variant: darker bg that matches user message bubble */
.msg.user .file-card {
  background: hsl(var(--c-primary) / 0.08);
  border-color: hsl(var(--c-primary) / 0.25);
  margin-left: auto;
}
.msg.user .file-card:hover {
  border-color: hsl(var(--c-primary) / 0.45);
}

/* ─── Attachment preview pill (pre-send) ───────── */
/* Container is a flex row that wraps — many attachments flow into a
   grid-like layout above the composer. Each pill stays compact. */
#attachment-preview {
  max-width: 820px;
  margin: 0 auto 8px;
  width: 100%;
  padding: 0;
  border: none;
  background: transparent;
  box-shadow: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
#attachment-preview.hidden { display: none !important; }

.attachment-pill {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px 8px 10px;
  border-radius: 10px;
  background: hsl(var(--c-primary) / 0.08);
  border: 1px solid hsl(var(--c-primary) / 0.3);
  max-width: 320px;
  flex-shrink: 0;
}
.attachment-pill.error {
  background: hsl(0 72% 51% / 0.1);
  border-color: hsl(0 72% 51% / 0.35);
}
.attachment-icon {
  width: 32px; height: 32px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 7px;
  background: hsl(var(--c-primary) / 0.15);
  color: hsl(var(--c-primary));
}
.attachment-pill.error .attachment-icon {
  background: hsl(0 72% 51% / 0.15);
  color: hsl(0 72% 61%);
}
.attachment-meta {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 1px;
}
.attachment-name {
  font-size: 12.5px; font-weight: 500;
  color: hsl(var(--c-fg));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.attachment-status {
  font-size: 10.5px;
  color: hsl(var(--c-muted-fg));
}
.attachment-pill.error .attachment-status {
  color: hsl(0 72% 68%);
}
.attachment-pill.ready .attachment-status {
  color: hsl(var(--c-primary));
}
.attachment-remove {
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: hsl(var(--c-muted-fg));
  font-size: 18px;
  line-height: 1;
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 5px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}

/* In-message attachments (post-send): stack file cards / image previews
   vertically below the text, with a small gap. Images can sit side-by-
   side on wide screens; cards always stack. */
.msg-attachments {
  display: flex; flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}
.attachment-remove:hover {
  background: hsl(var(--c-accent));
  color: hsl(var(--c-fg));
}

/* ─── Project list items ──────────────────────── */
.project-item-wrapper { position: relative; }

/* Work/project item — two-line row with folder icon + name + path.
   `.project-item-openable` marks rows that open the file viewer on
   click (the Work sidebar); other uses of .project-item (repos list,
   etc.) stay non-clickable. */
.project-item {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 8px 10px; border-radius: 6px;
  cursor: default; font-size: 12.5px;
  color: hsl(var(--c-fg) / 0.85);
  transition: background 0.1s;
}
.project-item-openable {
  cursor: pointer;
}
.project-item:hover {
  background: hsl(var(--c-muted) / 0.35);
}
.project-item.active {
  background: hsl(var(--c-muted) / 0.6);
}

/* Type icon: folder for Work items, git branch for Repositories. Green
   is reserved for Chats (busy/live dot), so active Work/Repo rows use a
   bg shift only — no colored dot. */
.project-icon {
  width: 14px; height: 14px;
  flex-shrink: 0;
  margin-top: 3px;
  color: hsl(var(--c-muted-fg) / 0.75);
}
.project-item.active .project-icon {
  color: hsl(var(--c-primary));
}
/* Legacy dot class kept as a no-op — only project-icon is used now. Old
   markup may still reference it briefly after a partial refresh. */
.project-dot { display: none; }

.project-info {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 1px;
}
.project-label {
  font-size: 12.5px;
  font-weight: 500;
  color: hsl(var(--c-fg));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Path/repo subtext under name — mono + muted. Hides gracefully on
   narrow repos (truncates with ellipsis, mirrors the sidebar width). */
.project-path {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 10.5px;
  color: hsl(var(--c-muted-fg) / 0.75);
  letter-spacing: 0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.project-edit-btn {
  background: transparent; border: none;
  color: hsl(var(--c-muted-fg));
  padding: 2px 6px; border-radius: 4px;
  font-size: 12px; cursor: pointer;
  opacity: 0; transition: opacity 0.12s, background 0.12s;
}
.project-item:hover .project-edit-btn { opacity: 0.6; }
.project-edit-btn:hover { opacity: 1 !important; background: hsl(var(--c-muted)); color: hsl(var(--c-fg)); }

.project-edit-form {
  display: none;
  flex-direction: column; gap: 6px;
  padding: 8px 10px 10px;
  background: hsl(var(--c-muted) / 0.4);
  border-radius: 7px; margin-top: 2px;
}
.project-item-wrapper.editing .project-edit-form { display: flex; }
.project-edit-form input {
  background: hsl(var(--c-bg) / 0.8);
  border: 1px solid hsl(var(--c-border));
  border-radius: 6px; padding: 6px 9px;
  font-size: 12px; color: hsl(var(--c-fg));
  outline: none;
}
.project-edit-form input:focus {
  border-color: hsl(var(--c-primary) / 0.5);
  box-shadow: 0 0 0 2px hsl(var(--c-primary) / 0.1);
}
.project-edit-actions { display: flex; gap: 6px; }
.project-edit-actions button {
  flex: 1; font-size: 11px; padding: 5px 10px;
  border-radius: 5px; cursor: pointer;
  border: 1px solid hsl(var(--c-border));
  transition: all 0.1s;
}
.project-edit-save {
  background: hsl(var(--c-primary));
  color: hsl(0 0% 100%);
  border-color: hsl(var(--c-primary));
}
.project-edit-save:hover { filter: brightness(1.1); }
.project-edit-cancel {
  background: transparent;
  color: hsl(var(--c-muted-fg));
}
.project-edit-cancel:hover { background: hsl(var(--c-muted)); color: hsl(var(--c-fg)); }
.project-edit-delete {
  background: transparent;
  color: hsl(0 72% 61%);
  border-color: hsl(0 72% 51% / 0.3);
  flex: 0 0 auto;
}
.project-edit-delete:hover {
  background: hsl(0 72% 51% / 0.12);
  border-color: hsl(0 72% 51% / 0.5);
}
.project-edit-delete.force-mode {
  background: hsl(0 72% 51%);
  color: hsl(0 0% 100%);
  border-color: hsl(0 72% 51%);
}
.project-edit-delete.force-mode:hover {
  background: hsl(0 72% 45%);
}
.project-edit-hint {
  font-size: 10px;
  color: hsl(var(--c-muted-fg) / 0.7);
  margin: -2px 0 2px;
  letter-spacing: 0.01em;
}
.project-edit-hint code {
  background: hsl(var(--c-muted));
  padding: 1px 4px;
  border-radius: 3px;
  font-size: 9px;
  color: hsl(var(--c-fg));
}

/* ─── Sidebar ─────────────────────────────────── */
.sidebar-default { transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1); }
.sidebar-action { position: relative; }
.sidebar-action::before {
  content: ''; position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
  width: 0; height: 0; border-radius: 2px;
  background: hsl(var(--c-primary)); opacity: 0; transition: all 0.15s;
}
.sidebar-action:hover::before { width: 3px; height: 14px; opacity: 1; }

details > summary { list-style: none; }
details > summary::-webkit-details-marker { display: none; }

@media (max-width: 768px) {
  .sidebar-default {
    position: fixed; left: 0; top: 0; bottom: 0;
    transform: translateX(-100%); z-index: 50;
    box-shadow: 4px 0 20px rgba(0,0,0,0.5);
  }
  .sidebar-default.open { transform: translateX(0); }
  #sidebar-toggle { display: block !important; }
  #sidebar-close { display: block !important; }
  .msg { max-width: 92%; }
}

/* Input wrapper */
.input-wrapper {
  box-shadow: 0 -2px 12px hsl(var(--c-bg) / 0.5);
  max-width: 800px;
  margin: 0 auto;
  width: 100%;
}

/* ─── Scrollbar ───────────────────────────────── */
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: hsl(var(--c-border)); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: hsl(var(--c-muted-fg)); }

#status-bar.hidden { display: none !important; }

/* ─── Chat list ───────────────────────────────── */
.chat-item {
  position: relative;
  /* No outer bleed — sidebar section padding provides breathing from the
     sidebar border. Items stay within the padded region. */
}

.chat-item-row {
  display: flex; align-items: stretch; gap: 0;
  position: relative;
  border-radius: 6px;
  border: 1px solid transparent;
  transition: background 0.12s;
}
.chat-item-row:hover {
  background: hsl(var(--c-accent) / 0.55);
}
/* Subtle active — just a slightly raised bg, no accent border, no
   colored text. Restraint here keeps the eye on chat content, not on
   which chat is selected. */
.chat-item.active .chat-item-row {
  background: hsl(var(--c-muted) / 0.8);
}
.chat-item.active .chat-item-row:hover {
  background: hsl(var(--c-muted));
}

.chat-item-main {
  flex: 1; min-width: 0;
  text-align: left;
  padding: 8px 12px;
  background: transparent;
  border: none;
  cursor: pointer;
  color: inherit;
  display: flex; flex-direction: column; gap: 2px;
}
.chat-item-label {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
}
.chat-item-title {
  flex: 1; min-width: 0;
  font-size: 12.5px;
  font-weight: 500;
  color: hsl(var(--c-muted-fg));
  letter-spacing: 0.01em;
}
.chat-item.active .chat-item-title {
  color: hsl(var(--c-fg));
  font-weight: 600;
}
.chat-item-row:hover .chat-item-title {
  color: hsl(var(--c-fg));
}
.chat-item-project {
  display: block;
  margin-left: 14px; /* align with title text, past the dot column */
  font-size: 10.5px;
  color: hsl(var(--c-muted-fg) / 0.75);
  letter-spacing: 0.015em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-item.active .chat-item-project {
  color: hsl(var(--c-primary) / 0.85);
}

.chat-item-actions {
  display: flex; align-items: center; gap: 2px;
  padding-right: 6px;
  opacity: 0;
  transition: opacity 0.12s;
}
.chat-item-row:hover .chat-item-actions { opacity: 1; }

.chat-item-action {
  background: transparent;
  border: none;
  color: hsl(var(--c-muted-fg));
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 5px;
  cursor: pointer;
  font-size: 12px; line-height: 1;
  transition: background 0.1s, color 0.1s;
}
.chat-item-action:hover {
  background: hsl(var(--c-accent));
  color: hsl(var(--c-fg));
}
.chat-item-action-danger:hover {
  background: hsl(0 72% 51% / 0.15);
  color: hsl(0 72% 61%);
}
.chat-item-action:first-child { font-size: 11px; }
.chat-item-action-danger { font-size: 16px; }

/* Inactive chat state: small grey dot. Busy state: pulsing green dot. */
.chat-dot {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: hsl(var(--c-muted-fg) / 0.4);
  flex-shrink: 0;
}
.chat-item.active .chat-dot {
  background: hsl(var(--c-primary));
  box-shadow: 0 0 6px hsl(var(--c-primary) / 0.45);
}
.chat-busy-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: hsl(142 71% 45%);
  box-shadow: 0 0 6px hsl(142 71% 45% / 0.5);
  animation: chat-busy-pulse 1.4s ease-in-out infinite;
  flex-shrink: 0;
}
@keyframes chat-busy-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.3; transform: scale(0.7); }
}
.chat-item.busy .chat-item-title {
  color: hsl(var(--c-fg));
}

/* Inline rename input — replaces the row while editing */
.chat-item-rename {
  padding: 6px 10px 6px 12px;
  border-radius: 8px;
  border: 1px solid hsl(var(--c-primary) / 0.4);
  background: hsl(var(--c-primary) / 0.06);
}
.chat-item-rename-input {
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  color: hsl(var(--c-fg));
  font-size: 12.5px;
  font-weight: 500;
  font-family: inherit;
}

/* ─── Todo checklist ──────────────────────────── */
/* Dock: pinned above input. Hidden when no live todos. Left-aligned
   with the rest of the chat column (gutter comes from the more
   specific `#todo-dock { padding: 8px 28px ... }` rule below). */
#todo-dock {
  width: 100%;
  margin: 0;
  animation: todo-dock-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
#todo-dock.hidden { display: none !important; }
@keyframes todo-dock-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Carded: the live checklist reads as a self-contained dock above the
   composer. Narrow (not full-width) so it looks like a focused panel,
   not a stretched header. Subtle border + slight tint distinguishes it
   from the chat canvas without competing with the input pill. */
.todo-box {
  background: hsl(var(--c-card));
  border: 1px solid hsl(var(--c-border) / 0.7);
  border-radius: 10px;
  padding: 10px 14px 12px;
  margin: 4px 0;
  max-width: 520px;
}
.todo-box.live {
  border-color: hsl(var(--c-primary) / 0.4);
  box-shadow: 0 0 20px hsl(var(--c-primary) / 0.06);
}
.todo-header {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 8px;
}
.todo-title {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
  color: hsl(var(--c-muted-fg)); font-weight: 600;
}
.todo-count {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 11px; color: hsl(var(--c-muted-fg));
}
.todo-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.todo-item {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 13px;
  color: hsl(var(--c-fg) / 0.85);
  padding: 2px 0;
}
.todo-glyph {
  display: inline-block; width: 14px; flex-shrink: 0;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px; line-height: 1.4;
  text-align: center;
  color: hsl(var(--c-muted-fg));
}
.todo-item.pending .todo-glyph { color: hsl(var(--c-muted-fg)); }
.todo-item.active .todo-glyph {
  color: hsl(var(--c-primary));
  animation: todo-pulse 1.4s ease-in-out infinite;
}
.todo-item.done .todo-glyph { color: hsl(142 71% 45%); }
.todo-item.done .todo-text {
  color: hsl(var(--c-muted-fg));
  text-decoration: line-through;
  text-decoration-color: hsl(var(--c-muted-fg) / 0.5);
}
.todo-item.active .todo-text {
  color: hsl(var(--c-fg));
  font-weight: 500;
}
@keyframes todo-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}
::selection { background: hsl(var(--c-primary) / 0.25); }

/* ─── ask_form card ───────────────────────────
   Structured-question bubble rendered when the agent calls
   ask_form. While interactive, each question renders its own
   control (radio / checkbox / input / textarea); once answered,
   the card collapses into a read-only summary of the chips. */
.form-card {
  width: 100%;
  max-width: var(--stream-max, 820px);
  box-sizing: border-box;
  padding: 14px 16px 12px;
  margin-left: var(--stream-gutter, 20px);
  margin-right: var(--stream-gutter, 20px);
  background: hsl(var(--c-card));
  border: 1px solid hsl(var(--c-primary) / 0.32);
  border-radius: 10px;
  box-shadow: 0 1px 4px hsl(228 20% 4% / 0.08);
  animation: msg-in 0.18s ease-out;
}
.form-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  gap: 8px;
}
.form-card-title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: hsl(var(--c-fg));
  text-transform: none;
}
.form-card-state {
  font-size: 10.5px;
  padding: 2px 8px;
  border-radius: 10px;
  font-family: 'IBM Plex Mono', monospace;
  background: hsl(var(--c-muted) / 0.5);
  color: hsl(var(--c-muted-fg));
}
.form-card-state.submitted { background: hsl(142 71% 45% / 0.18); color: hsl(142 60% 55%); }
.form-card-state.cancelled { background: hsl(0 72% 51% / 0.15); color: hsl(0 72% 65%); }
.form-card-state.timedout  { background: hsl(38 90% 55% / 0.18); color: hsl(38 90% 60%); }

/* Live countdown until the agent's turn-bound deadline. After the
   deadline passes the card stays submittable — the `.overdue` modifier
   flips styling to a muted amber so the user knows answering now
   starts a fresh turn rather than resuming the old one. */
.form-card-countdown, .form-countdown {
  font-size: 10.5px;
  padding: 2px 8px;
  border-radius: 10px;
  font-family: 'IBM Plex Mono', monospace;
  background: hsl(var(--c-muted) / 0.5);
  color: hsl(var(--c-muted-fg));
}
.form-countdown.overdue {
  background: hsl(38 90% 55% / 0.15);
  color: hsl(38 90% 60%);
}

.form-card-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.form-question {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.form-question.missing {
  padding-left: 8px;
  border-left: 2px solid hsl(0 72% 55%);
}
.form-question-label {
  font-size: 13px;
  font-weight: 500;
  color: hsl(var(--c-fg));
  display: inline-flex;
  gap: 4px;
  align-items: baseline;
}
.form-question-required {
  color: hsl(0 72% 60%);
  font-size: 11px;
}
.form-question-help {
  font-size: 11.5px;
  color: hsl(var(--c-muted-fg));
  line-height: 1.45;
}

.form-option-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.form-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 6px;
  cursor: pointer;
  background: hsl(var(--c-bg) / 0.5);
  border: 1px solid hsl(var(--c-border) / 0.5);
  transition: border-color 0.1s, background 0.1s;
  font-size: 12.5px;
  color: hsl(var(--c-fg));
}
.form-option:hover {
  border-color: hsl(var(--c-primary) / 0.4);
  background: hsl(var(--c-primary) / 0.04);
}
.form-option input {
  accent-color: hsl(var(--c-primary));
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}
.form-option input:checked + .form-option-text {
  color: hsl(var(--c-primary));
  font-weight: 500;
}
.form-option-other {
  background: hsl(var(--c-bg));
  border: 1px solid hsl(var(--c-border));
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 12.5px;
  color: hsl(var(--c-fg));
  outline: none;
  margin-left: 22px;
  margin-top: -2px;
}
.form-option-other:focus {
  border-color: hsl(var(--c-primary) / 0.5);
  box-shadow: 0 0 0 2px hsl(var(--c-primary) / 0.1);
}

.form-input,
.form-textarea {
  background: hsl(var(--c-bg));
  border: 1px solid hsl(var(--c-border));
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 12.5px;
  color: hsl(var(--c-fg));
  outline: none;
  font-family: inherit;
  transition: border-color 0.1s, box-shadow 0.1s;
}
.form-textarea {
  min-height: 60px;
  resize: vertical;
}
.form-input:focus,
.form-textarea:focus {
  border-color: hsl(var(--c-primary) / 0.5);
  box-shadow: 0 0 0 2px hsl(var(--c-primary) / 0.1);
}

.form-card-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 14px;
}
.form-cancel {
  padding: 6px 12px;
  border: 1px solid hsl(var(--c-border));
  background: transparent;
  color: hsl(var(--c-muted-fg));
  border-radius: 6px;
  font-size: 12px;
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}
.form-cancel:hover { color: hsl(var(--c-fg)); background: hsl(var(--c-accent) / 0.5); }
.form-submit-btn {
  padding: 6px 14px;
  border: none;
  background: hsl(var(--c-primary));
  color: hsl(0 0% 100%);
  border-radius: 6px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: filter 0.1s, transform 0.05s;
}
.form-submit-btn:hover { filter: brightness(1.1); }
.form-submit-btn:active { transform: translateY(0.5px); }
.form-submit-btn:disabled { opacity: 0.5; cursor: default; }

.form-card-error {
  margin-top: 8px;
  padding: 6px 10px;
  background: hsl(0 70% 55% / 0.12);
  color: hsl(0 70% 55%);
  border-radius: 6px;
  font-size: 12px;
}

/* Summary chips shown after submission */
.form-card-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid hsl(var(--c-border) / 0.5);
}
.form-summary-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 10px;
  background: hsl(var(--c-muted) / 0.6);
  border-radius: 14px;
  font-size: 11.5px;
}
.form-summary-key {
  color: hsl(var(--c-muted-fg));
  font-weight: 500;
}
.form-summary-val {
  color: hsl(var(--c-fg));
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
}

/* ─── Light mode highlight.js override ────────── */
.light .hljs { background: hsl(var(--c-code-bg)) !important; }

/* ─── Sidebar sections ──────────────────────────
   Three symmetric sections — Chats, Work, Repositories — each with a
   header row (label + + button) and an inline popover for adding. Shared
   spacing lives here rather than in per-section utility classes so the
   sidebar reads as one thing. */

.sb-section {
  /* Section outer padding drives the sidebar rhythm. Items live inside
     this padded box, so items don't need their own outer margins.
     No horizontal dividers between sections — spacing alone separates
     them, per the borderless direction. */
  padding: 8px 10px 12px;
}

.sb-section-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 2px 2px 4px;
  margin-bottom: 4px;
  user-select: none;
  gap: 4px;
}
/* Toggle button wraps the label + chevron — clicking anywhere on this
   button collapses/expands the section. The + button stays outside so it
   never toggles. */
.sb-section-toggle {
  flex: 1;
  display: flex; align-items: center; gap: 6px;
  padding: 4px 6px;
  border-radius: 6px;
  background: transparent;
  border: none;
  cursor: pointer;
  color: inherit;
  text-align: left;
  transition: background 0.12s;
}
.sb-section-toggle:hover { background: hsl(var(--c-accent) / 0.5); }
.sb-section-label {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
  color: hsl(var(--c-fg) / 0.92);
}
.sb-section-chevron {
  color: hsl(var(--c-muted-fg) / 0.7);
  transition: transform 0.18s ease;
  flex-shrink: 0;
}
/* Expanded (default): chevron points down. Collapsed: chevron points
   right (as the user drew it, ">"). */
.sb-section:not(.collapsed) .sb-section-chevron {
  transform: rotate(90deg);
}
.sb-section.collapsed .sb-section-body {
  display: none;
}

.sb-add-btn {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px;
  border-radius: 6px;
  color: hsl(var(--c-muted-fg));
  background: transparent;
  border: 1px solid transparent;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
  cursor: pointer;
  flex-shrink: 0;
}
.sb-add-btn svg { width: 16px; height: 16px; }
.sb-add-btn:hover {
  color: hsl(var(--c-primary));
  background: hsl(var(--c-primary) / 0.1);
  border-color: hsl(var(--c-primary) / 0.25);
}

.sb-popover {
  margin: 6px 6px 8px;
  padding: 10px;
  background: hsl(var(--c-bg) / 0.7);
  border: 1px solid hsl(var(--c-border));
  border-radius: 8px;
  display: flex; flex-direction: column; gap: 6px;
}
.sb-popover.hidden { display: none; }
.sb-popover-hint {
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: hsl(var(--c-muted-fg) / 0.7);
  padding: 2px 2px 4px;
}
.sb-popover-hint.muted {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  color: hsl(var(--c-muted-fg) / 0.55);
  font-size: 10.5px;
  font-style: italic;
  padding: 4px 2px 0;
}
.sb-popover-options {
  display: flex; flex-direction: column;
  max-height: 220px;
  overflow-y: auto;
}
.sb-input {
  background: hsl(var(--c-bg));
  border: 1px solid hsl(var(--c-border));
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 12px;
  color: hsl(var(--c-fg));
  outline: none;
  transition: border-color 0.12s, box-shadow 0.12s;
  font-family: inherit;
}
/* Select variant: native dropdown with some cleanup so the widget
   doesn't look like it leaked in from the browser's default styles. */
select.sb-input {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 28px;
  cursor: pointer;
}
.sb-input::placeholder {
  color: hsl(var(--c-muted-fg) / 0.55);
}
.sb-input:focus {
  border-color: hsl(var(--c-primary) / 0.5);
  box-shadow: 0 0 0 2px hsl(var(--c-primary) / 0.1);
}
.sb-submit {
  background: hsl(var(--c-primary));
  color: hsl(0 0% 100%);
  border: none;
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: filter 0.12s, transform 0.06s;
}
.sb-submit:hover { filter: brightness(1.08); }
.sb-submit:active { transform: translateY(0.5px); }

.sb-list {
  display: flex; flex-direction: column;
  gap: 1px;
}

/* Footer: single Log out action. Borderless — matches the chat pane's
   composer bottom so the app's lower edge reads as one horizontal line
   across sidebar + main. */
.sb-footer {
  padding: 10px 10px 14px;
}
.sb-footer-btn {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 8px 12px;
  border-radius: 6px;
  color: hsl(var(--c-muted-fg));
  background: transparent;
  font-size: 12.5px;
  transition: background 0.12s, color 0.12s;
  cursor: pointer;
}
.sb-footer-btn:hover {
  background: hsl(var(--c-accent) / 0.55);
  color: hsl(var(--c-fg));
}

/* ─── Sidebar dismiss / reopen ──────────────────
   The dismiss (panel-collapse icon) lives in the sidebar brand row.
   Visible on desktop AND mobile so users can fold the sidebar anywhere.
   The "reopen" icon lives in the chat header and is hidden until the
   body picks up the .sidebar-hidden class. */
.sb-dismiss-btn {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 6px;
  color: hsl(var(--c-muted-fg));
  background: transparent;
  transition: background 0.12s, color 0.12s;
  cursor: pointer;
  flex-shrink: 0;
}
.sb-dismiss-btn:hover {
  background: hsl(var(--c-accent) / 0.6);
  color: hsl(var(--c-fg));
}

body.sidebar-hidden #sidebar { display: none; }
.chat-header-open-sidebar {
  display: none;
  align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 6px;
  color: hsl(var(--c-muted-fg));
  background: transparent;
  transition: background 0.12s, color 0.12s;
  cursor: pointer;
  flex-shrink: 0;
}
.chat-header-open-sidebar:hover {
  background: hsl(var(--c-accent) / 0.6);
  color: hsl(var(--c-fg));
}
body.sidebar-hidden .chat-header-open-sidebar { display: flex; }

/* ─── Chat header (main pane) ───────────────────
   Borderless, shares --c-bg with the message stream. Two-line title:
   chat title primary, project secondary — mirrors the sidebar chat row. */
.chat-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 20px;
  min-height: 52px;
  background: hsl(var(--c-bg));
}
.chat-header-title {
  display: flex; flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}
/* Row 1 of the header title stack: connection dot + chat title. Dot
   sits centered to the title's cap-height, flashing green while the
   socket is alive. */
.chat-header-chat-row {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}
.chat-header-chat {
  font-size: 13.5px;
  font-weight: 600;
  color: hsl(var(--c-fg));
  letter-spacing: -0.005em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-header-project-row {
  display: flex; align-items: center; gap: 5px;
  margin-top: 1px;
  min-width: 0;
}
.chat-header-project-icon {
  width: 11px; height: 11px;
  flex-shrink: 0;
  color: hsl(var(--c-muted-fg) / 0.8);
}
.chat-header-project {
  font-size: 11px;
  color: hsl(var(--c-muted-fg));
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-empty {
  padding: 10px 12px;
  font-size: 11px;
  color: hsl(var(--c-muted-fg) / 0.65);
  font-style: italic;
}
.sb-loading {
  display: flex; flex-direction: column; gap: 4px;
  padding: 4px 10px;
}
.sb-loading-row {
  height: 28px;
  border-radius: 5px;
  background: hsl(var(--c-muted) / 0.5);
  animation: pulse-bg 1.4s ease-in-out infinite;
}
.sb-loading-row:nth-child(2) { background: hsl(var(--c-muted) / 0.35); }
.sb-loading-row:nth-child(3) { background: hsl(var(--c-muted) / 0.22); }
@keyframes pulse-bg {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

/* Repositories reuse .project-item so Work and Repos share one visual
   row style — see the two-line project-item block above. */

/* ─── Composer + status bar alignment ───────────
   All three (status, todo dock, composer) share the same left edge as the
   message stream so the chat pane reads as one continuous column.

   Alignment math: composer left edge = pane-edge + --stream-pad (16px by
   default) — same x as a user-message band. Composer text column starts
   slightly inside that, so the typed text aligns visually with message
   body text. */
.composer-wrap {
  padding: 12px 0 18px;
  /* box-sizing trick so padding on children doesn't overflow the viewport
     on narrow screens. The input pill uses calc(100% - 2*pad) which
     always fits. */
}
.composer-wrap > .input-wrapper,
.composer-wrap > #attachment-preview {
  max-width: 820px;
  margin-left: 28px;
  margin-right: 28px;
  box-sizing: border-box;
}

/* ─── External recording card ────────────────────────────────
   Floating bottom-right panel that owns the entire lifecycle of an
   externally-initiated meeting (another tab of the same owner, or the
   tab-audio Chrome extension). Three sections stacked vertically:
     · header  — favicon + page title + origin/path
     · body    — live transcript, scrollable, sticks to bottom
     · footer  — pulsing red dot + elapsed + Stop
   Fixed position so it never fights the composer or message stream for
   space; capped width so on narrow screens it doesn't eat the viewport. */
.external-rec-card {
  position: fixed;
  right: 18px;
  /* Anchor to TOP-right so the card can never overlap the composer,
     regardless of how many attachment pills the user has queued. */
  top: 18px;
  width: 520px;
  max-width: calc(100vw - 36px);
  max-height: calc(100vh - 36px);
  display: flex;
  flex-direction: column;
  background: hsl(var(--c-card));
  border: 1px solid hsl(var(--c-border));
  border-radius: 12px;
  box-shadow: 0 10px 28px hsl(0 0% 0% / 0.18), 0 2px 6px hsl(0 0% 0% / 0.08);
  font-size: 13px;
  color: hsl(var(--c-fg));
  z-index: 60;
  overflow: hidden;
  animation: erc-in 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes erc-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.erc-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px 10px;
  border-bottom: 1px solid hsl(var(--c-border) / 0.6);
}
.erc-favicon {
  width: 16px;
  height: 16px;
  border-radius: 3px;
  flex: 0 0 auto;
}
.erc-header-text {
  min-width: 0;
  flex: 1;
}
.erc-title {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.2;
  color: hsl(var(--c-fg));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.erc-url {
  margin-top: 2px;
  font-size: 11px;
  color: hsl(var(--c-muted-fg));
  font-family: 'IBM Plex Mono', monospace;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.erc-body {
  flex: 1 1 auto;
  min-height: 320px;
  max-height: 720px;
  overflow-y: auto;
  padding: 16px 18px;
  scroll-behavior: smooth;
  font-size: 14px;
  line-height: 1.6;
}
.erc-body::-webkit-scrollbar { width: 6px; }
.erc-body::-webkit-scrollbar-thumb {
  background: hsl(var(--c-border));
  border-radius: 3px;
}
.erc-placeholder {
  color: hsl(var(--c-muted-fg));
  font-style: italic;
  font-size: 12.5px;
}
.erc-text {
  line-height: 1.55;
  font-size: 13px;
  color: hsl(var(--c-fg));
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.erc-footer {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px 12px;
  border-top: 1px solid hsl(var(--c-border) / 0.6);
}
.erc-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: hsl(0 72% 55%);
  animation: erc-pulse 1.2s ease-in-out infinite;
}
@keyframes erc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.erc-elapsed {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  color: hsl(var(--c-muted-fg));
}
.erc-stop {
  appearance: none;
  border: 0;
  background: hsl(0 72% 55%);
  color: white;
  padding: 6px 14px;
  border-radius: 6px;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
}
.erc-stop:hover { background: hsl(0 72% 48%); }
.erc-stop:disabled { opacity: 0.6; cursor: default; }

@media (max-width: 640px) {
  .external-rec-card {
    right: 10px;
    left: 10px;
    width: auto;
    top: 10px;
  }
}

/* The input is a pure-white pill against the chat canvas — corner radius
   = half height so it's perfectly circular at each end. --composer-h
   drives both the pill height and the send button diameter; change it
   in one place and the geometry stays concentric.

   --pad-inner is the wrapper's internal padding: both the send button's
   distance from the pill's right edge AND the distance from the label to
   the pill's left edge. Keeping them equal is what makes the send button
   and the paperclip feel symmetric. */
/* Heights: --composer-h is the intended pill height (single-row). The
   textarea gets tight top/bottom padding so the wrapper naturally sits
   at --composer-h without needing min-height to force it. border-radius
   = 9999px guarantees a perfect pill at any computed height (including
   when the textarea expands to multiple rows). */
.composer-wrap .input-wrapper {
  --composer-h: 44px;
  --pad-inner: 4px;
  padding: var(--pad-inner);
  /* Fixed radius (matches half of the 44 px single-line height so the
     pill look is preserved in the common case). As the textarea grows
     into multi-line the corners stay at 22 px — the wrapper becomes a
     rounded rectangle instead of a tall elongated capsule. */
  border-radius: 22px;
  background: hsl(var(--c-input));
  /* Subtle soft border — no hard line, just a whisper around the pill. */
  border: 1px solid hsl(var(--c-border) / 0.5);
  box-shadow: 0 1px 3px hsl(228 20% 4% / 0.06);
  width: auto;
  min-height: var(--composer-h);
}

/* Pin action buttons (attach, enter-toggle, mic, send) to the BOTTOM
   of the wrapper so they stay parallel to the bottom border as the
   textarea expands to multiple lines — natural place for send.
   align-self overrides the parent's items-center utility per-child;
   the textarea-wrap still fills the flex row and grows downward.

   Per-button margin-bottom compensates for differing button heights
   so all vertical CENTERS line up on a shared y-axis. Reference is
   the 32 px attach/mic buttons; shorter buttons get lifted so their
   midline matches. */
.composer-wrap .input-wrapper > .attach-label,
.composer-wrap .input-wrapper > #enter-toggle,
.composer-wrap .input-wrapper > #record-btn,
.composer-wrap .input-wrapper > #send-btn {
  align-self: flex-end;
}
/* Uniform 1 px lift on all four buttons. Relative offsets below keep
   every button's center on the same horizontal line; shared +1 lift
   nudges the whole row a hair upward. */
.composer-wrap .input-wrapper > .attach-label,
.composer-wrap .input-wrapper > #record-btn {
  margin-bottom: 1px;
}
.composer-wrap .input-wrapper > #send-btn {
  /* Send (30 px) lifted +1 vs 32 px siblings. */
  margin-bottom: 2px;
}
.composer-wrap .input-wrapper > #enter-toggle {
  /* Enter toggle (22 px) lifted +5 vs 32 px siblings. */
  margin-bottom: 6px;
}
.composer-wrap .input-wrapper:focus-within {
  border-color: hsl(var(--c-primary) / 0.35);
  box-shadow: 0 0 0 3px hsl(var(--c-primary) / 0.1);
}
/* Textarea: tight vertical padding so the pill doesn't inflate beyond
   --composer-h. With 13.5px text × 1.625 line-height ≈ 22px, plus 7px
   top/bottom padding → 36px, plus 4px wrapper padding × 2 → 44px pill. */
.composer-wrap .input-wrapper > textarea {
  padding-top: 7px;
  padding-bottom: 7px;
  padding-left: 8px;
  padding-right: 8px;
  line-height: 1.4;
}

/* ─── Dictation mode ─────────────────────────────
   When the user taps the mic, the input-wrapper gains .recording:
   – attach button dims + becomes non-interactive
   – mic button swaps to an X (cancel)
   – send button swaps to a ✓ (finish)
   – textarea becomes read-only; transcript streams in live from the
     server's WS transcription updates
   – a dashed loudness viz is drawn on the <canvas> behind the text */

/* Textarea wrapper — relative so the canvas can sit underneath. Takes
   the flex: 1 slot that the textarea used to occupy. */
.textarea-wrap {
  flex: 1;
  position: relative;
  display: flex;
  align-items: stretch;
  min-width: 0;
}
.textarea-wrap > textarea {
  position: relative;
  z-index: 1;
  background: transparent;
  flex: 1;
}

/* Viz canvas: hidden when idle; fades to dimmed-behind-text during
   dictation. pointer-events: none so the textarea still receives focus
   and can show its placeholder / cursor over the top. */
.dictate-viz {
  position: absolute;
  left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: 32px;
  width: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.input-wrapper.recording .dictate-viz {
  opacity: 0.55;
}

/* Glyph swap on mic + send. Default icon visible when idle; recording
   icon (X for mic, check for send) visible when dictating. */
.input-wrapper .icon-recording { display: none; }
.input-wrapper.recording .icon-default { display: none; }
.input-wrapper.recording .icon-recording { display: inline-block; }

/* Mic button: in recording state it's a destructive-looking X. */
.input-wrapper.recording > #record-btn {
  background: hsl(0 72% 51% / 0.12);
  color: hsl(0 72% 60%);
}
.input-wrapper.recording > #record-btn:hover {
  background: hsl(0 72% 51% / 0.2);
  color: hsl(0 72% 65%);
}

/* Send button: stays primary but now carries a ✓ — "finalize" rather
   than "send". Subtle pulse so it reads as the recommended next action. */
.input-wrapper.recording > #send-btn {
  animation: dictate-pulse 1.8s ease-in-out infinite;
}
@keyframes dictate-pulse {
  0%, 100% { box-shadow: 0 0 0 0 hsl(var(--c-primary) / 0.4); }
  50%      { box-shadow: 0 0 0 6px hsl(var(--c-primary) / 0); }
}

/* Attach button: disabled while dictating. Muted color + can't-click. */
.input-wrapper.recording > .attach-label {
  opacity: 0.3;
  pointer-events: none;
}

/* Textarea while dictating: a slight tint so the user clearly sees it's
   read-only mid-recording. The dimmed waveform shows through from the
   canvas behind. */
.input-wrapper.recording > .textarea-wrap > textarea {
  color: hsl(var(--c-fg) / 0.92);
  cursor: default;
}

/* Paperclip + mic: sized to fit inside the pill's inner area with
   a small inset so icons don't crowd the pill edge. */
.composer-wrap .input-wrapper > label,
.composer-wrap .input-wrapper > #record-btn {
  width: calc(var(--composer-h) - 2 * var(--pad-inner) - 4px);
  height: calc(var(--composer-h) - 2 * var(--pad-inner) - 4px);
  border-radius: 50%;
  flex-shrink: 0;
}
.composer-wrap .input-wrapper > #record-btn {
  margin-right: 2px;
}

/* Send button: smaller than the pill's inner height so there's
   visible breathing room on all four sides of the circle — still
   concentric with the pill's right cap (gap is equal on top/right/
   bottom). The right-padding nudge on the wrapper keeps the gap
   uniform. --send-size and --send-margin-right can be tuned
   independently of the pill height. */
.composer-wrap .input-wrapper {
  --send-size: 30px;
  --send-gap: 3px;  /* visible gap between send button and pill edge */
  padding-right: calc(var(--pad-inner) + var(--send-gap));
}
.composer-wrap .input-wrapper > #send-btn {
  width: var(--send-size);
  height: var(--send-size);
  border-radius: 50%;
  flex-shrink: 0;
  margin-left: 2px;
}
.composer-wrap .input-wrapper > #send-btn > svg {
  width: 13px; height: 13px;
}

/* Enter-to-send toggle — slider-style. The knob carries the return
   glyph and slides right when the toggle is ON (Enter sends), left
   when OFF. Track + knob colors change with state so the meaning is
   readable at a glance. */
.composer-wrap .input-wrapper > #enter-toggle {
  --toggle-h: 22px;
  --toggle-w: 40px;
  --knob-size: 16px;
  position: relative;
  width: var(--toggle-w);
  height: var(--toggle-h);
  border-radius: calc(var(--toggle-h) / 2);
  background: hsl(var(--c-muted) / 0.85);
  border: 1px solid hsl(var(--c-border) / 0.6);
  padding: 0;
  flex-shrink: 0;
  cursor: pointer;
  transition: background 0.18s, border-color 0.18s;
  display: flex;
  align-items: center;
  margin-right: 4px;
}
.composer-wrap .input-wrapper > #enter-toggle.active {
  background: hsl(var(--c-primary) / 0.35);
  border-color: hsl(var(--c-primary) / 0.55);
}
.composer-wrap .input-wrapper > #enter-toggle:hover {
  border-color: hsl(var(--c-border));
}
.composer-wrap .input-wrapper > #enter-toggle.active:hover {
  background: hsl(var(--c-primary) / 0.45);
  border-color: hsl(var(--c-primary) / 0.7);
}
.enter-toggle-knob {
  position: absolute;
  top: 50%;
  left: 2px;
  width: var(--knob-size);
  height: var(--knob-size);
  transform: translateY(-50%);
  border-radius: 50%;
  background: hsl(var(--c-muted-fg) / 0.85);
  color: hsl(var(--c-card));
  display: flex;
  align-items: center;
  justify-content: center;
  transition: left 0.18s cubic-bezier(0.16, 1, 0.3, 1),
              background 0.18s, color 0.18s;
  box-shadow: 0 1px 2px hsl(0 0% 0% / 0.22);
}
.composer-wrap .input-wrapper > #enter-toggle.active .enter-toggle-knob {
  /* Knob slides to the right edge when Enter sends. Math: track
     width − knob width − right-padding (2px). */
  left: calc(var(--toggle-w) - var(--knob-size) - 4px);
  background: hsl(var(--c-primary));
  color: hsl(0 0% 100%);
}
.enter-toggle-knob svg {
  width: 10px;
  height: 10px;
}

/* First-child (paperclip label) gets a touch of left breathing so the
   icon isn't mashed against the pill's left curve. */
.composer-wrap .input-wrapper > label:first-child {
  margin-left: 4px;
}

#status-bar {
  padding: 8px 28px;
  font-size: 11.5px;
  max-width: calc(820px + 56px);
}
#todo-dock {
  padding: 8px 28px 4px;
  max-width: calc(820px + 56px);
}

/* ─── Mobile tightening ───────────────────────── */
@media (max-width: 640px) {
  .message-stream { --stream-pad: 12px; }
  .composer-wrap > .input-wrapper,
  .composer-wrap > #attachment-preview {
    margin-left: 12px;
    margin-right: 12px;
  }
  #status-bar,
  #todo-dock {
    padding-left: 12px;
    padding-right: 12px;
  }
}
