/* ============================================================================
   Echo — mobile-first chat dashboard
   Aesthetic: near-black "command deck" with a luminous mint-cyan "signal"
   accent. Calm, premium, thumb-first. Distinctive display type (Bricolage
   Grotesque) over a clean grotesk body (Hanken) with JetBrains Mono for code.
   ========================================================================== */

/* ----------------------------------------------------------------- Tokens */
:root {
  /* Surfaces — layered near-black blues */
  --bg:        #0a0c11;
  --bg-2:      #0c0f15;
  --panel:     rgba(13, 16, 22, 0.72);   /* blurred bars */
  --surface:   #131825;
  --surface-2: #1a2030;
  --surface-3: #232b3d;

  --line:    rgba(255, 255, 255, 0.08);
  --line-2:  rgba(255, 255, 255, 0.05);

  /* Text */
  --text:       #e8ecf3;
  --text-dim:   #a6b0c2;
  --text-faint: #6e7889;

  /* Signal accent — echo mint-cyan */
  --accent:      #5ce0c6;
  --accent-2:    #36c9aa;
  --accent-ink:  #042019;
  --accent-soft: rgba(92, 224, 198, 0.14);
  --accent-glow: rgba(92, 224, 198, 0.28);

  /* Semantic */
  --green:     #56cf80;
  --green-ink: #04210f;
  --red:       #ff6b6b;
  --red-soft:  rgba(255, 107, 107, 0.13);
  --amber:     #f5b94a;
  --amber-soft:rgba(245, 185, 74, 0.13);

  /* Geometry */
  --r-sm: 12px;
  --r:    18px;
  --r-lg: 24px;
  --r-pill: 999px;

  --shadow:    0 12px 34px rgba(0, 0, 0, 0.45);
  --shadow-sm: 0 4px 14px rgba(0, 0, 0, 0.35);

  --col: 720px;                 /* readable content column */
  --bar-h: 50px;

  /* Safe areas */
  --sa-t: env(safe-area-inset-top, 0px);
  --sa-b: env(safe-area-inset-bottom, 0px);
  --sa-l: env(safe-area-inset-left, 0px);
  --sa-r: env(safe-area-inset-right, 0px);

  --font-display: "Bricolage Grotesque", "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;
  --font-ui: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Code", Menlo, monospace;
}

/* ----------------------------------------------------------------- Reset */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html, body { height: 100%; max-width: 100%; overflow-x: hidden; }

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-ui);
  color: var(--text);
  background: var(--bg);
  line-height: 1.55;
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  max-width: 100%;
  overflow-x: hidden;
  overflow-y: hidden;               /* the app owns scrolling */
  overscroll-behavior: none;
  /* Atmosphere: two soft signal glows over the base, plus the base color. */
  background-image:
    radial-gradient(900px 600px at 82% -8%, rgba(92, 224, 198, 0.10), transparent 60%),
    radial-gradient(820px 560px at 0% 108%, rgba(72, 120, 200, 0.10), transparent 62%);
  background-attachment: fixed;
}

/* Fine grain overlay for depth (non-interactive) */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.025;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

button, input, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; -webkit-tap-highlight-color: transparent; }
[hidden] { display: none !important; }

::selection { background: var(--accent-soft); color: var(--text); }

/* Scrollbars — slim and unobtrusive */
* { scrollbar-width: thin; scrollbar-color: var(--surface-3) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 99px; border: 2px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-track { background: transparent; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* ----------------------------------------------------------------- Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px; padding: 0 18px;
  border-radius: var(--r-sm);
  font-weight: 600; font-size: 15px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  transition: transform .12s ease, background .18s ease, box-shadow .18s ease, opacity .18s ease;
}
.btn:active { transform: scale(.97); }
.btn--block { width: 100%; }
.ai-consent-list { margin: 4px 0 12px; padding-left: 18px; line-height: 1.5; }
.ai-consent-list li { margin: 6px 0; }
.ai-consent-fine { font-size: 12px; color: var(--text-dim); margin: 8px 0 16px; }
.btn--accent {
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  color: var(--accent-ink);
  border-color: transparent;
  box-shadow: 0 8px 22px -8px var(--accent-glow);
}
.btn--accent:disabled { opacity: .55; box-shadow: none; }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 11px; color: var(--text-dim);
  transition: background .16s ease, color .16s ease, transform .12s ease;
  flex: 0 0 auto;
}
.icon-btn:hover { color: var(--text); background: var(--surface-2); }
.icon-btn:active { transform: scale(.92); }

.link-btn { color: var(--text-faint); font-size: 14px; text-decoration: underline; text-underline-offset: 3px; }
.link-btn:hover { color: var(--text-dim); }

/* ============================================================ KEY SCREEN */
.key-screen {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  height: var(--vvh, 100dvh);              /* shrinks to the visible area when the keyboard opens */
  display: grid; place-items: safe center; /* centred, but top-aligns if it would overflow the visible area */
  padding: calc(24px + var(--sa-t)) 22px calc(24px + var(--sa-b));
  overflow: auto;
}
.key-card {
  width: 100%; max-width: 380px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 34px 26px 26px;
  background: linear-gradient(180deg, rgba(26, 32, 48, 0.55), rgba(15, 19, 27, 0.55));
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  backdrop-filter: blur(14px);
  animation: rise .5s cubic-bezier(.2,.7,.2,1) both;
}
/* Boot splash — clean branded cover over the cold-start gap (no home-screen flash) */
.boot-splash {
  position: fixed; inset: 0; z-index: 100;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
  background: radial-gradient(130% 100% at 50% 38%, #16313a 0%, #0c1016 60%, #0a0c11 100%);
  transition: opacity .42s ease;
}
.boot-splash.hide { opacity: 0; pointer-events: none; }
.boot-spark {
  width: 76px; height: 76px; overflow: visible;
  filter: drop-shadow(0 0 16px rgba(92, 224, 198, .4));
}
.boot-spark .boot-spin, .boot-spark .boot-twk { transform-box: view-box; transform-origin: 50px 50px; }
.boot-spark .boot-spin { animation: bootSpin 5s linear infinite; }
.boot-spark .boot-twk  { animation: bootTwk 1.8s ease-in-out infinite; }
@keyframes bootSpin { to { transform: rotate(360deg); } }
@keyframes bootTwk  { 0%, 100% { transform: scale(.9); } 50% { transform: scale(1.1); } }
.boot-msg {
  margin: 0; color: var(--text-dim); font-size: 14px; letter-spacing: .01em;
  font-family: var(--font-display); animation: bootMsg 1.8s ease-in-out infinite;
}
@keyframes bootMsg { 0%, 100% { opacity: .45; } 50% { opacity: .9; } }
@media (prefers-reduced-motion: reduce) {
  .boot-spark .boot-spin, .boot-spark .boot-twk, .boot-msg { animation: none; }
}

.key-logo {
  width: 96px; height: 96px; border-radius: 26px;
  display: grid; place-items: center; margin-bottom: 18px;
  background: radial-gradient(120% 120% at 50% 30%, rgba(92,224,198,.18), transparent 70%);
  box-shadow: 0 0 0 1px var(--line) inset, 0 18px 40px -16px var(--accent-glow);
}
.key-logo img { display: block; }
.key-title {
  font-family: var(--font-display);
  font-weight: 700; font-size: 38px; letter-spacing: -0.02em;
  background: linear-gradient(180deg, #fff, #b9f4e6);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.key-sub { color: var(--text-dim); margin-top: 4px; font-size: 15px; }

.health-chip {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 18px; padding: 7px 13px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--line);
  font-size: 13px; color: var(--text-dim);
  max-width: 100%;
}
.health-chip .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); flex: 0 0 auto; }
.health-chip .health-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.health-chip--checking .dot { background: var(--amber); animation: pulse 1.2s ease-in-out infinite; }
.health-chip--ok .dot { background: var(--green); box-shadow: 0 0 10px var(--green); }
.health-chip--ok { color: var(--text); }
.health-chip--down .dot { background: var(--red); }

.key-form { width: 100%; margin-top: 22px; display: flex; flex-direction: column; gap: 12px; }
.field { display: flex; flex-direction: column; gap: 7px; text-align: left; }
.field-label { font-size: 13px; color: var(--text-dim); font-weight: 600; padding-left: 2px; }
.field input {
  width: 100%; min-height: 50px; padding: 0 15px;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); color: var(--text); font-size: 16px;
  transition: border-color .16s ease, box-shadow .16s ease;
}
.field input::placeholder { color: var(--text-faint); }
.field input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.key-error { color: var(--red); font-size: 14px; margin-top: 2px; }
/* Cloudflare Turnstile mount. The widget brings its own sizing; we just give it
   room and center it within the create-account form. Hidden in sign-in mode. */
.turnstile-field { display: flex; justify-content: center; min-height: 65px; }
.turnstile-field > * { max-width: 100%; }
/* Sign-in / Create-account toggle. Reuses .link-btn; just centres + spaces it. */
.key-toggle { align-self: center; margin-top: 2px; font-size: 13.5px; }
.key-foot { color: var(--text-faint); font-size: 12.5px; margin-top: 18px; }

/* ================================================================== APP */
.app {
  /* Fixed + sized to the visual viewport (via JS below) so iOS Safari can't
     scroll the document up and shove the page off-screen when the keyboard opens.
     left/right:0 + margin-inline:auto + max-width keeps it centred on desktop. */
  position: fixed; z-index: 1;
  top: 0; left: 0; right: 0;
  margin-inline: auto; max-width: 920px;
  height: 100dvh;                     /* JS overrides via --app-h when keyboard opens */
  display: grid;
  grid-template-rows: auto auto 1fr auto;  /* topbar / verify-banner / messages / composer */
  grid-template-columns: minmax(0, 1fr);   /* the column can't be widened past the viewport by
                                              a wide child (e.g. the composer row) — was clipping
                                              the right edge of messages + composer */
  overflow-x: hidden;
}
/* When JS measures the visual viewport (keyboard open), it overrides height. */
.app[data-vv] { height: var(--app-h, 100dvh); }

/* ----------------------------------------------------------------- Topbar */
.topbar {
  grid-row: 1;
  display: flex; align-items: center; gap: 6px;
  height: calc(var(--bar-h) + var(--sa-t));
  padding: var(--sa-t) calc(8px + var(--sa-r)) 0 calc(8px + var(--sa-l));
  background: var(--panel);
  backdrop-filter: blur(16px) saturate(140%);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
.brand {
  display: flex; align-items: center; gap: 7px;
  min-width: 0; flex: 0 1 auto; overflow: hidden;
}
.brand-name {
  font-family: var(--font-display); font-weight: 700; font-size: 18px;
  letter-spacing: -0.02em; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--line);
  font-size: 11px; font-weight: 600; color: var(--text-dim);
  max-width: 110px; flex: 0 1 auto; min-width: 0;
}
.badge-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); flex: 0 0 auto; }
.badge.is-ok .badge-dot { background: var(--green); box-shadow: 0 0 8px var(--green); }
.badge.is-down .badge-dot { background: var(--red); }
.badge-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-transform: lowercase; }

/* Role badge — sits beside the brand to show who you're signed in as */
.role-badge {
  display: inline-flex; align-items: center;
  padding: 2px 7px; border-radius: var(--r-pill);
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text-dim);
  flex: 0 0 auto; white-space: nowrap;
}
.role-badge.is-owner {
  background: var(--accent-soft); border-color: var(--accent-soft); color: var(--accent);
}
.topbar-spacer { flex: 1 1 auto; }

/* Ask / Auto segmented control */
.mode {
  position: relative; display: inline-flex; align-items: center;
  padding: 3px; border-radius: var(--r-pill);
  background: var(--bg-2); border: 1px solid var(--line);
  flex: 0 0 auto;
}
.mode-opt {
  position: relative; z-index: 1;
  min-width: 42px; height: 28px; padding: 0 11px;
  font-size: 12.5px; font-weight: 600; color: var(--text-dim);
  border-radius: var(--r-pill);
  transition: color .2s ease;
}
.mode-opt.is-active { color: var(--accent-ink); }
.mode-thumb {
  position: absolute; top: 3px; left: 3px; z-index: 0;
  width: calc(50% - 3px); height: calc(100% - 6px);
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  box-shadow: 0 4px 12px -4px var(--accent-glow);
  transition: transform .26s cubic-bezier(.4,1.2,.4,1), background .2s ease;
}
.mode.is-auto .mode-thumb {
  transform: translateX(100%);
  background: linear-gradient(180deg, var(--amber), #e69b2a);
  box-shadow: 0 4px 12px -4px rgba(245,185,74,.4);
}
.mode.is-auto .mode-opt[data-mode="auto"] { color: #2a1c02; }
/* Engine toggle (Claude / Kimi) reuses the 2-way chrome; slide thumb right for Kimi. */
.mode.is-kimi .mode-thumb { transform: translateX(100%); }
.mode-row-note { margin-top: 8px; font-size: 11px; line-height: 1.45; color: var(--text-faint); }

/* ----------------------------------------------- Reasoning depth (3-way)
   Same segmented-pill chrome as Ask/Auto, but three options. The sliding thumb
   is sized to a third and translated by class (is-fast / is-balanced / is-deep)
   instead of the 2-way 50% rule. Owner-only (rides the #modeRow gate). */
.reasoning-head { margin-top: 14px; }
.reasoning .reasoning-thumb {
  width: calc(33.333% - 2px);
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
}
.reasoning.is-balanced .reasoning-thumb { transform: translateX(100%); }
.reasoning.is-deep .reasoning-thumb { transform: translateX(200%); }
.reasoning .mode-opt.is-active { color: var(--accent-ink); }

/* Mode control as it sits in the drawer foot: a labelled row above the
   Manage-users / Sign-out actions. Owner-only (applyRole hides #modeRow). */
.mode-row {
  padding: 4px 12px 12px; margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.mode-row-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin-bottom: 8px;
}
.mode-row-label { font-size: 13px; font-weight: 600; color: var(--text-dim); }
.mode-row-hint { font-size: 11px; color: var(--text-faint); }
/* The toggle spans the row inside the drawer (vs. the compact top-bar pill). */
.mode-row .mode { display: flex; width: 100%; }
.mode-row .mode-opt { flex: 1 1 0; min-width: 0; }

/* Auto-mode warning — shown inside the drawer mode row when Auto is selected */
.auto-banner {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 9px; padding: 9px 11px;
  background: var(--amber-soft);
  color: var(--amber);
  font-size: 12px; font-weight: 500; line-height: 1.4;
  border: 1px solid var(--amber-soft); border-radius: var(--r-sm);
}
.auto-banner strong { font-weight: 700; }
.auto-banner svg { flex: 0 0 auto; margin-top: 1px; }

/* ------------------------------------------------- "Confirm your email" banner
   Slim, dismissible rail under the topbar. Sits in its own grid row so it never
   overlaps the timeline; collapses to display:none (its track) when hidden. */
.verify-banner {
  grid-row: 2;
  display: flex; align-items: center; gap: 9px;
  padding: 8px calc(12px + var(--sa-r)) 8px calc(12px + var(--sa-l));
  background: var(--amber-soft);
  border-bottom: 1px solid var(--line);
  color: var(--amber); font-size: 13px; line-height: 1.35;
}
.verify-ico { flex: 0 0 auto; }
.verify-text { flex: 1 1 auto; min-width: 0; color: var(--text); font-weight: 500; }
.verify-resend {
  flex: 0 0 auto; padding: 5px 11px; border-radius: var(--r-pill);
  background: var(--amber-soft); border: 1px solid var(--amber);
  color: var(--amber); font-size: 12.5px; font-weight: 600;
  transition: background .14s ease, opacity .14s ease;
}
.verify-resend:hover { background: rgba(245, 185, 74, 0.22); }
.verify-resend:disabled { opacity: .55; }
.verify-dismiss { width: 30px; height: 30px; color: var(--amber); }
.verify-dismiss:hover { color: var(--text); background: var(--amber-soft); }
@media (max-width: 420px) {
  .verify-text { font-size: 12.5px; }
  .verify-resend { padding: 5px 9px; }
}

/* ----------------------------------------------------------- Message list */
.messages {
  grid-row: 3;
  min-width: 0;         /* messages can't widen the grid past the screen either */
  overflow-y: auto; overflow-x: hidden;
  /* none = no rubber-band bounce when the list doesn't overflow (empty/short chat),
     and no scroll-chaining to the page. It scrolls ONLY once content overflows. */
  overscroll-behavior: none;
  padding: 16px calc(15px + var(--sa-r)) 24px calc(15px + var(--sa-l));
  display: flex; flex-direction: column; gap: 13px;
  -webkit-overflow-scrolling: touch;
}
.messages > * { width: 100%; max-width: var(--col); margin-inline: auto; }

/* Empty state */
.empty {
  margin: auto; text-align: center; max-width: 460px;
  display: flex; flex-direction: column; align-items: center;
  padding: 24px 8px;
  animation: rise .5s cubic-bezier(.2,.7,.2,1) both;
}
.empty-rings {
  width: 76px; height: 76px; border-radius: 22px; display: grid; place-items: center;
  margin-bottom: 16px;
  background: radial-gradient(120% 120% at 50% 30%, rgba(92,224,198,.16), transparent 70%);
  box-shadow: 0 0 0 1px var(--line) inset;
}
.empty-title {
  font-family: var(--font-display); font-weight: 600; font-size: 1.3rem;
  letter-spacing: -0.02em; color: var(--text);
}
.empty-sub { color: var(--text-dim); margin-top: 7px; font-size: 14px; }

/* Echo "working" presence — a small radiating Echo mark pinned to the TOP BAR
   (the header's centre is empty on iPhone, where the brand name is hidden). Shown
   ONLY while the agent is thinking/working; fades out (still) when it stops.
   Non-interactive, sits just above the sticky bar, never overlaps the buttons. */
.echo-thinker {
  position: fixed;
  top: calc(var(--sa-t) + (var(--bar-h) - 34px) / 2);   /* vertically centred in the bar */
  left: 50%;
  width: 34px; height: 34px;
  transform: translateX(-50%);
  pointer-events: none;
  opacity: 1;                 /* ALWAYS present in the bar */
  z-index: 21;                /* just above the sticky top bar (z-index 20) */
  filter: drop-shadow(0 0 5px rgba(92, 224, 198, .3));
  transition: filter .35s ease;
}
.echo-thinker svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* Classic spark — STILL at rest (a clean 4-point sparkle), and while WORKING it
   layers three separate motions: a slow turn, a soft twinkle, and a gentle bob. */
.echo-thinker .sp-bob,
.echo-thinker .sp-spin,
.echo-thinker .sp-tw { transform-box: view-box; transform-origin: 50px 50px; }
/* Top-bar mark glows while working; its shape-morph is the SMIL <animate> baked
   into the SVG, started/stopped from setStreaming() so it's still when idle. */
body.echo-working .echo-thinker { filter: drop-shadow(0 0 12px rgba(92, 224, 198, .6)); }

/* --------------------------------------------------------------- Messages */
.msg { display: flex; animation: rise .32s cubic-bezier(.2,.7,.2,1) both; }
.msg.user { justify-content: flex-end; }
/* Assistant / error rows stack their child(ren) full-width as normal blocks.
   These rows can carry BOTH a text bubble AND a separate gallery row; a plain
   row-flex would let the text bubble collapse to its min-content width (a 1ch
   vertical sliver of letters) next to the image. Column + stretch makes the
   bubble fill the row and the gallery sit on its own line beneath it. */
.msg.assistant, .msg.error { flex-direction: column; align-items: stretch; }

.bubble {
  max-width: 86%;
  padding: 9px 13px;
  border-radius: var(--r);
  font-size: 15.5px;          /* comfortable reading size (was 14px) */
  word-wrap: break-word; overflow-wrap: anywhere;
}
/* User: compact, understated accent-tinted sent message — not a banner pill */
.msg.user .bubble {
  max-width: 82%;
  padding: 7px 12px;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  border: 1px solid var(--accent-soft);
  color: var(--text);
  border-bottom-right-radius: 6px;
  font-weight: 500;
}
.msg.user .user-text { white-space: pre-wrap; }
/* Queued follow-up: typed while a run is live, not yet sent. Muted until its turn. */
.msg.user.queued .bubble { opacity: 0.62; }
.queued-tag {
  margin-top: 5px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 5px;
}
.queued-tag::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
  animation: queuedPulse 1.6s ease-in-out infinite;
}
.queued-label { opacity: 0.7; }
.queued-now {
  margin-left: 4px; padding: 2px 9px; border-radius: var(--r-pill);
  border: 1px solid var(--accent-soft); background: var(--accent-soft);
  color: var(--accent); font: inherit; font-weight: 700; cursor: pointer;
}
.queued-now:active { opacity: 0.65; }
@keyframes queuedPulse { 0%,100% { opacity: 0.3; } 50% { opacity: 0.85; } }
/* Sent-attachment chips inside the user's own bubble */
.msg-attachments {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 6px;
}
.msg-attachments:first-child { margin-top: 0; }
.msg-attach-chip {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 4px 9px; border-radius: var(--r-pill);
  background: rgba(0, 0, 0, 0.22); border: 1px solid var(--line);
  font-size: 12px; color: var(--text-dim);
}
.msg-attach-chip .attach-ic { color: var(--accent); display: inline-flex; flex: 0 0 auto; }
.msg-attach-chip .attach-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px;
}

/* Assistant text: reads like a document — minimal chrome, full measure */
.msg.assistant .bubble {
  max-width: 100%;
  padding: 2px 0;
  background: transparent;
  border: none;
  border-radius: 0;
  color: var(--text);
}
.msg.error .bubble {
  max-width: 100%;
  padding: 9px 13px;
  background: var(--red-soft); border: 1px solid rgba(255,107,107,.3);
  color: #ffd9d9; border-bottom-left-radius: 6px; font-size: 13.5px;
}

/* Streaming caret */
.bubble.streaming .md > :last-child::after,
.bubble.streaming.empty-stream::after {
  content: "";
  display: inline-block; width: 8px; height: 1.05em;
  margin-left: 2px; vertical-align: -2px;
  background: var(--accent); border-radius: 2px;
  animation: blink 1s steps(2, start) infinite;
}

/* ------------------------------------------------- Live status line
   Replaces the old 3-dot typing indicator. One calm, compact row that updates
   in place as the agent works — a soft pulsing orb + a short muted label, with
   an inline monospace fragment for commands/files. Not a bubble; a quiet line. */
.status-line {
  display: flex; align-items: center; gap: 9px;
  padding: 3px 2px; min-height: 22px;
  color: var(--text-dim); font-size: 13px;
  animation: rise .26s cubic-bezier(.2,.7,.2,1) both;
}
.status-orb {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent-glow);
  animation: orbPulse 1.3s ease-in-out infinite;
}
/* The Echo spark as the live "thinking/working" indicator on the status line. */
/* Morph animation lives inside the SVG (SMIL <animate> on the path) so it renders
   reliably in the app webview. The spark itself just sits still — no spin, no bob. */
.status-spark {
  width: 15px; height: 15px; flex: 0 0 auto; overflow: visible;
  fill: var(--accent);
  filter: drop-shadow(0 0 4px var(--accent-glow));
}
.status-label {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: flex; align-items: baseline; gap: 6px;
}
.status-label .status-mono {
  font-family: var(--font-mono); font-size: 12px;
  color: var(--text-faint);
  background: none; border: none; padding: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* ------------------------------------------------------ Markdown content */
.md { font-size: 15.5px; line-height: 1.62; }   /* comfortable reading (was 14px / 1.55) */
.md > :first-child { margin-top: 0; }
.md > :last-child { margin-bottom: 0; }
.md p { margin: 0 0 11px; }
.md h1, .md h2, .md h3, .md h4 {
  font-family: var(--font-display); font-weight: 600; line-height: 1.3;
  letter-spacing: -0.01em; margin: 15px 0 7px;
}
.md h1 { font-size: 1.25rem; } .md h2 { font-size: 1.1rem; } .md h3 { font-size: 1rem; } .md h4 { font-size: .9rem; }
.md ul, .md ol { margin: 0 0 11px; padding-left: 1.35em; }
.md li { margin: 3px 0; }
.md li::marker { color: var(--accent); }
.md a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.md strong { font-weight: 700; color: #fff; }
.md em { font-style: italic; }
.md hr { border: none; border-top: 1px solid var(--line); margin: 14px 0; }
.md blockquote {
  margin: 0 0 9px; padding: 4px 13px;
  border-left: 3px solid var(--accent); background: var(--accent-soft);
  border-radius: 0 8px 8px 0; color: var(--text-dim);
}
.md code {
  font-family: var(--font-mono); font-size: 0.9em;
  background: var(--surface-2); padding: 1px 5px; border-radius: 5px;
  border: 1px solid var(--line-2);
}
.md pre {
  margin: 0 0 11px; padding: 12px 13px;
  background: #0a0d14; border: 1px solid var(--line);
  border-radius: var(--r-sm); overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.md pre code { background: none; border: none; padding: 0; font-size: 12.5px; line-height: 1.55; }

/* Code-block header bar + Copy button (Claude-style). The bar is a non-scrolling
   sticky header pinned to the top-right of the <pre>; the scrollable code sits
   below it. Minimal + muted, but readable on the dark code background. */
.md pre.code-block { position: relative; padding-top: 36px; }
.code-bar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 2;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  height: 30px; padding: 0 8px 0 12px;
  background: linear-gradient(180deg, rgba(18,24,36,.96), rgba(10,13,20,.72));
  border-bottom: 1px solid var(--line-2);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
}
.code-lang {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .03em;
  text-transform: lowercase; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.code-copy {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border-radius: 7px;
  font-size: 11.5px; font-weight: 600; color: var(--text-dim);
  background: rgba(255,255,255,.04); border: 1px solid var(--line);
  transition: color .14s ease, background .14s ease, border-color .14s ease, transform .1s ease;
}
.code-copy:hover { color: var(--text); background: rgba(255,255,255,.08); }
.code-copy:active { transform: scale(.94); }
.code-copy .cc-ico { display: inline-flex; }
.code-copy.is-copied { color: var(--accent); border-color: var(--accent-soft); background: var(--accent-soft); }

/* Copy-card for emails/messages (```email / ```message / ```draft …). Same header +
   Copy button as a code block, but readable proportional text that WRAPS on a soft
   surface — so a ready-to-send draft reads like the finished message, not code. */
.md pre.code-block.msg-card {
  background: var(--surface-2); border-color: var(--line-2);
}
.md pre.code-block.msg-card code {
  font-family: inherit; font-size: 14px; line-height: 1.62; color: var(--text);
  white-space: pre-wrap; word-break: break-word;
}
.md pre.code-block.msg-card .code-bar {
  background: linear-gradient(180deg, rgba(40,48,64,.96), rgba(28,34,48,.72));
}
.md pre.code-block.msg-card .code-lang {
  font-family: inherit; text-transform: none; font-weight: 600;
  font-size: 11.5px; letter-spacing: 0; color: var(--text-dim);
}
.md table { width: 100%; border-collapse: collapse; margin: 0 0 11px; display: block; overflow-x: auto; }
.md th, .md td { border: 1px solid var(--line); padding: 6px 9px; text-align: left; font-size: 13px; }
.md th { background: var(--surface-2); font-weight: 600; }

/* ----------------------------------------------- AI-generated image gallery
   ChatGPT-style hero + thumbnail rail. A turn's images live in one `.img-stage`:
   a large `.img-hero` plus an `.img-rail` of smaller `.img-thumb`s down the
   right (desktop/wide) or a horizontal scroll row below (narrow phones). A
   single image is just the hero. Tapping a thumb promotes it; tapping the hero
   opens the lightbox. Each cell keeps a corner download button. Placeholders
   carry a "generating" shimmer until the real image events arrive. */
/* The stage is a normal left-aligned block in the assistant message flow: it
   sits DIRECTLY BELOW the text, never floated/pushed right. Contained to a
   modest max-width (ChatGPT-style) so neither the placeholder nor a finished
   portrait result can ever balloon to fill the page. Hero on the left, a thumb
   rail down its right when there are multiple images. */
.img-stage {
  display: flex; flex-direction: row; flex-wrap: nowrap;
  align-items: flex-start; gap: 8px;
  width: 100%;                 /* fill the bubble up to the cap below */
  max-width: 460px;            /* desktop cap — keeps the gallery tidy + left */
  margin: 4px auto 0 0;        /* margin-right:auto => stays LEFT-aligned */
}
.img-stage.is-single { display: block; }

/* Hero — the LARGE primary image on the LEFT. Given an explicit, dominant share
   that the rail can't overtake. Portrait by default (2:3, matching the
   1024×1536 output) and height-capped so it never overflows var(--vvh). */
.img-hero {
  flex: 0 0 64%; max-width: 64%; min-width: 0;
  max-height: min(62vh, calc(var(--vvh, 100dvh) - 180px));
  transition: opacity .18s ease;
}
/* Single image: the hero reverts to a normal contained full-width block. */
.img-stage.is-single .img-hero { flex: 1 1 auto; max-width: 100%; }
.img-hero .img-shot { width: 100%; height: 100%; object-fit: contain; max-height: inherit; }
/* Brief fade during a swipe-driven hero swap (touch only). */
.img-hero.is-swapping .img-shot { opacity: .35; }

/* Rail — SMALLER thumbnails stacked down the RIGHT, never wider than the hero. */
.img-rail {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 8px;
  max-height: min(62vh, calc(var(--vvh, 100dvh) - 180px));
  overflow-y: auto; overflow-x: hidden;
}
.img-thumb { width: 100%; aspect-ratio: 1 / 1; cursor: pointer; }
.img-thumb .img-shot {
  width: 100%; height: 100%; object-fit: cover; cursor: pointer;
}
.img-thumb { transition: transform .14s ease, border-color .14s ease, box-shadow .14s ease; }
.img-thumb:hover { transform: translateY(-1px); border-color: var(--accent-soft); }
/* Active thumbnail = the one currently shown as the hero. */
.img-thumb.is-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

/* Narrow phones: KEEP the ChatGPT layout — a smaller hero on the LEFT with a
   VERTICAL thumbnail rail down the RIGHT (don't collapse to a column). The hero
   is capped well below full-screen so 2–3 thumbs sit comfortably down the side.
   A single image (no rail) stays a normal contained hero. */
@media (max-width: 560px) {
  /* Deterministic ROW: big hero LEFT (64%), vertical thumb rail RIGHT, no wrap. */
  .img-stage {
    max-width: 100%;
    display: flex; flex-direction: row; flex-wrap: nowrap;
    align-items: flex-start; gap: 8px;
  }
  .img-hero {
    flex: 0 0 64%; max-width: 64%; min-width: 0;   /* LARGE hero on the LEFT */
    max-height: min(52vh, calc(var(--vvh, 100dvh) - 200px));
  }
  .img-hero .img-shot { object-fit: contain; }
  /* Single image keeps a normal contained hero (no rail to make room for). */
  .img-stage.is-single .img-hero { flex: 1 1 auto; max-width: 100%; }
  .img-rail {
    flex: 1 1 auto; width: auto; min-width: 0;
    display: flex; flex-direction: column;   /* vertical rail on the RIGHT */
    gap: 8px;
    max-height: min(52vh, calc(var(--vvh, 100dvh) - 200px));
    overflow-y: auto; overflow-x: hidden;
  }
  /* Thumbs fill the rail width, square-ish (smaller than the hero); tap swaps. */
  .img-thumb { flex: 0 0 auto; width: 100%; aspect-ratio: 1 / 1; }
}

/* A single image with its tightly-fitting cell + corner controls. */
.img-cell {
  position: relative; margin: 0;
  border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--line);
  min-height: 90px;
  animation: imgIn .32s cubic-bezier(.2,.7,.2,1) both;
}
.img-shot {
  display: block; width: 100%; height: auto;
  background: var(--surface-2);
}
.img-shot.zoomable { cursor: zoom-in; }
/* Subtle loading shimmer until the image finishes loading. */
.img-cell.is-loading::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg,
    var(--surface-2) 30%, var(--surface-3) 50%, var(--surface-2) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}
.img-cell.is-loading .img-shot { opacity: 0; }
.img-cell.is-error { display: grid; place-items: center; min-height: 120px; }
.img-cell.is-error::after {
  content: "Image unavailable"; color: var(--text-faint); font-size: 12.5px;
}

/* "Generating" placeholder — a PORTRAIT tile (2:3, matching the real output)
   with a smooth diagonal shimmer sweep over a subtle pulsing base. Kept modest:
   a tidy shimmer tile, never a full-page box. */
.img-pending {
  aspect-ratio: 2 / 3;
  background: var(--surface-2);
  animation: phPulse 1.8s ease-in-out infinite, imgIn .32s cubic-bezier(.2,.7,.2,1) both;
}
/* Hero placeholder: a modest portrait shimmer (up to ~360px wide), not a giant box. */
.img-hero.img-pending {
  aspect-ratio: 2 / 3; flex: 0 1 auto;
  width: 100%; max-width: 360px;
  max-height: min(54vh, calc(var(--vvh, 100dvh) - 200px));
}
/* Rail placeholders stay small + square, matching the real thumbs. */
.img-thumb.img-pending { aspect-ratio: 1 / 1; }
.img-pending-glow {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg,
    transparent 30%, rgba(92,224,198,.16) 48%, rgba(255,255,255,.06) 52%, transparent 70%);
  background-size: 240% 100%;
  animation: sweep 1.5s ease-in-out infinite;
}

/* "Couldn't generate" tile — quiet, non-alarming. */
.img-failed {
  aspect-ratio: 1 / 1; display: grid; place-items: center;
  background: var(--surface); border-style: dashed;
}
.img-failed-text { color: var(--text-faint); font-size: 12.5px; }

/* Download affordance — a tiny ghost button in the corner. */
.img-dl {
  position: absolute; top: 7px; right: 7px; z-index: 1;
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; color: var(--text);
  background: rgba(8, 11, 16, 0.55); border: 1px solid var(--line);
  backdrop-filter: blur(6px);
  opacity: 0; transition: opacity .15s ease, background .15s ease, transform .12s ease;
  text-decoration: none;
}
.img-cell:hover .img-dl { opacity: 1; }
.img-dl:hover { background: rgba(8, 11, 16, 0.8); color: var(--accent); }
.img-dl:active { transform: scale(.9); }
/* Touch devices have no hover — keep the download button visible. */
@media (hover: none) {
  .img-dl { opacity: .85; }
}
/* Thumbnails are small — shrink the download button so it doesn't dominate. */
.img-thumb .img-dl { width: 24px; height: 24px; border-radius: 7px; top: 4px; right: 4px; }

@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
@keyframes sweep { 0% { background-position: 180% 0; } 100% { background-position: -80% 0; } }
@keyframes phPulse { 0%, 100% { background: var(--surface-2); } 50% { background: var(--surface-3); } }
@keyframes imgIn { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }

/* ----------------------------------------------- AI-generated promo videos
   Mirrors the image hero: a turn's rendered video(s) live in one left-aligned,
   contained `.video-stage` block sitting below the assistant text. Vertical
   (9:16) and capped so the placeholder OR the finished player can never balloon
   to fill the page. While rendering (~1–3 min) a `.video-pending` tile reuses
   the image shimmer with a "Rendering video…" label; the finished cell is a
   real <video controls playsinline> with a corner download button. */
.video-stage {
  display: flex; flex-direction: column; gap: 8px;
  width: 100%;
  max-width: 320px;            /* contained — vertical promo cap */
  margin: 4px auto 0 0;        /* margin-right:auto => stays LEFT-aligned */
}
/* The contained vertical cell — rounded, bordered, like an image cell. */
.video-cell {
  position: relative; margin: 0;
  width: 100%; max-width: 320px;
  border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--line);
  animation: imgIn .32s cubic-bezier(.2,.7,.2,1) both;
}
.video-hero {
  aspect-ratio: 9 / 16;
  max-height: min(70vh, calc(var(--vvh, 100dvh) - 160px));
}
.video-shot {
  display: block; width: 100%; height: 100%;
  object-fit: contain; background: #000;
}
/* "Rendering video…" placeholder — vertical shimmer tile (reuses .img-pending
   animation) with a small centred label. */
.video-pending { display: grid; place-items: center; }
.video-pending-label {
  position: relative; z-index: 1;
  padding: 0 14px; text-align: center;
  color: var(--text-dim); font-size: 12.5px; font-weight: 600;
  line-height: 1.4; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis;
}
/* Download affordance — corner ghost button, matching the image download. */
.video-dl {
  position: absolute; top: 7px; right: 7px; z-index: 2;
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; color: var(--text);
  background: rgba(8, 11, 16, 0.55); border: 1px solid var(--line);
  backdrop-filter: blur(6px);
  opacity: .85; transition: opacity .15s ease, background .15s ease, transform .12s ease;
  text-decoration: none;
}
.video-cell:hover .video-dl { opacity: 1; }
.video-dl:hover { background: rgba(8, 11, 16, 0.8); color: var(--accent); }
.video-dl:active { transform: scale(.9); }
@media (max-width: 560px) {
  .video-stage { max-width: 300px; }
  .video-hero { max-height: min(64vh, calc(var(--vvh, 100dvh) - 200px)); }
}

/* Full-screen lightbox: a dark backdrop with the image centered. Tap anywhere
   (or the close button / Esc) to dismiss. Dependency-free, created on demand. */
.lightbox {
  position: fixed; inset: 0; z-index: 70;
  display: none; align-items: center; justify-content: center;
  padding: calc(20px + var(--sa-t)) 16px calc(20px + var(--sa-b));
  background: rgba(3, 5, 9, 0.92); backdrop-filter: blur(6px);
}
.lightbox.open { display: flex; animation: fade .18s ease forwards; opacity: 0; }
.lightbox-img {
  max-width: 100%; max-height: 100%;
  border-radius: var(--r-sm); object-fit: contain;
  box-shadow: var(--shadow);
  /* Pinch-zoom / pan / swipe-to-dismiss drive this transform directly. The JS
     adds `.is-dragging` while a finger is down to suppress the eased snap so
     the image tracks the finger 1:1; on release the transition eases it back. */
  transform-origin: center center;
  transition: transform .22s cubic-bezier(.2,.7,.2,1);
  touch-action: none;                 /* we handle all touch gestures ourselves */
  will-change: transform;
}
.lightbox-img.is-dragging { transition: none; }
/* Brief fade as a new image swaps in while cycling (swipe left/right). The
   class is added on swap and removed ~220ms later; the keyframe eases opacity
   from a dimmed start back to full so the change reads as a subtle cross-fade. */
.lightbox-img.lb-cycling { animation: lbFade .22s ease; }
@keyframes lbFade { from { opacity: .35; } to { opacity: 1; } }
.lightbox-close {
  position: absolute; top: calc(12px + var(--sa-t)); right: 14px;
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--line);
}
.lightbox-close:active { transform: scale(.9); }

/* ---------------------------------------------------------- Thinking block
   A quiet, collapsed disclosure (à la Claude Code): one-line header with a
   subtle glyph + chevron; expands to dim, monospace reasoning. Out of the way,
   never a main bubble. Default collapsed. */
.thinking {
  border: none; border-radius: var(--r-sm);
  background: none;
  color: var(--text-faint); font-size: 13px;
}
.thinking > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 8px; padding: 4px 4px;
  color: var(--text-faint); font-weight: 500;
  border-radius: 8px;
  transition: color .15s ease, background .15s ease;
}
.thinking > summary:hover { color: var(--text-dim); background: var(--line-2); }
.thinking > summary::-webkit-details-marker { display: none; }
.thinking .th-glyph {
  font-size: 12px; line-height: 1; color: var(--accent); opacity: .8;
}
.thinking .th-spark { width: 12px; height: 12px; display: block; fill: currentColor; overflow: visible; }
.thinking[open] .th-glyph { opacity: .55; }
.thinking .th-text { flex: 0 0 auto; }
.thinking .th-chev {
  width: 7px; height: 7px; flex: 0 0 auto; margin-left: 2px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); transition: transform .18s ease; opacity: .6;
}
.thinking[open] .th-chev { transform: rotate(45deg); }
.thinking .think-body {
  padding: 6px 4px 8px 24px; white-space: pre-wrap; line-height: 1.55;
  font-family: var(--font-mono); font-size: 12px; color: var(--text-faint);
  border-left: 2px solid var(--line); margin-left: 6px;
}

/* ----------------------------------------------------- Activity group
   Claude-Code-style grouped tasks: a single quiet, collapsible row that holds
   all of a turn's tool calls. Collapsed by default — one line with a status dot,
   a "Working… · N steps" / "Worked · N steps · 8.4s" label, and a chevron.
   Expands to reveal the nested, indented tool rows (each still individually
   expandable). Approval cards are NOT nested here — they stay top-level. */
.activity {
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface);
  animation: rise .26s cubic-bezier(.2,.7,.2,1) both;
}
.activity[open] { background: var(--surface-2); }
.activity-head {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 11px; border-radius: var(--r-sm);
  list-style: none; cursor: pointer; user-select: none;
  font-size: 13px; line-height: 1.4; color: var(--text-dim);
  transition: background .15s ease, color .15s ease;
}
.activity-head::-webkit-details-marker { display: none; }
.activity-head:hover { background: var(--line-2); color: var(--text); }
.activity-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  background: var(--accent); box-shadow: 0 0 9px var(--accent-glow);
  animation: orbPulse 1.3s ease-in-out infinite;
}
.activity[data-s="done"] .activity-dot {
  background: var(--green); box-shadow: 0 0 7px rgba(86,207,128,.5); animation: none;
}
.activity-label {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 500;
}
.activity-chev {
  width: 7px; height: 7px; flex: 0 0 auto; margin-left: 2px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); transition: transform .18s ease; opacity: .55;
}
.activity[open] .activity-chev { transform: rotate(45deg); }
.activity-body {
  padding: 2px 6px 7px 14px; margin-left: 5px;
  border-left: 2px solid var(--line);
  display: flex; flex-direction: column; gap: 1px;
}
/* Nested tool rows sit a touch tighter inside the group. */
.activity-body .tool { border-radius: 7px; }

/* ------------------------------------------------------- Tool rows (compact)
   Claude-Code-style one-liners: a status dot + tool name + short inline target,
   the whole header tappable to expand the full input + output. Collapsed by
   default. Document-like and quiet — no heavy card chrome. */
.tool {
  border: none; border-radius: 8px;
  background: none;
}
.tool[open] { background: var(--line-2); }
.tool-head {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px; border-radius: 8px;
  list-style: none; cursor: pointer; user-select: none;
  font-size: 13px; line-height: 1.4;
  transition: background .15s ease;
}
.tool-head::-webkit-details-marker { display: none; }
.tool:not([open]) > .tool-head:hover { background: var(--line-2); }

/* Status dot reflects state: proposed/running = pulsing accent, done = green,
   error = red. */
.tool-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  background: var(--text-faint);
}
.tool[data-s="running"] .tool-dot,
.tool[data-s="proposed"] .tool-dot {
  background: var(--accent); box-shadow: 0 0 9px var(--accent-glow);
  animation: orbPulse 1.2s ease-in-out infinite;
}
.tool[data-s="done"] .tool-dot  { background: var(--green); box-shadow: 0 0 7px rgba(86,207,128,.5); }
.tool[data-s="error"] .tool-dot { background: var(--red); box-shadow: 0 0 7px rgba(255,107,107,.5); }

.tool-name { font-weight: 600; color: var(--text); flex: 0 0 auto; }
.tool-target {
  font-family: var(--font-mono); font-size: 12px; color: var(--text-faint);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tool-target:not(:empty)::before { content: "· "; color: var(--text-faint); opacity: .6; }
.tool-chev {
  width: 6px; height: 6px; flex: 0 0 auto; margin-left: auto;
  border-right: 1.5px solid var(--text-faint); border-bottom: 1.5px solid var(--text-faint);
  transform: rotate(-45deg); transition: transform .18s ease; opacity: .55;
}
.tool[open] .tool-chev { transform: rotate(45deg); }

.tool-code {
  margin: 4px 6px 4px; padding: 10px 12px; border-radius: var(--r-sm);
  background: #0a0d14; border: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 12px; line-height: 1.5;
  color: var(--text-dim); white-space: pre; overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.tool-code .tok-flag { color: var(--accent); }     /* light syntax tinting */
.tool-out { margin: 0 6px 6px; }
.tool-out-label {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-faint); padding: 2px 2px 5px;
}
.tool-out pre {
  margin: 0; padding: 10px 12px; max-height: 280px; overflow: auto;
  background: #0a0d14; border: 1px solid var(--line); border-radius: var(--r-sm);
  font-family: var(--font-mono); font-size: 12px; line-height: 1.5;
  color: var(--text-faint); white-space: pre-wrap; word-break: break-word;
}

/* --------------------------------------------------------- Approval cards */
.approval {
  border: 1px solid var(--accent-soft); border-radius: var(--r);
  background:
    radial-gradient(120% 120% at 0% 0%, rgba(92,224,198,.08), transparent 55%),
    var(--surface);
  box-shadow: 0 0 0 1px var(--accent-soft), 0 14px 34px -18px var(--accent-glow);
  animation: rise .32s cubic-bezier(.2,.7,.2,1) both;
}
.approval.is-waiting { animation: rise .32s cubic-bezier(.2,.7,.2,1) both, glowPulse 2.4s ease-in-out 1s infinite; }
.approval-head {
  display: flex; align-items: center; gap: 10px; padding: 12px 14px 10px;
}
.approval-head .a-ico {
  width: 30px; height: 30px; border-radius: 9px; flex: 0 0 auto; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-size: 16px;
}
.approval-title { font-weight: 700; font-size: 13.5px; }
.approval-title .a-tool { color: var(--accent); font-family: var(--font-mono); font-size: 12.5px; }
.approval-tag {
  margin-left: auto; font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent); padding: 3px 8px;
  border: 1px solid var(--accent-soft); border-radius: var(--r-pill);
}
.approval-code {
  margin: 0 14px; padding: 11px 12px;
  background: #0a0d14; border: 1px solid var(--line); border-radius: var(--r-sm);
  font-family: var(--font-mono); font-size: 12px; line-height: 1.55;
  color: var(--text); white-space: pre-wrap; word-break: break-word;
  max-height: 260px; overflow: auto;
}
.approval-code .a-path { color: var(--accent); display: block; margin-bottom: 6px; font-size: 12px; }
.approval-code .a-prompt { color: var(--accent); }
.approval-code .a-del { color: #ff9a9a; }
.approval-code .a-add { color: #9af0c4; }
.approval-actions {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 12px 14px 8px;
}
.approval-actions .btn { min-height: 46px; font-size: 14px; font-weight: 700; }
.btn--approve { background: linear-gradient(180deg, var(--green), #3fae66); color: var(--green-ink); border-color: transparent; }
.btn--deny    { background: var(--red-soft); color: #ffb3b3; border: 1px solid rgba(255,107,107,.35); }
.approval-auto { padding: 2px 14px 13px; text-align: center; }
.approval-auto button { color: var(--text-faint); font-size: 12.5px; text-decoration: underline; text-underline-offset: 3px; }
.approval-auto button:hover { color: var(--text-dim); }

/* Resolved state */
.approval.resolved { box-shadow: none; animation: none; }
.approval.resolved .approval-actions,
.approval.resolved .approval-auto { display: none; }
.approval-result {
  display: none; align-items: center; gap: 8px;
  padding: 11px 14px; margin: 0 14px 12px; border-radius: var(--r-sm);
  font-size: 13.5px; font-weight: 600;
}
.approval.resolved .approval-result { display: flex; }
.approval.resolved.allow .approval-result { background: rgba(86,207,128,.12); color: var(--green); }
.approval.resolved.deny  .approval-result { background: var(--red-soft); color: #ffb3b3; }
.approval.resolved.allow { border-color: rgba(86,207,128,.3); }
.approval.resolved.deny  { border-color: rgba(255,107,107,.3); }

/* ------------------------------------------------------------ Result line */
.result-line {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  color: var(--text-faint); font-size: 12.5px; font-family: var(--font-mono);
  padding: 2px 0 4px;
}
.result-line .sep { opacity: .5; }
.result-line .ok { color: var(--green); }

/* The "continuing previous session" note */
.note {
  text-align: center; color: var(--text-faint); font-size: 12.5px;
  padding: 6px 0; display: flex; align-items: center; gap: 8px; justify-content: center;
}
.note::before, .note::after { content: ""; height: 1px; width: 36px; background: var(--line); }

/* Context-compacted marker — a quiet, centered caption (not a bubble) dropped
   inline when the backend auto-summarizes earlier messages to fit the window.
   Gentle boundary marker, not an alert. */
.compact-note {
  text-align: center; color: var(--text-faint);
  font-size: 11.5px; letter-spacing: .2px; line-height: 1.4;
  padding: 8px 16px; margin: 2px auto; max-width: 30rem;
  display: flex; align-items: center; gap: 7px; justify-content: center;
  opacity: .85;
}
.compact-note::before, .compact-note::after {
  content: ""; flex: 0 1 28px; height: 1px; background: var(--line-2);
}
.compact-note .compact-star { color: var(--accent); opacity: .7; }

/* -------------------------------------------------------- Jump to latest */
.jump-btn {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(96px + var(--sa-b)); z-index: 15;
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  animation: rise .2s ease both;
}
.jump-btn:active { transform: translateX(-50%) scale(.92); }

/* ---------------------------------------------------------------- Composer */
.composer {
  grid-row: 4;
  min-width: 0;         /* don't let the composer row force the grid wider than the screen */
  position: relative;   /* anchor for the reasoning popover */
  padding: 8px calc(12px + var(--sa-r)) calc(8px + var(--sa-b)) calc(12px + var(--sa-l));
  background: var(--panel);
  backdrop-filter: blur(16px) saturate(140%);
  border-top: 1px solid var(--line);
}
.composer-form {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  max-width: var(--col); margin: 0 auto;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 8px 10px 8px 12px;
  transition: border-color .16s ease, box-shadow .16s ease;
}
/* Claude-style toolbar row beneath the textarea: attach/reasoning left,
   voice/translate/send pushed to the right by the spring. */
.composer-bar { display: flex; align-items: center; gap: 6px; }
.composer-spring { flex: 1 1 auto; min-width: 0; }
.composer-form:focus-within { border-color: var(--accent-soft); box-shadow: 0 0 0 3px var(--accent-soft); }
.composer-input {
  width: 100%; min-width: 0;
  background: none; border: none; resize: none;
  color: var(--text); font-size: 16px; line-height: 1.45;   /* >=16px stops iOS focus-zoom */
  padding: 4px 4px 2px 4px;
  /* Cap against the VISIBLE area (--app-h shrinks when the keyboard opens), not the
     full layout viewport — so a long draft can't grow over the visible composer. */
  max-height: min(42vh, calc(var(--app-h, 100dvh) * 0.42)); min-height: 24px;
}
.composer-input:focus { outline: none; }
.composer-input::placeholder { color: var(--text-faint); }

.send-btn {
  flex: 0 0 auto;
  width: 40px; height: 40px; border-radius: 12px;
  display: grid; place-items: center; color: var(--accent-ink);
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  box-shadow: 0 6px 16px -8px var(--accent-glow);
  transition: transform .12s ease, opacity .18s ease, background .2s ease;
}
.send-btn:active { transform: scale(.9); }
.send-btn:disabled { opacity: .4; box-shadow: none; cursor: default; }
.send-btn .ic-stop { display: none; }
/* Streaming → button becomes a stop control */
.send-btn.is-streaming { background: var(--surface-3); color: var(--text); box-shadow: none; opacity: 1; cursor: pointer; }
.send-btn.is-streaming .ic-send { display: none; }
.send-btn.is-streaming .ic-stop { display: block; }

/* Attach ("+") button — Claude-style: a small circular ghost glyph to the LEFT
   of the input. Muted by default, brightens on hover/active. No border/label so
   the accent send button stays the primary action. Sits flush with the input +
   send button on the composer baseline. */
.attach-btn {
  flex: 0 0 auto; align-self: flex-end;
  width: 30px; height: 30px; margin-bottom: 4px; border-radius: 50%;
  display: grid; place-items: center; color: var(--text-faint);
  transition: background .16s ease, color .16s ease, transform .12s ease;
}
.attach-btn:hover { color: var(--text); background: var(--surface-2); }
.attach-btn:active { transform: scale(.88); }
/* Native file input is driven by the paperclip button; keep it out of layout. */
.file-input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); border: 0;
}

/* Attachment chip row above the composer. Wraps + scrolls so it can never
   overflow the viewport on a phone. */
.attach-row {
  display: flex; flex-wrap: wrap; gap: 7px;
  max-width: var(--col); margin: 0 auto 8px;
  max-height: 96px; overflow-y: auto;
}
.attach-chip {
  display: inline-flex; align-items: center; gap: 7px; max-width: 100%;
  padding: 5px 6px 5px 10px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line);
  font-size: 12.5px; color: var(--text);
}
.attach-chip .attach-ic { color: var(--accent); display: inline-flex; flex: 0 0 auto; }
.attach-chip .attach-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 170px;
}
.attach-chip.is-uploading { color: var(--text-dim); }
.attach-x {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; color: var(--text-faint);
  transition: color .14s ease, background .14s ease;
}
.attach-x:hover { color: var(--red); background: var(--red-soft); }
.attach-x:active { transform: scale(.9); }
.attach-spin {
  flex: 0 0 auto; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--accent);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ----------------------------------------------------------------- Drawer */
.scrim {
  position: fixed; inset: 0; z-index: 30;
  background: rgba(4, 6, 10, 0.6); backdrop-filter: blur(2px);
  opacity: 0; animation: fade .2s ease forwards;
}
.drawer {
  position: fixed; z-index: 31; top: 0; bottom: 0; left: 0;
  width: min(86vw, 320px);
  padding-top: var(--sa-t); padding-left: var(--sa-l);
  background: linear-gradient(180deg, #0e121a, #0b0e14);
  border-right: 1px solid var(--line);
  box-shadow: var(--shadow);
  display: flex; flex-direction: column;
  transform: translateX(-102%);
  transition: transform .28s cubic-bezier(.3,.8,.3,1);
}
.drawer.open { transform: translateX(0); }
.drawer-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 8px 10px 14px; border-bottom: 1px solid var(--line-2);
}
.drawer-id { display: flex; align-items: center; gap: 9px; min-width: 0; flex-wrap: wrap; }
.drawer-title { font-family: var(--font-display); font-weight: 600; font-size: 15px; line-height: 1.1; color: var(--text-dim); }
.drawer-user {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
}
.drawer-user-name {
  font-size: 12.5px; font-weight: 500; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px;
}
.drawer-user-role {
  flex: 0 0 auto;
  font-size: 9px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-faint); padding: 1px 6px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line-2);
}

/* Small, muted "Chats" header above the session list (ChatGPT-style). */
.session-section-label {
  padding: 12px 12px 4px; font-size: 11px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint);
}

.session-list { flex: 1 1 auto; overflow-y: auto; padding: 0 6px 8px; }
/* Compact, single-line chat rows — ~34px tall so ~2x as many fit. */
.session-item {
  display: flex; align-items: center;
  min-height: 34px; padding: 6px 9px; border-radius: 9px;
  cursor: pointer; transition: background .14s ease;
  position: relative;
}
/* Clean slide-away when a chat is deleted: fade + slide right while collapsing the
   row's height to zero, so the rows below glide up to fill the gap. The element is
   removed from the DOM on transitionend (see deleteSession). */
.session-item.removing {
  overflow: hidden; pointer-events: none;
  min-height: 0; max-height: 0;
  opacity: 0; transform: translateX(28px);
  margin-top: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0;
  transition: max-height .26s ease, opacity .2s ease, transform .26s ease,
              padding .26s ease, margin .26s ease;
}
@media (prefers-reduced-motion: reduce) {
  .session-item.removing { transition: none; }
}
.session-item:hover { background: rgba(255,255,255,.045); }
.session-item.active { background: rgba(255,255,255,.08); }
.session-title {
  flex: 1 1 auto; min-width: 0;
  font-size: 13px; font-weight: 400; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 6px;
}
.session-item:hover .session-title,
.session-item.active .session-title { color: var(--text); }
.session-del {
  flex: 0 0 auto;
  width: 24px; height: 24px; border-radius: 7px; color: var(--text-faint);
  display: grid; place-items: center;
  /* Subtle-but-present on touch (no hover), brightens on hover for pointer. */
  opacity: .55; transition: opacity .14s ease, color .14s ease, background .14s ease;
}
.session-item:hover .session-del { opacity: 1; }
.session-del:hover { color: var(--red); background: var(--red-soft); }
.session-del:active { transform: scale(.9); color: var(--red); }
/* Pointer-capable devices: keep it hidden until row hover (cleaner desktop). */
@media (hover: hover) and (pointer: fine) {
  .session-del { opacity: 0; }
  .session-item:hover .session-del { opacity: 1; }
}
.session-empty { color: var(--text-faint); font-size: 13px; text-align: center; padding: 24px 16px; }
.drawer-foot {
  padding: 6px 8px calc(8px + var(--sa-b)); border-top: 1px solid var(--line-2);
  display: flex; flex-direction: column; gap: 1px;
}
.drawer-action {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 10px; border-radius: 9px;
  color: var(--text-dim); font-size: 13px; font-weight: 400;
  transition: background .14s ease, color .14s ease;
}
.drawer-action:hover { background: rgba(255,255,255,.045); color: var(--text); }
.drawer-action svg { flex: 0 0 auto; width: 17px; height: 17px; color: var(--text-faint); }
.drawer-foot .link-btn { align-self: stretch; text-align: left; text-decoration: none; padding: 8px 10px; font-size: 13px; border-radius: 9px; color: var(--text-faint); }
.drawer-foot .link-btn:hover { background: rgba(255,255,255,.045); color: var(--text-dim); }

/* --------------------------------------------------- Drawer quick-nav (New chat / Search / Library)
   A tidy top group of slim, quiet rows — small icon + ~13px label, tight height,
   subtle hover. ChatGPT-style: text-first, no chunky CTA. */
.drawer-nav {
  display: flex; flex-direction: column; gap: 1px;
  padding: 6px 6px 6px;
}
.drawer-nav-btn {
  display: flex; align-items: center; gap: 11px;
  min-height: 34px; padding: 7px 9px; border-radius: 9px;
  color: var(--text-dim); font-size: 13px; font-weight: 400; text-align: left;
  transition: background .14s ease, color .14s ease;
}
.drawer-nav-btn:hover { background: rgba(255,255,255,.045); color: var(--text); }
.drawer-nav-btn svg { flex: 0 0 auto; color: var(--text-faint); }
.drawer-nav-btn:hover svg { color: var(--text-dim); }
/* New chat — a very subtle accent on the icon so it reads as the primary row of
   the group, but stays a quiet row (not a filled CTA block). */
.drawer-nav-btn--new { color: var(--text); font-weight: 500; }
.drawer-nav-btn--new svg { color: var(--accent); }
.drawer-nav-btn--new:hover svg { color: var(--accent); }

/* ================================================ Chat search overlay
   Reuses the .modal / .modal-card shell (keyboard-safe via var(--vvh)). The
   head is the search field itself; results scroll below it. */
.search-card { max-width: 600px; }
.search-head {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 10px 10px 16px; border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}
.search-ico { flex: 0 0 auto; color: var(--text-faint); }
.search-input {
  flex: 1 1 auto; min-width: 0;
  background: none; border: none; outline: none;
  color: var(--text); font-size: 16px; padding: 8px 2px;
}
.search-input::placeholder { color: var(--text-faint); }
.search-results {
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 8px; display: flex; flex-direction: column; gap: 4px;
  /* Bound to the visible area above the keyboard, like .modal-body. */
  max-height: calc(var(--vvh, 100dvh) - 84px);
}
.search-hint {
  color: var(--text-faint); font-size: 14px; text-align: center;
  padding: 34px 16px;
}
.search-result {
  display: block; width: 100%; text-align: left;
  padding: 12px 14px; border-radius: var(--r-sm);
  border: 1px solid transparent;
  transition: background .12s ease, border-color .12s ease;
}
.search-result:hover { background: var(--surface); border-color: var(--line); }
.search-result:active { transform: scale(.995); }
.search-result-title {
  font-size: 15px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.search-result-snippet {
  margin-top: 3px; font-size: 13px; color: var(--text-dim);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.45;
}
.search-result-meta { margin-top: 4px; font-size: 11.5px; color: var(--text-faint); }
.search-mark {
  background: var(--accent-soft); color: var(--accent);
  border-radius: 4px; padding: 0 2px; font-weight: 600;
}

/* ================================================ Generated-media library
   Reuses .modal / .modal-card--wide; a responsive thumbnail grid + a video
   section. Tiles open the existing lightbox / play inline. */
.library-content { display: flex; flex-direction: column; gap: 14px; }
.library-loading { color: var(--text-faint); font-size: 14px; padding: 24px 2px; text-align: center; }
.library-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 12px; padding: 40px 18px;
}
.library-empty-glyph {
  width: 64px; height: 64px; border-radius: 18px;
  display: grid; place-items: center; color: var(--text-faint);
  background: var(--surface); border: 1px solid var(--line);
}
.library-empty-text { color: var(--text-dim); font-size: 14.5px; max-width: 280px; }

.lib-section-head {
  font-family: var(--font-display); font-weight: 600; font-size: 15px;
  color: var(--text-dim); margin-top: 4px;
}
.lib-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
}
.lib-grid--video { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }

/* Library "Pages" — a tappable list of saved HTML previews */
.lib-pages { display: flex; flex-direction: column; gap: 8px; }
.lib-page {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 11px 12px; border-radius: var(--r-sm);
  background: var(--surface); border: 1px solid var(--line); color: var(--text);
}
.lib-page:active { background: var(--surface-2); }
.lib-page-ico { display: grid; place-items: center; width: 30px; height: 30px; flex: 0 0 auto;
  border-radius: 8px; background: var(--accent-soft); color: var(--accent); }
.lib-page-ico svg { width: 17px; height: 17px; }
.lib-page-meta { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.lib-page-title { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-page-chat { font-size: 11.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-page-go { color: var(--text-faint); flex: 0 0 auto; }

.lib-tile {
  position: relative; overflow: hidden;
  border-radius: var(--r-sm); border: 1px solid var(--line);
  background: var(--surface);
}
.lib-tile--img { aspect-ratio: 1 / 1; cursor: zoom-in; }
.lib-tile.is-error { display: grid; place-items: center; }
.lib-thumb {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .2s ease;
}
.lib-tile--img:hover .lib-thumb { transform: scale(1.04); }
.lib-video { width: 100%; display: block; border-radius: var(--r-sm); background: #000; }

/* Corner affordances on tiles — download + open-chat. */
.lib-dl, .lib-open-chat {
  position: absolute; top: 6px;
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; color: var(--text);
  background: rgba(6, 9, 14, 0.66); border: 1px solid var(--line);
  backdrop-filter: blur(4px);
  opacity: 0; transition: opacity .14s ease, background .14s ease, transform .12s ease;
}
.lib-dl { right: 6px; }
.lib-open-chat { left: 6px; }
.lib-tile:hover .lib-dl, .lib-tile:hover .lib-open-chat,
.lib-tile:focus-within .lib-dl, .lib-tile:focus-within .lib-open-chat { opacity: 1; }
.lib-dl:hover, .lib-open-chat:hover { background: var(--surface-2); color: var(--accent); }
.lib-dl:active, .lib-open-chat:active { transform: scale(.9); }
/* Touch devices have no hover — keep the affordances faintly visible. */
@media (hover: none) {
  .lib-dl, .lib-open-chat { opacity: .85; }
}

/* Studio library tile: "regenerate (same model)" affordance, top-left. */
.lib-regen {
  position: absolute; top: 6px; left: 6px;
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; color: var(--text);
  background: rgba(6, 9, 14, 0.66); border: 1px solid var(--line);
  backdrop-filter: blur(4px); cursor: pointer;
  opacity: 0; transition: opacity .14s ease, background .14s ease, transform .12s ease;
}
.lib-tile:hover .lib-regen, .lib-tile:focus-within .lib-regen { opacity: 1; }
.lib-regen:hover { background: var(--surface-2); color: var(--accent); }
.lib-regen:active { transform: scale(.9); }
@media (hover: none) { .lib-regen { opacity: .85; } }
/* Studio library tile: "new outfit (same model)" affordance, beside regenerate. */
.lib-newfit {
  position: absolute; top: 6px; left: 42px;
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; color: var(--text);
  background: rgba(6, 9, 14, 0.66); border: 1px solid var(--line);
  backdrop-filter: blur(4px); cursor: pointer;
  opacity: 0; transition: opacity .14s ease, background .14s ease, transform .12s ease;
}
.lib-tile:hover .lib-newfit, .lib-tile:focus-within .lib-newfit { opacity: 1; }
.lib-newfit:hover { background: var(--surface-2); color: var(--accent); }
.lib-newfit:active { transform: scale(.9); }
@media (hover: none) { .lib-newfit { opacity: .85; } }
/* Studio library tile: delete affordance, BOTTOM-right (kept off the crowded top corners). */
.lib-trash {
  position: absolute; bottom: 6px; right: 6px;
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; color: var(--text);
  background: rgba(6, 9, 14, 0.66); border: 1px solid var(--line);
  backdrop-filter: blur(4px); cursor: pointer;
  opacity: 0; transition: opacity .14s ease, background .14s ease, transform .12s ease;
}
.lib-tile:hover .lib-trash, .lib-tile:focus-within .lib-trash { opacity: 1; }
.lib-trash:hover { background: rgba(220, 38, 38, 0.18); color: #f87171; border-color: rgba(220, 38, 38, 0.5); }
.lib-trash:active { transform: scale(.9); }
@media (hover: none) { .lib-trash { opacity: .85; } }

/* Text input inside the WKWebView-safe prompt dialog (uiPrompt). */
.confirm-input {
  width: 100%; box-sizing: border-box; margin: 4px 0 2px;
  padding: 10px 12px; border-radius: 10px; font-size: 15px;
  background: var(--surface-2); color: var(--text); border: 1px solid var(--line);
}
.confirm-input:focus { outline: none; border-color: var(--accent); }

/* ============================================================ Users modal */
.modal {
  /* Track the VISUAL viewport (var --vvh, set by syncViewport) instead of
     inset:0, so the on-screen keyboard SHRINKS the modal area rather than
     covering/pushing the card. Bottom-sheet on mobile, centred on desktop. */
  position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: 40;
  /* Full-screen + viewport-unit-INDEPENDENT. A WKWebView can report --vvh as 0
     (modal opens invisible) or stale (taps land offset) — top/bottom:0 is always
     correct, so the modal can neither collapse nor mis-hit-test. */
  display: flex; align-items: flex-end; justify-content: center;
  /* --kb = on-screen keyboard height (set by syncViewport). The bottom-sheet card
     lifts above the keyboard so a focused input + its submit button are never
     covered (the iOS layout viewport / position:fixed / dvh do NOT shrink for the
     keyboard — only visualViewport does, which syncViewport reads). */
  padding-bottom: var(--kb, 0px);
}
.modal-scrim {
  position: absolute; inset: 0;
  background: rgba(4, 6, 10, 0.62); backdrop-filter: blur(3px);
  animation: fade .2s ease forwards; opacity: 0;
}
.modal-card {
  position: relative; z-index: 1;
  width: 100%; max-width: 520px;
  /* Bound to the VISIBLE area (above the keyboard) so the card never extends
     under the on-screen keyboard; .modal-body scrolls within it. The -sa-t keeps
     the header (close X) below the iOS status bar/notch so it's always tappable. */
  max-height: calc(100dvh - 16px - var(--sa-t) - var(--sa-b) - var(--kb, 0px));
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, #11151f, #0c0f16);
  border: 1px solid var(--line);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--shadow);
  padding-bottom: var(--sa-b);
  animation: sheet .3s cubic-bezier(.2,.8,.2,1) both;
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 12px 14px 20px; border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}
.modal-title { font-family: var(--font-display); font-weight: 700; font-size: 20px; letter-spacing: -0.01em; }
.modal-body {
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;   /* the modal's own scroll never chains to the page behind */
  padding: 16px 20px 22px;
  display: flex; flex-direction: column; gap: 18px;
}

/* User list */
.users-list { display: flex; flex-direction: column; gap: 5px; }
.users-loading { color: var(--text-faint); font-size: 14px; padding: 10px 2px; }
.user-row {
  display: flex; flex-direction: column; gap: 0;
  padding: 7px 11px; border-radius: var(--r-sm);
  background: var(--surface); border: 1px solid var(--line);
}
.user-main { display: flex; align-items: center; gap: 10px; }
.user-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.user-info { min-width: 0; flex: 1 1 auto; }
.user-name {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 14px; font-weight: 600; color: var(--text);
}
.user-role {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-faint); padding: 2px 8px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line);
}
.user-role.is-owner { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-soft); }
.user-meta { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
.user-you {
  flex: 0 0 auto; font-size: 11px; font-weight: 600; color: var(--text-faint);
  padding: 4px 10px; border-radius: var(--r-pill); border: 1px solid var(--line);
}
.user-del {
  flex: 0 0 auto;
  width: 32px; height: 32px; border-radius: 9px; color: var(--text-faint);
  display: grid; place-items: center;
  transition: color .14s ease, background .14s ease;
}
.user-del:hover { color: var(--red); background: var(--red-soft); }
.user-del:active { transform: scale(.92); }

/* Reset-password control: a quiet text button + an inline reveal panel. */
.user-reset {
  flex: 0 0 auto; font-size: 12.5px; font-weight: 600; color: var(--text-dim);
  padding: 6px 11px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--surface-2);
  transition: color .14s ease, border-color .14s ease, background .14s ease;
}
.user-reset:hover { color: var(--text); border-color: var(--accent-soft); }
.user-reset.is-open { color: var(--accent); border-color: var(--accent-soft); background: var(--accent-soft); }
.user-reset-panel {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--line);
}
.user-reset-input {
  flex: 1 1 160px; min-width: 0;
  padding: 9px 12px; border-radius: var(--r-sm);
  background: var(--bg-2); border: 1px solid var(--line);
  color: var(--text); font-size: 14px;
}
.user-reset-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.user-reset-save { flex: 0 0 auto; padding: 9px 16px; font-size: 13.5px; }
.user-reset-cancel {
  flex: 0 0 auto; font-size: 13px; font-weight: 600; color: var(--text-faint);
  padding: 9px 12px; border-radius: var(--r-sm);
}
.user-reset-cancel:hover { color: var(--text-dim); }

/* Add-user form */
.add-user {
  display: flex; flex-direction: column; gap: 12px;
  padding-top: 18px; border-top: 1px solid var(--line);
}
.add-user-title { font-family: var(--font-display); font-weight: 600; font-size: 16px; }
.role-static {
  display: inline-flex; align-items: center; align-self: flex-start;
  padding: 7px 14px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text-dim); font-size: 14px; font-weight: 600;
}

/* Change-password form (self-service modal) — same stacked layout/spacing as
   the add-user form; reuses .field / .btn--accent / .key-error. */
.change-pw { display: flex; flex-direction: column; gap: 12px; }

/* ====================================================== Plans / pricing modal
   A 3-up pricing grid (Free · Plus · Pro) that stacks on mobile. Reuses the
   existing .modal / .modal-card shell (so it stays iOS-keyboard-safe via
   var(--vvh)); .modal-card--wide just lifts the desktop max-width so three
   cards breathe. Cards + CTA states are built by renderPlans() in app.js. */
.modal-card--wide { max-width: 880px; }

/* Subtle "upgrade" tint on the drawer entry so it reads as an upsell, not chrome. */
.drawer-action--upgrade { color: var(--accent); }
.drawer-action--upgrade svg { color: var(--accent); }
.drawer-action--upgrade:hover { background: var(--accent-soft); color: var(--accent); }

/* Danger zone: delete-account entry + its confirm modal. Muted red so it reads as
   destructive but doesn't shout in the drawer. */
.drawer-action--danger { color: #f87171; }
.drawer-action--danger svg { color: #f87171; }
.drawer-action--danger:hover { background: rgba(248,113,113,.10); color: #fca5a5; }
.drawer-action--danger:hover svg { color: #fca5a5; }
.delete-warn {
  font-size: 14px; line-height: 1.55; color: var(--text-dim);
  background: rgba(248,113,113,.07); border: 1px solid rgba(248,113,113,.22);
  border-radius: var(--r-sm); padding: 12px 14px; margin: 0 0 16px;
}
.delete-warn strong { color: #fca5a5; }
.delete-warn code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; color: var(--text); }
.btn--danger {
  background: #b91c1c; color: #fff; border: 1px solid transparent;
  box-shadow: 0 1px 0 rgba(0,0,0,.2);
}
.btn--danger:hover { background: #dc2626; }
.btn--danger:disabled { opacity: .5; box-shadow: none; }

/* In-drawer legal links (Privacy · Terms · Support) — quiet footer. */
.drawer-legal { display: flex; gap: 8px; justify-content: center; align-items: center; margin-top: 10px; }
.drawer-legal a { color: var(--text-faint); font-size: 12px; text-decoration: none; }
.drawer-legal a:hover { color: var(--text-dim); text-decoration: underline; }
.drawer-legal span { color: var(--text-faint); font-size: 12px; }

/* ---- Settings sheet: grouped sections inside the consolidated modal ---- */
.settings-body { gap: 16px; }
.settings-section { display: flex; flex-direction: column; gap: 6px; padding-bottom: 14px; border-bottom: 1px solid var(--line); }
.settings-section:last-child { border-bottom: none; padding-bottom: 0; }
.settings-label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); margin-bottom: 2px; }
.settings-section .drawer-action { width: 100%; }
.settings-legal { justify-content: flex-start; margin: 0 0 4px; }
.settings-body #signOut { align-self: flex-start; }

/* ---- Compact reasoning control in the composer (owner only) ---- */
.reasoning-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
  height: 38px; padding: 0 12px; border-radius: 19px;
  border: 1px solid var(--line); background: var(--surface); color: var(--text-dim);
  font-size: 13px; cursor: pointer; transition: border-color .12s ease, color .12s ease;
}
.reasoning-btn:hover, .reasoning-btn.is-open { border-color: var(--accent-soft); color: var(--text); }
.reasoning-btn svg { color: var(--text-faint); }
.reasoning-btn:hover svg, .reasoning-btn.is-open svg { color: var(--accent); }
.reasoning-btn-label { font-weight: 600; }
/* On phones, collapse the reasoning pill to just its icon so the composer (which
   now also has the mic button) always fits without clipping the send button. */
@media (max-width: 430px) { .reasoning-btn-label { display: none; } .reasoning-btn { padding: 0 9px; } }

.reasoning-pop {
  position: absolute; left: calc(12px + var(--sa-l)); bottom: calc(100% + 8px); z-index: 30;
  width: min(320px, calc(100vw - 24px));
  background: linear-gradient(180deg, #11151f, #0c0f16);
  border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow); padding: 12px 12px 10px;
  animation: rise .16s ease both;
}
.reasoning-pop-head { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); margin: 2px 4px 8px; }
/* Clean menu list (no sliding thumb to misalign) */
.reasoning-menu { display: flex; flex-direction: column; gap: 2px; }
.reasoning-opt {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; padding: 9px 11px; border-radius: var(--r-sm);
  background: none; border: none; cursor: pointer; text-align: left; color: var(--text);
}
.reasoning-opt:hover { background: rgba(255,255,255,.05); }
.reasoning-opt-text { display: flex; flex-direction: column; min-width: 0; }
.reasoning-opt-name { font-size: 14px; font-weight: 600; }
.reasoning-opt-desc { font-size: 12px; color: var(--text-faint); margin-top: 1px; }
.reasoning-opt-check { flex: 0 0 auto; color: var(--accent); opacity: 0; transition: opacity .12s ease; }
.reasoning-opt.is-active .reasoning-opt-check { opacity: 1; }
.reasoning-opt.is-active .reasoning-opt-name { color: var(--accent); }

.plan-intro { color: var(--text-dim); font-size: 14px; margin: -2px 0 2px; }
.plan-soon {
  margin-top: 2px; color: var(--text-faint); font-size: 13px; text-align: center;
}

.plan-grid {
  display: grid; grid-template-columns: 1fr; gap: 12px;
}
@media (min-width: 720px) {
  .plan-grid { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
}

.plan-card {
  position: relative;
  display: flex; flex-direction: column; gap: 7px;
  padding: 13px 14px 12px;
  border-radius: var(--r); border: 1px solid var(--line);
  background: var(--surface);
}
/* "Most popular" Pro card gets the signal accent ring + a faint glow. */
.plan-card.is-popular {
  border-color: var(--accent-soft);
  background: linear-gradient(180deg, rgba(92,224,198,0.06), var(--surface));
  box-shadow: 0 14px 34px -22px var(--accent-glow);
}
/* The user's current plan reads quieter (no CTA, dimmed). */
.plan-card.is-current { border-color: var(--line); }

.plan-badge {
  position: absolute; top: -10px; left: 16px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 4px 10px; border-radius: var(--r-pill);
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  color: var(--accent-ink); box-shadow: 0 6px 16px -8px var(--accent-glow);
}

.plan-name {
  font-family: var(--font-display); font-weight: 700; font-size: 18px; letter-spacing: -0.01em;
}
.plan-price { display: flex; align-items: baseline; gap: 4px; }
.plan-price-amt { font-family: var(--font-display); font-weight: 700; font-size: 22px; letter-spacing: -0.02em; }
.plan-price-per { color: var(--text-faint); font-size: 13px; font-weight: 500; }

.plan-feats { display: flex; flex-direction: column; gap: 5px; margin: 0; flex: 1 1 auto; }
.plan-feat { font-size: 13px; }
.plan-cta { padding-top: 10px; padding-bottom: 10px; margin-top: 4px; }
.plan-intro { margin: 0 0 10px; font-size: 13px; }

/* "Type DELETE to confirm" gate — a TOP-anchored overlay so the keyboard (which
   opens from the bottom) can never cover the input or the action buttons. */
.wipe-overlay { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.55); display: flex; align-items: flex-start; justify-content: center; padding: calc(env(safe-area-inset-top,0px) + 56px) 16px 16px; -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.wipe-card { width: 100%; max-width: 380px; background: var(--surface,#15171c); border: 1px solid var(--line); border-radius: 16px; padding: 18px 16px 16px; box-shadow: 0 20px 60px rgba(0,0,0,.5); display: flex; flex-direction: column; gap: 12px; }
.wipe-confirm-msg { margin: 0; font-size: 14px; color: var(--text-dim); line-height: 1.5; }
.wipe-confirm-input { width: 100%; padding: 11px 12px; font-size: 16px; border-radius: var(--r); border: 1px solid var(--line); background: var(--bg-2,#0f1115); color: var(--text); letter-spacing: .12em; text-transform: uppercase; }
.wipe-confirm-input:focus { outline: none; border-color: var(--accent-soft); }
.wipe-confirm-row { display: flex; gap: 8px; }
.wipe-confirm-row .btn { flex: 1 1 auto; padding: 11px; }
.wipe-confirm-go { background: #c0392b; color: #fff; border: none; }
.wipe-confirm-go:disabled { opacity: .45; }

/* In-app confirm — replaces native confirm(), which silently returns false in the
   iOS WKWebView (no WKUIDelegate) and dead-ended sign-out / delete actions. Top-
   anchored (clear of the keyboard) and z-index 90 so it sits above ANY overlay. */
.confirm-overlay { position: fixed; inset: 0; z-index: 90; background: rgba(0,0,0,.55); display: flex; align-items: flex-start; justify-content: center; padding: calc(env(safe-area-inset-top,0px) + 84px) 16px 16px; -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.confirm-card { width: 100%; max-width: 380px; background: var(--surface,#15171c); border: 1px solid var(--line); border-radius: 16px; padding: 18px 16px 14px; box-shadow: 0 20px 60px rgba(0,0,0,.5); display: flex; flex-direction: column; gap: 14px; }
.confirm-msg { margin: 0; font-size: 15px; color: var(--text); line-height: 1.5; }
.confirm-row { display: flex; gap: 8px; }
.confirm-row .btn { flex: 1 1 auto; padding: 11px; }
.confirm-go-danger { background: #c0392b; color: #fff; border: none; }

/* Monthly / Annual billing toggle + annual "save" tags */
.plan-period { display: inline-flex; align-self: center; margin: 0 auto 14px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-pill); padding: 3px; }
.plan-period-opt { display: inline-flex; align-items: center; gap: 6px; border: none; background: none; color: var(--text-dim); font: inherit; font-size: 13px; font-weight: 600; padding: 7px 15px; border-radius: var(--r-pill); cursor: pointer; }
.plan-period-opt.is-active { background: var(--accent, #34d399); color: #04240f; }
.plan-period-save { font-size: 10px; font-weight: 700; letter-spacing: .02em; background: rgba(255,255,255,.14); padding: 2px 6px; border-radius: var(--r-pill); }
.plan-period-opt.is-active .plan-period-save { background: rgba(0,0,0,.18); }
.plan-price-save { font-size: 11px; color: var(--accent, #34d399); font-weight: 600; margin-top: 2px; }

/* Lock the chat from scrolling behind an open modal or the side drawer.
   JS toggles body.scroll-locked — NOT CSS :has(), which on some WKWebView builds
   mis-hit-tests after a recalc and left tap targets dead after closing a menu. */
/* overflow:hidden alone doesn't reliably freeze an iOS WKWebView scroller that's
   already touch-driven — touch-action:none cancels the pan outright, so the chat
   can't scroll behind an open modal/drawer no matter where the drag starts. */
body.scroll-locked .messages { overflow: hidden; touch-action: none; }
.plan-feat { display: flex; align-items: flex-start; gap: 8px; font-size: 13.5px; color: var(--text-dim); }
.plan-feat svg { flex: 0 0 auto; margin-top: 2px; color: var(--accent); }
.plan-feat.is-key { color: var(--text); font-weight: 600; }

/* CTA — full-width per card. Subscribe/upgrade reuses .btn--accent; the
   current-plan and disabled ("coming soon") states use these quiet variants. */
.plan-cta { width: 100%; min-height: 44px; font-size: 14px; }
.plan-cta--current {
  width: 100%; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border-radius: var(--r-sm); font-weight: 600; font-size: 14px;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text-dim);
  cursor: default;
}
.plan-cta--current svg { color: var(--accent); }
.plan-cta--soon {
  width: 100%; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); font-weight: 600; font-size: 14px;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text-faint); cursor: not-allowed;
}
/* A lower/equal (non-current) tier the user can't "upgrade" to reads as a quiet
   outline button rather than an accent one. */
.plan-cta--ghost {
  background: var(--surface-2); border-color: var(--line); color: var(--text-dim);
}

/* ----------------------------------------------- Embedded Whop checkout modal
   The card is sized to the visible (above-keyboard) area; the mount fills it and
   the Whop iframe stretches to 100% so the in-app checkout never needs a page
   redirect. A faint spinner shows until loader.js swaps in the iframe. */
.modal-card--checkout { max-width: 560px; }
.checkout-body { padding: 0; }
.whop-checkout-mount {
  position: relative;
  width: 100%;
  min-height: min(620px, calc(var(--vvh, 100dvh) - 140px));
  background: var(--bg-2);
}
.whop-checkout-mount iframe { width: 100% !important; border: 0; display: block; }
/* Loading shimmer behind the iframe until Whop renders. */
.whop-checkout-mount::before {
  content: "Loading secure checkout…";
  position: absolute; inset: 0; z-index: 0;
  display: grid; place-items: center;
  color: var(--text-faint); font-size: 13px;
}
.whop-checkout-mount > * { position: relative; z-index: 1; }

/* ----------------------------------------------------- HTML artifact preview
   In-chat card (icon + title + View / Open-in-browser) plus a NEAR-FULLSCREEN
   modal whose iframe fills the whole stage. CRITICAL: without an explicit iframe
   height the previewed page (which often centres its content over 100vh) gets
   squished into the browser-default ~150px and its buttons fall off-screen — the
   "I see no button" bug. The stage flex-grows and the iframe is absolutely
   pinned to fill it. */
.artifact-card {
  align-self: flex-start; max-width: min(540px, 94%);
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--line);
}
.artifact-ico {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: var(--r-sm);
  background: var(--bg-2); color: var(--accent);
}
.artifact-meta { flex: 1 1 auto; min-width: 0; }
.artifact-title { font-size: 14px; font-weight: 650; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.artifact-kind { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; }
.artifact-actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; }
.artifact-view, .artifact-open {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 34px; padding: 0 12px; border-radius: var(--r-sm);
  font-size: 12.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--line); background: var(--bg-2); color: var(--text);
}
.artifact-view { background: var(--accent); color: #06231d; border-color: transparent; }
.artifact-view:hover { filter: brightness(1.05); }
.artifact-open:hover { border-color: var(--accent-soft); color: var(--accent); }

/* Downloadable-file card: reuses the artifact-card shell. The Download control is
   an <a download> so it must drop link underline; actions sit centred for one btn. */
.file-card .artifact-actions { justify-content: center; }
.file-dl, .file-dl:hover, .file-dl:visited { text-decoration: none; }

.modal-card--artifact {
  max-width: 1000px;
  height: calc(var(--vvh, 100dvh) - 12px - var(--sa-t));
}
.artifact-stage {
  position: relative; flex: 1 1 auto; min-height: 0;
  background: #fff; overflow: hidden;
}
.artifact-frame {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; background: #fff; opacity: 0; transition: opacity .18s ease;
}
.artifact-modal.is-loaded .artifact-frame { opacity: 1; }
.artifact-spinner {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: #fff; pointer-events: none; transition: opacity .18s ease;
}
.artifact-modal.is-loaded .artifact-spinner { opacity: 0; }
.artifact-spin {
  width: 26px; height: 26px; border-radius: 50%;
  border: 3px solid rgba(0,0,0,0.14); border-top-color: rgba(0,0,0,0.5);
  animation: aspin .7s linear infinite;
}
@keyframes aspin { to { transform: rotate(360deg); } }

/* ------------------------------------------------- Cap-hit inline upsell card
   Shown in the timeline when /api/chat returns 429 (daily message cap). Calm,
   accent-tinted, with a one-tap Upgrade button — not a nagging modal. */
.cap-upsell {
  align-self: center; max-width: min(560px, 92%);
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px 16px; border-radius: var(--r);
  background: linear-gradient(180deg, rgba(92,224,198,0.07), var(--surface));
  border: 1px solid var(--accent-soft);
}
.cap-upsell-text { font-size: 13.5px; color: var(--text-dim); line-height: 1.5; }
.cap-upsell-text strong { color: var(--text); }
.cap-upsell-btn { align-self: flex-start; min-height: 40px; padding: 0 18px; font-size: 14px; }

/* --------------------------------------------------------------- Toast */
.toast {
  position: fixed; left: 50%; bottom: calc(96px + var(--sa-b));
  transform: translateX(-50%) translateY(10px);
  z-index: 60; max-width: 90vw;
  padding: 11px 16px; border-radius: var(--r-pill);
  background: var(--surface-3); border: 1px solid var(--line);
  color: var(--text); font-size: 13.5px; box-shadow: var(--shadow);
  opacity: 0; transition: opacity .2s ease, transform .2s ease; pointer-events: none;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ------------------------------------------------------------- Animations */
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes fade { to { opacity: 1; } }
@keyframes sheet { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: none; } }
@keyframes blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@keyframes orbPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .45; transform: scale(.78); }
}
@keyframes glowPulse {
  0%, 100% { box-shadow: 0 0 0 1px var(--accent-soft), 0 14px 34px -18px var(--accent-glow); }
  50% { box-shadow: 0 0 0 1px var(--accent-glow), 0 18px 40px -16px var(--accent-glow); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
  .messages { scroll-behavior: auto; }
}

/* ----------------------------------------------------------- Small phones */
/* Minimal, Claude-style header on phones: just the menu (history) on the left
   and new-chat on the right. The brand wordmark + status/role badges are hidden
   on phones (identity lives in the drawer), and the Ask/Auto control now lives
   in the drawer too — so the bar can never overflow / clip on mid-width iPhones. */
@media (max-width: 620px) { .brand { display: none; } }
/* Hard stop: the bar itself never forces the page wider than the viewport. */
.topbar { max-width: 100%; overflow: hidden; }
@media (max-width: 360px) {
  .role-badge { display: none; }         /* drop the OWNER pill on the tightest phones */
  .messages { padding-left: calc(12px + var(--sa-l)); padding-right: calc(12px + var(--sa-r)); }
}
/* Slightly roomier type on large screens (still document-like, not oversized) */
@media (min-width: 768px) {
  .messages { padding-top: 22px; }
  .bubble { font-size: 14.5px; }
  .md { font-size: 14.5px; }
  .composer-input { font-size: 16px; }
  /* The users panel becomes a centered card instead of a bottom sheet. */
  .modal { align-items: center; padding: 24px; }
  .modal-card { border-radius: var(--r-lg); max-height: calc(var(--vvh, 100dvh) - 48px); animation-name: rise; }
}

/* ---------------------------------------------- Plan & usage (settings panel) */
.usage-card {
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r); padding: 14px 15px; margin-bottom: 10px;
}
.usage-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 13px; }
.usage-tier {
  font-family: var(--font-display); font-weight: 700; font-size: 13.5px;
  padding: 3px 11px; border-radius: var(--r-pill);
  background: var(--accent-soft); color: var(--accent); letter-spacing: -0.01em;
}
.usage-reset { font-size: 11.5px; color: var(--text-faint); }
.usage-row + .usage-row { margin-top: 12px; }
.usage-meta { display: flex; justify-content: space-between; font-size: 12.5px; color: var(--text-dim); margin-bottom: 6px; }
.usage-meta span:last-child { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.usage-bar { height: 7px; border-radius: var(--r-pill); background: rgba(255,255,255,.07); overflow: hidden; }
.usage-fill {
  display: block; height: 100%; width: 0; border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transition: width .45s cubic-bezier(.2,.7,.2,1);
}
.usage-fill.is-full { background: linear-gradient(90deg, var(--amber), var(--red)); }

/* Pricing: "Everything in Free, plus —" lead-in row (no tick) */
.plan-feat-lead {
  font-size: 12px; font-weight: 600; color: var(--text-dim);
  margin: 2px 0 7px; letter-spacing: .01em;
}

/* ============================================================================
   Upgrade celebration modal — shown post-tier-increase after reload. Reuses
   .modal / .modal-card; only the body contents differ from the plan modal. */
.upgrade-body { padding: 20px; display: flex; flex-direction: column; gap: 16px; }
.upgrade-sub { margin: 0; font-size: 13.5px; color: var(--text-dim); }
.upgrade-feats {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 7px;
}
.upgrade-feat {
  display: flex; align-items: flex-start; gap: 9px;
  font-size: 13.5px; color: var(--text); font-weight: 600;
}
.upgrade-feat svg { flex: 0 0 auto; margin-top: 2px; color: var(--accent); }
.upgrade-cta { margin-top: 4px; }

/* ============================================================================
   "Claude Shimmer" thinking/working indicator — GPU/compositor-cheap.
   Animates ONLY opacity, transform, and background-position. NO SVG `d` morph,
   no animated filter/box-shadow/backdrop-filter (the old `d`-morph re-rasterised
   every frame and janked streaming). All gated to body.echo-working so it is
   completely dead when idle.
   ========================================================================== */

/* 1. Signature label shimmer — a soft light sweep across the status text
      ("Working…", "Thinking…"). background-position is paint-only on one short
      single-line label = negligible cost. */
body.echo-working .status-line .status-label {
  background-image: linear-gradient(100deg,
    var(--text-dim) 0%, var(--text-dim) 38%, #ffffff 50%, var(--text-dim) 62%, var(--text-dim) 100%);
  background-size: 220% 100%;
  background-repeat: no-repeat;
  background-position: 130% 0;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  will-change: background-position;
  animation: echoShimmer 3.6s ease-in-out infinite;
}
/* Keep the inline monospace command fragment readable (not clipped away). */
body.echo-working .status-line .status-label .status-mono {
  -webkit-background-clip: border-box; background-clip: border-box;
  -webkit-text-fill-color: var(--text-faint); color: var(--text-faint);
  background-image: none; animation: none;
}
/* Fallback: if background-clip:text is unsupported, show plain dim text. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  body.echo-working .status-line .status-label {
    -webkit-text-fill-color: var(--text-dim); color: var(--text-dim);
    background-image: none; animation: none;
  }
}
@keyframes echoShimmer {
  0%   { background-position: 130% 0; }
  55%  { background-position: -30% 0; }   /* glide through, then hold calm */
  100% { background-position: -30% 0; }
}

/* 2. Calm spark with quick twinkle flashes — the subtle "quick flash changes".
      Transform + opacity only; the existing static drop-shadow is untouched. */
body.echo-working .status-spark,
body.echo-working .thinking .th-spark {
  transform-box: view-box; transform-origin: 50px 50px;
  will-change: transform, opacity;
  animation: echoTwinkle 3.6s ease-in-out infinite;
}
body.echo-working .status-spark { animation-delay: -0.35s; }  /* offset so flash lands as the shimmer crosses */
@keyframes echoTwinkle {
  0%   { opacity: .72; transform: scale(1);    }
  40%  { opacity: .90; transform: scale(1.02); }
  50%  { opacity: 1;   transform: scale(1.16); }   /* primary quick flash */
  60%  { opacity: .78; transform: scale(1);    }
  80%  { opacity: .95; transform: scale(1.07); }   /* secondary subtle glint */
  88%  { opacity: .74; transform: scale(1);    }
  100% { opacity: .72; transform: scale(1);    }
}

/* 3. Respect reduced-motion: steady label, still spark. */
@media (prefers-reduced-motion: reduce) {
  body.echo-working .status-line .status-label,
  body.echo-working .status-spark,
  body.echo-working .thinking .th-spark { animation: none; }
  body.echo-working .status-line .status-label {
    -webkit-text-fill-color: var(--text-dim); color: var(--text-dim); background-image: none;
  }
  body.echo-working .status-spark,
  body.echo-working .thinking .th-spark { opacity: .9; }
}

/* ===========================================================================
   Voice mode — composer mic button + full-screen "talk to Echo" overlay.
   ======================================================================== */
.mic-btn {
  flex: 0 0 auto; align-self: flex-end;
  width: 40px; height: 40px; margin-bottom: 0; border-radius: 12px;
  display: grid; place-items: center; color: var(--text-dim);
  background: var(--surface-2);
  transition: background .16s ease, color .16s ease, transform .12s ease;
}
.mic-btn:hover { color: var(--text); background: var(--surface-3); }
.mic-btn:active { transform: scale(.9); }

/* Overlay shell — full-screen, blurred dark wash, fades + lifts in. */
.voice-overlay {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column;
  padding: calc(var(--sa-t) + 14px) calc(var(--sa-r) + 16px) calc(var(--sa-b) + 22px) calc(var(--sa-l) + 16px);
  background: radial-gradient(120% 90% at 50% 18%, rgba(20,28,40,.96), rgba(8,10,14,.99));
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  opacity: 0; visibility: hidden; transform: translateY(8px);
  /* pointer-events:none is the SAFETY: even if opacity/visibility/class ever
     desync (WKWebView mid-transition, interrupted close), a non-open overlay can
     NEVER swallow taps across the screen — which used to freeze every button. */
  pointer-events: none;
  transition: opacity .26s ease, transform .26s ease, visibility 0s linear .26s;
}
.voice-overlay.open { opacity: 1; visibility: visible; transform: none; pointer-events: auto; transition: opacity .26s ease, transform .26s ease; }

.voice-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.voice-pick {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--text-dim);
  font: 600 13.5px/1 var(--font-ui); border: 1px solid var(--line);
}
.voice-pick:active { transform: scale(.96); }
.voice-close {
  width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
  color: var(--text-dim); background: var(--surface-2); border: 1px solid var(--line);
}
.voice-close:active { transform: scale(.92); }

/* Owner-only background-task strip (sits just under the voice top bar) */
.voice-tasks {
  display: flex; flex-direction: column; gap: 6px;
  margin-top: 10px; width: 100%;
}
.vtask {
  display: inline-flex; align-items: center; gap: 8px; align-self: flex-start;
  max-width: 100%; padding: 6px 11px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line);
  font: 600 12.5px/1 var(--font-ui); color: var(--text-dim);
  opacity: 1; transition: opacity .35s ease, transform .35s ease;
}
.vtask.vtask-out { opacity: 0; transform: translateY(-4px); }
.vtask-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--accent); box-shadow: 0 0 0 0 var(--accent-soft);
}
.vtask[data-s="running"] .vtask-dot { animation: vtaskPulse 1.1s ease-in-out infinite; }
.vtask[data-s="done"] .vtask-dot { background: #2ea36a; animation: none; }
.vtask[data-s="error"] .vtask-dot { background: #d9534f; animation: none; }
.vtask-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vtask-time { color: var(--text-faint); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
@keyframes vtaskPulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--accent-soft); opacity: 1; }
  50% { box-shadow: 0 0 0 5px transparent; opacity: .55; }
}

/* Voice picker dropdown */
.voice-picker {
  position: absolute; top: calc(var(--sa-t) + 58px); left: calc(var(--sa-l) + 16px); z-index: 2;
  width: min(280px, 78vw); padding: 6px; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow);
}
.voice-picker button {
  display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
  padding: 10px 12px; border-radius: var(--r-sm); color: var(--text);
}
.voice-picker button:hover { background: var(--surface-2); }
.voice-picker button[aria-selected="true"] { background: var(--accent-soft); }
.voice-picker .vp-name { font: 600 14.5px/1.2 var(--font-ui); }
.voice-picker .vp-desc { font: 400 12.5px/1.2 var(--font-ui); color: var(--text-faint); }

/* Stage — centered orb + caption */
.voice-stage { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 34px; }
.voice-orb {
  position: relative; width: 210px; height: 210px; border-radius: 50%;
  display: grid; place-items: center; background: none;
  animation: orbFloat 6.5s ease-in-out infinite;   /* gentle, always-on hover */
}
/* The luminous sky sphere: blue-sky gradient base, glossy spherical shading,
   clips the drifting cloud layer to a perfect circle. */
.orb-core {
  position: absolute; inset: 14px; border-radius: 50%; overflow: hidden;
  background: radial-gradient(125% 125% at 32% 20%, #f2faff 0%, #cfeaff 22%, #8fc8f7 52%, #4a96e4 80%, #2f6fc4 100%);
  box-shadow:
    0 22px 70px -14px rgba(90,165,245,.6),          /* outer sky glow */
    0 0 0 1px rgba(255,255,255,.06),
    inset 0 -26px 46px -20px rgba(12,42,92,.75),     /* shaded underside */
    inset 10px 12px 30px -8px rgba(255,255,255,.95); /* top-left sheen */
  transition: transform .2s ease, filter .35s ease, box-shadow .35s ease;
}
/* Circular clip for the BLURRED cloud layer. Safari does not reliably clip a
   blurred child with overflow+border-radius (it leaks a square), so clip-path
   on a shadow-free wrapper does the masking instead. */
.orb-skin {
  position: absolute; inset: 0; border-radius: 50%;
  overflow: hidden; -webkit-clip-path: circle(50% at 50% 50%); clip-path: circle(50% at 50% 50%);
}
/* Soft white clouds that slowly drift + curl for a living look. */
.orb-clouds {
  position: absolute; inset: -28%; border-radius: 50%;
  background:
    radial-gradient(40% 27% at 30% 38%, rgba(255,255,255,.96), rgba(255,255,255,0) 60%),
    radial-gradient(32% 23% at 66% 30%, rgba(255,255,255,.85), rgba(255,255,255,0) 62%),
    radial-gradient(46% 32% at 60% 70%, rgba(255,255,255,.72), rgba(255,255,255,0) 64%),
    radial-gradient(26% 19% at 22% 72%, rgba(255,255,255,.6),  rgba(255,255,255,0) 60%);
  filter: blur(3px);
  animation: orbDrift 16s linear infinite;
}
/* Specular highlight — the glossy catch-light near the top-left. */
.orb-gloss {
  position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  background: radial-gradient(26% 20% at 34% 24%, rgba(255,255,255,.95), rgba(255,255,255,0) 70%);
  mix-blend-mode: screen; opacity: .85;
}
.orb-ring {
  position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid rgba(150,205,255,.55); opacity: 0;
}
.voice-caption {
  max-width: 30ch; text-align: center; min-height: 2.6em;
  font: 500 17px/1.4 var(--font-ui); color: var(--text-dim);
  transition: color .2s ease;
}

/* --- In-window conversation transcript (ChatGPT-style) ------------------
   Hidden until the first turn lands; then it fills the space and the orb
   shrinks + docks above the controls (see .has-convo overrides below). */
.voice-convo { display: none; }
.voice-overlay.has-convo .voice-convo {
  display: flex; flex-direction: column; gap: 12px;
  flex: 1 1 auto; width: 100%; max-width: var(--col); margin: 4px auto 0; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; padding: 6px 2px 8px;
}
.vc-row { display: flex; }
.vc-row.vc-user { justify-content: flex-end; }
.vc-row.vc-bot  { justify-content: flex-start; }
.vc-bubble {
  max-width: 84%; padding: 11px 15px; border-radius: 20px;
  font: 400 16px/1.46 var(--font-ui); white-space: pre-wrap; overflow-wrap: anywhere;
}
.vc-user .vc-bubble { background: var(--surface-3); color: var(--text); border-bottom-right-radius: 7px; }
.vc-bot  .vc-bubble { background: transparent; color: var(--text); padding-left: 2px; padding-right: 2px; }

/* When a conversation is going, the orb is no longer the centered hero — it
   shrinks to a compact docked badge so the transcript gets the room. */
.voice-overlay.has-convo .voice-stage { flex: 0 0 auto; gap: 12px; padding-top: 2px; }
.voice-overlay.has-convo .voice-orb { width: 100px; height: 100px; animation-duration: 7.5s; }
.voice-overlay.has-convo .orb-core { inset: 7px; }
.voice-overlay.has-convo .voice-caption { min-height: 1.3em; font-size: 15px; }

/* Bottom control — just the close cross, bottom-right (ChatGPT-style). */
.voice-bottom { display: flex; justify-content: flex-end; align-items: center; padding: 0 2px; }
.voice-bottom .voice-close { width: 52px; height: 52px; }
.voice-bottom .voice-close svg { width: 24px; height: 24px; }
.voice-act {
  width: 66px; height: 66px; border-radius: 50%; display: grid; place-items: center;
  color: var(--text); background: var(--surface-2); border: 1px solid var(--line);
  transition: transform .12s ease, background .18s ease, opacity .18s ease;
}
.voice-act:active { transform: scale(.93); }
.voice-hint { font: 400 12.5px/1.3 var(--font-ui); color: var(--text-faint); text-align: center; max-width: 32ch; }

/* --- State machine: data-state drives the orb motion + caption tint ---
   The parent .voice-orb always floats; per-state animations live on .orb-core
   (transform) so the two compose. The cloud layer speeds up while Echo works. */
.voice-overlay[data-state="listening"] .orb-core { animation: orbBreath 3.4s ease-in-out infinite; }
.voice-overlay[data-state="listening"] .orb-ring { animation: voicePulse 2s ease-out infinite; }
.voice-overlay[data-state="listening"] .orb-ring.r2 { animation-delay: .66s; }
.voice-overlay[data-state="listening"] .orb-ring.r3 { animation-delay: 1.33s; }
.voice-overlay[data-state="listening"] .voice-caption { color: #9bd0ff; }

.voice-overlay[data-state="transcribing"] .orb-core,
.voice-overlay[data-state="thinking"] .orb-core { animation: orbThink 1.5s ease-in-out infinite; }
.voice-overlay[data-state="transcribing"] .orb-clouds,
.voice-overlay[data-state="thinking"] .orb-clouds { animation-duration: 5s; }   /* clouds churn faster while working */

.voice-overlay[data-state="speaking"] .orb-core { animation: orbSpeak .62s ease-in-out infinite; }
.voice-overlay[data-state="speaking"] .orb-clouds { animation-duration: 8s; }
.voice-overlay[data-state="speaking"] .voice-caption { color: var(--text); }

.voice-overlay[data-state="error"] .orb-core {
  background: radial-gradient(125% 125% at 34% 22%, #ffe3e3 0%, #ffb0b0 30%, #ff6b6b 75%, #d83a3a 100%);
  box-shadow: 0 18px 56px -14px rgba(255,107,107,.5), inset 0 -22px 40px -18px rgba(120,20,20,.6);
}
.voice-overlay[data-state="error"] .orb-clouds { opacity: .35; }
.voice-overlay[data-state="error"] .voice-caption { color: var(--red); }

/* Mic-permission / idle: dim the square stop button (nothing to stop) */
.voice-overlay[data-state="idle"] .voice-act,
.voice-overlay[data-state="speaking"] .voice-act { opacity: .55; }

@keyframes orbFloat { 0%,100% { transform: translateY(-7px); } 50% { transform: translateY(7px); } }
@keyframes orbDrift { 0% { transform: rotate(0deg) scale(1); } 50% { transform: rotate(180deg) scale(1.06); } 100% { transform: rotate(360deg) scale(1); } }
@keyframes orbBreath { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); filter: brightness(1.08); } }
@keyframes orbThink { 0%,100% { transform: scale(.96); filter: brightness(.9) saturate(.85); } 50% { transform: scale(1.02); filter: brightness(1.1) saturate(1.05); } }
@keyframes orbSpeak { 0%,100% { transform: scale(.98); } 50% { transform: scale(1.1); filter: brightness(1.12); } }
@media (prefers-reduced-motion: reduce) {
  .voice-orb, .orb-ring, .orb-clouds, .voice-overlay[data-state] .orb-core { animation: none !important; }
}

/* Weather card rendered in the voice transcript when get_weather runs. */
.wx-card {
  width: 100%; max-width: 320px;
  background: linear-gradient(160deg, rgba(80,150,230,.20), rgba(40,90,160,.12));
  border: 1px solid rgba(150,205,255,.22); border-radius: 18px;
  padding: 14px 16px; color: var(--text);
}
.wx-head { font: 600 15px/1.2 var(--font-ui); color: var(--text); margin-bottom: 8px; }
.wx-now { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.wx-now-ic { font-size: 30px; line-height: 1; }
.wx-now-t { font: 700 26px/1 var(--font-ui); }
.wx-now-c { font: 400 13px/1.2 var(--font-ui); color: var(--text-dim); text-transform: capitalize; }
.wx-days { display: flex; gap: 6px; }
.wx-day {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 9px 4px; border-radius: 12px; background: rgba(255,255,255,.05);
}
.wx-dl { font: 600 11.5px/1 var(--font-ui); color: var(--text-dim); }
.wx-di { font-size: 22px; line-height: 1; }
.wx-dt { font: 700 14px/1 var(--font-ui); color: var(--text); }
.wx-dt small { font-weight: 400; color: var(--text-faint); margin-left: 2px; }

/* Timer card rendered in the voice transcript when start_timer runs. */
.timer-card {
  width: 100%; max-width: 240px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: linear-gradient(160deg, rgba(120,120,130,.18), rgba(80,80,90,.10));
  border: 1px solid rgba(255,255,255,.12); border-radius: 18px;
  padding: 12px 16px; color: var(--text);
}
.timer-done { background: linear-gradient(160deg, rgba(80,180,120,.22), rgba(40,120,80,.12)); border-color: rgba(120,220,160,.28); }
.timer-label { font: 600 15px/1.2 var(--font-ui); }
.timer-time  { font: 700 18px/1 var(--font-ui); font-variant-numeric: tabular-nums; }

/* Memory modal — durable facts Echo remembers about the user. */
.memory-sub { font: 400 13.5px/1.5 var(--font-ui); color: var(--text-dim); margin: 0 0 14px; }
.memory-content { display: flex; flex-direction: column; gap: 8px; }
.memory-empty { font: 400 14px/1.55 var(--font-ui); color: var(--text-faint); text-align: center; padding: 22px 10px; }
.memory-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 12px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--line);
}
.memory-text { flex: 1; font: 400 14.5px/1.45 var(--font-ui); color: var(--text); }
.memory-del {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center;
  color: var(--text-faint); background: transparent; transition: color .15s ease, background .15s ease;
}
.memory-del:hover { color: var(--red); background: var(--red-soft); }
.memory-del:active { transform: scale(.9); }
.memory-clear {
  margin-top: 16px; width: 100%; padding: 11px; border-radius: var(--r-sm);
  font: 600 14px/1 var(--font-ui); color: var(--red);
  background: var(--red-soft); border: 1px solid transparent;
}
.memory-clear:active { transform: scale(.98); }

/* Onboarding — first-run welcome cards (shown once). */
.onb {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0 0 calc(var(--sa-b) + 20px);
  background: radial-gradient(120% 90% at 50% 18%, rgba(20,28,40,.82), rgba(6,8,12,.93));
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .3s ease, visibility 0s linear .3s;
}
.onb.open { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity .3s ease; }
.onb-card {
  width: min(440px, 92vw); margin: 0 16px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
  padding: 30px 24px 20px; transform: translateY(18px); transition: transform .32s cubic-bezier(.2,.8,.2,1);
}
.onb.open .onb-card { transform: none; }
.onb-viewport { overflow: hidden; }
.onb-track { display: flex; transition: transform .32s cubic-bezier(.2,.8,.2,1); }
.onb-slide { flex: 0 0 100%; display: flex; flex-direction: column; align-items: center; text-align: center; padding: 6px 8px 2px; }
.onb-art {
  width: 80px; height: 80px; border-radius: 50%; display: grid; place-items: center; margin-bottom: 20px;
  color: var(--accent-ink); background: linear-gradient(180deg, var(--accent), var(--accent-2));
  box-shadow: 0 12px 30px -10px var(--accent-glow), inset 0 0 22px rgba(255,255,255,.18);
}
.onb-title { font: 700 22px/1.2 var(--font-display, var(--font-ui)); margin: 0 0 9px; color: var(--text); }
.onb-text { font: 400 15px/1.55 var(--font-ui); color: var(--text-dim); margin: 0; max-width: 33ch; }
.onb-dots { display: flex; justify-content: center; gap: 7px; margin: 24px 0 18px; }
.onb-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--surface-3); transition: background .2s ease, width .2s ease; }
.onb-dot.on { background: var(--accent); width: 20px; border-radius: 4px; }
.onb-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.onb-skip { font: 600 14px/1 var(--font-ui); color: var(--text-faint); padding: 11px 10px; }
.onb-next {
  min-width: 130px; margin-left: auto; padding: 13px 22px; border-radius: var(--r-pill);
  font: 700 15px/1 var(--font-ui); color: var(--accent-ink);
  background: linear-gradient(180deg, var(--accent), var(--accent-2)); box-shadow: 0 8px 20px -10px var(--accent-glow);
}
.onb-next:active { transform: scale(.97); }
@media (prefers-reduced-motion: reduce) { .onb, .onb-card, .onb-track { transition: none !important; } }

/* Privacy & data (Settings) */
.privacy-card { background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 13px 14px; margin-bottom: 10px; }
.privacy-head { display: flex; align-items: center; gap: 7px; font: 600 14px/1 var(--font-ui); color: var(--accent); margin-bottom: 8px; }
.privacy-note { margin: 0; font: 400 13px/1.55 var(--font-ui); color: var(--text-dim); }
.privacy-note strong { color: var(--text); font-weight: 600; }
.drawer-action.is-arming { color: var(--red); background: var(--red-soft); }

/* Near-limit upgrade nudge — subtle inline pill shown when close to the daily cap. */
.quota-nudge {
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  margin: 10px auto; max-width: var(--col); width: 100%;
  padding: 9px 10px 9px 14px; border-radius: var(--r-pill);
  background: var(--accent-soft); border: 1px solid var(--accent-glow);
  font: 500 13px/1.35 var(--font-ui); color: var(--text-dim);
}
.quota-nudge-btn {
  flex: 0 0 auto; padding: 7px 14px; border-radius: var(--r-pill);
  font: 700 12.5px/1 var(--font-ui); color: var(--accent-ink);
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  box-shadow: 0 4px 12px -6px var(--accent-glow);
}
.quota-nudge-btn:active { transform: scale(.95); }

/* ===== Studio (owner-only ad-clone video generation) ===== */
.studio-form { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; }
.studio-row { display: flex; gap: 12px; }
.studio-field { display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 0; }
.studio-field > span { font-size: 12px; font-weight: 600; color: var(--text-dim, #9aa6b8); }
.studio-field input, .studio-field select {
  width: 100%; box-sizing: border-box; padding: 11px 13px; background: var(--surface, #131825);
  border: 1px solid var(--border, rgba(255,255,255,.09)); border-radius: var(--r-sm, 12px);
  color: var(--text, #e8ecf3); font: inherit; font-size: 14px; outline: none; transition: border-color .2s; }
.studio-field input:focus, .studio-field select:focus { border-color: var(--accent, #5ce0c6); }
.studio-actions { display: flex; }
.studio-actions .btn { width: 100%; }
.studio-stage:empty { display: none; }
.studio-card { background: var(--surface, #131825); border: 1px solid var(--border, rgba(255,255,255,.09)); border-radius: var(--r, 18px); padding: 18px; margin-bottom: 16px; }
.studio-prog-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.studio-prog-stage { flex: 1; min-width: 0; font-size: 14px; font-weight: 550; color: var(--text, #e8ecf3); }
.studio-prog-eta { font-size: 12px; color: var(--text-dim, #9aa6b8); white-space: nowrap; }
.studio-prog-note { font-size: 12px; line-height: 1.45; color: var(--text-faint, #5d6679); margin-top: 12px; }
.studio-spin { width: 16px; height: 16px; flex: 0 0 auto; border-radius: 50%; border: 2px solid rgba(92,224,198,.25); border-top-color: var(--accent, #5ce0c6); animation: studio-spin .8s linear infinite; }
@keyframes studio-spin { to { transform: rotate(360deg); } }
.studio-bar { height: 8px; background: var(--surface-2, #1a2030); border-radius: 999px; overflow: hidden; }
.studio-bar-fill { height: 100%; background: linear-gradient(90deg, var(--accent-2, #36c9aa), var(--accent, #5ce0c6)); border-radius: 999px; transition: width .6s cubic-bezier(.22,.61,.36,1); }
.studio-thumbs { display: flex; gap: 7px; margin-top: 12px; flex-wrap: wrap; }
.studio-thumb { width: 46px; height: 64px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border, rgba(255,255,255,.09)); }
.studio-bg-btn { margin-top: 14px; background: none; border: 0; color: var(--text-dim, #9aa6b8); font: inherit; font-size: 12.5px; text-decoration: underline; cursor: pointer; padding: 0; }
.studio-fail { font-size: 14px; font-weight: 600; color: var(--red, #ff7a7a); }
.studio-done-head { font-size: 14px; font-weight: 600; color: var(--accent, #5ce0c6); margin-bottom: 12px; }
.studio-result-vid { width: 100%; max-width: 300px; border-radius: var(--r-sm, 12px); display: block; background: #000; }
.studio-cands-head { font-size: 13px; font-weight: 600; color: var(--text-dim, #9aa6b8); margin-bottom: 12px; }
.studio-cands-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.studio-cand { background: var(--surface, #131825); border: 1px solid var(--border, rgba(255,255,255,.09)); border-radius: var(--r-sm, 12px); overflow: hidden; display: flex; flex-direction: column; }
.studio-cand-vid { width: 100%; height: 168px; object-fit: cover; background: #000; cursor: pointer; display: block; }
.studio-cand-meta { padding: 9px 10px; font-size: 12px; line-height: 1.35; color: var(--text, #e8ecf3); display: flex; flex-direction: column; gap: 3px; flex: 1; }
.studio-cand-sub { font-size: 11px; color: var(--text-faint, #5d6679); }
/* width:auto (overrides btn--block's 100%) + stretch lets the button fill the card
   MINUS its side margins, so it no longer overflows + gets clipped on the right. */
.studio-clone-btn { width: auto; align-self: stretch; box-sizing: border-box; margin: 2px 10px 12px; flex: 0 0 auto; }
.studio-check { display: flex; align-items: center; gap: 9px; cursor: pointer; font-size: 12.5px; color: var(--text-dim, #9aa6b8); }
.studio-check input { width: 16px; height: 16px; accent-color: var(--accent, #5ce0c6); flex: 0 0 auto; }
.studio-cand-media { position: relative; line-height: 0; }
.studio-cand-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 44px; height: 44px; border-radius: 50%; background: rgba(10,12,17,.55); display: flex; align-items: center; justify-content: center; color: #fff; pointer-events: none; transition: opacity .2s; }
.studio-cand-media.is-playing .studio-cand-play { opacity: 0; }
.studio-compare-grid { display: grid; grid-template-columns: 1fr; gap: 10px; max-width: 300px; }
.studio-compare-grid.is-compare { grid-template-columns: 1fr 1fr; max-width: 100%; }
.studio-cmp-col { min-width: 0; }
.studio-compare-grid .studio-result-vid { width: 100%; max-width: 100%; }
.studio-compare-lbl { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-dim, #9aa6b8); margin-bottom: 6px; }
.studio-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border, rgba(255,255,255,.09)); margin-bottom: 18px; }
.studio-tab { appearance: none; background: none; border: 0; border-bottom: 2px solid transparent; color: var(--text-dim, #9aa6b8); font: inherit; font-size: 13.5px; font-weight: 600; padding: 9px 12px; margin-bottom: -1px; cursor: pointer; transition: color .15s, border-color .15s; }
.studio-tab:hover { color: var(--text, #e8ecf3); }
.studio-tab.is-active { color: var(--accent, #5ce0c6); border-bottom-color: var(--accent, #5ce0c6); }
.studio-lib-empty { font-size: 13px; color: var(--text-faint, #5d6679); text-align: center; padding: 36px 12px; }
.studio-lib-group { margin-bottom: 22px; }
.studio-lib-brand { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-dim, #9aa6b8); margin: 2px 0 11px; padding-bottom: 7px; border-bottom: 1px solid var(--border, rgba(255,255,255,.08)); }
.studio-cand-hidden { display: none; }
.studio-cand-more { display: block; margin: 14px auto 2px; }

/* ============================================================
   ECHO - CALM / CLAUDE-STYLE THEME OVERRIDE  (2026-06-30)
   Warm, low-colour, document-like. Reversible: delete this block
   + remove "Source Serif 4" from the index.html font link.
   ============================================================ */
:root{
  --bg:#1b1b19; --bg-2:#211f1c;
  --surface:#262521; --surface-2:#2e2d29; --surface-3:#38362f;
  --text:#ECEAE3; --text-dim:#9b968c; --text-faint:#6f6b62;
  --line:rgba(255,255,255,.085); --line-2:rgba(255,255,255,.06);
  --panel:rgba(27,27,25,.84);
  --accent:#6ad9c0; --accent-2:#4ec3ab;
  --accent-soft:rgba(106,217,192,.12); --accent-glow:rgba(106,217,192,.22);
}
/* serif greeting, Claude-style */
.empty-title{ font-family:"Source Serif 4",Georgia,serif !important; font-weight:500; font-size:1.5rem; letter-spacing:-.01em; color:#F3F1EA; }
.empty-rings{ background:radial-gradient(120% 120% at 50% 30%, rgba(255,255,255,.06), transparent 70%) !important; }
/* user message: neutral + calm (not mint-tinted) */
.msg.user .bubble{ background:var(--surface) !important; border:1px solid var(--line) !important; color:var(--text); }
/* near-monochrome reading */
.md li::marker{ color:var(--text-faint); }
.md blockquote{ border-left-color:var(--text-faint) !important; background:rgba(255,255,255,.04) !important; }
.md strong{ color:#FAF8F2; }
/* code: warm surface, calm header */
.md pre{ background:#211f1c !important; }
.code-bar{ background:linear-gradient(180deg, rgba(46,45,41,.96), rgba(33,31,28,.72)) !important; }
.md code{ background:rgba(255,255,255,.06) !important; }
/* send button: neutral off-white (not bright mint) */
.send-btn{ background:#ECEAE3 !important; color:#1b1b19 !important; box-shadow:none !important; }
.send-btn:disabled{ opacity:.4; }
.send-btn.is-streaming{ background:var(--surface-3) !important; color:var(--text) !important; }
/* warm the drawer */
.drawer{ background:linear-gradient(180deg,#221f1b,#1b1916) !important; }
/* ===== END calm override ===== */

/* Live Translate temporarily hidden (feature not 100% yet). Forces it hidden even
   though translate.js un-hides it at runtime. Reversible: delete these 2 lines. */
#translateBtn{ display:none !important; }

/* ============================================================
   ECHO VOICE MODE - mint/teal energy orb + richer animation (2026-06-30)
   Recolours the blue sky-sphere to Echo brand mint/teal, adds a flowing internal
   energy sheen + an outer breathing halo. Reversible: delete this whole block.
   ============================================================ */
.voice-overlay{ background: radial-gradient(125% 95% at 50% 16%, rgba(26,26,24,.97), rgba(9,9,8,.99)) !important; }
.orb-core{
  background: radial-gradient(125% 125% at 32% 22%, #f0fffb 0%, #b9f6ea 20%, #6fe6cf 48%, #2bb89e 78%, #15806e 100%) !important;
  /* No large outer box-shadow glow: big soft shadows on near-black tile-band into
     a square with seams. The outer glow is drawn by .voice-orb::before (a smooth
     radial gradient), which does not band. Keep only the rim + inset shading. */
  box-shadow:
    0 0 0 1px rgba(255,255,255,.07),
    inset 0 -26px 46px -20px rgba(8,64,55,.72),
    inset 10px 12px 34px -8px rgba(255,255,255,.92) !important;
}
.orb-core::after{
  content:""; position:absolute; inset:0; border-radius:50%; pointer-events:none;
  /* clip-path forces a true-circle mask: a blurred pseudo-element leaks past
     overflow+border-radius as a square (the conic seams), so clip it directly. */
  -webkit-clip-path: circle(50% at 50% 50%); clip-path: circle(50% at 50% 50%);
  background:
    radial-gradient(38% 30% at 30% 30%, rgba(220,255,248,.7), transparent 62%),
    conic-gradient(from 0deg, rgba(125,240,219,0) 0deg, rgba(125,240,219,.34) 70deg, rgba(43,184,158,0) 150deg, rgba(168,247,231,.30) 250deg, rgba(125,240,219,0) 360deg);
  mix-blend-mode: screen; filter: blur(7px); opacity:.72;
  animation: orbEnergy 9s linear infinite;
}
@keyframes orbEnergy { to { transform: rotate(360deg); } }
.orb-clouds{
  background:
    radial-gradient(40% 27% at 30% 38%, rgba(240,255,251,.95), transparent 60%),
    radial-gradient(32% 23% at 66% 30%, rgba(214,255,246,.82), transparent 62%),
    radial-gradient(46% 32% at 60% 70%, rgba(176,244,229,.68), transparent 64%),
    radial-gradient(26% 19% at 22% 72%, rgba(214,255,246,.58), transparent 60%) !important;
}
.orb-ring{ border-color: rgba(106,217,192,.6) !important; }
.voice-overlay[data-state="listening"] .voice-caption{ color:#7fe9d3 !important; }
.voice-orb::before{
  content:""; position:absolute; inset:-30%; border-radius:50%; z-index:-1; pointer-events:none;
  /* Two stacked circular radials = the orb's full outer glow (replaces the old
     box-shadow). Both fade to transparent inside the box, so it stays a clean
     circle on dark - no square, no banding. */
  background:
    radial-gradient(circle at 50% 50%, rgba(120,235,212,.42) 28%, rgba(95,224,198,.24) 42%, rgba(95,224,198,0) 60%),
    radial-gradient(circle at 50% 50%, rgba(95,224,198,.14) 30%, rgba(95,224,198,0) 74%);
  animation: orbHalo 3.8s ease-in-out infinite;
}
@keyframes orbHalo { 0%,100%{ transform:scale(.9); opacity:.45; } 50%{ transform:scale(1.14); opacity:.9; } }
.voice-overlay[data-state="speaking"] .voice-orb::before{ animation-duration: 1.4s; }
.voice-overlay[data-state="speaking"] .orb-core::after{ animation-duration: 4.5s; opacity:.9; }
@media (prefers-reduced-motion: reduce){ .voice-orb::before, .orb-core::after{ animation:none !important; } }

/* Drag-and-drop attach hint: shown while a file is dragged over the window
   (body.is-dragging is toggled by the drop handlers in app.js). Pointer-events
   none so it never intercepts the drop itself. */
body.is-dragging::after {
  content: "Drop to attach";
  position: fixed;
  inset: 8px;
  z-index: 9990;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  font-weight: 650;
  letter-spacing: 0.2px;
  color: var(--accent);
  background: var(--accent-soft);
  border: 2px dashed var(--accent);
  border-radius: 16px;
  backdrop-filter: blur(2px);
  pointer-events: none;
}
