/* ╔════════════════════════════════════════════════════════════╗
   ║                                                            ║
   ║   COMPOSER ARCHITECTURE — LOAD-BEARING INVARIANTS          ║
   ║                                                            ║
   ║   Every rule below has a real-bug history. If a future     ║
   ║   pass quietly violates one, regression-hunt these first.  ║
   ║                                                            ║
   ║   1. `.chat__foot` is `position: absolute; bottom: 0`      ║
   ║      with `pointer-events: none` — chat content scrolls    ║
   ║      *behind* the composer. Only the pill + + + queue +    ║
   ║      attach-tray re-enable pointer-events. The gutter is   ║
   ║      transparent.                                          ║
   ║                                                            ║
   ║   2. `--composer-h` is published by a ResizeObserver in    ║
   ║      ChatView.jsx that watches `.chat__foot`. `.chat__list`║
   ║      reserves `padding-bottom: calc(var(--composer-h) +    ║
   ║      16px)` so the last message clears the pill as it      ║
   ║      grows (chips, queue tray, multi-line input).          ║
   ║                                                            ║
   ║   3. Empty-state wrap centers in the chat area with a      ║
   ║      FIXED 140px bottom reservation (NOT --composer-h).    ║
   ║      Logo moves up with the keyboard (viewport shrinks     ║
   ║      via interactive-widget=resizes-content → chat         ║
   ║      shrinks → centerpoint moves up) but stays put when    ║
   ║      the composer grows multi-line. --composer-h-based     ║
   ║      padding would drift the logo every keystroke.         ║
   ║                                                            ║
   ║   4. Pill + + button are a UNIFIED PAIR:                   ║
   ║        - Same background: color-mix(srgb, --surface 60%,   ║
   ║          transparent).                                     ║
   ║        - Same backdrop-filter: blur(16px) saturate(140%).  ║
   ║        - Pill min-height 48px === + button 48×48.          ║
   ║        - Same hairline border-color.                       ║
   ║      Touching one without the other breaks the visual      ║
   ║      pairing — they read as one composer surface.          ║
   ║                                                            ║
   ║   5. Active `+` (popover open) is PURPLE FROSTED GLASS:    ║
   ║      `color-mix(srgb, --accent 75%, transparent)` — not    ║
   ║      a flat solid. The 75% lets the backdrop-blur still    ║
   ║      show through, so the active state is the same         ║
   ║      material as the resting state, just tinted. The       ║
   ║      `!important` on background-color is belt-and-         ║
   ║      suspenders against `:hover` lingering on touch tap.   ║
   ║                                                            ║
   ║   6. ONE focus halo, on `.chat__pill:focus-within` only:   ║
   ║      `box-shadow: 0 0 0 3px var(--accent-dim)`. The +      ║
   ║      button gets NO halo. Two halos read as a broken       ║
   ║      double outline. (Restored exactly from commit         ║
   ║      be32e58 after two iterations adding overcomplicated   ║
   ║      multi-layer glows.)                                   ║
   ║                                                            ║
   ║   7. `.chat__pill--tall` toggles when textarea height > 45 ║
   ║      (not 30, not 50). 31 ≈ single line, 55 ≈ two lines,   ║
   ║      45 sits safely between. Earlier 30-threshold fired    ║
   ║      on every keystroke and yanked send/mic to the bottom. ║
   ║      Tall state anchors send/stop/mic to flex-end so the   ║
   ║      action stays near the cursor.                         ║
   ║                                                            ║
   ║   8. `.chat__pill--with-attach .chat__input-line` always   ║
   ║      uses align-items: flex-end. When chips are present    ║
   ║      and the user types multi-line, the cursor must stay   ║
   ║      pinned at the visible bottom of the pill — pure       ║
   ║      `--tall` alignment alone missed the chips case.       ║
   ║                                                            ║
   ║   9. Attach cards are UNIFORM 96×96 squares (image and     ║
   ║      file alike). Tray uses `align-items: center` so a     ║
   ║      mixed image+file row doesn't stretch shorter cards.   ║
   ║      File card name wraps to 2 lines (line-clamp) since    ║
   ║      96px is too tight for 1-line ellipsis. The badge      ║
   ║      already communicates the file type, so the display    ║
   ║      strips the extension; the agent receives the full     ║
   ║      original name regardless.                             ║
   ║                                                            ║
   ║  10. Soft-keyboard contract (CSS half — JSX half lives     ║
   ║      in ComposerPopover.jsx, ChatInputBar.jsx,             ║
   ║      ChatSettingsPanel.jsx):                               ║
   ║        - Pill is `font-size: 16px` minimum — iOS Safari    ║
   ║          zooms inputs below 16px on focus.                 ║
   ║        - `.chat__input` overrides `outline: none` on       ║
   ║          :focus-visible because the pill's :focus-within   ║
   ║          box-shadow already provides the focus ring;       ║
   ║          the inner outline drew a sharp square inside      ║
   ║          the rounded pill.                                 ║
   ║                                                            ║
   ╚════════════════════════════════════════════════════════════╝ */

/* CONTRACT: .chat paints `var(--bg)` as its own solid background.
   Message bubbles below paint `var(--surface)` / `var(--surface2)`
   on top of this — so both `--bg` and `--surface*` must read as
   OPAQUE FILL variables. Translucent values let the page background
   bleed through bubbles.

   `position: relative` anchors `.chat__foot` (absolutely positioned)
   so chat content can scroll *behind* the composer. The gutter
   around the pill is transparent — only the pill + `+` button are
   opaque. List bottom padding tracks the foot's measured height
   via the `--composer-h` CSS var. */
.chat {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--bg); /* CONTRACT: opaque app-wide fill — must hide content beneath */
  position: relative;
  overflow: hidden;
}

/* Empty state: centered in the available chat area, with a FIXED
   bottom reservation so the composer doesn't overlap the logo.
   Centering means the cluster moves up when the visible viewport
   shrinks (keyboard open via `interactive-widget=resizes-content`)
   — the chat container itself gets shorter, so the centerpoint
   moves up. The bottom padding is a CONSTANT (not --composer-h)
   so multi-line input growth doesn't push the cluster up; only
   the keyboard does. 140px clears a typical multi-line composer;
   if the user composes a wall of text on the empty state, slight
   overlap is acceptable. */
.chat--empty .chat__empty-wrap {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-bottom: 140px;
}

/* ── Scroll area ───────────────────────────────────── */

.chat__scroll {
  flex: 1;
  width: calc(100% + 32px);
  padding-right: 32px;
  box-sizing: border-box;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  overflow-anchor: none;  /* prevent browser scroll anchoring */
  transform: translateZ(0);  /* force hardware acceleration */
  position: relative;  /* makes this the offsetParent for children */
}

/* Chat scroll: always hidden, never reveals on scroll. Mirrors the
   global default in index.css after the on-scroll reveal mechanism
   was retired. ALL chat-scroll invariants in CLAUDE.md ("Chat UX —
   non-negotiable constraints") still hold: overflow, spacer math,
   scrollTop, overflow-anchor, useScrollMode listeners are untouched. */
.chat__scroll { scrollbar-width: none; }
.chat__scroll::-webkit-scrollbar { width: 0; height: 0; }

/* ── Message list ──────────────────────────────────── */

.chat__list {
  list-style: none;
  width: 100%;
  max-width: 768px;
  margin: 0 auto;
  /* Bottom padding clears the absolutely-positioned `.chat__foot`.
     Tracks the foot's measured height via the `--composer-h` CSS
     var (written by ChatView's ResizeObserver), plus a small
     breathing-room margin so chips/queue tray growing the foot
     keeps the last message clear of the pill. Fallback 80px is
     for the first paint before the RO fires. */
  padding: 8px 6px calc(var(--composer-h, 80px) + 16px);
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* Force content taller than scroll container so iOS
     triggers elastic bounce even when at the bottom. */
  min-height: calc(100% + 1px);
}

/* ── Load earlier ──────────────────────────────────── */

.chat__older { text-align: center; }

.chat__older button {
  background: none;
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  border-radius: 20px;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  font-size: 12px;
  padding: 5px 16px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

.chat__older button:hover {
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  border-color: var(--border); /* CONTRACT: 1px line on top of the inherited opaque fill */
}

/* ── Empty state ───────────────────────────────────── */

.chat__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 72px 24px 24px;
  gap: 10px;
  max-width: 768px;
  margin: 0 auto;
  /* Lift the empty-state cluster (logo + title + sub) ~5% above
     the visual centerline so it doesn't feel buried by the
     composer at the bottom. */
  transform: translateY(-5%);
}

.chat__empty-glyph { opacity: 0.85; margin-bottom: 12px; }
/* .chat__empty-title lives in index.css (gradient text). */
.chat__empty-sub { font-size: 14px; color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */ line-height: 1.7; }

/* App-embedded empty state: no shell-scale 72px top padding (the
   embed sheet is small) and no -5% lift — the quiet "Ask the agent
   to get started." line should read as a calm hint centered in the
   sheet, not a buried splash. Only the contextual subtitle renders
   (see ChatView.jsx's `embedded` branch), so this just tightens the
   wrapper. */
.chat-embed .chat__empty--embed {
  padding: 24px;
  transform: none;
}

/* Quick-action chip row — embedded empty state with app-declared suggestions.
   Horizontally scrollable pill row; max 4 chips rendered by ChatView.jsx.
   Tap pre-fills the composer (never auto-sends). */
.chat__empty--chips {
  padding: 20px 16px;
  align-items: flex-start;
  text-align: left;
}
.chat__quick-actions {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;        /* Firefox */
  padding-bottom: 2px;          /* room for focus ring */
  width: 100%;
}
.chat__quick-actions::-webkit-scrollbar { display: none; }
.chat__quick-action-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  border-radius: 20px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.3;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, border-color 0.12s;
  min-height: 36px;             /* 44px touch target via padding */
}
.chat__quick-action-chip:hover {
  background: var(--surface2, var(--surface));
  border-color: var(--accent-dim, var(--accent));
}
.chat__quick-action-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.chat__quick-action-chip:active {
  background: var(--accent-dim, color-mix(in srgb, var(--accent) 12%, transparent));
}

/* ── Messages ──────────────────────────────────────── */

.chat__msg {
  display: flex;
  flex-direction: column;
  max-width: 100%;
}

.chat__msg--user { align-items: flex-end; }
.chat__msg--assistant { align-items: flex-start; }

/* Tap-to-toggle timestamp below user messages.
   Absolutely positioned so it doesn't shift content below. */
.chat__ts {
  font-size: 11px;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  margin-top: 4px;
  opacity: 0;
  pointer-events: none;
  height: 0;
  overflow: visible;
  transition: opacity 0.15s ease;
}
.chat__ts--visible { opacity: 1; }

/* ── Bubble content ────────────────────────────────── */

.chat__text {
  font-size: 15px;
  line-height: 1.7;
  word-break: break-word;
}

/* Bubble layout only — background/color/box-shadow live in index.css
   (gradient + accent-dim halo) so the visual treatment is owned in
   one place. Do NOT re-declare those here. */
.chat__text--user {
  padding: 11px 16px;
  border-radius: 18px 18px 5px 18px;
  max-width: min(85%, 75ch);
  white-space: pre-wrap;
}

.chat__text--assistant {
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  max-width: 100%;
}

/* Error blocks read as a system notice, not an agent reply. Same
   shape during streaming and after promote (see MsgContent's
   `block.type === 'error'` branch + useStreamConnection's 'error'
   event handler — both push the `{type: 'error', message}` shape
   so the visual stays consistent across promote). Full-width, no
   bubble background, red bar + label so users can tell at a glance
   that the agent didn't say this — the system did. */
.chat__text--error {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
  border-left: 3px solid var(--danger);
  border-radius: 6px;
  padding: 10px 12px;
  color: var(--text);
  font-size: 13px;
  line-height: 1.45;
  margin: 6px 0;
  /* Cancel the assistant-bubble background that would otherwise be
     inherited from .chat__text--assistant — we want the error to
     read as standalone, not as something the agent typed. */
  max-width: none;
}
.chat__error-label {
  display: block;
  margin-bottom: 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--danger);
}

/* ── Typing indicator ─────────────────────────────── */

.chat__thinking {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 4px 6px;
}

.chat__thinking span {
  display: block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border); /* CONTRACT: used as a 1px separator stripe — border color repurposed as fill */
  animation: thinking 1.2s ease-in-out infinite;
}
.chat__thinking span:nth-child(2) { animation-delay: 0.2s; }
.chat__thinking span:nth-child(3) { animation-delay: 0.4s; }

@keyframes thinking {
  0%, 60%, 100% { transform: scale(1); opacity: 0.4; }
  30% { transform: scale(1.4); opacity: 1; }
}

.chat__cursor {
  display: inline-block;
  width: 6px;
  height: 6px;
  background: var(--accent); /* CONTRACT: vivid opaque accent fill */
  border-radius: 50%;
  margin-left: 4px;
  vertical-align: middle;
  animation: cursorPulse 1.5s ease-in-out infinite;
}

@keyframes cursorPulse {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 1; }
}

/* ── Spacer ──────────────────────────────────────── */

.spacer-dynamic { /* no transition — must be instant for scroll math to work */ }

/* ── Bottom sentinel ─────────────────────────────── */

/* Zero-size element at the absolute bottom of .chat__scroll, watched
   by an IntersectionObserver to detect "user is at the bottom".
   1px height (not 0) so the observer actually has a target to track;
   browsers can be quirky with zero-size IO targets. aria-hidden in
   JSX keeps it invisible to assistive tech. */
.chat__bottom-sentinel {
  height: 1px;
  width: 100%;
  pointer-events: none;
}

/* ── Connection status ───────────────────────────── */

.connection-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 6px 16px;
  font-size: 12px;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  background: var(--surface); /* CONTRACT: opaque card/panel fill, sits on --bg */
  border-top: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
}

.connection-status__text { opacity: 0.8; }

.connection-status__retry {
  background: none;
  border: 1px solid var(--border); /* CONTRACT: 1px line on opaque fill */
  border-radius: 6px;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  font-size: 11px;
  padding: 2px 10px;
  cursor: pointer;
}

.connection-status__retry:hover {
  background: var(--surface2); /* CONTRACT: opaque hover/raised fill, slightly above --surface */
}

/* ── Tool activity blocks ──────────────────────────── */

.chat__tools {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 4px 0;
  padding-left: 6px;
  max-width: min(100%, 480px);
}

.chat__text + .chat__tools { margin-top: 10px; }
.chat__tools + .chat__text { margin-top: 10px; }
.chat__tools + .chat__tools { margin-top: 2px; }
.chat__text--assistant + .chat__text--assistant { margin-top: 0.75rem; }

.chat__tool {
  border-radius: 10px;
  font-size: 12px; /* floor raised from 11px for readability */
  font-family: var(--mono); /* CONTRACT: monospace font stack for tool-call output */
  overflow: hidden;
}

.chat__tool--running {
  background: var(--surface); /* CONTRACT: opaque card/panel fill, sits on --bg */
  border: 1px solid var(--accent, var(--border-light)); /* CONTRACT: vivid accent line; falls back to hairline if accent unset */
}

.chat__tool--done {
  background: color-mix(in srgb, var(--accent) 5%, transparent); /* CONTRACT: 5% accent tint — must sit on the opaque message-list fill beneath, NOT used as a standalone surface */
  border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border-light)); /* CONTRACT: 1px line mixing accent with hairline */
}

.chat__tool-header {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  cursor: pointer;
  user-select: none;
}

.chat__tool-name {
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

.chat__tool--done .chat__tool-name { color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */ }
.chat__tool-toggle { color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */ font-size: 12px; /* floor raised from 10px */ flex-shrink: 0; }

.chat__tool-chip {
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: 9px;
  font-size: 12px; /* floor raised from 9.5px */
  font-family: var(--mono); /* CONTRACT: monospace font stack, matches tool output */
  color: var(--accent); /* CONTRACT: vivid accent text — marks the skill load */
  background: color-mix(in srgb, var(--accent) 12%, transparent); /* CONTRACT: faint accent tint on the opaque tool fill */
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border-light)); /* CONTRACT: accent-tinted hairline */
  white-space: nowrap;
}

.chat__tool-detail {
  padding: 0 10px 8px;
  font-size: 12px; /* floor raised from 10.5px */
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 200px;
  overflow-y: auto;
  line-height: 1.5;
}

.chat__tool-output { color: var(--text); /* CONTRACT: high-contrast text on opaque fill */ }

.chat__tool-spin {
  display: inline-block;
  width: 9px;
  height: 9px;
  border: 1.5px solid var(--border); /* CONTRACT: 1.5px ring on opaque fill (used for spinner track) */
  border-top-color: var(--accent); /* CONTRACT: vivid accent — spinning arc on top of the track */
  border-radius: 50%;
  animation: spin 0.75s linear infinite;
  flex-shrink: 0;
}

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

/* ── Input footer ──────────────────────────────────── */

/* Absolutely positioned at the bottom of `.chat` so the scroll
   area extends behind it. Background is TRANSPARENT — only the
   inner pill and `+` button are opaque, so chat content visibly
   scrolls under the composer gutter. Pointer events are disabled
   on the foot itself and re-enabled selectively on its interactive
   children, so taps on the gutter pass through to the chat. */
.chat__foot {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 8px 12px max(8px, env(safe-area-inset-bottom));
  background: transparent;
  pointer-events: none;
  z-index: 5;
}

.chat__foot .queued,
.chat__foot .connection-status,
.chat__foot .chat__attach-tray,
.chat__foot .chat__pill,
.chat__foot .composer-plus { pointer-events: auto; }

/* Embedded composer: the app sheet (not the chat) owns the device
   safe area, so the `env(safe-area-inset-bottom)` reservation here
   would double-gap the bottom inside the sheet. Pin the bottom
   padding to a flat 8px so the pill sits flush in the embed. */
.chat-embed .chat__foot {
  padding-bottom: 8px;
}

/* The form is a transparent shell. File chips render above the
   input row when present; the `+` button and pill share the row. */
.chat__form {
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.chat__offline-note {
  align-self: center;
  margin: 0 0 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  border: 1px solid var(--border-light);
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
}

/* The pill: TRANSLUCENT surface with backdrop-blur — chat content
   visibly softens through the pill background. Hairline border and
   slight surface tint keep it readable as a card. ~48px tall to
   match a WhatsApp-style composer; send/mic buttons live INSIDE
   at 32×32. */
.chat__pill {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  background: color-mix(in srgb, var(--surface) 60%, transparent);
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  border-radius: 24px;
  padding: 4px;
  min-height: 48px;
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  transition: border-color 0.2s, box-shadow 0.2s;
}

/* Multi-line: pill grows downward and the send/mic anchor to the
   bottom-right so the action stays near the cursor. */
.chat__pill--tall { align-items: flex-end; }
.chat__pill--tall .chat__send,
.chat__pill--tall .chat__stop,
.chat__pill--tall .chat__mic { align-self: flex-end; }

/* Focus halo — exact restore of the be32e58 rule that was on the
   old `.chat__form`: a single 3px translucent-accent ring around
   the focused composer. Same shape, just now on `.chat__pill`
   since the form is transparent in this redesign. */
.chat__pill:focus-within {
  border-color: var(--accent); /* CONTRACT: vivid accent line */
  box-shadow: 0 0 0 3px var(--accent-dim); /* CONTRACT: translucent accent glow — focus ring */
}

.chat__input {
  flex: 1;
  background: none;
  border: none;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  font-size: 16px;    /* iOS Safari no-zoom threshold — keep at 16px */
  font-weight: 400;
  line-height: 1.45;
  resize: none;
  outline: none;
  min-height: 24px;
  max-height: 280px;
  padding: 4px 8px 4px 12px;
  overflow-y: auto;
  display: block;
  /* Suppress the Android Chrome / iOS Safari tap-highlight that
     paints a translucent dark rectangle on the focused textarea.
     Tailwind v4's Preflight resets textarea border-radius to 0,
     which makes the highlight render as a sharp square INSIDE
     the rounded pill — looks like a "faint square background"
     against the pill surface. The pill itself already provides
     the focus ring via :focus-within. */
  -webkit-tap-highlight-color: transparent;
}

.chat__input::placeholder { color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */ }
.chat__input:disabled { opacity: 0.5; }

/* Suppress the global textarea:focus-visible outline (index.css) on
   the chat input specifically. The .chat__pill:focus-within box-shadow
   already provides a fully-visible, theme-aware keyboard focus ring
   around the pill; the inner 2px outline draws a sharp rectangle
   inside the rounded pill glow and looks like a purple square. Keep
   the global rule for Setup/Login/Settings inputs that don't have
   their own focus-within container. */
.chat__input:focus-visible { outline: none; }

/* Send / Stop / Mic buttons all live INSIDE `.chat__pill` at 36×36.
   Only `.chat__send` keeps the accent fill (primary action); stop +
   mic are borderless icons on the pill's own surface. */
.chat__send {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--accent); /* CONTRACT: vivid opaque accent fill */
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.15s, transform 0.08s;
}

.chat__send:hover:not(:disabled) {
  background: var(--accent-hover); /* CONTRACT: vivid opaque accent on hover */
}
.chat__send:active:not(:disabled) { transform: scale(0.94); }

.chat__send:disabled {
  background: var(--border); /* CONTRACT: used as a 1px separator stripe — border color repurposed as fill */
  cursor: not-allowed;
}

/* Stop button — red so the "interrupt running agent" affordance
   is unmissable. Solid red on hover, slightly desaturated at rest. */
.chat__stop {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--danger); /* CONTRACT: destructive accent on opaque fill */
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.chat__stop:hover {
  color: #fff;
  background: var(--danger); /* CONTRACT: destructive accent fill */
}

/* ── Voice input ──────────────────────────────────── */
.chat__mic {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: color 0.15s;
}
.chat__mic:hover { color: var(--text); /* CONTRACT: high-contrast text on opaque fill */ }

.chat__mic--active {
  color: var(--danger); /* CONTRACT: destructive accent on opaque fill */
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
  animation: mic-pulse 1.5s ease-in-out infinite;
}

@keyframes mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--danger) 20%, transparent); }
  50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--danger) 0%, transparent); }
}


/* Row aligns children to the BOTTOM so the `+` button stays 38×38
   pinned at the bottom-left when the pill grows multi-line. Without
   this the row would `stretch` the `+` button to the pill's grown
   height (top edge follows the pill upward). */
.chat__input-row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

/* ── + button (composer popover trigger) ──────────────── */
.composer-plus {
  position: relative;
  flex-shrink: 0;
  align-self: flex-end;
}

/* + button: 48×48, matched 1:1 to the pill's min-height — same
   frosted-glass surface tint (60%) and same hairline border so
   the two read as a unified pair. When the popover opens the
   trigger switches to a solid accent fill to signal active
   state. */
.chat__plus {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  background: color-mix(in srgb, var(--surface) 60%, transparent);
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  flex-shrink: 0;
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
}

.chat__plus:hover {
  background: color-mix(in srgb, var(--surface2) 70%, transparent);
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
}

/* Keyboard-only focus ring on the + button. No :focus rule (pointer
   focus must not show a ring — the one-halo invariant). :focus-visible
   fires only for keyboard navigation. When the popover is active the
   accent fill already signals the state, so we suppress the ring on
   --active to avoid a second concentric halo on top of the accent fill. */
.chat__plus:focus { outline: none; }
.chat__plus:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chat__plus.chat__plus--active:focus-visible { outline: none; }

/* Active state: PURPLE FROSTED GLASS — translucent accent fill so
   the same backdrop-blur the pill uses shows through, but tinted
   the platform accent. The double `.chat__plus.chat__plus--active`
   selector bumps specificity so this wins over any :hover state
   lingering from a touch tap; `!important` on background-color is
   belt-and-suspenders against any cascade additions. */
.chat__plus.chat__plus--active {
  background-color: color-mix(in srgb, var(--accent) 75%, transparent) !important;
  background-image: none;
  border-color: var(--accent);
  color: #fff;
  /* Keep the .chat__plus backdrop-filter blur — purple frosted
     glass instead of a flat opaque purple disc. */
  box-shadow: none;
}

/* ── Composer popover (Attach + Model picker) ────────── */

/* Drops UP from the trigger — composer sits at the bottom of the
   screen so a downward popover would clip. `bottom: calc(100% + 8px)`
   anchors the panel's bottom edge 8px above the trigger. Aligned to
   the LEFT edge of the trigger so the panel doesn't drift off-screen
   on narrow viewports; the panel is 296px wide which fits even at
   the 360px mobile baseline. */
.composer-popover {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  width: 296px;
  /* `dvh` (dynamic viewport height) shrinks when the soft keyboard
     opens, so the popover never extends past the visible area. Cap
     at ~50% of the visible viewport so the user can always see
     some chat above; absolute 420px cap on tall screens. */
  max-height: min(50dvh, 420px);
  overflow-y: auto;
  background: var(--surface); /* CONTRACT: opaque card/panel fill, sits on --bg */
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  border-radius: 14px;
  box-shadow: 0 12px 32px -8px rgba(0, 0, 0, 0.35);
  padding: 6px;
  z-index: 50;
  /* Touch-scroll inside the popover should not bubble up and close
     the keyboard or scroll the chat behind it. */
  overscroll-behavior: contain;
}

[data-theme="light"] .composer-popover {
  box-shadow: 0 12px 32px -8px rgba(15, 18, 25, 0.18);
}

.composer-popover__section {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* Hairline divider between the Attach section and the picker
   section so the popover reads as two distinct groups. */
.composer-popover__section--picker {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
}

/* Generic popover row — used by the Attach files entry. Same
   geometry as `.csp-row` in ChatSettingsPanel so the two sections
   align visually. */
.composer-popover__row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  background: none;
  border-radius: 8px;
  cursor: pointer;
  font: inherit;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  text-align: left;
  transition: background 0.12s ease;
}

.composer-popover__row:hover { background: var(--surface2); /* CONTRACT: opaque hover/raised fill, slightly above --surface */ }

.composer-popover__row-icon {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: var(--surface2); /* CONTRACT: opaque hover/raised fill, slightly above --surface */
  border: 1px solid rgba(255, 255, 255, 0.07);
  display: grid;
  place-items: center;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  flex-shrink: 0;
}
[data-theme="light"] .composer-popover__row-icon { border-color: rgba(15, 18, 25, 0.10); }

.composer-popover__row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.composer-popover__row-title {
  font-size: 14px;     /* --font-text-sm */
  font-weight: 500;
  line-height: 1.4;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
}

.composer-popover__row-sub {
  font-size: 12px;     /* --font-text-xs */
  font-weight: 400;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  line-height: 1.4;
}

/* ── File chips: attach cards inside the pill ────────────────
   When the user attaches files the pill flips to a column layout
   (.chat__pill--with-attach) with the cards on top and the input
   row below. Cards are fixed-box: images render as 72×72 thumbs,
   other files as 168px rectangles with a colored type badge. */

/* Inner row inside the pill containing the textarea + send/mic.
   Default: center-aligned so a single line of text + the mic icon
   read as vertically centered in the pill. When the textarea grows
   multi-line, `.chat__pill--tall` flips to flex-end so the buttons
   stay anchored at the bottom-right while the textarea expands up. */
.chat__input-line {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 2px;
}
.chat__pill--tall .chat__input-line {
  align-items: flex-end;
}

/* When file chips are attached the pill flips to a column with the
   chip tray on top and the input-line below. */
.chat__pill--with-attach {
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 8px;
  border-radius: 22px;
}
/* When chips are attached the pill is in column layout and the
   input-line sits BELOW the chip tray. Anchor send/mic to the
   bottom of input-line regardless of textarea height so they don't
   drift upward as the user types multiple lines. */
.chat__pill--with-attach .chat__input-line {
  min-height: 38px;
  align-items: flex-end;
}

/* `align-items: center` keeps the row from stretching a shorter
   card (the 80×80 image) to match a taller one — every card stays
   at its declared height. The tray's parent (.chat__pill) sets the
   overall vertical rhythm; the cards inside MUST be uniformly
   sized so the row doesn't grow or shrink as the user adds files. */
.chat__attach-tray {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 2px 4px;
  overflow-x: auto;
  scrollbar-width: none;
  min-width: 0;
}
.chat__attach-tray::-webkit-scrollbar { display: none; }

.chat__attach-card {
  position: relative;
  flex-shrink: 0;
}

/* File card: 96×96 square, matching the image card 1:1 so a mixed
   row of files and images reads as a uniform grid. Badge top-left,
   filename below as a 2-line caption (clamped). Width is the same
   as the image, so long names truncate — the badge already
   communicates the file's type and the agent receives the full
   name regardless of what we display. */
.chat__attach-card--file {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 6px;
  padding: 10px;
  background: var(--surface2); /* CONTRACT: opaque hover/raised fill, slightly above --surface */
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  border-radius: 14px;
  width: 96px;
  height: 96px;
}

.chat__attach-card--image {
  width: 96px;
  height: 96px;
  padding: 0;
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface2); /* CONTRACT: opaque hover/raised fill, slightly above --surface */
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
}

.chat__attach-card--error {
  border-color: rgba(248, 113, 113, 0.55);
}

.chat__attach-card-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.chat__attach-card-icon {
  width: 30px;
  height: 36px;
  border-radius: 4px;
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.06em;
  position: relative;
}
/* Folded-corner detail for the doc-icon — gives a paper feel
   without an SVG. */
.chat__attach-card-icon::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 8px;
  background: linear-gradient(135deg, transparent 50%, rgba(0,0,0,0.25) 50%);
}
.chat__attach-card-icon--pdf  { background: #e0382c; }
.chat__attach-card-icon--doc  { background: #2b7fff; }
.chat__attach-card-icon--code { background: var(--muted); }

/* Filename wraps to 2 lines (clamped) since the file card is a
   tight 96×96 square — a 1-line ellipsis at this width chops too
   aggressively. `break-all` keeps long unbroken sequences (hashes,
   tokens) from overflowing the card. */
.chat__attach-card-name {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-height: 1.25;
  word-break: break-all;
}

/* The remove × sits INSIDE the card corner — the pill's rounded
   boundary (and backdrop-filter clip region) would crop an outside-
   positioned × on small cards near the pill edge. Top/right at 4px
   keeps the button fully visible and within the card's bounds. */
.chat__attach-card-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  border: none;
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0;
  z-index: 1;
}
.chat__attach-card-remove:hover { background: rgba(0, 0, 0, 0.8); }
[data-theme="light"] .chat__attach-card-remove {
  background: rgba(255, 255, 255, 0.85);
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
}
[data-theme="light"] .chat__attach-card-remove:hover { background: rgba(255, 255, 255, 1); }

.chat__attach-card-spin {
  position: absolute;
  bottom: 6px;
  left: 6px;
  width: 12px;
  height: 12px;
  border: 1.5px solid var(--border); /* CONTRACT: 1.5px ring on opaque fill (used for spinner track) */
  border-top-color: var(--accent); /* CONTRACT: vivid accent — spinning arc on top of the track */
  border-radius: 50%;
  animation: spin 0.75s linear infinite;
}

/* ── Message attachments ─────────────────────────────── */

.chat__attachments {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: min(85%, 75ch);
}

.chat__attach-images {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.chat__attach-thumb {
  height: 80px;
  max-width: 160px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  cursor: pointer;
  transition: opacity 0.15s;
}

.chat__attach-thumb:hover {
  opacity: 0.85;
}

.chat__attach-file {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--surface); /* CONTRACT: opaque card/panel fill, sits on --bg */
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 12px;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  text-decoration: none;
  max-width: 200px;
  transition: background 0.15s;
}

.chat__attach-file:hover {
  background: var(--surface2); /* CONTRACT: opaque hover/raised fill, slightly above --surface */
}

.chat__attach-file-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}

.chat__attach-file-size {
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  font-size: 11px;
  white-space: nowrap;
}

/* ── Open app CTA ──────────────────────────────────── */

/* Floats ABOVE the composer (which is `position: absolute; bottom: 0;
   z-index: 5`). Without an explicit z-index this banner — a normal-
   flow sibling of `.chat__foot` — would render in the same visual
   slot as the composer pill and the pill's opaque frosted-glass
   surface would paint over it, making the "Open app →" button
   unclickable. `bottom: calc(var(--composer-h) + 8px)` anchors it
   8px above the composer's measured height (published by the
   ResizeObserver in ChatView.jsx). z-index 6 sits just above the
   composer's 5 — anything notification-shaped should be reachable
   over the chat surface but not over modal/drawer layers. */
.chat__open-app {
  position: absolute;
  bottom: calc(var(--composer-h, 80px) + 8px);
  right: 0;
  left: 0;
  display: flex;
  justify-content: flex-end;
  padding: 0 16px;
  pointer-events: none;
  z-index: 6;
}

.chat__open-app > * {
  pointer-events: auto;
}

.chat__open-app-btn {
  background: var(--accent-dim); /* CONTRACT: translucent accent glow — NOT a fill, relies on the surface beneath */
  border: 1px solid var(--accent); /* CONTRACT: vivid accent line */
  color: var(--accent); /* CONTRACT: vivid accent text */
  border-radius: 8px;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
}

.chat__open-app-btn:hover {
  background: var(--accent); /* CONTRACT: vivid opaque accent fill */
  color: var(--bg); /* CONTRACT: paint app-bg color on the accent fill — needs --bg solid to stay readable */
}

/* ── Queued messages tray ──────────────────────────── */

.queued {
  max-width: 768px;
  margin: 0 auto 8px;
  background: var(--surface); /* CONTRACT: opaque card/panel fill, sits on --bg */
  border: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  border-radius: 12px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.queued__hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 8px 6px;
  border-bottom: 1px solid var(--border-light); /* CONTRACT: subtle 1px hairline on opaque fill */
  margin-bottom: 4px;
  gap: 8px;
  cursor: pointer;
  border-radius: 6px;
  transition: background 0.15s;
  user-select: none;
}

.queued__hdr:hover {
  background: var(--surface2); /* CONTRACT: secondary opaque fill */
}

.queued__hdr:focus-visible {
  outline: 2px solid var(--accent, currentColor);
  outline-offset: -2px;
}

.queued--collapsed .queued__hdr {
  border-bottom: 0;
  margin-bottom: 0;
}

.queued--collapsed {
  gap: 0;
}

.queued__hdr-chevron {
  flex-shrink: 0;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  transition: transform 0.15s;
}

.queued__hdr-chevron--collapsed {
  transform: rotate(-90deg);
}

.queued__items {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.queued__count {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.queued__hint {
  font-size: 11px;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  opacity: 0.75;
  flex: 1;
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.queued__row {
  display: flex;
  align-items: flex-start;
  gap: 4px;
  border-radius: 8px;
  transition: background 0.15s;
}

.queued__row:hover {
  background: var(--bg); /* CONTRACT: opaque app-wide fill — must hide content beneath */
}

.queued__toggle {
  flex: 1;
  display: flex;
  align-items: flex-start;
  gap: 6px;
  padding: 8px 6px 8px 10px;
  background: none;
  border: none;
  text-align: left;
  font: inherit;
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
  cursor: pointer;
  min-width: 0;
}

.queued__toggle:disabled {
  cursor: default;
}

.queued__chevron {
  flex-shrink: 0;
  margin-top: 5px;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  transition: transform 0.15s;
}

.queued__chevron--open {
  transform: rotate(180deg);
}

.queued__text {
  font-size: 14px;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
  min-width: 0;
  flex: 1;
}

.queued__row:not(.queued__row--expanded) .queued__text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.queued__cancel {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  margin: 4px 4px 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 6px;
  color: var(--muted); /* CONTRACT: low-contrast text on opaque fill */
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.queued__cancel:hover {
  background: var(--bg); /* CONTRACT: opaque app-wide fill — must hide content beneath */
  color: var(--text); /* CONTRACT: high-contrast text on opaque fill */
}

/* ── Screen-reader-only utility ─────────────────────────────────────────── */
/* Visually hidden but fully accessible to AT. Standard clip-path technique. */
.chat__sr-status {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Hit-area expansion for small × buttons ─────────────────────────────── */
/* The inset-expansion ::after pattern from Drawer.css: the visual button
   stays small but the tap target grows to ≥40px without affecting layout.
   queued__cancel needs position: relative added (it has no positioning yet).
   chat__attach-card-remove is already position: absolute — its ::after
   simply expands the hit area in place. */
.queued__cancel {
  position: relative;
}

.queued__cancel::after {
  content: '';
  position: absolute;
  /* queued__cancel is 28×28; expand to 40px on each axis */
  inset: -6px;
}

.chat__attach-card-remove::after {
  content: '';
  position: absolute;
  /* chat__attach-card-remove is 20×20; expand to 40px on each axis */
  inset: -10px;
}
