/*
 * Quanta Sovereign Suite — Liquid Glass Design System
 * 
 * Minimalist, World-Class Luminescent Glass Theme.
 * Engineered for Zero Lag & Smooth 60fps Scrolling on Mobile & Android WebView.
 */

:root {
  --bg: #06070a;
  --bg-deep: #030406;
  --bg-gradient: radial-gradient(circle at 50% 0%, #101426 0%, #06070a 80%);
  
  --panel: rgba(var(--surf-20), 0.72);
  --panel-solid: #10131f;
  --panel-bright: #181d30;
  
  --glass-surface: rgba(var(--surf-24), 0.65);
  --glass-surface-hover: rgba(var(--surf-34), 0.78);
  --glass-surface-active: rgba(var(--surf-44), 0.88);
  --glass-dock: rgba(var(--surf-11), 0.82);
  --glass-card: rgba(var(--surf-24), 0.58);
  
  --raised: rgba(var(--lift-rgb), calc(0.045 * var(--lift-k)));
  --raised-hover: rgba(var(--lift-rgb), calc(0.085 * var(--lift-k)));
  --raised-active: rgba(var(--lift-rgb), calc(0.13 * var(--lift-k)));
  
  --line: rgba(var(--lift-rgb), calc(0.09 * var(--lift-k)));
  --line-subtle: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k)));
  --line-strong: rgba(56, 189, 248, 0.45);
  --line-glow: rgba(56, 189, 248, 0.25);
  
  --glass-specular: inset 0 1px 0 rgba(var(--gloss-rgb), 0.16), inset 0 0 16px rgba(56, 189, 248, 0.02);
  --glass-specular-bright: inset 0 1px 0 rgba(var(--gloss-rgb), 0.25), 0 0 20px rgba(56, 189, 248, 0.18);
  --glass-blur: blur(24px) saturate(180%);
  --glass-blur-heavy: blur(32px) saturate(190%);
  --glass-shadow: 0 12px 36px 0 rgba(var(--shade-rgb), calc(0.55 * var(--shadow-k)));
  
  --text: #f1f5f9;
  --text-bright: #ffffff;
  --muted: #94a3b8;
  --faint: #7a8798;
  
  --user-bubble: linear-gradient(135deg, rgba(var(--surf-40), 0.9) 0%, rgba(var(--surf-29), 0.9) 100%);
  --user-bubble-border: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  
  --accent: #38bdf8;
  --accent-hover: #0ea5e9;
  --accent-purple: #818cf8;
  --accent-gradient: linear-gradient(135deg, #38bdf8 0%, #6366f1 50%, #a855f7 100%);
  --cyan: #38bdf8;
  --violet: #a78bfa;
  --emerald: #34d399;
  --green: #34d399;
  --amber: #fbbf24;
  --red: #f87171;
  --indigo: #818cf8;
  
  --radius-sm: 9px;
  --radius: 15px;
  --radius-lg: 22px;
  --radius-pill: 9999px;
  
  --bar-h: 54px;

  /* ⛔ TYPOGRAPHY TOKENS — consumed ~30x across the app but never defined, so
     every `var(--font-*)` was invalid-at-computed-value and silently fell back
     to the inherited system sans: the intended mono labels and display
     headings never rendered. --font-sans mirrors the body stack below, so
     defining it changes nothing there. `Space Grotesk` is named first for when
     a webfont is loaded; until then --font-display degrades to the sans stack. */
  /* QUANTA GENEVA — the app's own text system. Built on Geist (Vercel's
     open-licensed 2024 typeface, OFL), self-hosted from our own origin so no
     third-party font request ever fires — which matters for a product whose
     promise is that your words never leave the device. Named first in every
     stack so every visitor, on any OS, sees the same state-of-the-art letter-
     forms rather than whatever their system happened to ship. The fallbacks
     below only ever show for the ~30KB blink before the woff2 lands. */
  --font-sans: "Quanta Geneva", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "Quanta Geneva Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --font-display: "Quanta Geneva", "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

@font-face {
  font-family: "Quanta Geneva";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/quanta-geneva.woff2") format("woff2");
}

/* The monospace half of the system — Geist Mono, self-hosted, so code blocks
   and editors share the same frontier voice as the prose. */
@font-face {
  font-family: "Quanta Geneva Mono";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/quanta-geneva-mono.woff2") format("woff2");
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--lift-rgb), calc(0.15 * var(--lift-k))) transparent !important;
}

::-webkit-scrollbar,
*::-webkit-scrollbar {
  width: 6px !important;
  height: 6px !important;
}
::-webkit-scrollbar-track,
*::-webkit-scrollbar-track {
  background: transparent !important;
}
::-webkit-scrollbar-thumb,
*::-webkit-scrollbar-thumb {
  background: rgba(var(--lift-rgb), calc(0.15 * var(--lift-k))) !important;
  border-radius: 9999px !important;
}
::-webkit-scrollbar-thumb:hover,
*::-webkit-scrollbar-thumb:hover {
  background: rgba(56, 189, 248, 0.35) !important;
}

.hidden {
  display: none !important;
}

button, input, textarea, select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  outline: none;
  box-sizing: border-box;
  touch-action: manipulation;
}

/* ⛔ RESTORE THE KEYBOARD FOCUS RING. The reset above sets `outline: none` on
   every control, which stripped the focus indicator app-wide — keyboard
   navigation was invisible on almost every button (only a handful re-added it
   individually). This puts it back for :focus-visible ONLY, so a mouse click
   never shows a ring but a Tab always does. The outline follows each element's
   own border-radius in modern browsers, so pills stay pill-shaped. */
button:focus-visible,
a:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[role="button"]:focus-visible,
[role="option"]:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

html,
body {
  color-scheme: dark;
  height: 100%;
  background: var(--bg);
  background-image: var(--bg-gradient);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  /* Quanta Geneva is drawn for the screen — render it that way: grayscale
     antialiasing kills the faux-bold fringing, and a hair of negative tracking
     gives the tight, engineered set that reads as "frontier" rather than
     "default system sans". Kept to -0.011em so line lengths barely move. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  letter-spacing: -0.011em;
  overscroll-behavior: none;
}

body {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* Quanta Geneva tracking pass. Prose carries a hair of negative tracking (on
   the root above). Monospace must NOT inherit it — a fixed-advance face fights
   negative tracking and the columns stop lining up — so code resets to normal.
   Headings inside a reply read sharper a touch tighter, the way display type
   wants. */
pre, code, kbd, samp { letter-spacing: normal; }
.bubble h1, .bubble h2, .bubble h3 { letter-spacing: -0.02em; }

.hidden { display: none !important; }
.sprite { position: absolute; width: 0; height: 0; }

.ico {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}

/* ------------------------------------------------------------------ *
 * Top App Bar Header (Liquid Glass Studio Aesthetic)
 * ------------------------------------------------------------------ */

.bar {
  position: relative;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 14px;
  padding-top: calc(8px + env(safe-area-inset-top));
  border-bottom: var(--line);
  background: var(--glass-surface);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: 0 4px 20px rgba(var(--shade-rgb), calc(0.35 * var(--shadow-k)));
  flex: none;
}

.drawer-brand-logo {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  border: 1px solid rgba(var(--lift-rgb), calc(0.15 * var(--lift-k)));
}

.quantum-emblem {
  display: none;
}

.icon-btn {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.18s cubic-bezier(0.16, 1, 0.3, 1);
  position: relative;
  flex: none;
}
.icon-btn:hover {
  background: rgba(var(--lift-rgb), calc(0.09 * var(--lift-k)));
  border-color: rgba(var(--lift-rgb), calc(0.14 * var(--lift-k)));
  color: var(--text-bright);
}
.icon-btn:active {
  background: rgba(var(--lift-rgb), calc(0.13 * var(--lift-k)));
  transform: scale(0.95);
}

.icon-btn-sm {
  width: 30px;
  height: 30px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.icon-btn-sm:active {
  color: var(--text-bright);
  background: var(--raised-hover);
}

.bar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  /* `.bar` is justify-content:space-between, so these actions are only on the
     right because the hamburger is on the left holding the other end. The
     moment that button is hidden - hero mode did it first, and the docked
     sidebar modes do it now - the actions slide to the left edge and the
     header looks broken. Anchor them right on their own instead of depending
     on a sibling that is conditionally displayed. */
  margin-left: auto;
}

.badge-dot {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cyan);
}

/* ------------------------------------------------------------------ *
 * Left Slide-out Drawer (Navigation Hub)
 * ------------------------------------------------------------------ */

.drawer-scrim {
  position: fixed;
  inset: 0;
  background: rgba(var(--shade-rgb), 0.65);
  z-index: 100;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
}
.drawer-scrim.visible {
  opacity: 1;
  pointer-events: auto;
}

.drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  width: min(300px, 84vw);
  background: var(--bg-deep);
  background-image: var(--drawer-gradient);
  border-right: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  z-index: 110;
  transform: translate3d(-100%, 0, 0);
  -webkit-transform: translate3d(-100%, 0, 0);
  transition: transform 0.24s cubic-bezier(0.16, 1, 0.3, 1);
  -webkit-transition: -webkit-transform 0.24s cubic-bezier(0.16, 1, 0.3, 1);
  display: flex;
  flex-direction: column;
  box-shadow: 16px 0 48px rgba(var(--shade-rgb), calc(0.85 * var(--shadow-k)));
  padding-top: env(safe-area-inset-top);
  /* ⛔ the bottom inset is owned by .drawer-footer, not here. Reserving it on
     the container left a strip below the footer painted with the drawer's base
     gradient — the grey dead band at the home-indicator on iOS standalone. */
  padding-bottom: 0;
}
.drawer.open {
  transform: translate3d(0, 0, 0) !important;
  -webkit-transform: translate3d(0, 0, 0) !important;
}

.drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 14px 10px 14px;
  border-bottom: 1px solid rgba(var(--lift-rgb), calc(0.06 * var(--lift-k)));
}
.drawer-brand {
  display: flex;
  align-items: center;
  gap: 8px;
}
.drawer-title-wrap {
  display: flex;
  flex-direction: column;
}
.drawer-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-bright);
}
.drawer-subtitle {
  font-size: 11px;
  color: var(--muted);
}

.drawer-action-row {
  padding: 10px 14px 6px 14px;
}
.btn-drawer-new {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--surf-21), 0.85);
  color: var(--text-bright);
  font-weight: 600;
  font-size: 13.5px;
  cursor: pointer;
  transition: all 0.16s ease;
}
.btn-drawer-new:hover {
  background: rgba(var(--surf-35), 0.95);
  border-color: rgba(56, 189, 248, 0.3);
}
.btn-drawer-new:active {
  background: var(--panel-bright);
}

.drawer-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 10px;
}
.nav-group-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--faint);
  padding: 6px 6px 2px 6px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.badge-count {
  background: var(--raised);
  padding: 1px 5px;
  border-radius: var(--radius-pill);
  font-size: 9.5px;
}

.drawer-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: all 0.12s ease;
}
.drawer-item:active {
  background: var(--raised);
  color: var(--text-bright);
}
.drawer-item.active {
  background: var(--panel-solid);
  border-color: var(--line);
  color: var(--text-bright);
}
.drawer-item.active .ico {
  color: var(--text-bright);
}
.nav-badge {
  margin-left: auto;
  font-size: 9.5px;
  font-weight: 600;
  padding: 1px 5px;
  border-radius: 4px;
}
.nav-badge.sota {
  background: var(--raised);
  border: 1px solid var(--line);
  color: var(--muted);
}
.nav-badge.studio {
  background: var(--raised);
  border: 1px solid var(--line);
  color: var(--muted);
}

.drawer-history-section {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 6px 10px;
  border-top: 1px solid var(--line);
  margin-top: 4px;
}
.drawer-chat-list {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.chat-history-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  cursor: pointer;
}
.chat-history-item:active {
  background: var(--raised);
  color: var(--text);
}
.chat-history-item.active {
  background: var(--panel-solid);
  color: var(--text-bright);
}
.chat-title-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
.chat-delete-btn {
  opacity: 0.35;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 4px;
  display: flex;
  align-items: center;
}
.chat-delete-btn:active {
  opacity: 1;
  color: var(--red);
}

.drawer-footer {
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 7px;
  align-items: center;
  justify-content: space-between;
  background: var(--bg);
}
.provider-status-row {
  display: flex;
  align-items: center;
  gap: 5px;
}
.provider-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--faint);
  display: inline-block;
}
.provider-dot.active {
  background: var(--emerald);
}
.provider-status-label {
  font-size: 10.5px;
  color: var(--muted);
  margin-left: 3px;
}
.drawer-settings-btn {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 4px;
  display: flex;
  align-items: center;
}
.drawer-settings-btn:active {
  color: var(--text-bright);
}

/* ------------------------------------------------------------------ *
 * Main Views Container
 * ------------------------------------------------------------------ */

.view {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  width: 100%;
  max-width: 100vw;
  overflow-y: auto;
  overflow-x: hidden;
  position: relative;
  -webkit-overflow-scrolling: touch;
  box-sizing: border-box;
}

.thread {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  max-width: var(--thread-max);
  margin: 0 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 24px 18px 32px 18px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.empty {
  margin: auto;
  text-align: center;
  max-width: 320px;
  padding: 24px 12px;
  color: var(--muted);
}
.empty-brand-logo {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  margin: 0 auto 14px auto;
  display: block;
  border: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
}
.empty h2 {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-bright);
  margin-bottom: 4px;
}
.empty p {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.45;
}
.empty-suggestions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 16px;
}
.suggest-card {
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease;
}
.suggest-card:active {
  background: var(--panel-bright);
  color: var(--text-bright);
}

/* Clean Message Stream (Anthropic Claude Editorial Style) */
.msg {
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: relative;
  word-break: break-word;
  contain: content;
}
.msg.user {
  align-self: flex-end;
  max-width: 86%;
}
.msg.user .bubble {
  background: var(--user-bubble);
  color: var(--text-bright);
  border-radius: 16px;
  padding: 10px 15px;
  font-size: 15px;
  line-height: 1.55;
  border: 1px solid var(--line);
  box-shadow: none;
}

.msg.model {
  align-self: flex-start;
  width: 100%;
  max-width: 100%;
  margin-top: 4px;
}
.msg.model .bubble {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 2px 0;
  font-size: 15.5px;
  line-height: 1.68;
  color: var(--text);
  letter-spacing: -0.01em;
  box-shadow: none;
}

.msg.model .bubble p {
  margin-bottom: 12px;
}
.msg.model .bubble p:last-child {
  margin-bottom: 0;
}

.msg.model .bubble h1,
.msg.model .bubble h2,
.msg.model .bubble h3,
.msg.model .bubble h4 {
  color: var(--text-bright);
  font-weight: 600;
  letter-spacing: -0.015em;
  margin-top: 18px;
  margin-bottom: 8px;
  line-height: 1.35;
}
.msg.model .bubble h1 { font-size: 19px; }
.msg.model .bubble h2 { font-size: 17px; }
.msg.model .bubble h3 { font-size: 15.5px; }

.msg.model .bubble blockquote {
  border-left: 3px solid var(--cyan);
  padding-left: 12px;
  margin: 10px 0;
  color: var(--muted);
  font-style: italic;
}

.msg.model .bubble ul,
.msg.model .bubble ol {
  padding-left: 20px;
  margin: 8px 0 12px 0;
}
.msg.model .bubble li {
  margin-bottom: 4px;
  line-height: 1.6;
}

.msg.model .bubble strong {
  color: var(--text-bright);
  font-weight: 600;
}

.msg.model .bubble hr {
  border: none;
  border-top: 1px solid var(--line);
  margin: 16px 0;
}

.msg-header {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 6px;
  font-size: 12px;
  color: var(--muted);
}
.msg-avatar {
  width: 18px;
  height: 18px;
  border-radius: 4px;
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.2), rgba(139, 92, 246, 0.2));
  color: var(--accent);
  border: 1px solid rgba(56, 189, 248, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
}
.msg-avatar .ico { width: 12px; height: 12px; stroke-width: 2; }
.msg-sender-name {
  font-weight: 600;
  color: var(--text-bright);
  font-size: 12px;
  letter-spacing: -0.01em;
}

.msg-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 4px;
}
.msg-btn-action {
  background: transparent;
  border: none;
  color: var(--faint);
  cursor: pointer;
  padding: 4px;
  display: flex;
  align-items: center;
  border-radius: 4px;
}
.msg-btn-action:active {
  color: var(--text-bright);
  background: var(--raised);
}

/* ------------------------------------------------------------------ *
 * Thought & Reasoning Accordion (Claude 3.7 & Gemini SOTA Style)
 * ------------------------------------------------------------------ */

.thought-box {
  margin-bottom: 8px;
  width: 100%;
}

.thought-pill-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px 5px 10px;
  border-radius: 9999px;
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--lift-rgb), calc(0.03 * var(--lift-k)));
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
  backdrop-filter: blur(8px);
}
.thought-pill-btn:active {
  background: rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  color: var(--text-bright);
}
.thought-brain-icon {
  color: var(--cyan);
  transition: color 0.2s ease;
}
.thought-shimmer-text {
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.01em;
}

/* Universal response timer — every reply that didn't stream a reasoning
   chain still shows how long it took, floating quietly to the right of the
   model name the way ChatGPT/Claude/DeepSeek do. A caption, not a control:
   no pill, no border, just a faint duration separated by a middot. */
.msg-gen-time {
  font-size: 11px;
  font-weight: 500;
  color: var(--faint);
  letter-spacing: 0.01em;
  opacity: 0.9;
  user-select: none;
  font-variant-numeric: tabular-nums;
}
.msg-gen-time::before {
  content: "·";
  margin-right: 6px;
  opacity: 0.6;
}

/* Active Thinking State Pulse & Glow */
.thought-box.thinking-active .thought-pill-btn {
  border-color: rgba(56, 189, 248, 0.35);
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.08), rgba(99, 102, 241, 0.08));
  box-shadow: 0 0 14px rgba(56, 189, 248, 0.15);
}
.thought-box.thinking-active .thought-brain-icon {
  color: var(--accent);
  animation: pulse-glow 1.5s infinite ease-in-out;
}
.thought-box.thinking-active .thought-shimmer-text {
  color: var(--accent);
  font-weight: 600;
}

@keyframes pulse-glow {
  0%, 100% { opacity: 0.6; transform: scale(0.95); }
  50% { opacity: 1; transform: scale(1.1); filter: drop-shadow(0 0 4px #38bdf8); }
}

.thought-chevron {
  font-size: 12px;
  color: var(--faint);
  transition: transform 0.2s ease;
  margin-left: 2px;
}
.thought-box.open .thought-chevron {
  transform: rotate(90deg);
  color: var(--cyan);
}

.thought-drawer {
  display: none;
  margin-top: 8px;
  margin-bottom: 8px;
  border-radius: 12px;
  background: rgba(var(--surf-8), 0.95);
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  border-left: 3px solid #38bdf8;
  overflow: hidden;
  box-shadow: 0 4px 20px rgba(var(--shade-rgb), calc(0.4 * var(--shadow-k)));
  animation: slide-in-down 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.thought-box.open .thought-drawer {
  display: block;
}

@keyframes slide-in-down {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

.thought-drawer-inner {
  padding: 12px 14px;
}
.thought-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 8px;
  margin-bottom: 8px;
  border-bottom: 1px solid rgba(var(--lift-rgb), calc(0.06 * var(--lift-k)));
  font-size: 11px;
}
.thought-meta-pill {
  color: var(--cyan);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 10px;
  display: flex;
  align-items: center;
  gap: 5px;
}
.btn-copy-thought-inline {
  background: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.1 * var(--lift-k)));
  color: var(--muted);
  border-radius: 6px;
  padding: 3px 8px;
  font-size: 10.5px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
  transition: all 0.15s ease;
}
.btn-copy-thought-inline:active {
  color: var(--text-bright);
  background: rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
}

.thought-drawer-content {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  color: #cbd5e1;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 260px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* ------------------------------------------------------------------ *
 * Clean In-Chat Media (Zero Sci-Fi Borders or Glowing Boxes)
 * ------------------------------------------------------------------ */

.chat-media-card {
  position: relative;
  display: inline-block;
  max-width: 100%;
  margin: 8px 0;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-inverse);
  border: 1px solid var(--line);
}

.chat-media-img {
  display: block;
  max-width: 100% !important;
  max-height: 320px !important;
  width: auto !important;
  height: auto !important;
  object-fit: contain !important;
  border-radius: calc(var(--radius) - 1px);
}

.chat-media-video {
  display: block;
  max-width: 100% !important;
  max-height: 320px !important;
  width: 100%;
  background: var(--surface-inverse);
  border-radius: calc(var(--radius) - 1px);
}

.chat-media-dl {
  position: absolute;
  bottom: 8px;
  right: 8px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(var(--shade-rgb), 0.7);
  border: 1px solid rgba(var(--lift-rgb), calc(0.2 * var(--lift-k)));
  color: var(--text-bright);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
  transition: transform 0.12s ease;
}
.chat-media-dl:active {
  transform: scale(0.92);
}

/* Global Bubble Image Clamp */
.bubble img {
  max-width: 100% !important;
  max-height: 260px !important;
  width: auto !important;
  height: auto !important;
  object-fit: contain !important;
  border-radius: 12px;
  display: block;
  margin: 6px 0;
  border: 1px solid var(--line);
}

/* ------------------------------------------------------------------ *
 * Code Blocks & Artifact Cards (Claude Artifacts Style)
 * ------------------------------------------------------------------ */

.code-card {
  margin: 8px 0;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--panel);
  overflow: hidden;
}
.code-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 5px 10px;
  background: var(--panel-solid);
  border-bottom: 1px solid var(--line);
}
.code-lang {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.code-actions-top {
  display: flex;
  align-items: center;
  gap: 4px;
}
.btn-code-action {
  padding: 3px 7px;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--muted);
  font-size: 10.5px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
}
.btn-code-action:active {
  color: var(--text-bright);
  background: var(--raised-hover);
}
.btn-code-action.primary {
  color: var(--text-bright);
  border-color: var(--line-strong);
}

pre {
  margin: 0;
  padding: 10px 12px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text);
}
code {
  font-family: inherit;
}
p code {
  background: var(--raised);
  padding: 2px 5px;
  border-radius: 4px;
  font-size: 13px;
  color: var(--text-bright);
}

/* Claude-Style Live Player Container */
.chat-player-container {
  width: 100%;
  margin: 10px 0 6px 0;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--panel);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.chat-player-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  background: var(--panel-solid);
  border-bottom: 1px solid var(--line);
  gap: 6px;
  flex-wrap: wrap;
}

.chat-player-title-box {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  min-width: 120px;
}

.chat-player-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-player-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
}

.btn-player-action {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 5px;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 500;
  cursor: pointer;
}
.btn-player-action:active {
  background: var(--raised-hover);
  color: var(--text-bright);
}
.btn-player-action.primary {
  color: var(--text-bright);
  border-color: var(--line-strong);
}

.chat-player-viewport {
  position: relative;
  width: 100%;
  height: 240px;
  background: var(--surface-inverse);
  overflow: hidden;
}

.chat-player-iframe {
  width: 100%;
  height: 100%;
  border: none;
  display: block;
}

.chat-player-code-drawer {
  background: var(--surface-inverse);
  border-top: 1px solid var(--line);
  padding: 8px 10px;
  max-height: 180px;
  overflow-y: auto;
}
.chat-player-code-drawer pre {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: #cbd5e1;
  line-height: 1.45;
  white-space: pre-wrap;
}

/* ------------------------------------------------------------------ *
 * Web Page Snapshot Cards (Interactive Grounding & Web Previews)
 * ------------------------------------------------------------------ */
.web-snapshot-card {
  width: 100%;
  margin: 8px 0;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--panel);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  user-select: none;
}
.web-snapshot-card:active {
  background: var(--panel-solid);
}

.web-snapshot-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  background: var(--panel-solid);
  border-bottom: 1px solid var(--line);
  gap: 6px;
}

.web-snapshot-domain-box {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 1;
}

.web-snapshot-favicon {
  width: 14px;
  height: 14px;
  border-radius: 3px;
  background: #ffffff;
  flex: none;
}

.web-snapshot-domain {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.web-snapshot-badge {
  font-size: 9px;
  font-weight: 600;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--raised);
  color: var(--muted);
  text-transform: uppercase;
  flex: none;
}

.web-snapshot-viewport {
  position: relative;
  width: 100%;
  height: 150px;
  background: var(--surface-inverse);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.web-snapshot-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.web-snapshot-fallback {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--panel);
  gap: 4px;
  padding: 12px;
}
.web-snapshot-fallback .fallback-domain {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-bright);
}
.web-snapshot-fallback .fallback-sub {
  font-size: 10.5px;
  color: var(--muted);
}

.web-snapshot-footer {
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--panel);
}

.web-snapshot-title {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.web-snapshot-url-line {
  font-size: 10.5px;
  font-family: var(--font-mono);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ------------------------------------------------------------------ *
 * CREATIVE STUDIOS: Image, Video, Music
 * ------------------------------------------------------------------ */

.studio-container {
  padding: 12px;
  padding-bottom: calc(85px + env(safe-area-inset-bottom));
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  flex: 1;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  contain: content;
  transform: translateZ(0);
}
.studio-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.studio-header-icon {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: var(--panel-solid);
  border: 1px solid var(--line);
  color: var(--text-bright);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.studio-title {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--text-bright);
  line-height: 1.2;
}
.studio-subtitle {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.3;
}
.studio-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: 14px;
}
.prompt-wrap {
  position: relative;
  margin-bottom: 10px;
}
.studio-textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-bright);
  font-size: 13px;
  padding: 10px;
  padding-bottom: 34px;
  resize: none;
  font-family: inherit;
  outline: none;
}
.studio-textarea:focus {
  border-color: var(--line-strong);
}
.btn-prompt-magic {
  position: absolute;
  bottom: 6px;
  right: 6px;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--panel-solid);
  color: var(--text-bright);
  font-size: 10.5px;
  font-weight: 600;
  cursor: pointer;
}
.btn-prompt-magic:active {
  background: var(--panel-bright);
}
.studio-section-label {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--faint);
  letter-spacing: 0.05em;
  margin-bottom: 4px;
  margin-top: 6px;
}

/* Spotlight Stage */
.spotlight-stage {
  margin-bottom: 8px;
  width: 100%;
}
.spotlight-card {
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.spotlight-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  background: var(--panel-solid);
  border-bottom: 1px solid var(--line);
  gap: 6px;
}
.spotlight-header-left {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  flex: 1;
}
.spotlight-pulse-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cyan);
}
.spotlight-header-title {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.spotlight-badges {
  display: flex;
  gap: 4px;
  flex: none;
}
.spotlight-badge {
  font-size: 8.5px;
  font-weight: 600;
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--raised);
  border: 1px solid var(--line);
  color: var(--muted);
  text-transform: uppercase;
}
/* The Quantalized badge is the mark of Quanta's finishing pass — give it the
   brand gradient so it reads as a feature, not just metadata. */
.spotlight-badge.quantalized-badge {
  color: var(--on-accent);
  border: none;
  background: linear-gradient(90deg, var(--cyan, #22D3EE), var(--violet, #8B5CF6));
  box-shadow: 0 0 10px rgba(139, 92, 246, 0.35);
  letter-spacing: 0.03em;
}
.btn-spotlight-compare.active {
  background: var(--violet, #8B5CF6);
  color: var(--on-accent);
}
.spotlight-viewport {
  position: relative;
  width: 100%;
  height: 190px;
  max-height: 190px;
  background: var(--surface-inverse);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.spotlight-img {
  max-width: 100%;
  max-height: 190px;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.spotlight-placeholder {
  width: 100%;
  height: 140px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 12px;
  background: var(--bg);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  text-align: center;
}
.spotlight-placeholder-icon {
  font-size: 24px;
  color: var(--muted);
}
.spotlight-placeholder-title {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-bright);
}
.spotlight-placeholder-sub {
  font-size: 10.5px;
  color: var(--muted);
  max-width: 220px;
}
.spotlight-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  background: var(--panel-solid);
  border-top: 1px solid var(--line);
  gap: 6px;
}
.spotlight-prompt {
  font-size: 10.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}

/* Recent Strip */
.recent-strip-container {
  margin-bottom: 8px;
}
.recent-strip-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-bottom: 4px;
  padding: 0 2px;
}
.recent-thumbnails-strip {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.recent-thumb-item {
  width: 48px;
  height: 48px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--panel);
  flex: none;
  cursor: pointer;
  position: relative;
}
.recent-thumb-item.active {
  border-color: var(--accent);
}
.recent-thumb-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Compact Generator Card & Flyout Toolbar */
.compact-generator-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  margin-bottom: 10px;
}
.studio-flyout-toolbar {
  display: flex;
  gap: 5px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding: 1px 0 3px 0;
}
.flyout-trigger-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 9px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--panel-solid);
  color: var(--text-bright);
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
}
.flyout-trigger-pill:active {
  background: var(--panel-bright);
}
.pill-val {
  color: var(--text-bright);
  font-weight: 600;
}
.pill-arrow {
  font-size: 8px;
  color: var(--muted);
}

/* Stage Video Player Footer */
.stage-player-footer {
  padding: 6px 10px;
  background: var(--panel-solid);
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.stage-player-info {
  font-size: 10.5px;
  color: var(--muted);
}
.stage-player-btn-group {
  display: flex;
  gap: 4px;
}

.btn-studio-action {
  width: 100%;
  padding: 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--accent);
  color: #121214;
  font-size: 13.5px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  transition: opacity 0.15s ease;
}
.btn-studio-action:active {
  opacity: 0.85;
}

/* Music Visualizer */
.music-visualizer-box {
  background: var(--surface-inverse);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  padding: 8px 10px;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.music-canvas {
  width: 100%;
  height: 80px;
}
.synth-status-pill {
  font-size: 10.5px;
  color: var(--muted);
  margin-top: 4px;
}
.music-actions-row {
  display: flex;
  gap: 6px;
  margin-top: 4px;
}
.btn-danger-action {
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  border: none;
  background: var(--red);
  color: var(--text-bright);
  font-size: 13px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  cursor: pointer;
}

/* ------------------------------------------------------------------ *
 * VIEW 5: Coding & Artifacts Studio
 * ------------------------------------------------------------------ */

.coding-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  gap: 6px;
}
.coding-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
}
.code-tab-btn {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}
.code-tab-btn.active {
  background: var(--panel-solid);
  border-color: var(--line);
  color: var(--text-bright);
}
.select-pill {
  background: var(--panel-solid);
  border: 1px solid var(--line);
  color: var(--text-bright);
  font-size: 11.5px;
  border-radius: var(--radius-pill);
  padding: 4px 8px;
  outline: none;
}

.coding-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.code-editor-wrap {
  flex: 1;
  display: flex;
  padding: 10px;
  background: var(--surface-inverse);
}
.code-editor {
  flex: 1;
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.45;
  color: #ececec;
  white-space: pre;
}
.code-actions-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  background: var(--panel-solid);
  border-top: 1px solid var(--line);
  overflow-x: auto;
}
.code-action-btn {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--text);
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}
.code-action-btn.highlight {
  background: var(--panel-bright);
  border-color: var(--line-strong);
  color: var(--text-bright);
}

.artifacts-grid {
  padding: 12px 10px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 8px;
}
.artifact-card-item {
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--panel);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  cursor: pointer;
}
.artifact-card-item:active {
  background: var(--panel-solid);
}
.empty-state-box {
  grid-column: 1 / -1;
  text-align: center;
  padding: 32px 12px;
  color: var(--muted);
}
.empty-icon {
  width: 40px;
  height: 40px;
  margin-bottom: 8px;
  color: var(--faint);
}
.empty-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-bright);
  margin-bottom: 2px;
}
.empty-desc {
  font-size: 11.5px;
  line-height: 1.4;
  max-width: 260px;
  margin: 0 auto;
}

/* ------------------------------------------------------------------ *
 * VIEW 6: Desktop IDE Remote
 * ------------------------------------------------------------------ */

.ide-container {
  padding: 12px 10px;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ide-status-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px;
  border-radius: var(--radius-sm);
  background: var(--panel);
  border: 1px solid var(--line);
}
.ide-status-indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--faint);
}
.ide-status-indicator.connected {
  background: var(--emerald);
}
.ide-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-bright);
}
.ide-subtitle {
  font-size: 10.5px;
  color: var(--muted);
}

.ide-terminal-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--surface-inverse);
  overflow: hidden;
}
.term-header {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 6px 10px;
  background: var(--panel-solid);
  border-bottom: 1px solid var(--line);
}
.term-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.term-dot.red { background: #ef4444; }
.term-dot.yellow { background: #eab308; }
.term-dot.green { background: #22c55e; }
.term-title {
  margin-left: 4px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--muted);
}
.terminal-body {
  flex: 1;
  padding: 10px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}
.term-line.prompt { color: var(--accent); }
.term-input-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: var(--panel-solid);
  border-top: 1px solid var(--line);
}
.term-input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  font-family: inherit;
  font-size: 12px;
  color: var(--text-bright);
}
.term-run-btn {
  padding: 3px 10px;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--text-bright);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

/* ------------------------------------------------------------------ *
 * VIEW 7: API Keys & Settings
 * ------------------------------------------------------------------ */

.settings-container {
  width: 100%;
  padding: 14px 12px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.settings-header {
  margin-bottom: 2px;
}
.settings-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--text-bright);
}
.settings-subtitle {
  font-size: 11.5px;
  color: var(--muted);
}

.settings-card {
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.provider-badge {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-bright);
}
.key-status-chip {
  font-size: 10px;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: var(--raised);
  color: var(--faint);
}
.key-status-chip.set {
  background: var(--raised);
  color: var(--emerald);
  border: 1px solid var(--line);
}
.card-desc {
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.35;
}
.input-group {
  display: flex;
  align-items: center;
  gap: 5px;
  position: relative;
}
.key-input {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 7px 9px;
  font-size: 12.5px;
  color: var(--text-bright);
  outline: none;
  font-family: inherit;
}
.key-input:focus { border-color: var(--line-strong); }
.btn-toggle-vis {
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 13px;
  padding: 3px;
}
.card-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}
.btn-save {
  flex: 1;
  padding: 7px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--accent);
  color: #121214;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.btn-fetch {
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--text);
  font-size: 12px;
  cursor: pointer;
}
.models-tag-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
}
.model-tag {
  font-size: 10px;
  padding: 2px 5px;
  border-radius: 3px;
  background: var(--raised);
  color: var(--muted);
  border: 1px solid var(--line);
}
.provider-badge.quanta-sota {
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.2), rgba(139, 92, 246, 0.2));
  color: var(--accent);
  border: 1px solid rgba(56, 189, 248, 0.35);
}

.gateway-mode-selector, .drawer-mode-toggle {
  display: flex;
  background: rgba(var(--surf-18), 0.8);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 3px;
  gap: 4px;
  margin-bottom: 12px;
}

.drawer-gateway-toggle-wrap {
  padding: 0 12px 8px 12px;
}

.gateway-tab, .drawer-mode-btn {
  flex: 1;
  padding: 8px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  background: transparent;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.18s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.gateway-tab.active, .drawer-mode-btn.active {
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.25), rgba(139, 92, 246, 0.25));
  color: var(--accent);
  border: 1px solid rgba(56, 189, 248, 0.5);
  box-shadow: 0 2px 8px rgba(56, 189, 248, 0.2);
}

.input-label {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 6px;
}

.gateway-config-section {
  display: flex;
  flex-direction: column;
}

.quanta-cluster-info-box {
  background: rgba(var(--surf-7), 0.7);
  border: 1px solid rgba(56, 189, 248, 0.2);
  border-radius: var(--radius-sm);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}

.cluster-stat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 11.5px;
}
.cluster-stat-row .stat-label {
  color: var(--muted);
}
.cluster-stat-row .stat-val {
  color: var(--text-bright);
  font-weight: 600;
  font-family: var(--font-mono);
}

.speed-tier-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 6px;
}

.speed-tier-item {
  background: rgba(var(--surf-18), 0.6);
  border: 1px solid rgba(var(--lift-rgb), calc(0.07 * var(--lift-k)));
  border-radius: var(--radius-sm);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.tier-top {
  display: flex;
  align-items: center;
  gap: 8px;
}

.tier-badge {
  font-size: 9.5px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 4px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* Guest lock in the model picker: a model that needs a free account. */
.tier-badge.locked-badge {
  background: rgba(148, 163, 184, 0.16);
  color: var(--muted, var(--muted));
  border: 1px solid var(--line, rgba(var(--lift-rgb), calc(0.12 * var(--lift-k))));
  text-transform: none;
  letter-spacing: 0;
}
.sheet-item.locked {
  opacity: 0.72;
}
.sheet-item.locked:hover {
  opacity: 1;
}
.tier-badge.flash {
  background: rgba(56, 189, 248, 0.2);
  color: var(--accent);
  border: 1px solid rgba(56, 189, 248, 0.4);
}
.tier-badge.pro {
  background: rgba(168, 85, 247, 0.2);
  color: var(--violet);
  border: 1px solid rgba(168, 85, 247, 0.4);
}
.tier-badge.frontier {
  background: rgba(236, 72, 153, 0.2);
  color: #f472b6;
  border: 1px solid rgba(236, 72, 153, 0.4);
}
.tier-badge.vision {
  background: rgba(16, 185, 129, 0.2);
  color: var(--green);
  border: 1px solid rgba(16, 185, 129, 0.4);
}
/* MASS is an experiment and the badge should say so — amber, not a tier colour. */
.tier-badge.beta {
  background: rgba(245, 158, 11, 0.18);
  color: #fbbf24;
  border: 1px solid rgba(245, 158, 11, 0.4);
}

.tier-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-bright);
}

.tier-speed {
  margin-left: auto;
  font-size: 11px;
  color: var(--green);
  font-weight: 600;
  font-family: var(--font-mono);
}

.tier-desc {
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.4;
}

.pref-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.pref-row label {
  font-size: 12px;
  color: var(--muted);
}

/* ------------------------------------------------------------------ *
 * Full-Screen Artifact Panel (Slide-over)
 * ------------------------------------------------------------------ */

.artifact-panel {
  position: fixed;
  inset: 0;
  background: var(--surface-inverse);
  z-index: 135;
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.24s cubic-bezier(0.16, 1, 0.3, 1);
  padding-top: env(safe-area-inset-top);
}
.artifact-panel.open {
  transform: translateY(0);
}
.artifact-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  gap: 6px;
}
.artifact-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 1;
}
.artifact-icon {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  background: var(--raised);
  color: var(--text-bright);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.artifact-titles {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.artifact-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.artifact-lang-badge {
  font-size: 9.5px;
  color: var(--muted);
  font-weight: 500;
}
.artifact-tabs {
  display: flex;
  gap: 3px;
  background: var(--raised);
  padding: 2px;
  border-radius: var(--radius-sm);
}
.art-tab {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 4px 8px;
  border-radius: 5px;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
}
.art-tab.active {
  background: var(--panel-solid);
  color: var(--text-bright);
}
.artifact-controls {
  display: flex;
  align-items: center;
  gap: 5px;
}
.artifact-body {
  flex: 1;
  position: relative;
  overflow: hidden;
}
.art-pane {
  position: absolute;
  inset: 0;
  overflow: auto;
}
.art-pre {
  height: 100%;
  padding: 12px;
  background: var(--surface-inverse);
}
.sandbox-iframe {
  width: 100%;
  height: 100%;
  border: none;
  background: #ffffff;
}

/* ------------------------------------------------------------------ *
 * QUANTA CANVAS — the panel docked beside the chat (2026-09-02)
 *
 * The full-screen sheet above is the phone shape. On a wide screen the
 * panel takes `.docked`: it becomes a right-hand column of --canvas-w, the
 * chat and the composer slide left to make room (body.canvas-open), and a
 * drag handle on its left edge sets the width. The game runs full height
 * while the conversation continues beside it.
 * ------------------------------------------------------------------ */
:root { --canvas-w: 46vw; }

.artifact-panel.docked {
  inset: 0 0 0 auto;
  width: var(--canvas-w);
  min-width: 380px;
  max-width: 78vw;
  transform: translateX(100%);
  border-left: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  box-shadow: -18px 0 48px rgba(var(--shade-rgb), calc(0.35 * var(--shadow-k)));
  z-index: 60;                       /* under the drawer and every modal, above the thread */
}
.artifact-panel.docked.open { transform: translateX(0); }

/* The chat column makes room. Padding, not width: the thread stays centred
   inside what is left, and the composer's dock follows it. */
body.canvas-open #view-chat,
body.canvas-open #composer {
  padding-right: var(--canvas-w);
  transition: padding-right 0.24s cubic-bezier(0.16, 1, 0.3, 1);
}
body.canvas-resizing #view-chat,
body.canvas-resizing #composer { transition: none; }
body.canvas-resizing { cursor: col-resize; user-select: none; }
body.canvas-resizing .sandbox-iframe { pointer-events: none; }  /* the iframe would eat the drag */

.art-resize {
  display: none;
  position: absolute;
  left: -4px;
  top: 0;
  bottom: 0;
  width: 9px;
  cursor: col-resize;
  z-index: 3;
}
.art-resize::after {
  content: '';
  position: absolute;
  left: 3px;
  top: 50%;
  width: 3px;
  height: 44px;
  margin-top: -22px;
  border-radius: 3px;
  background: rgba(var(--lift-rgb), calc(0.22 * var(--lift-k)));
  transition: background 0.16s;
}
.art-resize:hover::after,
body.canvas-resizing .art-resize::after { background: var(--cyan); }
.artifact-panel.docked .art-resize { display: block; }

.art-expand.active { color: var(--cyan); }

/* The Code tab's file switcher for a project. */
.art-file-tabs {
  display: flex;
  gap: 4px;
  padding: 6px 12px 0;
  overflow-x: auto;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
.art-file-tab {
  border: 1px solid var(--line);
  border-bottom: none;
  background: transparent;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 5px 10px;
  border-radius: 7px 7px 0 0;
  cursor: pointer;
  white-space: nowrap;
}
.art-file-tab.active { background: var(--panel-solid); color: var(--text-bright); }

/* The card in the thread that stands for an artifact on desktop: the app
   itself runs in the Canvas, so the bubble stays light. */
.artifact-card {
  margin: 10px 0 6px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--panel);
  overflow: hidden;
}
.artifact-card-main {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  border: none;
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  font: inherit;
}
.artifact-card-main:hover { background: var(--raised); }
.artifact-card-icon {
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, rgba(34, 211, 238, 0.18), rgba(168, 85, 247, 0.18));
  color: var(--cyan);
}
.artifact-card-icon .ico { width: 18px; height: 18px; }
.artifact-card-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.artifact-card-title { font-size: 13.5px; font-weight: 600; color: var(--text-bright); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.artifact-card-meta { font-size: 11px; color: var(--muted); }
.artifact-card-open {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 11px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: var(--raised);
  color: var(--text-bright);
  font-size: 11.5px;
  font-weight: 600;
}
.artifact-card-open .ico { width: 12px; height: 12px; }
.artifact-card-actions {
  display: flex;
  gap: 6px;
  padding: 0 14px 10px;
}

/* A conversation with a reply being written in it, in the drawer: a small
   pulsing dot after the title, so switching away never means losing track. */
.chat-history-item.live .chat-title-text::after {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 7px;
  border-radius: 50%;
  background: var(--cyan);
  vertical-align: middle;
  animation: genLivePulse 1.6s ease-in-out infinite;
}

/* The live wait counter beside the model name: counts up from send until
   the reply settles into its exact figure. Tabular digits so it does not
   jitter; a soft pulse so it reads as "alive", not stuck. */
.msg-gen-time.live {
  font-variant-numeric: tabular-nums;
  color: var(--cyan);
  animation: genLivePulse 1.6s ease-in-out infinite;
}
@keyframes genLivePulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .msg-gen-time.live { animation: none; opacity: 0.85; }
}

/* ------------------------------------------------------------------ *
 * Composer & Docked Model Selection Bar (Liquid Glass Dock)
 * ------------------------------------------------------------------ */

.composer {
  position: relative;
  z-index: 10;
  width: 100%;
  padding: 8px 14px;
  padding-bottom: max(14px, calc(8px + env(safe-area-inset-bottom)));
  background: var(--glass-dock);
  backdrop-filter: var(--glass-blur-heavy);
  -webkit-backdrop-filter: var(--glass-blur-heavy);
  border-top: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  box-shadow: 0 -8px 32px rgba(var(--shade-rgb), calc(0.45 * var(--shadow-k)));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  flex: none;
}

.composer-control-dock,
.input-row,
.attachments {
  width: 100%;
  max-width: var(--thread-max);
}

.composer-control-dock {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 2px;
}

/* Floating Model Selector Pill */
.model-pill {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(var(--lift-rgb), calc(0.1 * var(--lift-k)));
  background: rgba(var(--surf-33), 0.6);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-specular);
  color: var(--text-bright);
  cursor: pointer;
  transition: all 0.16s cubic-bezier(0.16, 1, 0.3, 1);
  flex: 1;
  min-width: 0;
  max-width: 220px;
}
.model-pill:hover {
  background: rgba(var(--surf-39), 0.75);
  border-color: var(--line-strong);
  box-shadow: var(--glass-specular-bright);
}
.model-pill:active {
  background: rgba(var(--surf-45), 0.9);
  transform: scale(0.98);
}
.pill-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--cyan);
}
.pill-badge .ico { width: 13px; height: 13px; }
.pill-name {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  text-align: left;
  letter-spacing: -0.01em;
}
.pill-caret {
  width: 12px;
  height: 12px;
  color: var(--muted);
  flex: none;
}

.dock-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}
.dock-chip {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--surf-33), 0.5);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.16s cubic-bezier(0.16, 1, 0.3, 1);
}
.dock-chip:hover {
  color: var(--text-bright);
  background: rgba(var(--surf-43), 0.7);
  border-color: rgba(var(--lift-rgb), calc(0.15 * var(--lift-k)));
}
.dock-chip:active,
.dock-chip.active {
  color: var(--text-bright);
  border-color: var(--line-strong);
  background: rgba(var(--surf-46), 0.85);
  box-shadow: 0 0 12px rgba(56, 189, 248, 0.2);
}
.dock-chip .ico { width: 12px; height: 12px; }

.input-row {
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(var(--surf-27), 0.7);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(var(--lift-rgb), calc(0.1 * var(--lift-k)));
  box-shadow: inset 0 1px 0 rgba(var(--gloss-rgb), 0.08), 0 4px 16px rgba(var(--shade-rgb), calc(0.25 * var(--shadow-k)));
  border-radius: var(--radius-lg);
  padding: 4px 8px;
  min-height: 44px;
  transition: all 0.18s cubic-bezier(0.16, 1, 0.3, 1);
}
.input-row:focus-within {
  border-color: var(--line-strong);
  background: rgba(var(--surf-37), 0.85);
  box-shadow: inset 0 1px 0 rgba(var(--gloss-rgb), 0.15), 0 0 16px rgba(56, 189, 248, 0.18);
}

textarea#input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  font-family: inherit;
  font-size: 14.5px;
  line-height: 20px;
  color: var(--text-bright);
  min-height: 20px;
  max-height: 120px;
  padding: 2px 4px;
  align-self: center;
}
textarea#input::placeholder {
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.attachments {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 2px 4px 2px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.attachment-chip {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 6px 3px 3px;
  border-radius: 6px;
  background: var(--raised);
  border: 1px solid var(--line);
  font-size: 11px;
  color: var(--text);
  flex: none;
}
.attachment-thumb {
  width: 22px;
  height: 22px;
  border-radius: 3px;
  object-fit: cover;
}
.attachment-name {
  max-width: 100px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.attachment-remove {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 2px;
  display: flex;
  align-items: center;
}
.attachment-remove:active { color: var(--red); }

.round {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
  transition: opacity 0.12s ease;
}
.round.ghost {
  background: transparent;
  color: var(--muted);
}
.round.ghost:active {
  background: var(--raised);
  color: var(--text-bright);
}
.round.ghost.listening {
  color: var(--text-bright);
  background: var(--red);
}

.round.voice-waveform-btn {
  background: rgba(56, 189, 248, 0.1);
  border: 1px solid rgba(56, 189, 248, 0.35);
  color: var(--accent);
  transition: all 0.15s ease;
}
.round.voice-waveform-btn:hover {
  background: rgba(56, 189, 248, 0.2);
  border-color: rgba(56, 189, 248, 0.6);
  box-shadow: 0 0 10px rgba(56, 189, 248, 0.3);
}
.round.voice-waveform-btn:active {
  transform: scale(0.92);
  background: var(--accent-gradient);
  color: var(--on-accent);
}
.waveform-ico {
  width: 16px;
  height: 16px;
  stroke-width: 2.2;
}

.round.send {
  background: var(--accent-gradient);
  color: var(--on-accent);
  box-shadow: 0 3px 12px rgba(56, 189, 248, 0.35);
  transition: transform 0.15s ease, filter 0.15s ease;
}
.round.send:active {
  filter: brightness(1.15);
  transform: scale(0.92);
}

/* Universal Bottom Sheet (Liquid Frosted Glass Modal) */
.scrim {
  position: fixed;
  inset: 0;
  background: rgba(var(--shade-rgb), 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 120;
}
.sheet {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  max-height: 82vh;
  background: rgba(var(--surf-17), 0.92);
  backdrop-filter: var(--glass-blur-heavy);
  -webkit-backdrop-filter: var(--glass-blur-heavy);
  border-radius: 24px 24px 0 0;
  border-top: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  box-shadow: 0 -12px 48px rgba(var(--shade-rgb), calc(0.7 * var(--shadow-k)));
  z-index: 130;
  display: flex;
  flex-direction: column;
  padding: 14px 16px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  contain: content;
}

.sheet-grip {
  /* Grip removed — the switcher is a centered modal now, not a drag-up bottom
     sheet, so it needs no handle (and this handle doubled with .sheet::before,
     the "two bars" bug). */
  display: none;
}
.sheet-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-bright);
  margin-bottom: 6px;
}
.sheet-subtitle-meta {
  font-size: 11.5px;
  color: var(--muted);
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
}
.sheet-body {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.sheet-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--surf-31), 0.5);
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
  transition: all 0.16s cubic-bezier(0.16, 1, 0.3, 1);
}
.sheet-item:hover {
  background: rgba(var(--surf-41), 0.7);
  border-color: rgba(var(--lift-rgb), calc(0.15 * var(--lift-k)));
}
.sheet-item:active, .sheet-item.active {
  border-color: var(--line-strong);
  background: rgba(var(--surf-47), 0.85);
  box-shadow: 0 0 16px rgba(56, 189, 248, 0.25);
  color: var(--text-bright);
}

.sheet-item-left {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
}
.sheet-item-icon {
  font-size: 16px;
  flex: none;
}
.sheet-item-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-bright);
}
.sheet-item-desc {
  font-size: 11px;
  color: var(--muted);
}

.toast {
  position: fixed;
  bottom: calc(75px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  background: #27272a;
  border: 1px solid var(--line-strong);
  color: var(--text-bright);
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  box-shadow: 0 4px 16px rgba(var(--shade-rgb), calc(0.5 * var(--shadow-k)));
  z-index: 150;
  pointer-events: none;
}

.nav-badge.live {
  background: rgba(239, 68, 68, 0.22);
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.4);
  font-weight: 700;
  letter-spacing: 0.05em;
}

/* ------------------------------------------------------------------ *
 * Full-Screen Morphing Live Voice Stage (SOTA Ambient Experience)
 * ------------------------------------------------------------------ */

.live-voice-stage {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  padding: calc(16px + env(safe-area-inset-top)) 20px calc(24px + env(safe-area-inset-bottom)) 20px;
  background: #040711;
  overflow: hidden;
  opacity: 1;
  transform: scale(1);
  transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1), transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
  user-select: none;
}

.live-voice-stage.hidden {
  display: none !important;
  opacity: 0;
  transform: scale(0.96);
  pointer-events: none;
}
.live-voice-stage.hidden * {
  animation: none !important;
}

.voice-backdrop {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
  background: #02040a;
}

/* Wandering Blue Flashlight in the Dark - 60 FPS GPU Accelerated */
.flashlight-beam-wrap {
  position: absolute;
  inset: 0;
  overflow: hidden;
  transform: translateZ(0);
  pointer-events: none;
}

.flashlight-blue-beam {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 440px;
  height: 440px;
  margin-top: -220px;
  margin-left: -220px;
  border-radius: 50%;
  background: radial-gradient(circle at center, rgba(56, 189, 248, 0.55) 0%, rgba(37, 99, 235, 0.25) 40%, rgba(14, 165, 233, 0.08) 65%, transparent 80%);
  animation: flashlightSearchGpu 18s ease-in-out infinite alternate;
  pointer-events: none;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

.flashlight-secondary-orb {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 320px;
  height: 320px;
  margin-top: -160px;
  margin-left: -160px;
  border-radius: 50%;
  background: radial-gradient(circle at center, rgba(168, 85, 247, 0.35) 0%, rgba(56, 189, 248, 0.15) 50%, transparent 75%);
  animation: secondaryBeamWanderGpu 24s ease-in-out infinite alternate;
  pointer-events: none;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

@keyframes flashlightSearchGpu {
  0% { transform: translate3d(-35vw, -30vh, 0) scale(1); }
  25% { transform: translate3d(25vw, 15vh, 0) scale(1.2); }
  50% { transform: translate3d(30vw, -18vh, 0) scale(0.95); }
  75% { transform: translate3d(-25vw, 22vh, 0) scale(1.15); }
  100% { transform: translate3d(10vw, -20vh, 0) scale(1.05); }
}

@keyframes secondaryBeamWanderGpu {
  0% { transform: translate3d(28vw, 25vh, 0) scale(1); }
  33% { transform: translate3d(-28vw, -12vh, 0) scale(1.15); }
  66% { transform: translate3d(12vw, -25vh, 0) scale(0.9); }
  100% { transform: translate3d(-15vw, 18vh, 0) scale(1.1); }
}

/* Frosted Glass Scrim - Pure Hardware Accelerated Gradients */
.voice-frosted-glass-scrim {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at center, rgba(56, 189, 248, 0.05) 0%, rgba(var(--surf-1), 0.82) 85%);
  box-shadow: inset 0 0 80px rgba(56, 189, 248, 0.04), inset 0 1px 0 rgba(var(--gloss-rgb), 0.15);
  transform: translateZ(0);
  pointer-events: none;
}

.voice-vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, transparent 40%, #02040a 98%);
  pointer-events: none;
  transform: translateZ(0);
}

.voice-top-bar {
  position: relative;
  z-index: 10;
  width: 100%;
  max-width: 480px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  transform: translateZ(0);
}

/* A wordmark, not a chip. The capsule, its fill and its border were chrome
   drawn around two words on an otherwise empty field - the frame was doing
   more work than the thing it framed. What is left is the live dot, which
   carries actual meaning, and the name. */
.voice-model-pill {
  display: inline-flex;
  align-items: center;
  gap: 0;
  padding: 0;
  border: none;
  background: none;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.voice-model-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #38bdf8;
  box-shadow: 0 0 7px #38bdf8;
  /* The one part that is not decoration: it says the session is live. */
  animation: voiceLiveDot 2.6s ease-in-out infinite;
}

@keyframes voiceLiveDot {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}

.voice-status-text {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: color 0.25s ease;
}

.voice-status-text.listening { color: var(--accent); text-shadow: 0 0 8px rgba(56, 189, 248, 0.4); }
.voice-status-text.speaking { color: var(--violet); text-shadow: 0 0 8px rgba(192, 132, 252, 0.4); }
.voice-status-text.thinking { color: var(--indigo); text-shadow: 0 0 8px rgba(129, 140, 248, 0.4); }
.voice-status-text.muted { color: var(--faint); text-shadow: none; }

.icon-btn-glass {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.15 * var(--lift-k)));
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.2s ease;
}

.icon-btn-glass:hover {
  background: rgba(var(--lift-rgb), calc(0.16 * var(--lift-k)));
  transform: scale(1.05);
}

.icon-btn-glass:active {
  transform: scale(0.95);
}

/* Center Stage: Living Quantum Logo & Breathing Orb */
.voice-center-stage {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  width: 100%;
  max-width: 420px;
  transform: translateZ(0);
}

/* THE LIVING QUANTA ORB canvas — full-bleed and SCREEN-BLENDED. The shader
   renders the orb (no nebula) on black; mix-blend-mode:screen makes black read
   as transparent and adds the glowing orb over the app's own static backdrop,
   which shows through unchanged. So the orb floats on the background we already
   had — no nebula, no double background. Painted after the backdrop (blend
   lands on it); the captions/controls float above. */
.voice-orb-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;
  mix-blend-mode: screen;
}

/* Pulsing Acoustic Rings - 100% GPU Transform */
.voice-ripple-ring {
  position: absolute;
  border-radius: 50%;
  border: 1px solid rgba(56, 189, 248, 0.3);
  pointer-events: none;
  opacity: 0;
  will-change: transform, opacity;
  transform: translateZ(0);
}

.voice-ripple-ring.ring-1 {
  width: 160px;
  height: 160px;
  animation: rippleOutGpu 3.2s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}

.voice-ripple-ring.ring-2 {
  width: 160px;
  height: 160px;
  animation: rippleOutGpu 3.2s cubic-bezier(0.16, 1, 0.3, 1) infinite 1.1s;
}

.voice-ripple-ring.ring-3 {
  width: 160px;
  height: 160px;
  animation: rippleOutGpu 3.2s cubic-bezier(0.16, 1, 0.3, 1) infinite 2.2s;
}

@keyframes rippleOutGpu {
  0% { transform: scale(0.85) translateZ(0); opacity: 0.65; border-color: rgba(56, 189, 248, 0.6); }
  60% { opacity: 0.25; }
  100% { transform: scale(1.9) translateZ(0); opacity: 0; border-color: rgba(168, 85, 247, 0); }
}

/* Outer Fluid Plasma Halo - Ultra-Smooth GPU Layers */
.voice-fluid-halo {
  position: absolute;
  width: 190px;
  height: 190px;
  border-radius: 50%;
  pointer-events: none;
  transform: translateZ(0);
}

.orb-lobe {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  opacity: 0.55;
  will-change: transform;
  transform: translateZ(0);
}

.orb-lobe.lobe-1 {
  background: radial-gradient(circle at 35% 35%, rgba(56, 189, 248, 0.4) 0%, transparent 60%);
  animation: lobeRotateGpu1 16s linear infinite;
}

.orb-lobe.lobe-2 {
  background: radial-gradient(circle at 65% 65%, rgba(129, 140, 248, 0.35) 0%, transparent 60%);
  animation: lobeRotateGpu2 20s linear infinite reverse;
}

.orb-lobe.lobe-3 {
  background: radial-gradient(circle at 50% 25%, rgba(168, 85, 247, 0.3) 0%, transparent 60%);
  animation: lobeRotateGpu3 14s linear infinite;
}

@keyframes lobeRotateGpu1 {
  0% { transform: rotate(0deg) translateZ(0); }
  100% { transform: rotate(360deg) translateZ(0); }
}

@keyframes lobeRotateGpu2 {
  0% { transform: rotate(0deg) translateZ(0); }
  100% { transform: rotate(-360deg) translateZ(0); }
}

@keyframes lobeRotateGpu3 {
  0% { transform: rotate(0deg) translateZ(0); }
  100% { transform: rotate(360deg) translateZ(0); }
}

/* The dashed and dotted rings that used to sit here are gone. They were a
   generic orbit - they belonged to no particular mark, and they read as chrome
   around the logo rather than as part of it. The spiral system further down
   replaces them with the logo's own gesture. */

/* The Living Animated Quantum Logo Orb */
.voice-living-logo-orb {
  position: relative;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  /* NO HARD EDGE. There was a 2px ring here and an `overflow: hidden` circle
     behind it, and between them they sliced straight through the logo's spiral
     arms - a boundary drawn across somebody's mark. The artwork supplies its
     own edge now, faded rather than cut, and the glow is cast around it
     instead of being fenced in by it. */
  background: none;
  border: none;
  box-shadow: 0 0 46px rgba(56, 189, 248, 0.28);
  overflow: visible;
  cursor: pointer;
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.3s ease;
  will-change: transform;
  transform: translateZ(0);
}

.voice-living-logo-orb.speaking {
  box-shadow: 0 0 40px rgba(168, 85, 247, 0.6), inset 0 0 20px rgba(var(--gloss-rgb), 0.3);
  animation: logoSpeakingBreath 1.6s ease-in-out infinite alternate;
}

.voice-living-logo-orb.listening {
  box-shadow: 0 0 30px rgba(56, 189, 248, 0.5), inset 0 0 16px rgba(56, 189, 248, 0.25);
  animation: logoListeningDrift 3.5s ease-in-out infinite alternate;
}

.voice-living-logo-orb.thinking {
  box-shadow: 0 0 35px rgba(129, 140, 248, 0.6);
  animation: logoThinkingSpin 1.4s linear infinite;
}

.voice-living-logo-orb.muted {
  box-shadow: 0 0 12px rgba(100, 116, 139, 0.2);
  filter: grayscale(0.85) opacity(0.5);
}

@keyframes logoSpeakingBreath {
  0% { transform: scale(1.02) translateZ(0); }
  100% { transform: scale(1.12) translateZ(0); }
}

@keyframes logoListeningDrift {
  0% { transform: scale(1) translateZ(0); }
  100% { transform: scale(1.05) translateZ(0); }
}

@keyframes logoThinkingSpin {
  0% { transform: scale(1.04) rotate(0deg) translateZ(0); }
  100% { transform: scale(1.04) rotate(360deg) translateZ(0); }
}

/* Living Logo Layers */
.living-logo-img-wrap {
  position: relative;
  width: 100%;
  height: 100%;
  /* Clipping here was the second half of the same problem. */
  overflow: visible;
  display: block;
}

/* Both layers occupy the same square and are the same artwork at the same
   size, so they register pixel for pixel. `contain`, not `cover`: `cover`
   cropped the spiral arms off at the edges of the box before the mask ever
   got a say. */
.living-logo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  filter: contrast(1.12) brightness(1.06);
}

/* THE SPIRAL, ALIVE. The purple arms the logo already has, turning.
   Slow on purpose - fast enough to read as living, slow enough that it never
   reads as a loading spinner. The mask fades the artwork out at its rim
   instead of a hard circle cutting through the arms. */
.living-logo-spiral {
  -webkit-mask-image: radial-gradient(circle closest-side, #000 62%, rgba(0, 0, 0, 0.72) 82%, transparent 99%);
  mask-image: radial-gradient(circle closest-side, #000 62%, rgba(0, 0, 0, 0.72) 82%, transparent 99%);
  animation: logoSpiralTurn 34s linear infinite;
}

@keyframes logoSpiralTurn {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* THE CORE, HELD STILL. Masked to the lattice sphere and the waveform running
   through it - the part of the mark that must never tilt. It breathes, which
   is motion the geometry survives. */
.living-logo-core {
  -webkit-mask-image: radial-gradient(circle closest-side, #000 47%, rgba(0, 0, 0, 0.6) 56%, transparent 68%);
  mask-image: radial-gradient(circle closest-side, #000 47%, rgba(0, 0, 0, 0.6) 56%, transparent 68%);
  animation: logoCoreBreathe 7.5s ease-in-out infinite alternate;
}

@keyframes logoCoreBreathe {
  0%   { transform: scale(1); }
  100% { transform: scale(1.045); }
}

.living-logo-specular-shimmer {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: linear-gradient(135deg, rgba(var(--lift-rgb), calc(0.45 * var(--lift-k))) 0%, transparent 40%, rgba(var(--lift-rgb), calc(0.28 * var(--lift-k))) 60%, transparent 100%);
  pointer-events: none;
  mix-blend-mode: overlay;
  animation: specularSweep 6s ease-in-out infinite;
}

@keyframes specularSweep {
  0%, 100% { opacity: 0.3; transform: scale(1) rotate(0deg); }
  50% { opacity: 0.8; transform: scale(1.05) rotate(180deg); }
}

.living-logo-chroma-glow {
  position: absolute;
  inset: -10%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(56, 189, 248, 0.4) 0%, rgba(168, 85, 247, 0.25) 50%, transparent 75%);
  pointer-events: none;
  mix-blend-mode: screen;
  animation: chromaBreathe 4s ease-in-out infinite alternate;
}

@keyframes chromaBreathe {
  0% { opacity: 0.4; transform: scale(0.95); }
  100% { opacity: 0.85; transform: scale(1.1); }
}

.living-equator-wave {
  position: absolute;
  left: 0;
  right: 0;
  height: 3px;
  top: 50%;
  transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, rgba(56, 189, 248, 0.85), #ffffff, rgba(168, 85, 247, 0.85), transparent);
  filter: blur(0.5px);
  box-shadow: 0 0 12px #38bdf8;
  opacity: 0.75;
  pointer-events: none;
  animation: equatorPulse 2.2s ease-in-out infinite alternate;
}

@keyframes equatorPulse {
  0% { opacity: 0.4; transform: translateY(-50%) scaleX(0.85); }
  100% { opacity: 0.95; transform: translateY(-50%) scaleX(1.15); box-shadow: 0 0 16px #38bdf8; }
}

.voice-canvas-wave {
  width: 280px;
  height: 50px;
  opacity: 0.9;
}

/* Floating Live Captions & Subtitles */
.voice-captions-container {
  position: relative;
  z-index: 10;
  width: 100%;
  max-width: 440px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 20px;
  max-height: min(46vh, 320px);
  overflow-y: auto;
  padding: 0 4px;
}

.voice-caption-user {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--accent);
  text-align: center;
  line-height: 1.4;
  padding: 8px 16px;
  border-radius: 14px;
  background: rgba(14, 165, 233, 0.12);
  border: 1px solid rgba(56, 189, 248, 0.3);
  min-height: 24px;
  display: none;
  box-shadow: 0 4px 16px rgba(var(--shade-rgb), calc(0.3 * var(--shadow-k)));
}

.voice-caption-user:not(:empty) {
  display: block;
  animation: fadeInCaption 0.25s ease;
}

@keyframes fadeInCaption {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* THE TRANSCRIPTION PILL IS GONE.
   Watching your own words appear in a box is a chat affordance, not a voice
   one - you already know what you just said, and it put a second pill on a
   screen that is meant to hold one thing. The element stays in the DOM because
   the halt state still writes to it and screen readers still read it; it is
   simply not drawn. */
.voice-caption-user { display: none !important; }

/* ==================================================================== *
 * THE LEVEL LIVES IN THE MUTE BUTTON
 *
 * The waveform strip under the orb was a second thing to look at, saying what
 * the orb's bloom already said. The level belongs ON the control it concerns:
 * the button that decides whether Quanta can hear you now shows how much it
 * is hearing.
 *
 * Built as a gradient with a moving colour stop rather than a resized element,
 * so nothing lays out on any frame - only the paint changes. The stop is
 * driven by --voice-level, which the analyser publishes, and the circle is cut
 * by border-radius rather than `overflow: hidden`, because hidden would clip
 * the 44px hit area the mobile rules extend beyond the button.
 * ==================================================================== */

#btn-voice-mute {
  position: relative;
  isolation: isolate;
  overflow: visible;
}

#btn-voice-mute::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  z-index: 0;
  pointer-events: none;
  /* Deep water below, a bright crest exactly at the waterline, nothing above. */
  background: linear-gradient(
    to top,
    rgba(129, 140, 248, 0.55) 0%,
    rgba(56, 189, 248, 0.72) calc(var(--voice-level, 0) * 92% - 8%),
    rgba(224, 242, 254, 0.98) calc(var(--voice-level, 0) * 92%),
    transparent calc(var(--voice-level, 0) * 92% + 1%)
  );
  /* Fast enough to feel like your voice, slow enough not to strobe on the
     analyser's frame-to-frame jitter. */
  transition: background 0.07s linear, box-shadow 0.12s linear;
  box-shadow: inset 0 0 12px rgba(56, 189, 248, calc(var(--voice-level, 0) * 0.5));
}

/* A quiet ring that swells with the loudest moments. */
#btn-voice-mute::after {
  box-shadow: 0 0 calc(4px + var(--voice-level, 0) * 14px)
              rgba(56, 189, 248, calc(var(--voice-level, 0) * 0.55));
  transition: box-shadow 0.12s linear;
}

/* Muted means not hearing you, so the water drains rather than lying at a
   level nothing is feeding. */
.live-voice-stage[data-voice-state="muted"] #btn-voice-mute::before,
.live-voice-stage.voice-typed-only #btn-voice-mute::before {
  background: none;
  box-shadow: none;
}

/* The icon rides above the water on every screen size, not just mobile. */
#btn-voice-mute > * {
  position: relative;
  z-index: 1;
}

/* The strip under the orb is retired: the level is on the control now. */
.voice-canvas-wave { display: none; }

/* The raw signal, quiet enough to ignore and precise enough to act on. */
.voice-diagnostic {
  font-family: var(--font-mono);
  font-size: 10.5px;
  line-height: 1.5;
  color: var(--faint);
  text-align: center;
  word-break: break-word;
  padding: 0 6px;
}
.voice-diagnostic:empty { display: none; }

.voice-caption-ai {
  font-size: 15px;
  font-weight: 500;
  color: #f8fafc;
  text-align: center;
  line-height: 1.45;
  letter-spacing: -0.01em;
  text-shadow: 0 2px 8px rgba(var(--shade-rgb), calc(0.6 * var(--shadow-k)));
  padding: 12px 18px;
  background: rgba(var(--surf-30), 0.75);
  border: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  border-radius: var(--radius);
  /* A fixed 100px cut the halt message off mid-sentence on a phone - the one
     screen where the sentence is the entire point, truncated to "...and Quanta
     will still speak the". The cap now scales with the window instead of
     assuming a desktop's line lengths. */
  max-height: min(38vh, 280px);
  overflow-y: auto;
  box-shadow: 0 8px 32px rgba(var(--shade-rgb), calc(0.4 * var(--shadow-k)));
}

/* Bottom Glass Control Dock */
.voice-controls-dock {
  position: relative;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  width: 100%;
  max-width: 380px;
  padding: 8px 16px;
  background: rgba(var(--lift-rgb), calc(0.06 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  border-radius: var(--radius-pill);
  backdrop-filter: blur(24px) saturate(180%);
  box-shadow: 0 8px 32px rgba(var(--shade-rgb), calc(0.5 * var(--shadow-k))), inset 0 1px 0 rgba(var(--gloss-rgb), 0.15);
}

.voice-control-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  background: rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.14 * var(--lift-k)));
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

.voice-control-btn:hover {
  background: rgba(var(--lift-rgb), calc(0.15 * var(--lift-k)));
  color: var(--text-bright);
  transform: translateY(-1px);
}

.voice-control-btn:active {
  transform: scale(0.95) translateY(0);
}

.voice-control-btn.active-muted {
  background: rgba(239, 68, 68, 0.22);
  border-color: rgba(239, 68, 68, 0.4);
  color: #fca5a5;
}

.voice-control-btn.btn-interrupt {
  background: rgba(168, 85, 247, 0.15);
  border-color: rgba(168, 85, 247, 0.3);
  color: #d8b4fe;
}

.voice-control-btn.btn-end-call {
  background: rgba(239, 68, 68, 0.18);
  border-color: rgba(239, 68, 68, 0.35);
  color: var(--red);
}

.voice-control-btn.btn-end-call:hover {
  background: rgba(239, 68, 68, 0.35);
  color: var(--text-bright);
}

/* ------------------------------------------------------------------ *
 * Desktop & Large Screen Optimizations (ChatGPT / Claude Web Style)
 * ------------------------------------------------------------------ */

@media (min-width: 768px) {
  .bar {
    padding: 10px 24px;
  }
  .thread {
    max-width: var(--thread-max);
    width: 100%;
    margin: 0 auto;
    padding: 24px 20px;
  }
  .msg.user {
    max-width: 75%;
  }
  .composer {
    padding: 10px 20px 14px 20px;
  }
  .composer-control-dock {
    max-width: var(--thread-max);
    width: 100%;
    margin: 0 auto;
  }
  .input-row {
    max-width: var(--thread-max);
    width: 100%;
    margin: 0 auto;
    padding: 6px 12px;
  }
  .attachments {
    max-width: var(--thread-max);
    width: 100%;
    margin: 0 auto;
  }
  .studio-container, .settings-container {
    max-width: 860px;
    width: 100%;
    margin: 0 auto;
  }
  .sheet {
    max-width: 560px;
    left: 50% !important;
    transform: translateX(-50%) !important;
    border-radius: 24px 24px 0 0;
    border-left: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
    border-right: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  }
}

@media (min-width: 1100px) {
  /* The chat column is no longer stepped by viewport - it is the user's
     choice now, and --thread-max already carries it. Studio and Settings
     are not the transcript and keep the old step. */
  .studio-container, .settings-container {
    max-width: 920px;
  }
}

/* ------------------------------------------------------------------ *
 * Quanta Sovereign Welcome Hero Stage (Center Experience)
 * ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ *
 * THE THOUGHT FIELD, BEHIND THE WELCOME SCREEN
 *
 * The same drifting field the Live Voice stage uses, reused rather than
 * rebuilt - one set of keyframes, one set of positions, two places it appears.
 * Rebuilding it here would have been the moment the two quietly diverged.
 *
 * Three differences, all deliberate:
 *   - It is ALWAYS on here. In the voice stage it only appears while Quanta is
 *     thinking, because there it means something. On the welcome screen it is
 *     ambience, so it simply lives there.
 *   - It runs quieter (0.5). The hero has a title, a tagline and four chips
 *     that people actually need to read; ambience that competes with them is
 *     not ambience.
 *   - The words are what the system DOES, not what it is doing right now -
 *     there is nothing being thought about yet.
 *
 * The bubbles are already positioned clear of the middle (see .tb-1..8), which
 * is exactly where the orb, the title and the pill sit. That was luck the
 * first time and is now load-bearing.
 * ------------------------------------------------------------------ */
.quanta-hero-stage { position: relative; }

.hero-thought-field {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  opacity: 0.5;
  transform: translateZ(0);
  /* Fades the field out towards the centre so nothing drifts under the title.
     A mask, not a gradient overlay: an overlay would also dim the hero. */
  -webkit-mask-image: radial-gradient(ellipse 46% 40% at 50% 46%, transparent 40%, #000 100%);
  mask-image: radial-gradient(ellipse 46% 40% at 50% 46%, transparent 40%, #000 100%);
}

/* Everything the person came to read sits above the field. */
.quanta-hero-stage > *:not(.hero-thought-field) {
  position: relative;
  z-index: 1;
}

.quanta-hero-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* Top padding was SMALLER than bottom, biasing the centred group upward so the
     emblem crowded the top. Bias it slightly top-heavy instead: the whole hero
     (emblem, wordmark, pill, chips) slides down and reads as centred in the field.
     Founder's call, 2026-08-31 — applies on web and mobile alike. */
  padding: clamp(56px, 10vh, 112px) 20px clamp(28px, 4vh, 48px);
  min-height: calc(100vh - 160px);
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.quanta-hero-stage.hidden {
  display: none !important;
}

.hero-orb-wrap {
  position: relative;
  width: 190px;
  height: 190px;
  margin: 0 auto 16px auto;
}
.hero-orb-glow {
  position: absolute;
  inset: -20px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(34, 211, 238, 0.45), rgba(139, 92, 246, 0.3), transparent 70%);
  filter: blur(16px);
  animation: heroPulse 3.5s ease-in-out infinite alternate;
}
.hero-orb-ring {
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 1.5px dashed rgba(34, 211, 238, 0.5);
  animation: heroRotate 18s linear infinite;
}
/* ⛔ A CLEAN HARD CIRCLE — founder's call, 2026-08.
 *
 * This was a soft radial mask, introduced to avoid clipping the heart's upper
 * lobes on the full-bleed square artwork. In practice the mask kept the
 * artwork's square EDGES fully opaque (it only dissolved the extreme corners),
 * so the emblem read as a squircle sitting inside the round dashed ring — "a
 * square in the circle". Compared side by side at size, a plain
 * `border-radius: 50%` reads unmistakably rounder AND the heart survives: the
 * heart sits within the inscribed circle, so all the circle removes are the
 * emblem's dark full-bleed corners. So the mask is gone and the hard circle is
 * back, deliberately.
 *
 * `.hero-orb-glow` behind this element is the halo; the old tight box-shadow is
 * not reinstated (the glow was always what carried it).
 */
.hero-orb-emblem {
  position: relative;
  z-index: 2;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  /* cover, not fill: centres the emblem and keeps it uncropped-in-aspect if the
     source ratio ever changes; today the source is square, so it is a no-op. */
  object-fit: cover;
  /* HIDDEN by default so the old mark never flashes before the orb mounts.
     It is only revealed when we know the orb cannot run (no WebGL / reduced
     motion), via the .orb-fallback class the boot sets in that case. */
  opacity: 0;
  transition: opacity 700ms var(--ease-out, ease-out);
}
.hero-orb-wrap.orb-fallback .hero-orb-emblem { opacity: 1; }

/* The living orb. It sits above the emblem and fades in once VoiceOrb mounts,
 * so the still emblem is what shows if WebGL is unavailable or motion is
 * reduced — no blank canvas, no flash of nothing.
 *
 * The engine renders the sphere on its own dark field (the canvas is opaque).
 * We want JUST the sphere, so a radial mask keeps the luminous core and
 * dissolves the outer field into the page — the `.hero-orb-glow` halo behind
 * carries the bloom, exactly as it did for the emblem. */
.hero-orb-live {
  position: absolute;
  /* The engine draws her at a fixed FRACTION of the canvas, so to make her
     BODY big we give the canvas room and crop into it: a large render centred
     over the small frame. Her dark-violet field lands out at the edges. */
  width: 520px;
  height: 420px;
  left: 50%;
  top: 47%;
  transform: translate(-50%, -50%);
  z-index: 3;
  opacity: 0;
  transition: opacity 1000ms var(--ease-out, ease-out);
  pointer-events: none;
  /* No mask, no blend — the canvas is genuinely transparent now (the shader
     outputs her luminance as alpha, so the field is real see-through and her
     glow fades to nothing on its own). She simply floats on the page. */
}
.hero-orb-wrap.orb-live .hero-orb-live { opacity: 1; }
.hero-orb-wrap.orb-live .hero-orb-emblem { opacity: 0; }

/* A living centre wants a softer, slower halo than the static mark — breath
 * around her, not a strobe. */
.hero-orb-wrap.orb-live .hero-orb-glow { animation-duration: 5.5s; opacity: 0.92; }

@media (prefers-reduced-motion: reduce) {
  /* Stillness for those who ask for it: never reveal the animated canvas.
     The boot takes the .orb-fallback path under reduced motion, so the still
     emblem is what shows. */
  .hero-orb-wrap .hero-orb-live { opacity: 0 !important; }
  .hero-orb-wrap.orb-fallback .hero-orb-emblem { opacity: 1 !important; }
}

@keyframes heroPulse {
  0% { transform: scale(0.9); opacity: 0.7; }
  100% { transform: scale(1.15); opacity: 1; }
}
@keyframes heroRotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* ------------------------------------------------------------------ *
 * SELF-VERIFICATION BAR (artifact panel)
 *
 * Three states and they must be visually unmistakable, because the whole
 * point is that a failure cannot be mistaken for a success:
 *   running  - neutral, no verdict claimed yet
 *   pass     - emerald
 *   fail     - amber, and the "Fix it" button appears beside it
 * ------------------------------------------------------------------ */
.art-verify-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-bottom: var(--line);
  background: rgba(var(--shade-rgb), 0.18);
  font-size: 12px;
  flex: none;
}
.art-verify-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.art-verify-bar[data-state="running"] .art-verify-status { color: var(--muted); }
.art-verify-bar[data-state="pass"] .art-verify-status { color: var(--emerald); }
.art-verify-bar[data-state="fail"] .art-verify-status { color: #fbbf24; }
.art-verify-detail {
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
.art-verify-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 8px;
  border: var(--line);
  background: transparent;
  color: var(--text);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  flex: none;
  transition: all 0.15s ease;
}
.art-verify-btn:hover {
  background: rgba(var(--shade-rgb), 0.4);
  color: var(--text-bright);
}
.art-verify-btn .ico { width: 12px; height: 12px; }
.art-verify-fix {
  border-color: rgba(251, 191, 36, 0.45);
  color: #fbbf24;
}
.art-verify-fix:hover {
  background: rgba(251, 191, 36, 0.14);
  color: #fbbf24;
}

/* ------------------------------------------------------------------ *
 * YOUTUBE CARD
 *
 * The thumbnail IS the content, so it gets the whole card. Nothing loads
 * from YouTube until the play button is pressed - see the click handler.
 * ------------------------------------------------------------------ */
.yt-card {
  margin: 10px 0 4px;
  border-radius: 14px;
  overflow: hidden;
  border: var(--line);
  background: rgba(var(--shade-rgb), 0.28);
  cursor: pointer;
  max-width: 520px;
  transition: border-color 0.2s ease, transform 0.2s ease;
}
.yt-card:hover {
  border-color: rgba(255, 0, 0, 0.4);
  transform: translateY(-2px);
}
/* The "show me videos" strip: a horizontal, snap-scrolling row of the same
   yt-cards. One video shows one card; several scroll sideways rather than
   stacking the whole reply into a tower of players. */
.web-videos-bar { margin-top: 10px; }
.web-videos-label {
  font-size: 10.5px;
  color: var(--muted);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 6px;
}
.web-videos-row {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 6px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.web-videos-row .yt-card {
  margin: 0;
  flex: 0 0 min(280px, 78vw);
  scroll-snap-align: start;
}
.yt-viewport {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  display: block;
}
.yt-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.yt-frame {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.yt-play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 60px;
  height: 42px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.yt-play-bg {
  fill: rgba(0, 0, 0, 0.72);
  transition: fill 0.2s ease;
}
.yt-card:hover .yt-play-bg { fill: #ff0000; }
.yt-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  padding: 3px 7px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.05em;
}
.yt-footer {
  padding: 9px 12px 11px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.yt-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-bright);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.yt-open {
  font-size: 11px;
  color: var(--muted);
  text-decoration: none;
}
.yt-open:hover { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .yt-card, .yt-card:hover { transition: none; transform: none; }
}

.hero-brand-title {
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-size: clamp(36px, 6vw, 54px);
  font-weight: 700;
  letter-spacing: 0.15em;
  /* First stop was a hard #ffffff — the first ~30% of "QUANTA" vanished on the
     light theme's pale ground. --text-bright is white in dark and near-black in
     light, so the wordmark stays legible in both. */
  background: linear-gradient(135deg, var(--text-bright) 30%, var(--cyan) 75%, var(--violet) 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  margin-bottom: 8px;
  line-height: 1.1;
}
.hero-brand-tagline {
  font-family: var(--font-mono);
  font-size: 15px;
  color: var(--text);
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}
/* Sovereign identity + attribution, demoted below the free/kind wedge so the
   first impression leads with what converts a stranger: free, instant, kind. */
.hero-brand-subtagline {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.1em;
  opacity: 0.85;
  margin-bottom: 34px;
}

/* Markdown headings inside chat replies (## / ### from parseMarkdownToHTML),
   which used to render as literal hashes. Modest sizes — this is a chat bubble,
   not a document. */
.md-heading { font-weight: 700; color: var(--text-bright); line-height: 1.3; margin: 12px 0 5px; }
.md-h1 { font-size: 1.32em; }
.md-h2 { font-size: 1.18em; }
.md-h3 { font-size: 1.07em; }
.md-h4, .md-h5, .md-h6 { font-size: 1em; color: var(--text); }

.hero-center-pill-wrap {
  width: 100%;
  max-width: 740px;
  margin: 0 auto 22px auto;
}
.hero-center-pill {
  display: flex;
  align-items: center;
  gap: 12px;
  background: rgba(var(--surf-28), 0.8);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  border: 1px solid rgba(var(--lift-rgb), calc(0.16 * var(--lift-k)));
  border-radius: 100px;
  padding: 8px 12px 8px 20px;
  box-shadow: 0 16px 48px rgba(var(--shade-rgb), calc(0.55 * var(--shadow-k))), 0 0 28px rgba(34, 211, 238, 0.16);
  transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.hero-center-pill:focus-within {
  border-color: var(--cyan);
  background: rgba(var(--surf-38), 0.92);
  box-shadow: 0 20px 60px rgba(var(--shade-rgb), calc(0.7 * var(--shadow-k))), 0 0 36px rgba(34, 211, 238, 0.32);
  transform: translateY(-2px);
}
.center-pill-sparkle {
  color: var(--cyan);
  display: flex;
  align-items: center;
  flex: none;
}
.center-pill-sparkle .ico {
  width: 20px;
  height: 20px;
}
#center-pill-input {
  flex: 1;
  min-width: 0;   /* let the input shrink so the send button stays INSIDE the pill */
  background: transparent;
  border: none;
  outline: none;
  font-family: inherit;
  font-size: 16px;
  color: var(--text-bright);
  line-height: 1.5;
}
#center-pill-input::placeholder {
  /* --dim was never defined, so the placeholder fell back to the input's own
     bright text colour and read almost like typed text on the app's most
     prominent field. --faint is the placeholder colour the main composer uses. */
  color: var(--faint);
}
.btn-center-pill-send {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;   /* never let the flex row squeeze it into a vertical oval */
  border-radius: 50%;
  background: linear-gradient(135deg, var(--cyan), var(--violet));
  border: none;
  color: #050609;
  display: grid;
  place-items: center;
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(34, 211, 238, 0.4);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.btn-center-pill-send:hover {
  transform: scale(1.05);
  box-shadow: 0 6px 22px rgba(34, 211, 238, 0.6);
}
.btn-center-pill-send .ico {
  width: 18px;
  height: 18px;
}

.hero-prompt-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  max-width: 780px;
  margin: 0 auto 30px auto;
}
.hero-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.1 * var(--lift-k)));
  border-radius: 100px;
  padding: 8px 18px;
  color: var(--muted);
  font-size: 13px;
  font-family: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
}
.hero-chip .chip-ico {
  width: 15px;
  height: 15px;
  color: var(--cyan);
  opacity: 0.85;
  flex: 0 0 auto;
}
.hero-chip:hover .chip-ico { opacity: 1; }
.hero-chip:hover {
  background: rgba(34, 211, 238, 0.12);
  border-color: rgba(34, 211, 238, 0.35);
  color: var(--text-bright);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(34, 211, 238, 0.15);
}

/* PHONE (<=700px): the desktop hero crowds a small screen. The drifting word-
   field reads as blurred smudges over the emblem at phone width, so it is hidden
   here; the wordmark and tagline are tightened so nothing looks jammed. */
@media (max-width: 700px) {
  /* Keep the drifting words — the founder wants them — but make them CALM on a
     small screen: lower the field opacity and cut the soft copy's blur so they
     read as gentle ambience, not smudges over the emblem. */
  .hero-thought-field { opacity: 0.34; }
  .hero-thought-field .tb-soft { filter: blur(1.3px); opacity: 0.5; }
  .hero-thought-field .thought-bubble { font-size: 11.5px; }
  .hero-brand-title { font-size: 34px; letter-spacing: 0.08em; }
  .hero-brand-tagline { font-size: 12.5px; letter-spacing: 0.02em; }
  .hero-brand-subtagline { font-size: 11px; letter-spacing: 0.06em; }
}

/* In Development Badges & Pulse Indicators */
.in-dev-chip {
  font-size: 8.5px;
  font-weight: 800;
  letter-spacing: 0.05em;
  padding: 1px 6px;
  border-radius: 9999px;
  background: rgba(56, 189, 248, 0.15);
  border: 1px solid rgba(56, 189, 248, 0.35);
  color: var(--cyan);
  display: inline-block;
  line-height: 1.2;
}

.header-dev-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 9999px;
  background: rgba(56, 189, 248, 0.08);
  border: 1px solid rgba(56, 189, 248, 0.22);
  color: var(--cyan);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.header-dev-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  border-radius: 9999px;
  background: rgba(56, 189, 248, 0.1);
  border: 1px solid rgba(56, 189, 248, 0.28);
  color: var(--cyan);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.dev-pulse-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 8px var(--cyan);
  animation: pulseDevDot 2s infinite ease-in-out;
}

@keyframes pulseDevDot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.7); }
}

/* ------------------------------------------------------------------ *
 * Minimalist Glowing 3-Dot Thinking Animation (Beside Quanta Bubble)
 * ------------------------------------------------------------------ */

.quanta-thinking-indicator {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 12px;
  border-radius: 9999px;
  background: rgba(56, 189, 248, 0.08);
  border: 1px solid rgba(56, 189, 248, 0.2);
  width: fit-content;
  margin: 2px 0;
}

.quanta-thinking-indicator .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 6px var(--cyan);
  animation: quantaPulseDot 1.4s infinite ease-in-out both;
}

.quanta-thinking-indicator .dot:nth-child(1) { animation-delay: -0.32s; }
.quanta-thinking-indicator .dot:nth-child(2) { animation-delay: -0.16s; }
.quanta-thinking-indicator .dot:nth-child(3) { animation-delay: 0s; }

@keyframes quantaPulseDot {
  0%, 80%, 100% {
    transform: scale(0.6);
    opacity: 0.35;
  }
  40% {
    transform: scale(1.15);
    opacity: 1;
    box-shadow: 0 0 10px var(--cyan);
  }
}

/* ------------------------------------------------------------------ *
 * Drawer User Account Strip & Card (Anthropic / OpenAI style)
 * ------------------------------------------------------------------ */

.drawer-account-section {
  padding: 8px 12px;
  border-top: 1px solid rgba(var(--lift-rgb), calc(0.06 * var(--lift-k)));
  background: rgba(var(--surf-3), 0.4);
}

.drawer-user-card {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  text-align: left;
  transition: all 0.16s ease;
}

.drawer-user-card:hover {
  background: rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  border-color: rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
}

.user-avatar-wrap {
  position: relative;
  flex: none;
}

.user-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent-fill);
  color: var(--on-accent);
  font-size: 11.5px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 10px rgba(56, 189, 248, 0.3);
}

.user-status-online {
  position: absolute;
  bottom: 0px;
  right: 0px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #10b981;
  border: 2px solid var(--bg);
}

.user-info-wrap {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.user-display-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.user-plan-badge {
  font-size: 10.5px;
  color: var(--cyan);
  font-family: var(--font-mono);
}

.user-menu-dots {
  color: var(--muted);
}

/* ------------------------------------------------------------------ *
 * Universal Drag & Drop Ingestion Overlay
 * ------------------------------------------------------------------ */

.dropzone-overlay {
  position: fixed;
  inset: 0;
  z-index: 999;
  background: rgba(var(--surf-6), 0.82);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.dropzone-box {
  width: min(520px, 90vw);
  padding: 36px 24px;
  border-radius: var(--radius-lg);
  border: 2px dashed rgba(56, 189, 248, 0.5);
  background: radial-gradient(circle at 50% 50%, rgba(56, 189, 248, 0.12) 0%, rgba(var(--surf-13), 0.8) 100%);
  box-shadow: 0 0 40px rgba(56, 189, 248, 0.25);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 12px;
  animation: dropzonePulse 2.5s infinite ease-in-out;
}

@keyframes dropzonePulse {
  0%, 100% { transform: scale(1); border-color: rgba(56, 189, 248, 0.5); }
  50% { transform: scale(1.02); border-color: rgba(56, 189, 248, 0.9); box-shadow: 0 0 60px rgba(56, 189, 248, 0.4); }
}

.dropzone-icon-ring {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  background: rgba(56, 189, 248, 0.15);
  border: 1px solid rgba(56, 189, 248, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--cyan);
  box-shadow: 0 0 20px rgba(56, 189, 248, 0.3);
}
.dropzone-icon {
  width: 26px;
  height: 26px;
}
.dropzone-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--text-bright);
}
.dropzone-sub {
  font-size: 13px;
  color: var(--muted);
}

/* Rich Multi-File Attachment Chips */
.attachment-chip {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 4px 8px 4px 6px;
  border-radius: 8px;
  background: rgba(var(--surf-35), 0.85);
  border: 1px solid rgba(56, 189, 248, 0.25);
  font-size: 12px;
  color: var(--text-bright);
  flex: none;
  box-shadow: 0 2px 8px rgba(var(--shade-rgb), calc(0.3 * var(--shadow-k)));
}
.attachment-file-icon {
  font-size: 14px;
}
.attachment-meta {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}
.attachment-name {
  max-width: 140px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
  color: var(--text-bright);
}
.attachment-size {
  font-size: 9.5px;
  color: var(--muted);
  font-family: var(--font-mono);
}

/* ------------------------------------------------------------------ *
 * Header Account Profile Avatar Button
 * ------------------------------------------------------------------ */

.header-account-btn {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent-fill);
  border: 1.5px solid rgba(var(--lift-rgb), calc(0.25 * var(--lift-k)));
  box-shadow: 0 0 12px rgba(56, 189, 248, 0.35);
  color: var(--on-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
  transition: all 0.18s cubic-bezier(0.16, 1, 0.3, 1);
  flex: none;
}
.header-account-btn:hover {
  transform: scale(1.06);
  box-shadow: 0 0 16px rgba(56, 189, 248, 0.55);
}
.header-account-btn:active {
  transform: scale(0.95);
}

/* ------------------------------------------------------------------ *
 * Dock Live Voice & Vision Mode Buttons
 * ------------------------------------------------------------------ */

.dock-chip-voice {
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.15) 0%, rgba(99, 102, 241, 0.15) 100%) !important;
  border-color: rgba(56, 189, 248, 0.35) !important;
  color: var(--accent) !important;
  font-weight: 600 !important;
}
.dock-chip-voice:hover {
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.25) 0%, rgba(99, 102, 241, 0.25) 100%) !important;
  border-color: rgba(56, 189, 248, 0.6) !important;
  box-shadow: 0 0 14px rgba(56, 189, 248, 0.25);
}

.dock-chip-vision {
  background: linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(56, 189, 248, 0.12) 100%) !important;
  border-color: rgba(16, 185, 129, 0.3) !important;
  color: var(--green) !important;
  font-weight: 600 !important;
}
.dock-chip-vision:hover {
  background: linear-gradient(135deg, rgba(16, 185, 129, 0.22) 0%, rgba(56, 189, 248, 0.22) 100%) !important;
  border-color: rgba(16, 185, 129, 0.6) !important;
  box-shadow: 0 0 14px rgba(16, 185, 129, 0.25);
}
.dock-chip-vision.active {
  background: linear-gradient(135deg, rgba(16, 185, 129, 0.3) 0%, rgba(56, 189, 248, 0.3) 100%) !important;
  border-color: var(--green) !important;
  color: var(--accent) !important;
  box-shadow: 0 0 16px rgba(16, 185, 129, 0.4);
}

.live-voice-pulse-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #38bdf8;
  box-shadow: 0 0 8px #38bdf8;
  animation: voiceDotPulse 1.8s infinite ease-in-out;
  display: inline-block;
  flex: none;
}
@keyframes voiceDotPulse {
  0%, 100% { opacity: 0.4; transform: scale(0.9); }
  50% { opacity: 1; transform: scale(1.3); box-shadow: 0 0 12px #38bdf8; }
}

/* ------------------------------------------------------------------ *
 * Top-Right Anchored Sovereign Account Dropdown Card
 * ------------------------------------------------------------------ */

.account-dropdown-wrapper {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  justify-content: flex-end;
  padding: 58px 16px 16px 16px;
  pointer-events: none;
}
.account-dropdown-wrapper.hidden {
  display: none !important;
}

.account-dropdown-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(var(--shade-rgb), 0.45);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: 1;
  pointer-events: auto;
  animation: fadeIn 0.15s ease;
}

.account-auth-panel {
  position: relative;
  z-index: 2;
  pointer-events: auto;
  max-width: 420px !important;
  width: min(420px, 94vw);
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: rgba(var(--surf-12), 0.92) !important;
  backdrop-filter: var(--glass-blur-heavy) !important;
  -webkit-backdrop-filter: var(--glass-blur-heavy) !important;
  border: 1px solid rgba(56, 189, 248, 0.28) !important;
  box-shadow: 0 16px 48px rgba(var(--shade-rgb), calc(0.8 * var(--shadow-k))), 0 0 30px rgba(56, 189, 248, 0.2) !important;
  border-radius: 18px !important;
  transform-origin: top right;
  animation: dropdownSlideScale 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes dropdownSlideScale {
  0% {
    opacity: 0;
    transform: scale(0.88) translateY(-14px);
  }
  100% {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

.account-tabs {
  display: flex;
  background: rgba(var(--surf-22), 0.75);
  border-bottom: 1px solid var(--line);
  padding: 4px;
  gap: 4px;
}

.account-tab {
  flex: 1;
  padding: 8px 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  background: transparent;
  border: none;
  border-radius: 7px;
  cursor: pointer;
  transition: all 0.15s ease;
  text-align: center;
}
.account-tab:hover {
  color: var(--text-bright);
}
.account-tab.active {
  background: rgba(56, 189, 248, 0.14);
  color: var(--accent);
  border: 1px solid rgba(56, 189, 248, 0.25);
  box-shadow: 0 2px 8px rgba(var(--shade-rgb), calc(0.25 * var(--shadow-k)));
}

.auth-section {
  padding: 20px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.auth-field-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.auth-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.auth-label {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.auth-link-btn {
  background: transparent;
  border: none;
  font-size: 11px;
  color: var(--cyan);
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
}

.auth-password-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.auth-password-wrap input {
  padding-right: 36px;
}
.btn-toggle-pass {
  position: absolute;
  right: 8px;
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 13px;
  opacity: 0.7;
  padding: 4px;
}
.btn-toggle-pass:hover { opacity: 1; }

.auth-select {
  cursor: pointer;
  background-color: var(--bg);
}

.auth-error-msg {
  padding: 8px 12px;
  border-radius: 8px;
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid rgba(239, 68, 68, 0.35);
  color: var(--red);
  font-size: 12px;
  line-height: 1.4;
}

/* The quiet counterpart to .auth-error-msg: a reset link is on its way.
   Deliberately not green-for-success — the server says the same thing
   whether or not the account exists, and a triumphant tick would turn
   this box into the oracle the route refuses to be. */
.auth-note {
  padding: 8px 12px;
  border-radius: 8px;
  background: rgba(148, 163, 184, 0.10);
  border: 1px solid rgba(148, 163, 184, 0.28);
  color: var(--text-secondary, #c9c9dd);
  font-size: 12px;
  line-height: 1.45;
}

.btn-auth-primary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 11px;
  border-radius: var(--radius-sm);
  background: var(--accent-fill);
  border: 1px solid rgba(var(--lift-rgb), calc(0.2 * var(--lift-k)));
  box-shadow: 0 4px 16px rgba(2, 132, 199, 0.35);
  color: var(--on-accent);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: all 0.16s ease;
  margin-top: 4px;
}
.btn-auth-primary:hover {
  box-shadow: 0 6px 22px rgba(56, 189, 248, 0.5);
  transform: translateY(-1px);
}
.btn-auth-primary:active {
  transform: translateY(1px);
}

/* Logged In Profile Card */
.profile-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px;
  border-radius: 12px;
  background: rgba(var(--lift-rgb), calc(0.035 * var(--lift-k)));
  border: 1px solid var(--line);
}
.profile-avatar-large {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--accent-fill);
  border: 2px solid rgba(var(--lift-rgb), calc(0.25 * var(--lift-k)));
  box-shadow: 0 0 16px rgba(56, 189, 248, 0.4);
  color: var(--on-accent);
  font-size: 16px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.profile-details {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.profile-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-email {
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-badge-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}
.profile-tier-chip {
  font-size: 9.5px;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(56, 189, 248, 0.15);
  border: 1px solid rgba(56, 189, 248, 0.3);
  color: var(--cyan);
  letter-spacing: 0.03em;
}
.profile-sync-chip {
  font-size: 10px;
  color: var(--green);
  display: flex;
  align-items: center;
  gap: 4px;
}

.sovereign-id-box {
  background: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k)));
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.sovereign-id-label {
  font-size: 10px;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.05em;
}
.sovereign-id-code {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--cyan);
}

.profile-actions-grid {
  display: flex;
  gap: 8px;
}
.profile-actions-grid button {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px;
  font-size: 11.5px;
}
.btn-fetch.danger {
  color: var(--red);
  border-color: rgba(239, 68, 68, 0.25);
}
.btn-fetch.danger:hover {
  background: rgba(239, 68, 68, 0.12);
}

.btn-logout {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 9px;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
  transition: all 0.15s ease;
}
.btn-logout:hover {
  color: var(--text-bright);
  border-color: rgba(var(--lift-rgb), calc(0.2 * var(--lift-k)));
  background: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k)));
}

/* ------------------------------------------------------------------ *
 * Web Grounding Citations Bar (Claude & Gemini SOTA Style)
 * ------------------------------------------------------------------ */

.web-citations-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid rgba(var(--lift-rgb), calc(0.07 * var(--lift-k)));
}
.web-citation-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  border-radius: 6px;
  background: rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.09 * var(--lift-k)));
  color: var(--muted);
  font-size: 11px;
  text-decoration: none;
  transition: all 0.15s ease;
}
.web-citation-chip:hover {
  color: var(--text-bright);
  border-color: rgba(56, 189, 248, 0.4);
  background: rgba(56, 189, 248, 0.08);
}
.web-citation-favicon {
  width: 12px;
  height: 12px;
  border-radius: 2px;
}
.web-citation-domain {
  font-weight: 500;
}

/* ------------------------------------------------------------------ *
 * Desktop Collapsible Persistent Sidebar Layout & Mini Rail
 * ------------------------------------------------------------------ */

.sidebar-mini-rail {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  width: 60px;
  background: rgba(var(--surf-9), 0.95);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 0;
  gap: 8px;
  z-index: 95;
  backdrop-filter: var(--glass-blur-heavy);
  -webkit-backdrop-filter: var(--glass-blur-heavy);
  transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.sidebar-mini-rail.hidden {
  display: none !important;
}
.mini-rail-btn {
  width: 42px;
  height: 42px;
  border-radius: 10px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.18s ease;
}
.mini-rail-btn:hover {
  background: rgba(var(--lift-rgb), calc(0.06 * var(--lift-k)));
  color: var(--text-bright);
  border-color: rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
}
.mini-rail-btn.active {
  background: rgba(56, 189, 248, 0.15);
  color: var(--accent);
  border-color: rgba(56, 189, 248, 0.3);
  box-shadow: 0 0 12px rgba(56, 189, 248, 0.25);
}
.mini-rail-brand {
  margin-bottom: 4px;
}
.mini-rail-logo {
  width: 28px;
  height: 28px;
  border-radius: 7px;
  object-fit: contain;
}
.mini-rail-divider {
  width: 32px;
  height: 1px;
  background: var(--line);
  margin: 4px 0;
}
.mini-rail-spacer {
  flex: 1;
}

@media (min-width: 900px) {
  body.sidebar-expanded .drawer {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    transform: translateX(0) !important;
    box-shadow: 4px 0 24px rgba(var(--shade-rgb), calc(0.45 * var(--shadow-k)));
    z-index: 90;
  }
  body.sidebar-expanded .drawer-scrim {
    display: none !important;
  }
  /* The gutter. These rules used to target `.app-shell`, which does not exist
     in index.html — so they parsed, shipped, and did nothing, and the docked
     sidebar has been sitting ON TOP of the header (hamburger included) and the
     content this whole time. body's real children are .bar, .view and
     #composer, so those are what has to move. This matters more now that
     Minimal is a resting state on the welcome screen: a rail covering the
     hamburger would leave no way to change the sidebar by hand. */
  body.sidebar-expanded .bar,
  body.sidebar-expanded .view,
  body.sidebar-expanded #composer {
    padding-left: 294px;
    transition: padding-left 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  }
  body.sidebar-collapsed .sidebar-mini-rail {
    display: flex !important;
  }
  body.sidebar-collapsed .bar,
  body.sidebar-collapsed .view,
  body.sidebar-collapsed #composer {
    padding-left: 74px;
    transition: padding-left 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  }
  body.sidebar-collapsed .drawer {
    transform: translateX(-100%) !important;
  }

  /* ONE control for the sidebar, never two.
     ------------------------------------------------------------------
     The top-bar hamburger used to sit there in every mode. With the rail
     showing that made two controls for one job, and with the sidebar
     EXPANDED it made two hamburgers on screen at once, side by side — the
     drawer's own at its top-right and this one immediately beside it.

     Both docked modes already carry their own control:
       · minimal  — the Quanta logo (#mini-rail-expand) expands the rail
       · expanded — the drawer's own hamburger (#btn-drawer-close) collapses it

     So it is hidden in those two, and ONLY those two. It is deliberately
     still rendered when body.sidebar-hidden is set, because in that mode
     there is no rail and no drawer on screen and this button is the only
     way to get the sidebar back at all. It also survives below 900px, where
     none of these classes are ever applied and the overlay drawer has no
     other opener. Delete it outright and you strand both of those users. */
  body.sidebar-collapsed #btn-drawer-toggle,
  body.sidebar-expanded  #btn-drawer-toggle {
    display: none;
  }
}

/* ------------------------------------------------------------------ *
 * Welcome screen — navigation is gated until the first message.
 *
 * `hero-mode` is set on <body> by setHeroMode() in app.js whenever the
 * active chat has no messages: first run, a new chat, or reopening the app.
 * The first thing anyone sees should be the mark and the prompt box, not
 * the chrome. The existing layout rules above use !important, so these have
 * to outrank them on specificity as well as order.
 * ------------------------------------------------------------------ */
/* THE WELCOME SCREEN OPENS BARE AGAIN — BUT NOT FROM HERE.

   It once did this in CSS, and the rule that hid the rail hid
   #btn-drawer-toggle with it: the one control that governs the sidebar was
   missing exactly where people first meet it. That is why the rules were
   deleted, and it is why they have NOT come back.

   setHeroMode() in app.js now moves the sidebar to 'hidden' on arrival
   instead. That mode deliberately keeps the hamburger on screen (see the
   #btn-drawer-toggle rule above), so the welcome screen is uncluttered and
   the sidebar is still one click away. Leaving restores the user's
   preference, unless they opened it themselves while they were here.

   DO NOT re-add hero-mode sidebar rules here. A CSS rule cannot tell the
   difference between "the screen wants this hidden" and "the user just asked
   for it", so it hides the control along with the panel every time.

   The studio capsule stays gated — it is a switcher for studios you have not
   entered yet, not a sidebar control. */
body.hero-mode #btn-brand-menu {
  display: none !important;
}

/* .bar is justify-content:space-between, so with its first two children gone
   the remaining actions would slide to the left edge. Push them back. */
body.hero-mode .bar-actions {
  margin-left: auto;
}

/* The desktop hero overrides that lived here are GONE, all of them: they hid
   the rail under both sidebar classes and un-docked the drawer, and they took
   the hamburger with them. There is deliberately nothing left — the bare
   welcome screen is produced by setHeroMode() setting the sidebar mode to
   'hidden', which keeps that control on screen. See the note above.

   The gutter that used to be described here as "nothing to reclaim" is real
   again: the .app-shell rules above were dead and now target .bar / .view /
   #composer instead. */

/* ------------------------------------------------------------------ *
 * QuantaCode Velocity Slider Engine (Luminous Speed Track & Spectrum)
 * ------------------------------------------------------------------ */

.quantacode-slider-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(var(--surf-15), 0.85);
  border: 1px solid rgba(56, 189, 248, 0.22);
  border-radius: 100px;
  padding: 4px 12px 4px 10px;
  box-shadow: 0 4px 14px rgba(var(--shade-rgb), calc(0.4 * var(--shadow-k))), inset 0 0 12px rgba(56, 189, 248, 0.06);
  transition: all 0.2s ease;
  position: relative;
  user-select: none;
  -webkit-user-select: none;
}
.quantacode-slider-pill:hover {
  border-color: rgba(56, 189, 248, 0.4);
  box-shadow: 0 4px 18px rgba(var(--shade-rgb), calc(0.5 * var(--shadow-k))), 0 0 16px rgba(56, 189, 248, 0.2);
}

.slider-mini-header {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: none;
}

.quantacode-wordmark {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: linear-gradient(90deg, #f87171, #fbbf24, #4ade80, #22d3ee, #818cf8, #e879f9, #f87171);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: rainbowShift 6s linear infinite;
}

@keyframes rainbowShift {
  0% { background-position: 0% 50%; }
  100% { background-position: 220% 50%; }
}

.slider-gear-name {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  color: var(--accent);
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(56, 189, 248, 0.12);
  border: 1px solid rgba(56, 189, 248, 0.25);
}

.slider-track-container {
  position: relative;
  width: 90px;
  height: 18px;
  background: rgba(var(--surf-5), 0.95);
  border: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  border-radius: 20px;
  overflow: hidden;
  display: flex;
  align-items: center;
}

.slider-spectrum-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 33%;
  background: linear-gradient(90deg, #3730a3, #6366f1, #38bdf8);
  opacity: 0.65;
  transition: width 0.18s cubic-bezier(0.16, 1, 0.3, 1), background 0.2s ease;
  pointer-events: none;
}

.quantacode-slider-pill.gear-3 .slider-spectrum-fill {
  background: linear-gradient(90deg, #4338ca 0%, #7c3aed 30%, #d946ef 60%, #22d3ee 100%);
  opacity: 0.9;
}

.slider-bars-spectrum {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  padding: 2px 4px;
  pointer-events: none;
}
.spectrum-bar {
  flex: 1;
  background: rgba(99, 102, 241, 0.6);
  border-radius: 1px;
  height: 20%;
  transition: height 0.15s ease, background 0.2s ease;
}
.quantacode-slider-pill.gear-3 .spectrum-bar {
  background: #22d3ee;
  box-shadow: 0 0 4px #22d3ee;
  animation: equalizerBounce 0.6s ease-in-out infinite alternate;
}

@keyframes equalizerBounce {
  0% { height: 25%; }
  100% { height: 90%; }
}

.slider-quantum-wave {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.slider-quantum-wave.hidden {
  display: none !important;
}
.quantum-wave-svg {
  width: 100%;
  height: 100%;
}
.quantum-wave-path {
  fill: none;
  stroke: #22d3ee;
  stroke-width: 1.5;
  stroke-opacity: 0.85;
  animation: waveMotion 1s ease-in-out infinite alternate;
}

@keyframes waveMotion {
  0% { d: path("M0,12 Q12.5,4 25,12 T50,12 T75,12 T100,12"); }
  100% { d: path("M0,12 Q12.5,20 25,12 T50,12 T75,12 T100,12"); }
}

.slider-real-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  z-index: 10;
  margin: 0;
}

/* ------------------------------------------------------------------ *
 * Drawer Usage Meter Box
 * ------------------------------------------------------------------ */

.drawer-usage-meter-box {
  margin: 10px 14px;
  padding: 12px;
  background: rgba(var(--surf-23), 0.6);
  border: 1px solid var(--line);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.usage-meter-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 11px;
}
.usage-meter-label {
  color: var(--muted);
  font-weight: 600;
}
.usage-meter-val {
  color: var(--accent);
  font-family: var(--font-mono);
  font-weight: 700;
}
.usage-meter-track {
  width: 100%;
  height: 6px;
  background: rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  border-radius: 10px;
  overflow: hidden;
}
.usage-meter-bar {
  height: 100%;
  background: linear-gradient(90deg, #38bdf8, #818cf8);
  border-radius: 10px;
  box-shadow: 0 0 8px rgba(56, 189, 248, 0.5);
  transition: width 0.3s ease;
}
.btn-drawer-upgrade-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 7px;
  border-radius: 8px;
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.18), rgba(129, 140, 248, 0.18));
  border: 1px solid rgba(56, 189, 248, 0.35);
  color: var(--accent);
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
  transition: all 0.15s ease;
}
.btn-drawer-upgrade-cta:hover {
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.3), rgba(129, 140, 248, 0.3));
  box-shadow: 0 0 14px rgba(56, 189, 248, 0.35);
  transform: translateY(-1px);
}

/* ------------------------------------------------------------------ *
 * Strategic Pricing Modal & Centered Overlays (Luxury SOTA UI)
 * ------------------------------------------------------------------ */

.pricing-modal-overlay,
.paywall-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
  overflow-y: auto;
  pointer-events: auto;
}
.pricing-modal-overlay.hidden,
.paywall-modal-overlay.hidden {
  display: none !important;
}

.pricing-modal-backdrop,
.paywall-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(var(--surf-4), 0.85);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  z-index: 10000;
}

.pricing-modal-panel {
  position: relative;
  max-width: 1160px !important;
  width: min(1160px, 96vw);
  max-height: calc(100vh - 60px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: rgba(var(--surf-10), 0.96) !important;
  backdrop-filter: var(--glass-blur-heavy) !important;
  -webkit-backdrop-filter: var(--glass-blur-heavy) !important;
  border: 1px solid rgba(56, 189, 248, 0.35) !important;
  box-shadow: 0 24px 60px rgba(var(--shade-rgb), calc(0.85 * var(--shadow-k))), 0 0 40px rgba(56, 189, 248, 0.25) !important;
  border-radius: 20px !important;
  padding: 26px !important;
  z-index: 10001;
  animation: modalScaleIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.pricing-cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
  margin: 20px 0;
}

.pricing-card {
  position: relative;
  background: rgba(var(--surf-26), 0.7);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  transition: all 0.22s ease;
}
.pricing-card:hover {
  border-color: rgba(var(--lift-rgb), calc(0.3 * var(--lift-k)));
  transform: translateY(-2px);
  box-shadow: 0 12px 30px rgba(var(--shade-rgb), calc(0.5 * var(--shadow-k)));
}
.pricing-card.active-selected {
  outline: 2px solid #38bdf8;
  box-shadow: 0 0 28px rgba(56, 189, 248, 0.4);
}

.pricing-card.featured {
  background: linear-gradient(180deg, rgba(var(--surf-36), 0.85) 0%, rgba(var(--surf-19), 0.9) 100%);
  border: 1.5px solid #38bdf8;
  box-shadow: 0 0 24px rgba(56, 189, 248, 0.25);
}
.pricing-card.featured:hover {
  box-shadow: 0 0 32px rgba(56, 189, 248, 0.4);
}

.pricing-card.ultra {
  background: linear-gradient(180deg, rgba(var(--surf-32), 0.85) 0%, rgba(var(--surf-14), 0.9) 100%);
  border: 1.5px solid rgba(168, 85, 247, 0.5);
}
.pricing-card.ultra:hover {
  border-color: var(--violet);
  box-shadow: 0 0 30px rgba(168, 85, 247, 0.35);
}
.pricing-card.ultra.active-selected {
  outline: 2px solid #c084fc;
  box-shadow: 0 0 28px rgba(192, 132, 252, 0.4);
}

/* ------------------------------------------------------------------ *
 * THE INFINITE TIER — the $222 Sovereign card wears the QuantaCode
 * velocity-slider spectrum (indigo → violet → magenta → cyan) as a living,
 * flowing gradient border, so the flagship reads as the flagship at a
 * glance. Display-only for now: Infinite is in beta and the button says so,
 * but the card is the aspiration people are meant to want. Only this tier
 * gets the treatment, on purpose.
 * ------------------------------------------------------------------ */
.pricing-card.sovereign {
  border: 1.5px solid transparent;
  /* Two layers: a solid card fill clipped to the padding box, the spectrum
     clipped to the border box — the classic animated gradient-border trick.
     Only the border layer sweeps; the fill stays put. */
  background:
    linear-gradient(180deg, rgba(var(--surf-32), 0.9) 0%, rgba(var(--surf-14), 0.94) 100%) padding-box,
    linear-gradient(115deg, #4338ca 0%, #7c3aed 30%, #d946ef 60%, #22d3ee 100%) border-box;
  background-size: 100% 100%, 220% 220%;
  animation: sovereignSpectrum 6s ease infinite;
  box-shadow: 0 0 30px rgba(124, 58, 237, 0.28), 0 0 60px rgba(34, 211, 238, 0.12);
}
.pricing-card.sovereign:hover {
  transform: translateY(-2px);
  box-shadow: 0 0 42px rgba(217, 70, 239, 0.42), 0 0 74px rgba(34, 211, 238, 0.2);
}
@keyframes sovereignSpectrum {
  0%   { background-position: 0% 50%, 0% 50%; }
  50%  { background-position: 0% 50%, 100% 50%; }
  100% { background-position: 0% 50%, 0% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .pricing-card.sovereign { animation: none; background-position: 0% 50%, 50% 50%; }
}

/* The tier tag glows in the spectrum instead of a flat colour. */
.pricing-tier-tag.sovereign {
  background: linear-gradient(90deg, #a855f7, #d946ef, #22d3ee);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-weight: 800;
}

/* Beta button — present and clearly not purchasable, but dressed in the
   spectrum so it reads as "coming, and it's the flagship", not "broken". */
.btn-tier-action.beta {
  background: rgba(124, 58, 237, 0.16);
  border: 1px solid rgba(217, 70, 239, 0.5);
  color: #e9d5ff;
  cursor: not-allowed;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.pricing-card-ribbon {
  position: absolute;
  top: -11px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--accent-fill);
  color: var(--on-accent);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.08em;
  padding: 3px 10px;
  border-radius: 100px;
  box-shadow: 0 2px 10px rgba(56, 189, 248, 0.5);
  white-space: nowrap;
}

.pricing-card-header {
  margin-bottom: 14px;
}
.pricing-tier-tag {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--muted);
  display: inline-block;
  margin-bottom: 6px;
}
.pricing-tier-tag.pro { color: var(--accent); }
.pricing-tier-tag.ultra { color: var(--violet); }

.pricing-card-title {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  color: var(--text-bright);
  margin-bottom: 8px;
}
.pricing-price-row {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin-bottom: 8px;
}
.pricing-price-val {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 800;
  color: var(--text-bright);
}
.pricing-price-period {
  font-size: 11px;
  color: var(--muted);
}
.pricing-card-desc {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
}

.pricing-features-list {
  list-style: none;
  padding: 0;
  margin: 14px 0 20px 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
}
.pricing-features-list li {
  font-size: 12.5px;
  color: var(--text-bright);
  display: flex;
  align-items: center;
  gap: 8px;
}
.pricing-features-list li svg.check {
  width: 14px;
  height: 14px;
  color: var(--green);
  flex: none;
}

.btn-tier-action {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 12px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: all 0.16s ease;
  border: 1px solid transparent;
}
.btn-tier-action.current {
  background: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k)));
  color: var(--muted);
  border-color: var(--line);
  cursor: default;
}
.btn-tier-action.primary {
  background: var(--accent-fill);
  color: var(--on-accent);
  box-shadow: 0 4px 16px rgba(56, 189, 248, 0.4);
}
.btn-tier-action.primary:hover {
  box-shadow: 0 6px 24px rgba(56, 189, 248, 0.6);
  transform: translateY(-1px);
}
.btn-tier-action.ultra {
  background: linear-gradient(135deg, #5b21b6 0%, #7c3aed 100%);
  color: var(--on-accent);
  box-shadow: 0 4px 16px rgba(168, 85, 247, 0.4);
}
.btn-tier-action.ultra:hover {
  box-shadow: 0 6px 24px rgba(168, 85, 247, 0.6);
  transform: translateY(-1px);
}

.pricing-license-row {
  margin-top: 14px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.pricing-payment-note {
  margin-top: 10px;
  font-size: 11px;
  color: var(--muted);
  text-align: center;
}

/* ------------------------------------------------------------------ *
 * Paywall Usage Gate Panel
 * ------------------------------------------------------------------ */

.paywall-panel {
  position: relative;
  max-width: 450px !important;
  width: min(450px, 94vw);
  background: rgba(var(--surf-16), 0.96) !important;
  backdrop-filter: var(--glass-blur-heavy) !important;
  -webkit-backdrop-filter: var(--glass-blur-heavy) !important;
  border: 1.5px solid rgba(56, 189, 248, 0.4) !important;
  box-shadow: 0 20px 50px rgba(var(--shade-rgb), calc(0.85 * var(--shadow-k))), 0 0 32px rgba(56, 189, 248, 0.28) !important;
  border-radius: 20px !important;
  padding: 28px 24px !important;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 10001;
  animation: modalScaleIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.paywall-icon-ring {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(56, 189, 248, 0.12);
  border: 1px solid rgba(56, 189, 248, 0.35);
  box-shadow: 0 0 20px rgba(56, 189, 248, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 16px;
}
.paywall-title {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  color: var(--text-bright);
  margin-bottom: 8px;
}
.paywall-desc {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.55;
  margin-bottom: 20px;
}
.paywall-actions {
  display: flex;
  flex-direction: column;
  width: 100%;
}

/* ------------------------------------------------------------------ *
 * Live Voice Quick Input & Push-to-Talk Additions
 * ------------------------------------------------------------------ */

.voice-quick-input-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  width: min(440px, 90vw);
  margin: 14px auto 0 auto;
  padding: 6px 8px 6px 16px;
  background: rgba(var(--surf-25), 0.85);
  border: 1px solid rgba(56, 189, 248, 0.3);
  border-radius: 100px;
  box-shadow: 0 4px 20px rgba(var(--shade-rgb), calc(0.4 * var(--shadow-k)));
}
.voice-text-prompt-input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text-bright);
  font-size: 13px;
  font-family: inherit;
}
.voice-btn-send {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent-fill);
  border: none;
  color: var(--on-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(56, 189, 248, 0.5);
  transition: all 0.15s ease;
}
.voice-btn-send:hover {
  transform: scale(1.08);
}
.voice-control-btn.btn-push-speak {
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.25), rgba(14, 165, 233, 0.35));
  border: 1px solid #38bdf8;
  color: var(--accent);
  box-shadow: 0 0 16px rgba(56, 189, 248, 0.4);
}
.voice-control-btn.btn-push-speak:hover {
  background: var(--accent-fill);
  box-shadow: 0 0 24px rgba(56, 189, 248, 0.65);
}

/* ------------------------------------------------------------------ *
 * LIVE METER STRIP — context · today · week
 *
 * Lives inside the composer, below the input pill. On iOS the composer
 * already reserves `env(safe-area-inset-bottom)` to clear the home
 * indicator; that band was empty, so the meters fill it and cost the
 * conversation no vertical height at all.
 *
 * Each meter stacks label+value over a full-width bar. Side-by-side, the
 * word "CONTEXT" ate so much of a 106px column that the bar shrank to a
 * stub; stacking gives the bar the whole width and still fits the band.
 *
 * Colour runs sky -> indigo, leaving that range only to warn: amber past
 * 75%, red past 92%. Nothing here is green.
 * ------------------------------------------------------------------ */

/* Accuracy disclaimer — quiet but never hidden. Small and low-contrast so it
   does not compete with the conversation, but it is real text in the flow,
   not a tooltip and not dismissible. */
.composer-disclaimer {
  margin: 6px 0 2px;
  text-align: center;
  font-size: 11px;
  line-height: 1.4;
  color: var(--muted);
  opacity: 0.62;
  padding: 0 14px;
  user-select: none;
}

@media (max-width: 560px) {
  .composer-disclaimer {
    font-size: 10px;
    margin: 5px 0 1px;
  }
}

.meter-strip {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  width: 100%;
  max-width: var(--thread-max);
  margin: 3px auto 0;
  padding: 0 6px;
}

.meter {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  -webkit-tap-highlight-color: transparent;
  transition: opacity .18s ease;
  opacity: .82;
}
.meter:hover  { opacity: 1; }
.meter:active { opacity: .6; }
.meter:focus-visible { outline: 1px solid var(--line-strong); outline-offset: 3px; border-radius: 5px; }

.meter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
  line-height: 1;
}

.meter-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.meter-pct {
  flex: none;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 9.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  transition: color .3s ease;
}
.meter-pct.warn { color: var(--amber); }
.meter-pct.crit { color: var(--red); }

.meter-track {
  position: relative;
  display: block;          /* it is a <span>; without this it stays inline and
                              silently ignores width/height outside a flex row */
  width: 100%;
  height: 3px;
  border-radius: 999px;
  background: rgba(var(--lift-rgb), calc(0.09 * var(--lift-k)));
  overflow: hidden;
}

.meter-fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: linear-gradient(90deg, #38bdf8 0%, #818cf8 100%);
  box-shadow: 0 0 7px rgba(56, 189, 248, 0.45);
  transition: width .45s cubic-bezier(.16,1,.3,1), background .3s ease, box-shadow .3s ease;
}
.meter-fill.warn {
  background: linear-gradient(90deg, #60a5fa 0%, #fbbf24 100%);
  box-shadow: 0 0 7px rgba(251, 191, 36, 0.45);
}
.meter-fill.crit {
  background: linear-gradient(90deg, #fbbf24 0%, #ef4444 100%);
  box-shadow: 0 0 8px rgba(239, 68, 68, 0.5);
}

/* Unlimited tiers have no ceiling to draw — keep the row, drop the urgency. */
.meter.unlimited .meter-fill { background: linear-gradient(90deg, #38bdf8, #a78bfa); opacity: .45; }
.meter.unlimited .meter-pct  { color: var(--faint); }

@media (max-width: 400px) {
  .meter-strip { gap: 11px; }
  .meter-label { font-size: 8.5px; letter-spacing: .06em; }
  .meter-pct   { font-size: 9px; }
}

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

/* ------------------------------------------------------------------ *
 * METER EXPAND + USAGE FLY-OUT
 * The strip shows only what gates sending right now (5-hour, week).
 * Context and storage are reference, not gates, so they live one tap
 * away rather than competing for the same few pixels.
 * ------------------------------------------------------------------ */

.meter-expand {
  flex: none;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 6px;
  color: var(--faint);
  cursor: pointer;
  opacity: .8;
  -webkit-tap-highlight-color: transparent;
  transition: color .18s ease, opacity .18s ease, transform .22s cubic-bezier(.16,1,.3,1);
}
.meter-expand .ico { width: 13px; height: 13px; }
.meter-expand:hover { color: var(--text); opacity: 1; }
.meter-expand[aria-expanded="true"] { transform: rotate(180deg); color: var(--accent); opacity: 1; }
.meter-expand:focus-visible { outline: 1px solid var(--line-strong); outline-offset: 2px; }

.meter-flyout {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  transform: translateX(-50%);
  z-index: 60;
  width: min(340px, calc(100vw - 28px));
  padding: 16px 18px 14px;
  background: var(--panel-solid, #10131f);
  background: color-mix(in srgb, var(--panel-solid, #10131f) 94%, transparent);
  backdrop-filter: var(--glass-blur, blur(24px));
  -webkit-backdrop-filter: var(--glass-blur, blur(24px));
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: 0 20px 50px -12px rgba(var(--shade-rgb), calc(.8 * var(--shadow-k))), 0 0 0 1px rgba(var(--gloss-rgb), .03) inset;
  animation: flyoutIn .22s cubic-bezier(.16,1,.3,1);
}
.meter-flyout.hidden { display: none; }

@keyframes flyoutIn {
  from { opacity: 0; transform: translateX(-50%) translateY(8px) scale(.97); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

.meter-flyout-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line-subtle);
}
.meter-flyout-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-bright);
  letter-spacing: -0.01em;
}
.meter-flyout-tier {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(56, 189, 248, .12);
  border: 1px solid rgba(56, 189, 248, .28);
  border-radius: 999px;
  padding: 3px 9px;
}

.flyout-meter { margin-bottom: 13px; }
.flyout-meter:last-of-type { margin-bottom: 4px; }

.flyout-meter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.flyout-meter-label {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text);
}
.flyout-meter-val {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.flyout-meter-val.warn { color: var(--amber); }
.flyout-meter-val.crit { color: var(--red); }

.flyout-meter .meter-track { height: 4px; }

.flyout-meter-sub {
  margin-top: 5px;
  font-size: 10px;
  color: var(--faint);
  letter-spacing: .01em;
}

.flyout-upgrade {
  width: 100%;
  margin-top: 12px;
  padding: 9px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  color: #050609;
  background: linear-gradient(135deg, #38bdf8, #818cf8);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  transition: filter .18s ease, transform .18s ease;
}
.flyout-upgrade:hover  { filter: brightness(1.08); }
.flyout-upgrade:active { transform: scale(.98); }
.flyout-upgrade.hidden { display: none; }

@media (prefers-reduced-motion: reduce) {
  .meter-flyout { animation: none; }
  .meter-expand { transition: none; }
}

/* Feature rows read as one sentence, not as flex items.
   As a flex row, each <strong> and each bare text node became its own item,
   so "Quanta-Pro" and the phrase after it wrapped independently and broke
   mid-name. Inline flow keeps a feature on one line and wraps naturally. */
.pricing-features-list li {
  display: block;
  line-height: 1.45;
  padding-left: 24px;
  position: relative;
}
.pricing-features-list li .ico.check,
.pricing-features-list li > svg.ico {
  position: absolute;
  left: 0;
  top: 0.28em;
  width: 14px;
  height: 14px;
  flex: none;
}

/* ------------------------------------------------------------------ *
 * SETTINGS
 * Grouped cards with a titled section above each, the pattern every
 * major assistant settles on: scannable, one concern per row, and the
 * control always on the right edge where the thumb expects it.
 * ------------------------------------------------------------------ */

.settings-scroll {
  flex: 1;
  width: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding: 0 16px calc(40px + env(safe-area-inset-bottom));
}
.settings-wrap { max-width: 720px; margin: 0 auto; }

.settings-masthead { padding: 28px 4px 20px; }
.settings-h1 {
  font-size: clamp(24px, 5vw, 31px);
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--text-bright);
  margin: 0 0 5px;
}
.settings-sub { font-size: 13px; color: var(--faint); margin: 0; }

.settings-group { margin-bottom: 26px; }
.settings-group-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 9px 4px;
}

.settings-card {
  background: var(--glass-card, rgba(var(--surf-24),.58));
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
  backdrop-filter: var(--glass-blur, blur(24px));
  -webkit-backdrop-filter: var(--glass-blur, blur(24px));
}

.settings-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 15px 17px;
  border-bottom: 1px solid var(--line-subtle);
  text-decoration: none;
  color: inherit;
}
.settings-row:last-child { border-bottom: 0; }
.settings-row-stack { flex-direction: column; align-items: stretch; gap: 11px; }

.settings-row-tappable { cursor: pointer; transition: background .15s ease; }
.settings-row-tappable:hover  { background: rgba(var(--lift-rgb), calc(.035 * var(--lift-k))); }
.settings-row-tappable:active { background: rgba(var(--lift-rgb), calc(.06 * var(--lift-k))); }
.settings-row-tappable.danger .settings-row-label { color: var(--red); }
.settings-row-tappable.danger:hover { background: rgba(239,68,68,.07); }

.settings-row-text { flex: 1 1 auto; min-width: 0; }
.settings-row-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-bright);
  line-height: 1.35;
}
.settings-row-desc {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.5;
  margin-top: 3px;
}
.settings-inline-val {
  font-family: var(--font-mono, ui-monospace, monospace);
  color: var(--accent);
  font-weight: 700;
  margin-left: 4px;
}
.settings-chevron { width: 15px; height: 15px; color: var(--faint); flex: none; }
/* A small action that lives inside a settings row — the one that sends the
   email-confirmation link. Sized to sit on the row like .settings-value
   rather than pushing it taller. */
.btn-settings-inline {
  flex: none;
  padding: 7px 13px;
  border-radius: 9px;
  border: 1px solid rgba(139, 92, 246, 0.35);
  background: rgba(139, 92, 246, 0.14);
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.18s ease, opacity 0.18s ease;
}
.btn-settings-inline:hover:not(:disabled) { background: rgba(139, 92, 246, 0.24); }
.btn-settings-inline:disabled { opacity: 0.55; cursor: default; }

.settings-value {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 12px;
  color: var(--muted);
  flex: none;
}

/* ---- account row ---- */
.settings-row-account { gap: 13px; }
.set-account-avatar {
  flex: none;
  width: 40px; height: 40px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 15px; color: #050609;
  background: linear-gradient(135deg, #38bdf8, #818cf8);
}
.set-plan-chip {
  flex: none;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 9.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent);
  background: rgba(56,189,248,.12);
  border: 1px solid rgba(56,189,248,.28);
  border-radius: 999px;
  padding: 4px 10px;
}

/* ---- switch ---- */
.switch {
  flex: none;
  position: relative;
  width: 46px; height: 27px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(var(--lift-rgb), calc(.13 * var(--lift-k)));
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background .22s cubic-bezier(.16,1,.3,1);
}
.switch-thumb {
  position: absolute;
  top: 3px; left: 3px;
  width: 21px; height: 21px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(var(--shade-rgb), calc(.45 * var(--shadow-k)));
  transition: transform .22s cubic-bezier(.16,1,.3,1);
}
.switch[aria-checked="true"] {
  background: linear-gradient(135deg, #38bdf8, #818cf8);
}
.switch[aria-checked="true"] .switch-thumb { transform: translateX(19px); }
.switch:focus-visible { outline: 2px solid var(--line-strong); outline-offset: 2px; }

/* ---- inputs ---- */
.settings-select,
.settings-input {
  flex: none;
  font-family: inherit;
  font-size: 13px;
  color: var(--text-bright);
  background: rgba(var(--lift-rgb), calc(.05 * var(--lift-k)));
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 11px;
  min-width: 0;
}
.settings-select { max-width: 190px; cursor: pointer; }
.settings-input  { flex: 1 1 auto; }
.settings-select:focus,
.settings-input:focus { outline: none; border-color: var(--line-strong); }

.settings-inline-fields { display: flex; gap: 9px; align-items: center; width: 100%; }
.settings-btn {
  flex: none;
  font-family: inherit; font-size: 12.5px; font-weight: 600;
  color: #050609;
  background: linear-gradient(135deg, #38bdf8, #818cf8);
  border: 0; border-radius: 9px;
  padding: 9px 15px;
  cursor: pointer;
  transition: filter .16s ease;
}
.settings-btn:hover { filter: brightness(1.08); }

.settings-range {
  width: 100%;
  -webkit-appearance: none; appearance: none;
  height: 4px; border-radius: 999px;
  background: rgba(var(--lift-rgb), calc(.13 * var(--lift-k)));
  outline: none;
}
.settings-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 17px; height: 17px; border-radius: 50%;
  background: linear-gradient(135deg, #38bdf8, #818cf8);
  box-shadow: 0 1px 5px rgba(var(--shade-rgb), calc(.5 * var(--shadow-k)));
  cursor: pointer;
}
.settings-range::-moz-range-thumb {
  width: 17px; height: 17px; border: 0; border-radius: 50%;
  background: linear-gradient(135deg, #38bdf8, #818cf8);
  cursor: pointer;
}

/* ---- connection status ---- */
.settings-status {
  flex: none;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; font-weight: 600;
  color: var(--muted);
}
.settings-status-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--faint);
  box-shadow: 0 0 7px currentColor;
}
.settings-status.online  { color: var(--accent); }
.settings-status.online  .settings-status-dot { background: var(--accent); }
.settings-status.offline { color: var(--red); }
.settings-status.offline .settings-status-dot { background: var(--red); }

@media (max-width: 480px) {
  .settings-row { padding: 14px 14px; gap: 12px; }
  .settings-select { max-width: 148px; }
}
@media (prefers-reduced-motion: reduce) {
  .switch, .switch-thumb { transition: none; }
}

/* ------------------------------------------------------------------ *
 * CREATIVE STUDIOS — Image & Video (Quanta Create)
 *
 * Same skeleton as the chat view: results scroll in the upper area, the
 * controls dock at the bottom. The old layout stacked a tall header, a
 * dashed empty box and a control card into one scrolling column, so the
 * primary button ended up mid-page with dead space beneath it, and the
 * option pills overflowed sideways behind a scrollbar.
 * ------------------------------------------------------------------ */

.studio-shell {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  width: 100%;
}

.studio-canvas {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding: 16px 14px 8px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* ---- empty state ---- */
.studio-empty {
  flex: 1;
  min-height: 210px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 34px 22px;
  text-align: center;
  border: 1px solid var(--line-subtle);
  border-radius: 18px;
  background: rgba(var(--lift-rgb), calc(.018 * var(--lift-k)));
}
.studio-empty-icon { width: 30px; height: 30px; color: var(--faint); opacity: .75; }
.studio-empty-title { font-size: 15px; font-weight: 650; color: var(--text); }
.studio-empty-sub {
  font-size: 12.5px; color: var(--muted);
  max-width: 30ch; line-height: 1.5;
}

.spotlight-stage { display: flex; flex-direction: column; flex: 1; min-height: 0; }

/* ---- docked controls ---- */
.studio-dock {
  flex: none;
  width: 100%;
  padding: 10px 14px max(14px, calc(8px + env(safe-area-inset-bottom)));
  background: var(--glass-dock);
  backdrop-filter: var(--glass-blur-heavy);
  -webkit-backdrop-filter: var(--glass-blur-heavy);
  border-top: 1px solid rgba(var(--lift-rgb), calc(.08 * var(--lift-k)));
  box-shadow: 0 -8px 32px rgba(var(--shade-rgb), calc(.45 * var(--shadow-k)));
}
.studio-dock-inner {
  max-width: 860px; margin: 0 auto;
  display: flex; flex-direction: column; gap: 9px;
}

.prompt-wrap { position: relative; }
.studio-textarea {
  width: 100%;
  min-height: 54px;
  max-height: 132px;
  resize: none;
  font-family: inherit;
  font-size: 14.5px;
  line-height: 1.45;
  color: var(--text);
  background: rgba(var(--lift-rgb), calc(.045 * var(--lift-k)));
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 12px 96px 12px 14px;
}
.studio-textarea:focus { outline: none; border-color: var(--line-strong); }
.studio-textarea::placeholder { color: var(--faint); }

.btn-prompt-magic {
  position: absolute; right: 8px; bottom: 8px;
  display: inline-flex; align-items: center; gap: 5px;
  font-family: inherit; font-size: 11.5px; font-weight: 600;
  color: var(--muted);
  background: rgba(var(--lift-rgb), calc(.06 * var(--lift-k)));
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 5px 9px;
  cursor: pointer;
  transition: color .16s ease, border-color .16s ease;
}
.btn-prompt-magic .ico { width: 12px; height: 12px; }
.btn-prompt-magic:hover { color: var(--text); border-color: var(--line-strong); }

/* Controls wrap onto a second line instead of scrolling off the edge. */
.studio-flyout-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
}
.flyout-trigger-pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: 12px; font-weight: 600;
  color: var(--text);
  background: rgba(var(--lift-rgb), calc(.05 * var(--lift-k)));
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 7px 12px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .16s ease, border-color .16s ease;
}
.flyout-trigger-pill .ico { width: 13px; height: 13px; flex: none; color: var(--accent); }
.flyout-trigger-pill .ico.caret { width: 11px; height: 11px; color: var(--faint); }
.flyout-trigger-pill:hover { background: rgba(var(--lift-rgb), calc(.09 * var(--lift-k))); border-color: var(--line-strong); }

/* The primary action was rendering as bare text — no background, no shape. */
.btn-generate {
  margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font-family: inherit; font-size: 13px; font-weight: 700;
  color: #050609;
  background: linear-gradient(135deg, #38bdf8, #818cf8);
  border: 0;
  border-radius: 999px;
  padding: 9px 20px;
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(56, 189, 248, .3);
  transition: filter .16s ease, transform .16s ease;
}
.btn-generate .ico { width: 14px; height: 14px; }
.btn-generate:hover  { filter: brightness(1.08); }
.btn-generate:active { transform: scale(.97); }
.btn-generate:disabled { opacity: .55; cursor: default; filter: none; transform: none; }

/* ---- recent strip ---- */
.recent-strip-container { flex: none; }
.recent-strip-container.hidden { display: none; }
.recent-strip-header {
  display: flex; align-items: center; gap: 8px;
  font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}
.recent-thumbnails-strip {
  display: flex; gap: 8px;
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: 4px;
  scrollbar-width: none;
}
.recent-thumbnails-strip::-webkit-scrollbar { display: none; }
.recent-thumb-item {
  flex: none;
  width: 58px; height: 58px;
  border-radius: 11px;
  overflow: hidden;
  border: 1px solid var(--line);
  cursor: pointer;
  transition: border-color .16s ease;
}
.recent-thumb-item:hover  { border-color: var(--line-strong); }
.recent-thumb-item.active { border-color: var(--accent); }

@media (min-width: 700px) {
  .studio-canvas { padding: 22px 22px 10px; }
  .studio-dock   { padding: 12px 22px max(16px, calc(8px + env(safe-area-inset-bottom))); }
}

/* ---- video stage card ------------------------------------------------ *
 * The overlay text was flowing beneath the canvas instead of sitting on
 * it, and the card had no bounds, so the whole stage read as loose text.
 * -------------------------------------------------------------------- */
.stage-video-card {
  border: 1px solid var(--line);
  border-radius: 18px;
  overflow: hidden;
  background: var(--surface-inverse);
}
.video-ambient-player {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface-inverse);
}
.stage-video-canvas-el {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
.matrix-simulation-overlay { position: absolute; inset: 0; pointer-events: none; }

.video-overlay-info {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 30px 14px 12px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  background: linear-gradient(to top, rgba(var(--surf-2),.92) 0%, rgba(var(--surf-2),.55) 55%, transparent 100%);
}
.video-model-badge, .video-motion-tag {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 9.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px;
  background: rgba(56,189,248,.15);
  border: 1px solid rgba(56,189,248,.3);
  color: var(--accent);
}
.video-motion-tag { background: rgba(var(--lift-rgb), calc(.07 * var(--lift-k))); border-color: var(--line); color: var(--muted); }
.stage-video-title {
  flex: 1 1 100%;
  font-size: 14.5px; font-weight: 650; line-height: 1.35;
  color: var(--text-bright); margin: 3px 0 0;
}

.stage-card-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px;
  border-top: 1px solid var(--line-subtle);
  background: rgba(var(--lift-rgb), calc(.02 * var(--lift-k)));
}
.video-footer-info { font-size: 11.5px; color: var(--faint); }
.stage-actions { display: flex; gap: 7px; }
.stage-actions .icon-btn-sm {
  width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px;
  background: rgba(var(--lift-rgb), calc(.05 * var(--lift-k)));
  border: 1px solid var(--line);
  color: var(--muted);
  cursor: pointer;
  transition: color .16s ease, border-color .16s ease;
}
.stage-actions .icon-btn-sm .ico { width: 14px; height: 14px; }
.stage-actions .icon-btn-sm:hover { color: var(--text); border-color: var(--line-strong); }

/* ==================================================================== *
 * MOTION
 *
 * The app toggles `display:none`, which cannot transition — so entrances
 * are keyframe animations that fire the moment an element becomes
 * visible, and exits are handled by the few elements that stay in flow.
 *
 * The rules this follows, so it reads as calm rather than busy:
 *   · Distances stay small (6–12px). Motion should be felt, not watched.
 *   · Durations stay short (160–280ms). Anything longer feels like lag.
 *   · One decelerating curve everywhere, so the whole app moves alike.
 *   · Nothing animates that the user is waiting on.
 *   · Everything collapses to nothing under prefers-reduced-motion.
 * ==================================================================== */

:root {
  --ease-out: cubic-bezier(.16, 1, .3, 1);      /* decelerate — entrances */
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);  /* symmetrical — moves */
  --dur-fast: .16s;
  --dur: .22s;
  --dur-slow: .28s;
}

/* ---------- view / studio switching ---------- */
@keyframes viewIn {
  from { opacity: 0; transform: translate3d(0, 8px, 0); }
  to   { opacity: 1; transform: none; }
}

/* Structural containers fade only — they never move.
   Translating a flex child that defines the viewport boundary leaves it
   holding the keyframe's start offset if the animation does not run to
   completion, which pushed the composer 12px below the bottom edge and hid
   part of the meter strip behind the home indicator. Motion is for content;
   the frame stays still. */
@keyframes fadeInSoft { from { opacity: 0; } to { opacity: 1; } }
.view:not(.hidden) {
  animation: fadeInSoft var(--dur-slow) var(--ease-out) both;
}

/* The composer belongs to the chat view but sits outside it. */
@keyframes dockIn {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to   { opacity: 1; transform: none; }
}
.composer:not(.hidden),
.studio-dock { animation: fadeInSoft var(--dur-slow) var(--ease-out) both; }

/* ---------- messages ---------- */
@keyframes msgIn {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to   { opacity: 1; transform: none; }
}
.msg { animation: msgIn var(--dur) var(--ease-out) both; }
/* Verified: streaming rewrites only the bubble innerHTML, never the
   thread, so a message animates once on creation and not per token. */

/* ---------- drawer ---------- */
.drawer {
  transition: transform var(--dur-slow) var(--ease-out),
              box-shadow var(--dur-slow) var(--ease-out) !important;
  -webkit-transition: -webkit-transform var(--dur-slow) var(--ease-out) !important;
  will-change: transform;
}
.scrim {
  transition: opacity var(--dur) var(--ease-in-out);
}
.scrim.hidden { display: none !important; }
.scrim:not(.hidden) { animation: fadeIn var(--dur) var(--ease-in-out) both; }

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* Drawer rows arrive in a short stagger — enough to feel considered,
   short enough that the last one is not still moving when you look. */
@keyframes railIn {
  from { opacity: 0; transform: translate3d(-8px, 0, 0); }
  to   { opacity: 1; transform: none; }
}
.drawer.open .drawer-item {
  animation: railIn var(--dur) var(--ease-out) both;
}
.drawer.open .drawer-item:nth-child(1) { animation-delay: .02s; }
.drawer.open .drawer-item:nth-child(2) { animation-delay: .04s; }
.drawer.open .drawer-item:nth-child(3) { animation-delay: .06s; }
.drawer.open .drawer-item:nth-child(4) { animation-delay: .08s; }
.drawer.open .drawer-item:nth-child(5) { animation-delay: .10s; }
.drawer.open .drawer-item:nth-child(6) { animation-delay: .12s; }

/* ---------- sheets, modals, popovers ---------- */
@keyframes sheetIn {
  from { opacity: 0; transform: translate3d(0, 26px, 0); }
  to   { opacity: 1; transform: none; }
}
/* Centered-modal entrance: the resting transform (translate(-50%,-50%)) is
   locked with !important for centering, so we animate the independent `scale`
   + opacity instead of the `transform` shorthand — a soft pop, no slide. */
@keyframes sheetPop {
  from { opacity: 0; scale: .96; }
  to   { opacity: 1; scale: 1; }
}
.sheet:not(.hidden) { animation: sheetPop var(--dur-slow) var(--ease-out) both; }

@keyframes modalIn {
  from { opacity: 0; transform: translate3d(0, 10px, 0) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.pricing-modal-panel,
.auth-modal-panel,
.paywall-panel,
.artifact-panel:not(.hidden) {
  animation: modalIn var(--dur-slow) var(--ease-out) both;
}
.pricing-modal-backdrop,
.paywall-backdrop { animation: fadeIn var(--dur) var(--ease-in-out) both; }

/* ---------- settings ---------- */
.view#view-settings:not(.hidden) .settings-group {
  animation: viewIn var(--dur) var(--ease-out) both;
}
#view-settings:not(.hidden) .settings-group:nth-of-type(1) { animation-delay: .02s; }
#view-settings:not(.hidden) .settings-group:nth-of-type(2) { animation-delay: .05s; }
#view-settings:not(.hidden) .settings-group:nth-of-type(3) { animation-delay: .08s; }
#view-settings:not(.hidden) .settings-group:nth-of-type(4) { animation-delay: .11s; }
#view-settings:not(.hidden) .settings-group:nth-of-type(5) { animation-delay: .14s; }
#view-settings:not(.hidden) .settings-group:nth-of-type(6) { animation-delay: .17s; }

/* ---------- touch feedback ---------- */
/* A press should acknowledge itself immediately; release can ease back. */
.icon-btn, .drawer-item, .flyout-trigger-pill, .model-pill,
.btn-generate, .btn-tier-action, .round {
  transition: transform var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}
.icon-btn:active, .drawer-item:active, .flyout-trigger-pill:active,
.model-pill:active, .round:active { transform: scale(.94); }
.btn-generate:active, .btn-tier-action:active { transform: scale(.975); }

/* ---------- new-chat clear ---------- */
/* Starting a fresh chat should feel like a page turning, not a blank. */
@keyframes heroIn {
  from { opacity: 0; transform: translate3d(0, 14px, 0) scale(.99); }
  to   { opacity: 1; transform: none; }
}
.quanta-hero-stage:not(.hidden) {
  animation: heroIn .34s var(--ease-out) both;
}

/* ---------- meters ---------- */
/* Bars already ease their width; the fly-out gets the same entrance as
   every other surface so nothing in the app pops. */
.meter-flyout:not(.hidden) { animation: modalIn var(--dur) var(--ease-out) both; }

/* ==================================================================== *
 * Reduced motion: honour it completely. Someone who asks for less
 * motion should get none of this, not a faster version of it.
 * ==================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- waking notice --------------------------------------------------- *
 * Shown only when the model is genuinely still loading. An unexplained
 * wait feels twice as long as an explained one, and this one happens once
 * per session — people need to know that, or they assume it is always
 * this slow and leave.
 * --------------------------------------------------------------------- */
/* ==================================================================== *
 * SKELETON / SHIMMER LOADING SURFACES (Track 3 — perceived performance)
 *
 * One reusable shimmer for media still being generated. Primary use: the
 * image studio spotlight while a render is in flight (the stage otherwise
 * sits frozen on the previous image). Calm, cyan-tinted so it reads as
 * "Quanta working" rather than a generic grey box, and static under
 * prefers-reduced-motion.
 * ==================================================================== */
.qskel {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(
    90deg,
    rgba(var(--lift-rgb), calc(0.04 * var(--lift-k))) 0%,
    rgba(var(--lift-rgb), calc(0.09 * var(--lift-k))) 50%,
    rgba(var(--lift-rgb), calc(0.04 * var(--lift-k))) 100%
  );
  background-size: 200% 100%;
}
.qskel-shimmer {
  animation: qskelSweep 1.4s ease-in-out infinite;
}
/* A second cyan sheen, offset, so the surface has Quanta's colour on it. */
.qskel-shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(56, 189, 248, 0.10) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  animation: qskelSweep 1.4s ease-in-out infinite;
  animation-delay: 0.2s;
}
@keyframes qskelSweep {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.qskel-viewport { position: relative; }
.qgen-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 12px;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
  z-index: 1;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .qskel-shimmer,
  .qskel-shimmer::after { animation: none; }
}

.waking-note {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 10px;
  padding: 11px 14px;
  border-radius: 12px;
  background: rgba(56, 189, 248, .07);
  border: 1px solid rgba(56, 189, 248, .18);
  animation: viewIn .3s var(--ease-out) both;
}
.waking-title {
  font-size: 13px; font-weight: 650; color: var(--accent);
  display: flex; align-items: center; gap: 7px;
}
.waking-title::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
  animation: wakePulse 1.4s ease-in-out infinite;
}
@keyframes wakePulse { 0%,100% { opacity: .35; } 50% { opacity: 1; } }
.waking-sub { font-size: 12px; line-height: 1.5; color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .waking-note { animation: none; }
  .waking-title::before { animation: none; opacity: 1; }
}

/* ==================================================================== *
 * RENDER READINESS
 *
 * A long conversation makes every scroll and every streamed repaint more
 * expensive, because the browser lays out and paints messages nobody can
 * see. These rules bound that work so the cost of receiving a response
 * stays flat as a thread grows.
 * ==================================================================== */

/* Skip layout and paint for messages scrolled out of view. The intrinsic
   size keeps the scrollbar honest so this does not cause jumping. */
.msg {
  content-visibility: auto;
  contain-intrinsic-size: auto 120px;
}

/* The message currently streaming must never be skipped — it is the one
   thing on screen that is changing. */
.msg:last-child {
  content-visibility: visible;
  contain-intrinsic-size: none;
}

/* Isolate the thread's layout from the rest of the page, so a repaint
   inside it cannot force the app bar, drawer or composer to re-layout. */
.studio-thread {
  contain: layout paint style;
}

/* The composer is fixed furniture — its own layer, never re-laid-out by
   thread changes behind it. */
.composer,
.bar {
  contain: layout paint;
}

/* Scroll on the compositor rather than blocking on JS listeners. */
.studio-thread,
.settings-scroll,
.studio-canvas,
.drawer {
  overscroll-behavior: contain;
  touch-action: pan-y;
}

/* ==================================================================== *
 * COMPOSER DOCK — speed gear + bottom sheets
 *
 * The velocity slider used to sit inline in the dock. At its full width it
 * pushed the model pill past the right edge of a phone, so the control you
 * change most often was the one you could not reach. It is a trigger now,
 * and the slider gets a sheet with room to breathe.
 * ==================================================================== */

.composer-control-dock {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  max-width: var(--thread-max);
  margin: 0 auto;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.composer-control-dock::-webkit-scrollbar { display: none; }

/* Center the flow pill on the TRUE container centre.
 *
 * The dock was flex + `justify-content: space-between`, which centres the
 * middle pill in the gap between the side controls — and because the model
 * pill is wider than the voice chip, that midpoint sits right of centre, so
 * the flow pill visibly drifts right. A 1fr/auto/1fr grid gives the two sides
 * equal tracks, which puts the auto (flow) column dead centre regardless of
 * how wide the model name is. Desktop only; the mobile dock keeps its flex
 * scroll layout (above), where horizontal room is the scarce thing.
 *
 * The flow popout re-anchors to the pill's live rect on every open
 * (anchorToPill in app.js), so moving the pill re-centres the popout for free. */
@media (min-width: 768px) {
  .composer-control-dock {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    overflow: visible;
  }
  .composer-control-dock > .model-pill { justify-self: start; }
  .composer-control-dock > .speed-gear-pill { justify-self: center; }
  .composer-control-dock > .dock-actions { justify-self: end; }
}

/* The model pill is the primary control — it never shrinks or scrolls away. */
.model-pill { flex: 0 0 auto; }

.speed-gear-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 650;
  color: var(--text);
  background: rgba(var(--lift-rgb), calc(.05 * var(--lift-k)));
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}
.speed-gear-pill .ico { width: 13px; height: 13px; color: var(--accent); flex: none; }
.speed-gear-pill .pill-caret { width: 11px; height: 11px; color: var(--faint); }
.speed-gear-pill:hover { background: rgba(var(--lift-rgb), calc(.09 * var(--lift-k))); border-color: var(--line-strong); }
.speed-gear-pill[aria-expanded="true"] { border-color: var(--line-strong); color: var(--text-bright); }

/* ---- shared bottom sheet ---- */
.bottom-sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 130;
  max-height: 78vh;
  overflow-y: auto;
  padding: 10px 20px calc(24px + env(safe-area-inset-bottom));
  background: rgba(var(--surf-17), .96);
  backdrop-filter: var(--glass-blur-heavy);
  -webkit-backdrop-filter: var(--glass-blur-heavy);
  border-top: 1px solid var(--line-strong);
  border-radius: 26px 26px 0 0;
  box-shadow: 0 -20px 60px -18px rgba(var(--shade-rgb), calc(.9 * var(--shadow-k)));
  animation: sheetIn var(--dur-slow) var(--ease-out) both;
}
.bottom-sheet.hidden { display: none; }

/* DESKTOP: the sheet is a centered, max-width floating card — not an
   edge-to-edge bar. Full width made the QuantaCode speed slider stretch across
   the whole window and clip on the right. position:fixed + left/right:0 +
   max-width + margin:auto centers it without touching the slide-up transform. */
@media (min-width: 760px) {
  .bottom-sheet {
    left: 0;
    right: 0;
    margin: 0 auto;
    max-width: 560px;
    bottom: 26px;
    border-radius: 22px;
    border: 1px solid var(--line-strong);
  }
}

.bottom-sheet-grip {
  width: 38px; height: 4px; border-radius: 999px;
  background: rgba(var(--lift-rgb), calc(.22 * var(--lift-k)));
  margin: 2px auto 14px;
}
.bottom-sheet-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-bottom: 18px;
}
.bottom-sheet-title {
  font-size: 16px; font-weight: 700; color: var(--text-bright);
  letter-spacing: -.01em;
}
.bottom-sheet-sub { font-size: 12.5px; color: var(--muted); margin-top: 3px; line-height: 1.45; }

.speed-scale {
  display: flex; justify-content: space-between;
  margin-top: 10px;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 9.5px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint);
}

/* In a sheet the slider is the content, not a pill squeezed into a row. */
#speed-sheet .quantacode-slider-pill {
  width: 100%;
  max-width: none;
  padding: 14px 16px 12px;
  border-radius: 16px;
}

/* ==================================================================== *
 * iOS VIEWPORT — driven by window.visualViewport, not a CSS unit
 *
 * The bug, finally pinned down: BOTH `100dvh` AND `position:fixed;inset:0`
 * resolve against the LAYOUT viewport on iOS, which is the toolbar-HIDDEN
 * height. While Safari's bottom toolbar is up, that is taller than the area
 * actually painted, so the composer is laid out below the real bottom and a
 * dark dead band appears under the meter strip. Hiding the toolbar makes the
 * two heights agree — which is exactly the reported symptom: "hide the toolbar
 * and the meters drop to the bottom where they belong."
 *
 * The only thing that reports the REAL painted height — and updates as the
 * toolbar shows, hides or minimises, and as the keyboard opens — is
 * window.visualViewport. The tiny script in index.html writes its height to
 * --app-h on load and on every viewport change; the body is exactly that tall,
 * so the composer always sits on the true bottom edge, toolbar up or down, in
 * Safari and in a standalone home-screen app. It is the programmatic
 * equivalent of tapping Safari's own hide-toolbar control, run continuously.
 * `100dvh` stays as the first-frame fallback and for anything without the API.
 * ==================================================================== */

html {
  height: 100%;
  overscroll-behavior: none;
}

body {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: auto;                  /* height is explicit, NOT stretched to the layout-viewport bottom */
  height: var(--app-h, 100dvh);  /* the script sets --app-h from visualViewport; dvh is the fallback */
  width: 100%;
  overflow: hidden;
}

/* The composer clears the home indicator and nothing more. The 8px that
   used to be added on top of the inset read as an unexplained gap under
   the meter strip. */
.composer {
  padding-bottom: max(10px, env(safe-area-inset-bottom));
}

/* ---- model picker sheet: match the speed sheet exactly ---------------- *
 * Both are bottom sheets, so both should read as the same object. This
 * lifts the picker off the very bottom edge, gives it a grip, and clears
 * the home indicator so the last model in the list is never half-hidden
 * behind it.
 * --------------------------------------------------------------------- */
.sheet {
  /* Centered floating modal — lifted off the bottom edge. Was a full-width
     bottom-anchored sheet ("looks gross" per founder); now a card centered in
     the viewport. */
  top: 50% !important;
  left: 50% !important;
  right: auto !important;
  bottom: auto !important;
  transform: translate(-50%, -50%) !important;
  width: min(460px, calc(100vw - 32px)) !important;
  max-height: min(76vh, 660px) !important;
  padding: 18px 18px calc(18px + env(safe-area-inset-bottom)) !important;
  border-radius: 22px !important;
  background: rgba(var(--surf-17), .96) !important;
  border: 1px solid var(--line-strong) !important;
  box-shadow: 0 30px 80px -24px rgba(var(--shade-rgb), calc(.9 * var(--shadow-k))) !important;
}
.sheet::before {
  /* Was a second grip bar stacked on top of .sheet-grip — the "two bars" the
     founder saw. The centered modal wants no grip at all. */
  display: none;
}
.sheet-item {
  border-radius: 14px;
  padding: 13px 14px;
  gap: 12px;
}
.sheet-item.active { background: rgba(56,189,248,.10); }

/* ---- flex basis: let the thread yield, never the composer ------------- *
 * `.view` was `flex: 1 1 auto`, so its content height took part in sizing
 * the column. On a short viewport that pushed the composer past the bottom
 * edge — measured at 824px inside an 812px viewport, hiding the last 12px
 * of the meter strip behind the home indicator.
 *
 * A zero basis makes the thread take only the space left over after the
 * fixed furniture, so the composer is always fully on screen and the
 * conversation is what scrolls.
 * --------------------------------------------------------------------- */
.view {
  flex: 1 1 0 !important;
  min-height: 0;
}
.composer,
.bar {
  flex: 0 0 auto;
}


/* Belt and braces: nothing structural may hold a transform. */
.view, .composer, .studio-dock, .bar { transform: none !important; }

/* ==================================================================== *
 * FIX: sheet clipping + slider orientation
 *
 * `contain: layout paint` on .composer was doing two harmful things at
 * once — making the composer a containing block for position:fixed
 * descendants, and clipping anything drawn outside its box. The speed
 * sheet lives inside the composer, so it was being cut off at the top.
 * Containment stays where it pays (the scrolling thread) and comes off
 * the furniture that has to host overlays.
 * ==================================================================== */
.composer,
.bar {
  contain: none !important;
}

/* The slider was authored as a horizontal dock pill. In a sheet it is the
   content, so it stacks: header, track, scale. Without this its children
   sit on one line and the scale labels run together into one word. */
#speed-sheet .quantacode-slider-pill {
  display: flex !important;
  flex-direction: column !important;
  align-items: stretch !important;
  gap: 12px;
  width: 100%;
  max-width: none;
  min-width: 0;
  padding: 16px;
  border-radius: 16px;
}
#speed-sheet .slider-mini-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}
#speed-sheet .slider-track-container {
  position: relative;
  width: 100%;
  min-width: 0;
  height: 30px;
}
#speed-sheet .slider-real-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  margin: 0;
}
#speed-sheet .speed-scale {
  display: flex !important;
  justify-content: space-between;
  width: 100%;
  gap: 8px;
}
#speed-sheet .speed-scale span { flex: 0 0 auto; white-space: nowrap; }

/* Give the sheet room and let it sit above everything. */
#speed-sheet {
  z-index: 140;
  padding-top: 12px;
}

/* deploy marker 1787773942 */

/* ==================================================================== *
 * POPOVER HEADER — the component that was never written
 *
 * Three surfaces mark up a header with .popover-header / .popover-title-row
 * / .popover-quantum-icon / .popover-title / .popover-subtitle:
 *
 *     · the account + auth panel        (#account-modal)
 *     · the reasoning inspector         (#thinking-popover)
 *     · the Plans modal                 (#pricing-modal)
 *
 * None of those class names had a single rule in this file — the word
 * "popover" appeared in it exactly once, in a section comment. So all three
 * rendered as an unstyled icon with raw text stacked beside it and the close
 * button dropped into the flow underneath. It read as a broken image, which
 * is precisely what it was reported as.
 *
 * Written once here, on purpose: these three are the same component and must
 * not drift apart again.
 * ==================================================================== */

.popover-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 16px 14px 16px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(56, 189, 248, 0.05) 0%, rgba(56, 189, 248, 0) 100%);
}

.popover-title-row {
  display: flex;
  align-items: center;
  gap: 12px;
  /* Without this a long subtitle refuses to wrap and pushes the close
     button off the panel instead of shrinking. */
  min-width: 0;
}

/* The "armour button": a 38px glass tile, not a bare 18px stroke glyph
   floating in the corner. */
.popover-quantum-icon {
  width: 38px;
  height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 11px;
  color: var(--accent);
  background: linear-gradient(140deg, rgba(56, 189, 248, 0.16) 0%, rgba(129, 140, 248, 0.10) 100%);
  border: 1px solid rgba(56, 189, 248, 0.30);
  box-shadow: var(--glass-specular), 0 2px 10px rgba(var(--shade-rgb), calc(0.35 * var(--shadow-k)));
}
.popover-quantum-icon .ico {
  width: 19px;
  height: 19px;
}

.popover-title {
  font-size: 14.5px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--text-bright);
}

.popover-subtitle {
  margin-top: 3px;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--muted);
}

.popover-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

/* ==================================================================== *
 * REASONING INSPECTOR SHELL
 * #thinking-popover had no rules either, so opening it dropped an
 * unstyled block into the page flow.
 * ==================================================================== */

.thinking-popover {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
}
.thinking-popover.hidden {
  display: none !important;
}

.popover-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1;
  background: rgba(var(--shade-rgb), 0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  animation: fadeIn var(--dur-fast) ease;
}

.popover-panel {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  width: min(680px, 94vw);
  max-height: min(78vh, 720px);
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: rgba(var(--surf-12), 0.92);
  backdrop-filter: var(--glass-blur-heavy);
  -webkit-backdrop-filter: var(--glass-blur-heavy);
  border: 1px solid rgba(56, 189, 248, 0.28);
  box-shadow: 0 16px 48px rgba(var(--shade-rgb), calc(0.8 * var(--shadow-k))), 0 0 30px rgba(56, 189, 248, 0.16);
  animation: dropdownSlideScale var(--dur) var(--ease-out);
}

.popover-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 16px 18px 20px 18px;
  font-size: 13px;
  line-height: 1.68;
  color: var(--text);
  white-space: pre-wrap;
  word-break: break-word;
}

/* ==================================================================== *
 * ACCOUNT PANEL — stop it stretching to the full window height
 *
 * .account-dropdown-wrapper is a flex row, and the default align-items on a
 * flex container is `stretch`. With no rule to say otherwise the panel was
 * pulled to the full height of the viewport, which is why the content sat
 * bunched at the top above a large empty area. It should hug its content.
 * ==================================================================== */

.account-dropdown-wrapper {
  align-items: flex-start;
}

/* ==================================================================== *
 * A STOPPED REPLY
 * The partial answer is kept and labelled rather than replaced with an
 * error, so stopping reads as the button working, not as a crash.
 * ==================================================================== */

.msg-stopped-note {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 7px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--faint);
}
.msg-stopped-note::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background: currentColor;
}

/* A route into Settings from the account panel — sits between the identity
   block and the destructive actions, so it reads as navigation rather than
   as one more button in the danger row. */
.profile-settings-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin: 0 0 10px 0;
  padding: 11px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.profile-settings-row:hover {
  background: var(--raised-hover);
  border-color: var(--line-glow);
}
.profile-settings-row .ico {
  color: var(--muted);
}
.profile-settings-label {
  flex: 1 1 auto;
}
.profile-settings-chevron {
  width: 15px;
  height: 15px;
  opacity: 0.7;
}

/* A visible way out of Settings.
   Settings is reachable from the account panel, which is reachable on the
   welcome screen — where the mini rail and the hamburger are deliberately
   hidden. That combination left no exit at all: you could get in and not
   get back. Shown always rather than only when the rail is hidden, because
   a control that appears conditionally is one people learn not to look for. */
.settings-back {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 0 0 14px -6px;
  padding: 6px 11px 6px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.settings-back:hover {
  background: var(--raised-hover);
  border-color: var(--line-glow);
  color: var(--text-bright);
}
/* The sprite has no left chevron; this is the right one turned around. */
.settings-back .ico {
  width: 15px;
  height: 15px;
  transform: rotate(180deg);
}

/* ==================================================================== *
 * THEMING — channels, and the light palette
 *
 * The app was built dark-only: 144 colour literals hard-coded white or
 * black, and no theme scaffolding of any kind. Rather than snap ~33 distinct
 * alpha values onto a tidy scale — which would have quietly redesigned the
 * app in a hundred places — the sweep replaced only the CHANNEL and left
 * every alpha exactly as it was:
 *
 *     rgba(255,255,255,0.08)  ->  rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)))
 *
 * Three channels, because these three behave differently when the lights
 * come on and conflating them is what makes a light theme look cheap:
 *
 *   --lift-rgb    a raised surface or a hairline border. INVERTS: white on
 *                 black becomes near-black on white.
 *   --gloss-rgb   specular highlight along a glass edge. Light reflecting off
 *                 a surface is white on a dark ground AND on a light one, so
 *                 this NEVER inverts. Inverting it is what turns frosted
 *                 glass into a dirty smudge.
 *   --shade-rgb   cast shadows and modal scrims. Stays dark in both themes —
 *                 a light UI still has dark shadows — but shadows need to be
 *                 far softer on white, which is what --shadow-k is for.
 *
 * The k multipliers exist so the whole theme can be tuned from two numbers
 * instead of by editing 129 declarations.
 * ==================================================================== */

:root {
  color-scheme: dark;
  --lift-rgb: 255, 255, 255;
  --lift-k: 1;
  --gloss-rgb: 255, 255, 255;
  --shade-rgb: 0, 0, 0;
  --shadow-k: 1;
  /* Text that sits ON an accent fill. White on both themes, because the fill
     underneath it is the same colour in both. */
  --on-accent: #ffffff;
}

/* -------------------------------------------------------------------- *
 * LIGHT
 *
 * Designed, not inverted. A pure #fff ground under a glass UI reads as an
 * unfinished wireframe, so the page is a soft cool white with a light
 * gradient, panels are near-white frosted glass, and the text is slate
 * rather than black — black on white at this text size is harsh to read for
 * any length of time.
 *
 * The accent moves from sky-400 to sky-600. Same hue, same identity, but
 * #38bdf8 on white is roughly 1.9:1 — unreadable as text. #0284c7 clears
 * 4.5:1 while still being unmistakably the brand.
 * -------------------------------------------------------------------- */

:root[data-theme="light"] {
  color-scheme: light;

  --lift-rgb: 15, 23, 42;     /* slate-900: raised tints and hairlines invert */
  --lift-k: 0.85;             /* a dark tint on white reads stronger than the reverse */
  --gloss-rgb: 255, 255, 255; /* unchanged, by design */
  --shade-rgb: 2, 6, 23;      /* slate-950: cooler, softer shadow than pure black */
  --shadow-k: 0.28;           /* the single biggest lever on how light this feels */

  --bg: #f6f8fc;
  --bg-deep: #eaeff7;
  --bg-gradient: radial-gradient(circle at 50% 0%, #ffffff 0%, #eaf0f9 80%);

  --panel: rgba(255, 255, 255, 0.74);
  --panel-solid: #ffffff;
  --panel-bright: #f3f6fb;

  --glass-surface: rgba(255, 255, 255, 0.70);
  --glass-surface-hover: rgba(255, 255, 255, 0.88);
  --glass-surface-active: rgba(237, 243, 251, 0.94);
  --glass-dock: rgba(255, 255, 255, 0.82);
  --glass-card: rgba(255, 255, 255, 0.66);

  --line-strong: rgba(2, 132, 199, 0.42);
  --line-glow: rgba(2, 132, 199, 0.22);

  --glass-specular: inset 0 1px 0 rgba(255, 255, 255, 0.9), inset 0 0 16px rgba(2, 132, 199, 0.03);
  --glass-specular-bright: inset 0 1px 0 rgba(255, 255, 255, 1), 0 0 20px rgba(2, 132, 199, 0.12);
  --glass-shadow: 0 12px 30px 0 rgba(2, 6, 23, 0.10);

  --text: #101828;
  --text-bright: #05070f;
  --muted: #4a5568;           /* 7.0:1 on the page ground */
  --faint: #5f697d;           /* the speed-scale labels sit on a raised card, which is lighter than the page - this clears AA there too */

  --user-bubble: linear-gradient(135deg, #e9eff9 0%, #dfe8f5 100%);

  --accent: #075985;
  --accent-hover: #0c4a6e;
  --accent-purple: #6366f1;
  --cyan: #075985;
  --violet: #7c3aed;
  --emerald: #047857;
  --green: #047857;
  --amber: #b45309;
  --red: #b91c1c;
  --indigo: #4f46e5;
}

/* The page ground itself. Set explicitly so nothing inherits a dark body. */
:root[data-theme="light"] body {
  background: var(--bg-gradient);
  color: var(--text);
}

/* Theme picker. A segmented control, not a dropdown: three mutually
   exclusive options someone switches between and compares, where seeing all
   three at once is the whole point. */
.theme-segmented {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border-radius: var(--radius-pill);
  background: var(--raised);
  border: 1px solid var(--line);
}
.theme-seg {
  padding: 6px 15px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.theme-seg:hover { color: var(--text-bright); }
.theme-seg.active {
  background: var(--glass-surface-active);
  color: var(--text-bright);
  box-shadow: var(--glass-specular),
              0 1px 3px rgba(var(--shade-rgb), calc(0.30 * var(--shadow-k)));
}

/* Reduce motion. Honoured from the setting AND from the device, because
   someone who has already asked their OS for less motion should not have to
   ask again here. Durations are cut rather than set to 0 so anything that
   waits on transitionend/animationend still fires and does not hang. */
:root[data-reduce-motion] .tb-soft  { opacity: 0 !important; animation: none !important; }
:root[data-reduce-motion] .tb-clear { opacity: 1 !important; animation: none !important; }

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

/* ==================================================================== *
 * SURFACES THAT ARE DARK IN EVERY THEME
 *
 * A video player, a code editor, a terminal and the immersive voice stage
 * stay dark when the lights come on — that is what every other product does,
 * and it is correct: you do not watch video on white.
 *
 * The trap is what lives INSIDE them. Their contents were written against
 * dark tokens, so in the light theme --text-bright resolves to near-black and
 * the labels vanish into the black surface. Rather than hand-picking colours
 * for every descendant, the dark token set is simply re-declared on these
 * subtrees: everything below inherits it, including borders and raised tints,
 * and any future child is correct without anyone remembering this rule.
 * ==================================================================== */

:root {
  --surface-inverse: #000000;
  --drawer-gradient: radial-gradient(circle at 0% 0%, #0d1222 0%, #05070c 100%);
}

:root[data-theme="light"] {
  --drawer-gradient: radial-gradient(circle at 0% 0%, #ffffff 0%, #eef3fa 100%);
}

.chat-media-card,
.chat-media-video,
.chat-player-viewport,
.chat-player-code-drawer,
.web-snapshot-viewport,
.spotlight-viewport,
.music-visualizer-box,
.code-editor-wrap,
.ide-terminal-card,
.artifact-panel,
.art-pre,
.toast,
.live-voice-stage,
.voice-backdrop,
.stage-video-card,
.video-ambient-player {
  --text: #f1f5f9;
  --text-bright: #ffffff;
  --muted: #94a3b8;
  --faint: #94a3b8;
  --lift-rgb: 255, 255, 255;
  --lift-k: 1;
  --shade-rgb: 0, 0, 0;
  color: var(--text);
}

/* ==================================================================== *
 * SURFACE RAMP
 *
 * Panel backgrounds were hard-coded as dark navy rgba() literals instead of
 * going through a token, so they stayed dark when the theme flipped - which
 * is what put near-black text on near-black panels in the first light audit.
 *
 * Every surface keeps its EXACT dark value, so the dark theme is unchanged.
 * The light counterparts are generated from the same ordering: a surface
 * sitting further back in the dark theme (darker) sits further back in the
 * light one (greyer), and a raised surface becomes whiter. Depth ordering is
 * preserved rather than flattened - the difference between a light theme
 * with real hierarchy and a white rectangle.
 * ==================================================================== */

:root {
  --surf-1: 2, 4, 10;
  --surf-2: 3, 5, 10;
  --surf-3: 4, 6, 12;
  --surf-4: 3, 6, 15;
  --surf-5: 2, 6, 23;
  --surf-6: 6, 8, 14;
  --surf-7: 8, 10, 20;
  --surf-8: 10, 11, 16;
  --surf-9: 8, 11, 22;
  --surf-10: 8, 11, 24;
  --surf-11: 9, 12, 20;
  --surf-12: 10, 13, 26;
  --surf-13: 10, 14, 26;
  --surf-14: 14, 12, 32;
  --surf-15: 10, 14, 28;
  --surf-16: 10, 14, 30;
  --surf-17: 13, 16, 26;
  --surf-18: 13, 16, 32;
  --surf-19: 12, 16, 36;
  --surf-20: 14, 17, 28;
  --surf-21: 14, 18, 30;
  --surf-22: 14, 18, 34;
  --surf-23: 14, 18, 36;
  --surf-24: 15, 19, 32;
  --surf-25: 14, 19, 36;
  --surf-26: 15, 20, 40;
  --surf-27: 18, 22, 36;
  --surf-28: 18, 22, 38;
  --surf-29: 18, 23, 38;
  --surf-30: 15, 23, 42;
  --surf-31: 20, 25, 42;
  --surf-32: 30, 20, 55;
  --surf-33: 22, 27, 44;
  --surf-34: 22, 28, 46;
  --surf-35: 22, 28, 48;
  --surf-36: 20, 28, 60;
  --surf-37: 24, 30, 48;
  --surf-38: 24, 30, 52;
  --surf-39: 28, 35, 56;
  --surf-40: 28, 36, 56;
  --surf-41: 28, 36, 60;
  --surf-42: 30, 37, 60;
  --surf-43: 30, 38, 60;
  --surf-44: 30, 38, 62;
  --surf-45: 36, 44, 72;
  --surf-46: 35, 45, 75;
  --surf-47: 35, 46, 76;
}

:root[data-theme="light"] {
  --surf-1: 234, 240, 248;
  --surf-2: 234, 240, 248;
  --surf-3: 234, 240, 248;
  --surf-4: 234, 240, 248;
  --surf-5: 235, 240, 248;
  --surf-6: 235, 241, 248;
  --surf-7: 236, 241, 249;
  --surf-8: 236, 241, 249;
  --surf-9: 236, 241, 249;
  --surf-10: 236, 241, 249;
  --surf-11: 236, 241, 249;
  --surf-12: 236, 242, 249;
  --surf-13: 237, 242, 249;
  --surf-14: 237, 242, 249;
  --surf-15: 237, 242, 249;
  --surf-16: 237, 242, 249;
  --surf-17: 237, 242, 249;
  --surf-18: 237, 242, 249;
  --surf-19: 238, 243, 249;
  --surf-20: 238, 243, 249;
  --surf-21: 238, 243, 249;
  --surf-22: 238, 243, 249;
  --surf-23: 238, 243, 249;
  --surf-24: 238, 243, 249;
  --surf-25: 238, 243, 249;
  --surf-26: 239, 244, 250;
  --surf-27: 239, 244, 250;
  --surf-28: 240, 244, 250;
  --surf-29: 240, 244, 250;
  --surf-30: 240, 244, 250;
  --surf-31: 241, 245, 250;
  --surf-32: 241, 245, 250;
  --surf-33: 242, 246, 251;
  --surf-34: 242, 246, 251;
  --surf-35: 242, 246, 251;
  --surf-36: 243, 247, 251;
  --surf-37: 243, 247, 251;
  --surf-38: 244, 247, 251;
  --surf-39: 246, 249, 252;
  --surf-40: 247, 249, 252;
  --surf-41: 247, 249, 252;
  --surf-42: 248, 250, 253;
  --surf-43: 248, 250, 253;
  --surf-44: 249, 250, 253;
  --surf-45: 254, 254, 255;
  --surf-46: 254, 254, 255;
  --surf-47: 255, 255, 255;
}

/* The accent fill that carries white text.
   Deliberately deeper than --accent-gradient: white on #38bdf8 is about
   1.9:1, which is not readable at avatar-initial or button-label size. This
   keeps the same blue identity while clearing AA, and it applies to both
   themes because the fill is the same colour in both. */
:root {
  --accent-fill: linear-gradient(135deg, #075985 0%, #0284c7 100%);
}

/* ==================================================================== *
 * THE SPEED CARD - grows out of the Flow pill, collapses back into it
 *
 * It was a bottom sheet: a bar pinned to the bottom edge of the window,
 * unrelated in space to the control that opened it. Anchoring it above the
 * pill and scaling from the pill's centre is what makes the two read as one
 * object - you press the pill, the pill becomes the card, closing puts it
 * back.
 *
 * --pop-origin-x is written by app.js on every open: the pill's centre
 * measured in the card's own coordinates, so the scale still runs toward the
 * pill when the card has been clamped away from centre near a window edge.
 *
 * Appended after the earlier #speed-sheet rule on purpose - later wins.
 * ==================================================================== */

#speed-sheet {
  /* undo the bottom-sheet docking: this one is anchored, not docked */
  left: auto;
  right: auto;
  top: auto;
  width: min(420px, calc(100vw - 24px));
  max-height: min(70vh, 560px);
  padding: 12px 18px 18px;
  border: 1px solid var(--line-strong);
  border-radius: 22px;
  transform-origin: var(--pop-origin-x, 50%) 100%;
  box-shadow: 0 18px 50px -12px rgba(var(--shade-rgb), calc(0.85 * var(--shadow-k))),
              0 0 0 1px rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  animation: none;   /* the shared sheetIn slide would fight the pop */
}

/* A grip is the affordance for a sheet you drag up. This is a popover, so
   showing one would be a lie about how it works. */
#speed-sheet .bottom-sheet-grip { display: none; }

/* A small tail, so the card visibly belongs to the pill beneath it. */
#speed-sheet::after {
  content: '';
  position: absolute;
  bottom: -6px;
  left: calc(var(--pop-origin-x, 50%) - 6px);
  width: 12px;
  height: 12px;
  background: rgba(var(--surf-17), .96);
  border-right: 1px solid var(--line-strong);
  border-bottom: 1px solid var(--line-strong);
  transform: rotate(45deg);
  border-bottom-right-radius: 3px;
}

#speed-sheet.popping-in  { animation: speedPopIn 260ms var(--ease-out) both; }
#speed-sheet.popping-out { animation: speedPopOut 190ms var(--ease-in-out) both; }

@keyframes speedPopIn {
  0%   { opacity: 0; transform: translateY(8px) scale(0.82); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes speedPopOut {
  0%   { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(6px) scale(0.86); }
}

/* The pill stays lit while its card is open, so the pair reads as one
   control instead of a card floating loose above the dock. */
.speed-gear-pill.sheet-open {
  border-color: var(--line-strong);
  background: var(--glass-surface-active);
  color: var(--text-bright);
}
.speed-gear-pill .pill-caret { transition: transform var(--dur) var(--ease-out); }
.speed-gear-pill.sheet-open .pill-caret { transform: rotate(180deg); }

/* The desktop rule that centred the old bottom sheet must not drag this one
   back into the middle of the window. */
@media (min-width: 760px) {
  #speed-sheet {
    left: auto;
    right: auto;
    margin: 0;
    max-width: none;
    width: min(420px, calc(100vw - 24px));
  }
}

/* ==================================================================== *
 * TEXT SIZE, DENSITY AND CHAT WIDTH
 *
 * Scaled from the existing sizes rather than replacing them, so the
 * relationship between the user bubble (15px) and the model's reply (15.5px)
 * survives at every step - a flat override would flatten that difference and
 * make the two speakers look the same.
 * ==================================================================== */

:root {
  --msg-scale: 1;
  --thread-gap: 20px;
  --bubble-pad-y: 10px;
  /* The reading column. Every element that has to line up with the
     transcript - the composer, the input row, the attachment tray, the
     meter strip - reads this one value, so they can never drift apart. */
  --thread-max: 920px;
}

:root[data-text-size="s"]  { --msg-scale: 0.88; }
:root[data-text-size="l"]  { --msg-scale: 1.10; }
:root[data-text-size="xl"] { --msg-scale: 1.22; }

/* Chat width. Narrow is a book column - shortest line, easiest to read.
   Default is the 920px the app has always used on a large screen. Wide
   still leaves real margin on a 1080p display (1920px, minus the expanded
   sidebar, leaves roughly 190px either side) rather than running edge to
   edge, which is what makes a wide setting unreadable elsewhere. */
:root[data-chat-width="narrow"] { --thread-max: 720px; }
:root[data-chat-width="medium"] { --thread-max: 920px; }
:root[data-chat-width="wide"]   { --thread-max: 1280px; }

:root[data-density="compact"] {
  --thread-gap: 11px;
  --bubble-pad-y: 7px;
}

.thread { gap: var(--thread-gap); }
.msg.user .bubble  { font-size: calc(15px * var(--msg-scale)); padding-top: var(--bubble-pad-y); padding-bottom: var(--bubble-pad-y); }
.msg.model .bubble { font-size: calc(15.5px * var(--msg-scale)); }
/* The composer follows the reading size, or you end up typing at one size
   and reading the answer at another. */
textarea#input { font-size: calc(15px * var(--msg-scale)); }
/* Paragraph rhythm inside a reply tightens with the density setting too. */
:root[data-density="compact"] .msg.model .bubble p { margin-bottom: 8px; }
:root[data-density="compact"] .msg.model { margin-top: 0; }

/* ==================================================================== *
 * CONVERSATION ROW - menu, rename, archive, unsaved
 * ==================================================================== */

.chat-history-item { position: relative; }

.chat-menu-btn {
  flex: none;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
/* Revealed on hover on a pointer device, but ALWAYS present on touch, where
   there is no hover and a hidden control is simply a missing one. */
.chat-history-item:hover .chat-menu-btn,
.chat-history-item.active .chat-menu-btn { opacity: 1; }
@media (hover: none) { .chat-menu-btn { opacity: 1; } }
.chat-menu-btn:hover { background: var(--raised-hover); color: var(--text-bright); }

.chat-row-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 6px;
  z-index: 40;
  min-width: 208px;
  padding: 5px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  border-radius: 13px;
  background: rgba(var(--surf-20), 0.97);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line-strong);
  box-shadow: 0 14px 34px -10px rgba(var(--shade-rgb), calc(0.8 * var(--shadow-k)));
  animation: chatMenuIn 150ms var(--ease-out) both;
}
@keyframes chatMenuIn {
  from { opacity: 0; transform: translateY(-4px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.chat-row-menu-item {
  text-align: left;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.chat-row-menu-item:hover { background: var(--raised-hover); }
.chat-row-menu-item.danger { color: var(--red); }
.chat-row-menu-item.danger[data-armed="1"] {
  background: rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  font-weight: 700;
}

.chat-rename-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 4px 7px;
  border-radius: 7px;
  border: 1px solid var(--line-strong);
  background: var(--panel-solid);
  color: var(--text-bright);
  font-size: 12.5px;
  font-family: inherit;
}
.chat-rename-input:focus { outline: none; border-color: var(--accent); }

/* An unsaved conversation says so on its own row. A mode with no visible
   marker is a mode people forget they are in. */
.chat-unsaved-mark {
  flex: none;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--amber);
  border: 1px solid var(--amber);
  border-radius: 5px;
  padding: 1px 4px;
  opacity: 0.85;
}
.chat-history-item.ephemeral .chat-title-text { font-style: italic; }

.chat-list-empty {
  padding: 14px 10px;
  font-size: 12px;
  color: var(--faint);
  text-align: center;
}

.chat-archive-toggle {
  width: 100%;
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.chat-archive-toggle:hover { background: var(--raised); color: var(--text-bright); }

/* ==================================================================== *
 * NATIVE SELECT POPUPS
 *
 * The select had a TRANSLUCENT background. A translucent value is fine for
 * the closed control, but the popup list the OS draws is a native widget: it
 * takes the background literally, ends up near-white, and then inherits
 * --text-bright - white text on white. The list was unreadable.
 *
 * An opaque background plus explicitly coloured options fixes it on every
 * platform, and color-scheme tells the OS which way to draw the scrollbar
 * and the highlight.
 * ==================================================================== */

.settings-select {
  background-color: var(--panel-solid);
  background-image: none;
  color: var(--text-bright);
  color-scheme: dark;
}
:root[data-theme="light"] .settings-select { color-scheme: light; }

.settings-select option,
.settings-select optgroup {
  background-color: var(--panel-solid);
  color: var(--text);
}

/* ==================================================================== *
 * SETTINGS AS A CENTRED PANEL (desktop)
 *
 * Full-screen settings hides the app behind it and reads as navigating away
 * rather than as opening a dialog. On a desktop-sized window it is now a
 * centred card over a dimmed app, the way a desktop app does it. Below 760px
 * it stays full-screen, which is correct on a phone.
 * ==================================================================== */

@media (min-width: 760px) {
  #view-settings:not(.hidden) {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px 20px;
    width: 100%;
    max-width: none;
    overflow: hidden;
    background: rgba(var(--shade-rgb), 0.55);
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    animation: settingsFade 160ms ease both;
  }

  #view-settings .settings-scroll {
    flex: 0 1 auto;
    width: min(760px, 100%);
    max-height: min(82vh, 800px);
    padding: 0 28px calc(30px);
    border-radius: 24px;
    background: rgba(var(--surf-17), 0.98);
    border: 1px solid var(--line-strong);
    box-shadow: 0 26px 64px -16px rgba(var(--shade-rgb), calc(0.85 * var(--shadow-k))),
                var(--glass-specular);
    animation: settingsPopIn 220ms var(--ease-out) both;
  }

  /* The masthead is the panel's header, so it stays put while the body
     scrolls under it. */
  #view-settings .settings-masthead {
    position: sticky;
    top: 0;
    z-index: 2;
    padding: 22px 4px 16px;
    background: rgba(var(--surf-17), 0.98);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
  }
}

@keyframes settingsFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes settingsPopIn {
  from { opacity: 0; transform: translateY(10px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* One tab left is not a choice, so the strip stops looking like one. */
.account-tabs.single .account-tab.hidden { display: none !important; }

/* ==================================================================== *
 * LIVE VOICE STAGE - layout
 *
 * HISTORY, BECAUSE THE PREVIOUS FIX SILENTLY DID NOTHING
 * The stage began as `justify-content: space-between` over its direct
 * children, which threw the bar, orb, captions and dock to the extremes of a
 * tall window and left a dead void through the middle. The replacement lifted
 * the bar and the dock out of the flow with `position: absolute` so the middle
 * could centre as one unit - and then constrained the middle with selectors
 * (`.live-voice-stage > .voice-caption-user`, `.voice-text-prompt-row`) that
 * match NOTHING in the markup: the captions live inside
 * `.voice-captions-container`, and `.voice-text-prompt-row` does not exist.
 * The measured result on the live site was worse than the original:
 *
 *   · the top bar was over-constrained (`width: 100%` WITH `left` and
 *     `right`, so `right` is ignored) and ran 20px past the viewport - the
 *     close button was off-screen and unreachable on a phone
 *   · the dock's four buttons measured -27px to 402px inside a 335px pill:
 *     out of the glass on both sides, and off both screen edges
 *   · at 620px tall the typed prompt bar OVERLAPPED the dock by 18px
 *
 * WHY THIS VERSION CANNOT DO THAT
 * Absolute chrome over centred flow content means the two are laid out in
 * ignorance of each other, so any change in either one's height reopens the
 * overlap. This is an explicit grid instead: five rows, every child assigned
 * to one, and only the orb's row elastic. Overlap stops being a bug to fix and
 * becomes a state the layout cannot express.
 * ==================================================================== */

.live-voice-stage {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto auto auto;
  grid-template-columns: 100%;
  justify-items: center;
  align-content: stretch;
  gap: 16px;
}

/* `.voice-backdrop` is absolutely positioned, so it takes no grid cell. */
.voice-top-bar          { grid-row: 1; }
.voice-center-stage     { grid-row: 2; }
.voice-captions-container { grid-row: 3; }
.voice-quick-input-bar  { grid-row: 4; }
.voice-controls-dock    { grid-row: 5; }

/* Back in the flow. `width: 100%` no longer fights `left`/`right`, because
   there is no longer a `left`/`right` to fight: the stage's own 20px padding
   positions it, and a percentage cannot overflow a padding box. */
.voice-top-bar {
  position: static;
  z-index: 12;
  width: 100%;
  max-width: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  transform: none;
}

/* The elastic row. It absorbs the spare height so the orb sits optically
   centred, and `min-height: 0` lets it yield first on a short window instead
   of pushing the dock off the bottom.

   `align-self: stretch` + `overflow: hidden` is the part that matters. With
   `align-self: center` the group was CENTRED IN a row it was taller than, so
   it spilled equally out of both ends - measured at 360x640, a 290px group in
   a 229px row put the orb 15px above the status bar. Stretching it to the row
   and clipping means the worst case is a trimmed outer glow ring rather than
   an orb drawn over the status text. */
.voice-center-stage {
  align-self: stretch;
  min-height: 0;
  overflow: hidden;
  width: 100%;
  gap: 14px;
}

/* The orb tracks the window rather than insisting on 230px. On a short laptop
   window the fixed size was what forced the middle group into the dock. */
.voice-orb-container {
  width: min(230px, 30vh);
  height: min(230px, 30vh);
  flex: 0 0 auto;
  margin-bottom: 8px;
}

/* THE STATUS IS A LIGHT, NOT A SENTENCE.
   It began as "LISTENING..." restating what the orb already said, and it could
   never be centred - the bar is `space-between` over three items of different
   widths, so the middle one lands wherever its siblings leave it. Worse, once
   the wording became honest it became LONG, and the top bar truncated it to
   "COULD NOT START LIS..." - a status that cannot even finish saying what is
   wrong.

   A dot cannot truncate, cannot be off-centre, and needs no translation:

       red    something is wrong - not connected
       blue   connected, holding (muted)
       green  live

   The words are NOT thrown away. `font-size: 0` keeps the exact same text in
   the accessibility tree for screen readers, so the light is a visual summary
   of a message that is still, literally, there. The full explanation lives in
   the caption below, where there is room for it. */
.voice-top-bar { position: relative; }

.voice-top-bar .voice-status-text {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 10px;
  height: 10px;
  padding: 0;
  border-radius: 50%;
  /* The label survives for assistive tech; only its glyphs are gone. */
  font-size: 0;
  line-height: 0;
  overflow: hidden;
  background: var(--faint);
  transition: background 0.3s ease, box-shadow 0.3s ease;
}

/* LIVE - the session is running: hearing you, thinking, or answering. */
.voice-top-bar .voice-status-text.listening,
.voice-top-bar .voice-status-text.thinking,
.voice-top-bar .voice-status-text.speaking {
  background: #22c55e;
  box-shadow: 0 0 9px rgba(34, 197, 94, 0.75);
}

/* CONNECTED, but deliberately holding. */
.voice-top-bar .voice-status-text.muted {
  background: #38bdf8;
  box-shadow: 0 0 8px rgba(56, 189, 248, 0.6);
}

/* NOT CONNECTED - and this is the one that must never be missed. */
.voice-top-bar .voice-status-text.halted {
  background: #ef4444;
  box-shadow: 0 0 10px rgba(239, 68, 68, 0.8);
  animation: voiceFaultPulse 1.7s ease-in-out infinite;
}

@keyframes voiceFaultPulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* One light, not two. The wordmark's own dot became redundant the moment the
   centre one started carrying the state. */
.voice-model-dot { display: none; }
.voice-top-bar .voice-model-pill {
  min-width: 0;
  flex: 0 1 auto;
  overflow: hidden;
  white-space: nowrap;
}

/* Short windows - a laptop with devtools open, or a phone in landscape. The
   waveform is decoration and goes first; the orb and the captions tighten so
   the conversation itself keeps its room. */
@media (max-height: 680px) {
  .live-voice-stage { gap: 10px; }
  .voice-canvas-wave { display: none; }
  .voice-captions-container { max-height: 40vh; }
  .voice-orb-container {
    width: min(170px, 26vh);
    height: min(170px, 26vh);
    margin-bottom: 4px;
  }
}

/* One measure for the whole conversation column, applied to the elements that
   actually exist this time. */
.voice-captions-container,
.voice-quick-input-bar {
  width: min(560px, 100%);
  margin: 0;
}

/* THE TYPED PROMPT WAS INVISIBLE, AND HAD BEEN ALL ALONG.
   Every other child of the stage carries `position: relative` and a z-index;
   this one was left `static` with none. A positioned element paints above a
   non-positioned one regardless of source order, so `.voice-backdrop`
   (absolute, z-index 0) laid its vignette straight over the input - which is
   almost opaque by the bottom of the screen, where this sits.
   It was hit-testable the whole time, so it measured perfectly fine and simply
   could not be seen. That matters more than it looks: when the microphone is
   blocked, the stage says "type below" and focuses THIS box. It was pointing
   people at something invisible. */
.voice-quick-input-bar {
  position: relative;
  z-index: 10;
}

/* MINIMAL BY DEFAULT.
   A typed prompt box is not part of a voice conversation - it is the fallback
   for when voice input is impossible. Sitting on the stage permanently it was
   a second pill under the caption pill, competing with the controls for the
   bottom of the screen and making a voice product look like a chat product.
   It is NOT deleted, because deleting it would strand exactly the people who
   need it: when the microphone is blocked or the browser cannot listen at all,
   the stage says "type below" and focuses this box. So it appears in precisely
   that state and nowhere else. The grid row collapses to zero when it is
   hidden, so the layout closes up rather than leaving a gap. */
.voice-quick-input-bar { display: none; }
.live-voice-stage.voice-typed-only .voice-quick-input-bar { display: flex; }

/* THE DOCK. It was a 380px pill holding 430px of buttons, with `nowrap` and
   `justify-content: center` spilling them symmetrically out of both ends.
   Sizing the pill to its contents is the fix; `max-content` cannot be
   narrower than what it holds. */
.voice-controls-dock {
  position: static;
  transform: none;
  z-index: 12;
  width: max-content;
  max-width: 100%;
  flex-wrap: nowrap;
  gap: 10px;
  padding: 8px 12px;
}

/* "Tap to Speak" wrapped to two lines, which made that one button 70px tall
   beside three 40px ones - a visibly ragged row, and the reason the dock was
   88px deep and colliding with the prompt bar. */
.voice-control-btn {
  flex: 0 0 auto;
  min-width: 0;
  padding: 10px 14px;
  white-space: nowrap;
}

/* Below this the four labelled buttons genuinely cannot fit, so they become
   icons. Every one already carries an `aria-label` and a `title`, so nothing
   is lost to a screen reader or a long press, and the primary action keeps its
   accent fill to stay legible as the primary action. */
@media (max-width: 500px) {
  .voice-control-btn span { display: none; }
  .voice-control-btn {
    width: 44px;
    height: 44px;
    padding: 0;
    justify-content: center;
  }
}

/* MUTE is meaningless with nothing being heard, so it dims. "TAP TO SPEAK"
   MUST NOT: it is the retry, and on browsers that only permit recognition
   directly after a tap it is the ONLY way back. Disabling it meant the stage
   said "tap the mic to try again" while the mic button was dead - the way out
   greyed out and unclickable. */
.live-voice-stage.voice-typed-only #btn-voice-mute {
  opacity: 0.35;
  pointer-events: none;
}
.live-voice-stage.voice-typed-only .voice-status-text,
.voice-status-text.halted {
  color: var(--amber);
  text-shadow: none;
}

/* ==================================================================== *
 * LIVE VOICE - THE LIVING STAGE
 *
 * Three layers, and each one earns its place rather than decorating:
 *
 *   the SPIRAL   the logo's own gesture, turning
 *   the BLOOM    the orb answering the voice actually in the room
 *   the THOUGHTS activity behind the glass while Quanta is working
 *
 * The presence here is deliberately NOT a face. It is a mark, a light and a
 * motion - something you can read the state of at a glance without it
 * pretending to be a person looking back at you.
 * ==================================================================== */

/* The level the analyser is already computing for the waveform, published so
   the whole stage can answer it. Declared here so `calc()` below always has a
   number even before the first animation frame writes one. */
.live-voice-stage { --voice-level: 0; }

/* ------------------------------------------- the orb answering the voice */

.voice-reactive-bloom {
  position: absolute;
  inset: 6%;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle,
    rgba(125, 211, 252, 0.55) 0%,
    rgba(56, 189, 248, 0.24) 42%,
    rgba(168, 85, 247, 0.10) 62%,
    transparent 74%);
  mix-blend-mode: screen;
  opacity: calc(0.06 + var(--voice-level) * 0.6);
  transform: scale(calc(0.9 + var(--voice-level) * 0.24));
  /* Short, not instant: raw analyser output twitches, and a light that
     twitches reads as a glitch rather than as breath. */
  transition: opacity 0.1s linear, transform 0.1s linear;
  will-change: opacity, transform;
}

/* --------------------------------------------------- the thought field */

.voice-thought-field {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1);
  transform: translateZ(0);
}

.live-voice-stage[data-voice-state="thinking"] .voice-thought-field { opacity: 1; }
/* Once real topic words are surfacing, the field stays alive through the whole
   conversation (listening/speaking too), a touch softer than the thinking beat
   so it reads as ambient texture, not a heads-up display. */
.voice-thought-field.live { opacity: 0.92; }

.thought-bubble {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  padding: 9px 15px;
  border-radius: 15px 15px 15px 5px;
  background: rgba(10, 22, 40, 0.34);
  border: 1px solid rgba(56, 189, 248, 0.16);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  box-shadow: 0 10px 34px rgba(2, 4, 10, 0.55);
  white-space: nowrap;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  will-change: transform, opacity;
  animation: thoughtDrift var(--dur) ease-in-out infinite alternate;
  animation-delay: var(--delay);
}

/* Both copies occupy the same cell, so the bubble is sized by the word once. */
.thought-bubble span {
  grid-area: 1 / 1;
  color: #d7ecff;
}

/* THE TWO COPIES MUST HAND OVER, NOT OVERLAP.
   Both spans share one grid cell, so while the sharp copy surfaced the blurred
   one was still sitting at 0.8 underneath it - you were reading the word
   THROUGH its own ghost, which is what made the field pretty but hard to read.
   The soft copy now recedes on exactly the timing the sharp one rises, so at
   the moment of legibility there is only one word on screen. */
.tb-soft {
  filter: blur(2.6px);
  opacity: 0.8;
  animation: thoughtRecede var(--surface) ease-in-out infinite;
  animation-delay: var(--delay);
}

/* One surfaces now and then. This animates OPACITY, never `filter: blur`:
   blur is a per-frame repaint, and there are eight of these drifting at once. */
.tb-clear {
  filter: blur(0.28px);
  opacity: 0;
  color: #f0f8ff;
  /* Separates the word from whatever drifts behind it. Cheap: a static shadow
     on a layer that only animates opacity does not re-rasterise. */
  text-shadow: 0 1px 12px rgba(2, 6, 16, 0.9), 0 0 2px rgba(2, 6, 16, 0.7);
  animation: thoughtSurface var(--surface) ease-in-out infinite;
  animation-delay: var(--delay);
}

@keyframes thoughtDrift {
  0%   { transform: translate3d(var(--x0), var(--y0), 0) scale(0.9) rotate(var(--rot)); opacity: 0.18; }
  50%  { opacity: 0.85; }
  100% { transform: translate3d(var(--x1), var(--y1), 0) scale(1.08) rotate(0deg); opacity: 0.3; }
}

/* Wider and fuller than before: it used to peak at 0.7 for 12% of the cycle,
   which is not long enough to read a two-word phrase. Now full opacity across
   a 22% window. */
@keyframes thoughtSurface {
  0%, 58%, 100% { opacity: 0; }
  68%, 90%      { opacity: 1; }
}

/* The inverse, on the same clock. */
@keyframes thoughtRecede {
  0%, 58%, 100% { opacity: 0.8; }
  68%, 90%      { opacity: 0.06; }
}

/* Placed around the edges, deliberately clear of the middle where the orb and
   the captions live. A thought drifting across Quanta's own face would be
   decoration fighting the thing it decorates. */
.tb-1 { --x0: -37vw; --y0: -31vh; --x1: -19vw; --y1: -14vh; --dur: 21s; --delay: 0s;    --surface: 13s;  --rot: -3deg; }
.tb-2 { --x0:  31vw; --y0: -27vh; --x1:  14vw; --y1: -10vh; --dur: 26s; --delay: -4s;   --surface: 15s;  --rot: 2deg;  }
.tb-3 { --x0: -33vw; --y0:  20vh; --x1: -16vw; --y1:  33vh; --dur: 23s; --delay: -9s;   --surface: 12s;  --rot: 3deg;  }
.tb-4 { --x0:  35vw; --y0:  24vh; --x1:  17vw; --y1:  35vh; --dur: 28s; --delay: -14s;  --surface: 17s;  --rot: -2deg; }
.tb-5 { --x0: -42vw; --y0:  -4vh; --x1: -27vw; --y1:  10vh; --dur: 19s; --delay: -6s;   --surface: 11s;  --rot: 1deg;  }
.tb-6 { --x0:  40vw; --y0:   2vh; --x1:  25vw; --y1: -13vh; --dur: 24s; --delay: -11s;  --surface: 16s;  --rot: -1deg; }
.tb-7 { --x0:  -8vw; --y0: -38vh; --x1:   9vw; --y1: -28vh; --dur: 30s; --delay: -17s;  --surface: 14s;  --rot: 2deg;  }
.tb-8 { --x0:   6vw; --y0:  36vh; --x1: -10vw; --y1:  27vh; --dur: 22s; --delay: -2s;   --surface: 18s;  --rot: -2deg; }

/* ------------------------------------------------------------------ *
 * Respect a stated preference for less motion. Everything above is
 * atmosphere; none of it carries information the stage does not also state in
 * words, so all of it can stop without the screen losing meaning.
 * ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .living-logo-spiral,
  .living-logo-core,
  .tb-clear,
  .tb-soft {
    animation: none;
  }
  .voice-reactive-bloom { transition: none; }
  /* Held at their resting positions rather than stacked in the middle, which
     is where "animation: none" alone would leave them. */
  .thought-bubble {
    animation: none;
    transform: translate3d(var(--x1), var(--y1), 0);
    opacity: 0.42;
  }
  /* SHOW THE SHARP COPY, NOT THE BLURRED ONE.
     With the animations stopped, `.tb-clear` rests at opacity 0 and `.tb-soft`
     at 0.8 - so switching motion off left ONLY the deliberately-blurred word
     on screen, which is the least legible of the two. Someone who asked for
     less motion has not asked for less clarity. */
  .tb-soft  { opacity: 0; }
  .tb-clear { opacity: 1; }
}

/* ==================================================================== *
 * MOBILE ERGONOMICS - measured, not guessed
 *
 * Audited on a 390x844 viewport across chat, the drawer, settings, the account
 * modal and pricing. The page itself never overflowed horizontally, which is
 * the good news. What it did have was touch targets built for a mouse:
 *
 *     btn-drawer-close        30 x 30
 *     btn-header-account      32 x 32
 *     btn-new                 38 x 38
 *     drawer items            38 tall
 *     upgrade CTA             31 tall
 *     footer links            15 TALL
 *
 * Apple's minimum is 44x44, and 15px is roughly a third of a fingertip.
 *
 * THE HIT AREA GROWS, THE DESIGN DOES NOT. Every rule below expands what you
 * can TAP without moving a single visible pixel - a centred 44x44 pseudo
 * element, or padding that the layout already had room for. Scaling the
 * buttons themselves would have re-flowed a header and a drawer that are
 * otherwise fine, and traded one mobile problem for another.
 * ==================================================================== */

@media (max-width: 600px) {

  .icon-btn,
  .icon-btn-sm,
  .header-account-btn,
  .btn-drawer-upgrade-cta,
  .hero-chip {
    position: relative;
  }

  .icon-btn::after,
  .icon-btn-sm::after,
  .header-account-btn::after,
  .btn-drawer-upgrade-cta::after,
  .hero-chip::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
    /* Invisible, and never in front of the thing it is enlarging. */
    background: transparent;
    pointer-events: auto;
    z-index: 0;
  }

  /* The icon must stay above its own hit area or the tap lands on nothing. */
  .icon-btn > *,
  .icon-btn-sm > *,
  .header-account-btn > *,
  .btn-drawer-upgrade-cta > *,
  .hero-chip > * {
    position: relative;
    z-index: 1;
  }

  /* Rows have the width already - they only ever lacked height. */
  .drawer-item {
    min-height: 44px;
  }

  /* 15px tall, and the two most legally significant links in the product. */
  .drawer-footer a,
  .drawer-legal a {
    display: inline-block;
    padding: 12px 4px;
  }

  /* A readability floor. 9.5px badges are decoration you have to squint at;
     11px is the smallest Apple recommends anybody actually read. */
  .nav-badge,
  .badge-count {
    font-size: 11px;
  }
  .nav-group-label {
    font-size: 11px;
  }
}

/* ------------------------------------------------------------------ *
 * QUANTA KINETIC — the real clip on the stage
 *
 * Added 2026-08-27 with the generation path. Before that the stage held only
 * a <canvas> running a particle animation, because /api/video did not exist
 * and the studio was a placeholder. The canvas is still here and still earns
 * its place — it is what plays WHILE a render is running — but a finished mp4
 * is a real <video> now, so it can be scrubbed, paused and saved.
 * ------------------------------------------------------------------ */
.stage-video-el {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  /* contain, not cover: a 9:16 clip in a 16:9 stage must letterbox rather
     than have its subject cropped out of frame. */
  object-fit: contain;
  background: rgba(var(--surf-3), 1);
  border-radius: inherit;
  z-index: 2;
}

/* What today's allowance is, under the dock — shown BEFORE a clip is spent.
   A daily number that only ever appears in a refusal reads as a trap; shown
   up front it reads as a budget. */
.studio-allowance {
  margin-top: 8px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  text-align: center;
  min-height: 18px;
}
.studio-allowance strong { color: var(--text-bright); font-weight: 650; }

.link-btn {
  background: none; border: 0; padding: 0;
  font: inherit; color: var(--accent);
  cursor: pointer; text-decoration: underline;
  text-underline-offset: 2px;
}
.link-btn:hover { color: var(--text-bright); }
.link-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* The recent strip holds <video> thumbnails now, not emoji in a box. */
.recent-thumb-item video {
  display: block; width: 100%; height: 100%;
  object-fit: cover; border-radius: inherit;
}

/* The pricing grid is filled from /api/plans, so it needs a resting state
   for the moment before the fetch lands. */
.pricing-loading {
  grid-column: 1 / -1;
  text-align: center;
  padding: 32px 12px;
  color: var(--muted);
  font-size: 13px;
}

/* ==================================================================== *
 * THE DIGITAL DISSOLVE — Quanta's signature transition
 *
 * Thomas's brief, in his words: the purple "digital fade away, blocky, kind
 * of Stardust" that Claude's slider has. Subtle and professional, and
 * explicitly NOT cyberpunk — no scanlines, no glitch offset, no chromatic
 * aberration, nothing that reads as a broken screen. A panel should look like
 * it was *composed of pixels that let go*, not like the display faulted.
 *
 * HOW IT WORKS, because the trick is not obvious.
 *
 * A mask cannot randomise per cell in plain CSS, so the blockiness is made by
 * MULTIPLYING two mask layers:
 *
 *   1. A sweep — one soft diagonal gradient whose threshold is driven by the
 *      registered custom property `--qc-dsv` (0 -> 1). On its own this is an
 *      ordinary wipe.
 *   2. A 7px checkerboard at 86% alpha. Multiplied into the sweep it does
 *      almost nothing in the solid region (86-100% opaque reads as flat), but
 *      where the sweep is already mid-alpha it pushes alternating cells below
 *      visibility FIRST. The edge therefore crumbles into squares instead of
 *      wiping cleanly. A second 11px grid at a different period breaks up the
 *      regularity so it scatters rather than checkers.
 *
 * `--qc-dsv` must be a REGISTERED property (@property below) or it cannot be
 * animated — an unregistered custom property is a string to the animation
 * engine and would jump 0 -> 1 with no interpolation.
 *
 * The mask is applied ONLY while the transient class is present, so there is
 * no permanent rendering cost and no permanent 86% alpha on the panel.
 *
 * Reduced motion is already handled globally: `:root[data-reduce-motion] *`
 * and the `prefers-reduced-motion` block cut every duration to 1ms rather
 * than to 0, so anything waiting on animationend still fires.
 * ==================================================================== */

/* ⛔ `inherits: true` IS LOAD-BEARING, NOT A STYLE CHOICE.
   A pseudo-element resolves var() against its OWN computed value, and with
   `inherits: false` the ::after bloom below read the initial 0 on every
   frame while the element animated — all of its gradient stops sat at
   negative percentages and the violet edge never once appeared. The first
   ship of this effect was colourless because of this single line. Children
   of a dissolving panel don't reference the property, so inheriting it
   costs nothing. */
@property --qc-dsv {
  syntax: '<number>';
  initial-value: 0;
  inherits: true;
}

.qc-dissolve-out,
.qc-dissolve-in {
  --qc-dsv: 1;
  /* TWO CELL GRIDS OVER ONE SWEEP.
     The cells are radial stops whose RADIUS is driven by --qc-dsv. At 1 the
     radius exceeds the tile, so every tile is fully covered and the panel is
     solid; as it falls the circles shrink to specks and the surface breaks
     into grains. That shrink is what makes it blocky - an earlier draft tried
     to do it by multiplying two CONSTANT alphas, which cannot work: alpha
     multiplication has no threshold, so it just dims the whole panel evenly
     and wipes rather than crumbles.
     The 7px and 11px tiles are deliberately co-prime-ish so their grains never
     line up into a visible checker.
     THE RADIUS MUST REACH THE TILE CORNER, not its edge. A circle centred in
     a 7px square needs r = sqrt(3.5^2 + 3.5^2) = 4.95px to cover it; the first
     draft topped out at 4.9px, so at full state the panel kept a faint grid of
     corner pinholes and popped when the class was removed. 5.4px and 8.2px
     clear both tiles with margin. Found by reading the COMPUTED mask back out
     of the browser, not from the source. */
  -webkit-mask-image:
    linear-gradient(107deg,
      rgba(0, 0, 0, 1) calc(var(--qc-dsv) * 172% - 72%),
      rgba(0, 0, 0, 1) calc(var(--qc-dsv) * 172% - 26%),
      rgba(0, 0, 0, 0) calc(var(--qc-dsv) * 172%)),
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 5.0px + 0.4px), transparent 0),
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 7.6px + 0.6px), transparent 0);
  mask-image:
    linear-gradient(107deg,
      rgba(0, 0, 0, 1) calc(var(--qc-dsv) * 172% - 72%),
      rgba(0, 0, 0, 1) calc(var(--qc-dsv) * 172% - 26%),
      rgba(0, 0, 0, 0) calc(var(--qc-dsv) * 172%)),
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 5.0px + 0.4px), transparent 0),
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 7.6px + 0.6px), transparent 0);
  -webkit-mask-size: 100% 100%, 7px 7px, 11px 11px;
  mask-size: 100% 100%, 7px 7px, 11px 11px;
  /* `intersect`, NOT `multiply`. The first draft used `multiply`, which is not
     a value this property has - the spec allows add | subtract | intersect |
     exclude - so it was dropped as invalid and the layers fell back to `add`,
     which UNIONS them and shows the panel wherever ANY layer is opaque. The
     dissolve would never have happened at all. Caught by asking the browser
     with CSS.supports() rather than by reading the file back. */
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
  will-change: --qc-dsv;
  /* A panel mid-crumble is on its way out (or in) — it must never eat the
     tap that was aimed at what's behind it. */
  pointer-events: none;
}

/* 440/400ms. Long enough that the crumble and its stardust read as an
   event, short enough that a person opening a menu never waits for it.
   (Reduced motion cuts these to 1ms globally, and everything sequenced on
   animationend still fires.) */
.qc-dissolve-out { animation: qcDissolveOut 440ms cubic-bezier(0.4, 0, 0.7, 0.2) both; }
.qc-dissolve-in  { animation: qcDissolveIn  400ms cubic-bezier(0.2, 0.8, 0.3, 1) both; }

@keyframes qcDissolveOut {
  from { --qc-dsv: 1; }
  to   { --qc-dsv: 0; }
}
@keyframes qcDissolveIn {
  from { --qc-dsv: 0; }
  to   { --qc-dsv: 1; }
}

/* THE BLOOM RIDING THE EDGE.
   A narrow violet band tracking the dissolve front, at low opacity. This is
   the only part that is actually coloured — the dissolve itself is a mask and
   therefore colourless, which is what keeps it from looking like a filter
   slapped over the UI. `mix-blend-mode: screen` lifts it off the panel
   without washing the text underneath. */
.qc-dissolve-out::after,
.qc-dissolve-in::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  mix-blend-mode: screen;
  opacity: 0.85;
  /* AN EMBER FRONT, NOT A TINTED BAND. The heat lives in the last few
     percent before a cell lets go — a near-white violet line right at the
     crumble edge, cooling through violet-400 into a faint violet-500 wake
     behind it. Reads as energy leaving the surface, which is the story the
     motes then continue. */
  background:
    linear-gradient(107deg,
      transparent               calc(var(--qc-dsv) * 168% - 58%),
      rgba(139, 92, 246, 0.10)  calc(var(--qc-dsv) * 168% - 38%),
      rgba(167, 139, 250, 0.22) calc(var(--qc-dsv) * 168% - 20%),
      rgba(196, 181, 253, 0.42) calc(var(--qc-dsv) * 168% - 7%),
      rgba(233, 213, 255, 0.60) calc(var(--qc-dsv) * 168% - 1.5%),
      transparent               calc(var(--qc-dsv) * 168%));
  /* THE GLINT SITS ON THE SURVIVING CELLS. The bloom is masked by the SAME
     radius-driven grains as the panel itself (not a fixed checker), so the
     light lands exactly on the pixels that are still holding on and dies
     with them — the edge sparkles cell by cell instead of glowing through
     the gaps. Requires the inherited --qc-dsv above. */
  -webkit-mask-image:
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 5.0px + 0.4px), transparent 0),
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 7.6px + 0.6px), transparent 0);
  mask-image:
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 5.0px + 0.4px), transparent 0),
    repeating-radial-gradient(circle at 50% 50%,
      #000 0 calc(var(--qc-dsv) * 7.6px + 0.6px), transparent 0);
  -webkit-mask-size: 7px 7px, 11px 11px;
  mask-size: 7px 7px, 11px 11px;
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* The drawer is the first surface to carry it. `position: fixed` already
   establishes the containing block the ::after needs. */
.drawer.qc-dissolve-out,
.drawer.qc-dissolve-in { transition: none !important; }

/* ==================================================================== *
 * THE MODEL PICKER
 *
 * Rebuilt 2026-08-28. What was wrong with it, because the list is the
 * argument for every choice below:
 *
 *   · The row was assembled from INLINE STYLES, including `color:#fff`.
 *     That is a hard-coded literal in an app whose entire theming pass
 *     existed to remove them — it would have stayed white-on-white the
 *     moment anyone opened the light theme.
 *   · FIVE badge colours: cyan, violet, pink, green, amber. Five hues
 *     across four rows is not a hierarchy, it is a rainbow, and it made
 *     the tier label shout louder than the model name.
 *   · Everything sat on ONE line — mark, name, tier, lock, speed — with
 *     `margin-left:auto` doing the layout. Nothing was subordinate to
 *     anything else.
 *   · Four arbitrary type sizes: 14 / 11.5 / 11 / 9.5.
 *   · `locked` was `opacity: 0.72`, which reads as BROKEN rather than as
 *     "there is a free account behind this".
 *   · The row was a <div> with a click handler: no keyboard, no focus
 *     ring, nothing announced.
 *
 * The rules here: one accent, one type scale, and colour reserved for the
 * two things that are genuinely states — what is selected, and what is
 * still in Beta. Everything else earns its place through weight and space.
 * ==================================================================== */

.qm-list {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

/* A real <button>. Keyboard, Enter/Space and screen-reader semantics come
   free, and they are not worth reimplementing badly on a <div>. */
.qm-row {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    'mark name meta'
    'mark desc desc';
  align-items: center;
  column-gap: 11px;
  row-gap: 3px;
  width: 100%;
  padding: 12px 14px 12px 13px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--surf-31), 0.5);
  color: var(--text);
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition: background 0.16s var(--ease-out), border-color 0.16s var(--ease-out);
}

.qm-row:hover {
  background: rgba(var(--surf-41), 0.7);
  border-color: rgba(var(--lift-rgb), calc(0.16 * var(--lift-k)));
}

/* Visible, and only for keyboard users. A picker without this is unusable
   without a mouse, which is the most common accessibility failure there is. */
.qm-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* SELECTED. The one place colour is spent, and it is spent on a rail down the
   left edge rather than a glow around the whole row — a border that lights up
   competes with the row below it, a rail does not. */
.qm-row.is-active {
  background: rgba(var(--surf-47), 0.85);
  border-color: var(--line-strong);
}
.qm-row.is-active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 10px;
  bottom: 10px;
  width: 2.5px;
  border-radius: 0 2px 2px 0;
  background: var(--accent);
}

.qm-mark {
  grid-area: mark;
  width: 16px;
  height: 16px;
  color: var(--muted);
  flex: none;
}
.qm-row.is-active .qm-mark { color: var(--accent); }

.qm-name {
  grid-area: name;
  font-size: 14px;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--text-bright);
}

.qm-desc {
  grid-area: desc;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}

/* Tier and speed, right-aligned and quiet. This is metadata: it should be
   findable when looked for and invisible when not. */
.qm-meta {
  grid-area: meta;
  display: flex;
  align-items: center;
  gap: 9px;
  flex: none;
}

.qm-tier {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* THE ONE COLOURED LABEL LEFT. Beta is not decoration — it is a warning that
   the thing may change under you, and a warning that looks like every other
   label is not a warning. */
.qm-tier.is-beta { color: #fbbf24; }

.qm-speed {
  font-size: 10.5px;
  font-family: var(--font-mono);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Sits inside .qm-meta now, after the tier, rather than owning the whole
   grid cell — the tier is shown on every row including the selected one. */
.qm-check {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--accent);
}

/* LOCKED. Not dimmed — a dimmed row reads as broken, and this one works
   perfectly the moment someone makes an account. It says the price instead,
   and the price is nothing. */
.qm-row.is-locked .qm-name,
.qm-row.is-locked .qm-mark { color: var(--muted); }

.qm-unlock {
  font-size: 10.5px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 999px;
  color: var(--text);
  background: rgba(var(--lift-rgb), calc(0.07 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.13 * var(--lift-k)));
  white-space: nowrap;
}
.qm-row.is-locked:hover .qm-unlock {
  color: var(--text-bright);
  border-color: var(--line-strong);
}

@media (max-width: 420px) {
  /* The description earns the width before the metadata does. */
  .qm-row { grid-template-areas: 'mark name meta' 'mark desc desc'; }
  .qm-speed { display: none; }
}

/* The one line of explanation above the size list. It says what is actually
   unusual — nothing leaves the device — rather than just "free", which reads
   as a trial with a catch. */
.qm-note {
  margin: 0 2px 4px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}

/* A size that cannot run here (no WebGPU) is disabled rather than hidden:
   knowing the feature exists and needs a different browser is more useful
   than a menu that silently has fewer options in it. */
.qm-row:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
.qm-row:disabled:hover {
  background: rgba(var(--surf-31), 0.5);
  border-color: rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
}

/* ------------------------------------------------------------------ *
 * Profile picture
 *
 * The four avatar elements already carry their own size, rounding and
 * gradient. A picture is applied as a background-image on top of that, so
 * none of them needed restructuring -- which is also why the initials are
 * still in the DOM underneath. They are the fallback and the accessible
 * name; .has-avatar only makes them invisible, never absent.
 * ------------------------------------------------------------------ */
.has-avatar {
  color: transparent !important;
  text-shadow: none !important;
  background-color: transparent !important;
}

/* The ten built-in marks. Square-ish grid, circular swatches, because that
   is the shape they will actually be worn in. */
.av-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
  margin: 4px 0 14px;
}
.av-swatch {
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  border: 2px solid transparent;
  background-size: cover;
  background-position: center;
  cursor: pointer;
  padding: 0;
  transition: transform 0.14s ease, border-color 0.14s ease;
}
.av-swatch:hover { transform: scale(1.06); }
.av-swatch:focus-visible {
  outline: none;
  border-color: rgba(56, 189, 248, 0.9);
}
/* The one you are wearing. A ring rather than a tick: a tick would have to
   sit on top of the artwork and every mark is a different colour under it. */
.av-swatch.is-active {
  border-color: rgba(56, 189, 248, 0.95);
  box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.18);
}

/* ==================================================================
 * TWO-FACTOR AUTHENTICATION
 *
 * Two surfaces: the row and sheet in Settings, and the second step of the
 * sign-in form. Everything borrows the existing sheet vocabulary (.qm-row,
 * .qm-note, .key-input) so this reads as part of the app rather than as a
 * security feature bolted to the side of it.
 * ================================================================== */

/* ---- the settings row's state chip ---- */
/* Deliberately shaped like .set-plan-chip. It answers one question at a
   glance - is this on - and it is the muted colour when it is not, because
   "Off" is the default state and not a warning. */
.set-2fa-chip {
  flex: none;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 9.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint);
  background: rgba(var(--lift-rgb), calc(0.06 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.12 * var(--lift-k)));
  border-radius: 999px;
  padding: 4px 10px;
  margin-right: 8px;
}
.set-2fa-chip.is-on {
  color: var(--emerald);
  background: rgba(52, 211, 153, .12);
  border-color: rgba(52, 211, 153, .30);
}

/* ---- the sheet ---- */
.tf-field { margin: 10px 2px 2px; }
.tf-label {
  display: block;
  margin: 0 0 6px 2px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--muted);
}

/* THE QR NEEDS A LIGHT GROUND IN BOTH THEMES. A reader expects dark
   modules on light; inverted, most will not read it at all. The SVG carries
   its own white rectangle, and this padding is the quiet zone's frame. */
.tf-qr {
  display: flex;
  justify-content: center;
  margin: 4px auto 12px;
  padding: 10px;
  width: fit-content;
  background: #ffffff;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(var(--lift-rgb), calc(0.14 * var(--lift-k)));
}
.tf-qr svg {
  display: block;
  width: 188px;
  height: 188px;
}

/* The typed key, for anyone who cannot scan. Monospace and spaced, because
   this gets copied by eye onto a phone keyboard. */
.tf-secret {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 13px;
  letter-spacing: .08em;
  line-height: 1.7;
  text-align: center;
  word-break: break-all;
  color: var(--text);
  background: rgba(var(--surf-31), 0.5);
  border: 1px solid rgba(var(--lift-rgb), calc(0.10 * var(--lift-k)));
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin: 2px 2px 8px;
  user-select: all;
}

.tf-copy { margin-bottom: 4px; }

.tf-error {
  margin: 8px 2px 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--red);
}

.tf-primary { margin-top: 12px; width: 100%; }

/* Turning it OFF is the destructive direction, so it is the one that is
   coloured. Turning it on is ordinary and uses the normal accent. */
.tf-danger {
  background: rgba(248, 113, 113, .12) !important;
  border-color: rgba(248, 113, 113, .32) !important;
  color: var(--red) !important;
}
.tf-danger:hover:not(:disabled) {
  background: rgba(248, 113, 113, .18) !important;
}

/* ---- the recovery codes ---- */
/* Two columns, monospace, generously spaced. These are read once, under
   mild stress, and transcribed. Nothing here should be ambiguous. */
.tf-codes {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  margin: 10px 2px;
}
.tf-code {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 13px;
  letter-spacing: .06em;
  text-align: center;
  padding: 9px 4px;
  color: var(--text);
  background: rgba(var(--surf-31), 0.55);
  border: 1px solid rgba(var(--lift-rgb), calc(0.10 * var(--lift-k)));
  border-radius: var(--radius-sm);
  user-select: all;
}

/* ---- the second step of the sign-in form ---- */
.auth-mfa-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px; height: 46px;
  margin: 2px auto 12px;
  border-radius: 50%;
  color: var(--accent);
  background: rgba(56, 189, 248, .10);
  border: 1px solid rgba(56, 189, 248, .26);
}
.auth-mfa-mark .ico { width: 20px; height: 20px; }

.auth-mfa-lead {
  margin: 0 2px 14px;
  font-size: 12.5px;
  line-height: 1.5;
  text-align: center;
  color: var(--muted);
}

/* Wide tracking and centred, so six digits read as six digits. */
.auth-code-input {
  text-align: center;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 19px;
  letter-spacing: .34em;
  text-indent: .34em;
}

.auth-mfa-back {
  display: block;
  margin: 12px auto 0;
  font-size: 12px;
}

@media (max-width: 420px) {
  .tf-qr svg { width: 158px; height: 158px; }
  .tf-codes { grid-template-columns: 1fr; }
}


/* ==================================================================== *
 * SESSION 15 UI POLISH — floating chrome, blurred edges, quieter meters
 *
 * The brief: strip the solid top and bottom bars, let the controls float
 * the way Anthropic's and Google's surfaces do, and replace the hard bar
 * edges with BLUR that the conversation dissolves into as it scrolls.
 *
 * ⛔ These rules are appended deliberately. The originals stay where they
 * are, readable, and this block overrides them by cascade order. Editing
 * the definitions in place would scatter one visual decision across a
 * 240 KB file.
 * ==================================================================== */

/* -------------------------------------------------------------------- *
 * 1. THE TOP BAR FLOATS, AND THE THREAD SCROLLS UNDER A BLUR
 *
 * The header keeps its place in the flex column (removing it from flow
 * would collapse the layout every view depends on) but paints nothing.
 * The scrim behind it is a masked backdrop-filter, so text does not hit a
 * hard edge - it softens, then fades. `pointer-events` is released on the
 * bar and taken back by the controls, so the blur strip is not an
 * invisible lid over the top of the conversation.
 * -------------------------------------------------------------------- */
.bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  background: none;
  border-bottom: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  pointer-events: none;
}
.bar > *,
.bar .icon-btn,
.bar .bar-actions,
.bar .header-dev-chip,
.bar .header-account-btn {
  pointer-events: auto;
}

/* ⛔ THE RAMP IS A SMOOTHSTEP ON EVENLY SPACED STOPS, AND THAT IS THE FIX
   FOR THE FAINT LINES ACROSS THE SCREEN.

   These masks used to carry four hand-picked stops at uneven offsets —
   .78 at 27px, .45 at 16px, .16 at 7px. A linear gradient interpolates
   linearly BETWEEN stops, so every stop is a kink in the alpha curve, and
   a kink in an alpha ramp is a visible horizontal line. Four stops, four
   faint lines, brighter or dimmer depending on what was behind them.
   Reported from the field 2026-08-29: "it looks like there's a very, very
   faint line across my screen, like a bunch of them."

   The stops below are the smoothstep curve 1 - (3f² - 2f³) sampled at
   SIXTEEN EQUAL intervals. Two properties do the work. Equal spacing plus a
   curve with no corners means no inflection for the eye to catch. And
   smoothstep leaves at zero slope, so the boundary where the solid paint
   meets the ramp — the edge that was most visible — is now a smooth
   departure rather than a corner.

   ⛔ SIXTEEN IS NOT ARBITRARY, IT IS BELOW THE 8-BIT FLOOR. A piecewise-
   linear approximation of this curve deviates from it by at most:
       8 intervals  → 0.0103 alpha = 2.6 of a 255-step  (still visible)
      16 intervals  → 0.0028 alpha = 0.7 of a 255-step  (below one step)
   A mask is quantised to 1/255, so at sixteen the approximation error is
   smaller than the smallest value the mask can even represent — the
   segments become mathematically indistinguishable from the curve. Eight
   was not, which is why the first version of this fix did not go far
   enough. What is left is the inherent 8-bit banding of any ramp, and the
   only lever beyond this point is dithering.

   ⛔ DO NOT "SIMPLIFY" THIS BACK TO A FEW STOPS. Fewer stops is exactly
   the bug. If a scrim needs a different length, change `--ramp` — every
   offset is a multiple of it, so all four scrims stay in step. */
.bar::after,
.bar::before {
  -webkit-mask-image: linear-gradient(to bottom,
      #000 calc(100% - var(--ramp)),
      rgba(0, 0, 0, .9888) calc(100% - var(--ramp) * 0.9375),
      rgba(0, 0, 0, .9570) calc(100% - var(--ramp) * 0.875),
      rgba(0, 0, 0, .9077) calc(100% - var(--ramp) * 0.8125),
      rgba(0, 0, 0, .8438) calc(100% - var(--ramp) * 0.75),
      rgba(0, 0, 0, .7681) calc(100% - var(--ramp) * 0.6875),
      rgba(0, 0, 0, .6836) calc(100% - var(--ramp) * 0.625),
      rgba(0, 0, 0, .5933) calc(100% - var(--ramp) * 0.5625),
      rgba(0, 0, 0, .5000) calc(100% - var(--ramp) * 0.5),
      rgba(0, 0, 0, .4067) calc(100% - var(--ramp) * 0.4375),
      rgba(0, 0, 0, .3164) calc(100% - var(--ramp) * 0.375),
      rgba(0, 0, 0, .2319) calc(100% - var(--ramp) * 0.3125),
      rgba(0, 0, 0, .1562) calc(100% - var(--ramp) * 0.25),
      rgba(0, 0, 0, .0923) calc(100% - var(--ramp) * 0.1875),
      rgba(0, 0, 0, .0430) calc(100% - var(--ramp) * 0.125),
      rgba(0, 0, 0, .0112) calc(100% - var(--ramp) * 0.0625),
      transparent 100%);
  mask-image: linear-gradient(to bottom,
      #000 calc(100% - var(--ramp)),
      rgba(0, 0, 0, .9888) calc(100% - var(--ramp) * 0.9375),
      rgba(0, 0, 0, .9570) calc(100% - var(--ramp) * 0.875),
      rgba(0, 0, 0, .9077) calc(100% - var(--ramp) * 0.8125),
      rgba(0, 0, 0, .8438) calc(100% - var(--ramp) * 0.75),
      rgba(0, 0, 0, .7681) calc(100% - var(--ramp) * 0.6875),
      rgba(0, 0, 0, .6836) calc(100% - var(--ramp) * 0.625),
      rgba(0, 0, 0, .5933) calc(100% - var(--ramp) * 0.5625),
      rgba(0, 0, 0, .5000) calc(100% - var(--ramp) * 0.5),
      rgba(0, 0, 0, .4067) calc(100% - var(--ramp) * 0.4375),
      rgba(0, 0, 0, .3164) calc(100% - var(--ramp) * 0.375),
      rgba(0, 0, 0, .2319) calc(100% - var(--ramp) * 0.3125),
      rgba(0, 0, 0, .1562) calc(100% - var(--ramp) * 0.25),
      rgba(0, 0, 0, .0923) calc(100% - var(--ramp) * 0.1875),
      rgba(0, 0, 0, .0430) calc(100% - var(--ramp) * 0.125),
      rgba(0, 0, 0, .0112) calc(100% - var(--ramp) * 0.0625),
      transparent 100%);
}

/* The bar region is painted OPAQUE in the page's own background — the
   same footprint the old solid bar had — and releases its grip just below
   the controls. The page background is a viewport-anchored radial
   gradient, not a flat colour, so the paint uses the same gradient with
   `background-attachment: fixed`: it samples the exact pixels the body
   would paint there, and the bar becomes indistinguishable from the page.

   ⛔ THE FADE ENDS UNDER THE BUTTONS, NOT WELL BELOW THEM. Measured
   2026-08-29: the bar is 58px tall and its controls bottom out at 48px,
   but the paint used to run 38px PAST the bar and finish at 96px — half
   again as far down the conversation as the controls it exists to protect,
   which read as a band sitting too low over the thread. A 12px overhang
   with a 22px ramp holds the paint solid to 48px (exactly the underside of
   the buttons) and clears it by 70px. Keep the ramp longer than the
   overhang: that is what puts the opaque edge under the controls instead
   of at the bar's own boundary. */
.bar::after {
  content: '';
  position: absolute;
  inset: 0;
  bottom: -12px;
  --ramp: 22px;
  z-index: -2;
  pointer-events: none;
  background-color: var(--bg);
  background-image: var(--bg-gradient);
  background-attachment: fixed;
}

/* The blur outlives the colour by 8px, so text softens before the tint
   takes it and a whisper of blur survives past the paint. On an engine
   without backdrop-filter the opaque fade above is the whole effect,
   which is why it is a real background up there and not an opacity
   trick. */
.bar::before {
  content: '';
  position: absolute;
  inset: 0;
  bottom: -20px;
  --ramp: 30px;
  z-index: -1;
  pointer-events: none;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* The conversation has to start below the floating controls now that the
   header no longer reserves the space by existing. */
.view > .thread,
.view > .hero-wrap,
.thread {
  padding-top: 64px;
}

/* -------------------------------------------------------------------- *
 * 2. THE COMPOSER LOSES ITS BAR AND GAINS A BLUR
 *
 * Same idea, mirrored. The dock keeps its position in the column - the
 * thread must not scroll underneath a control you are typing into - but
 * the slab, the border and the drop shadow go, and the conversation above
 * dissolves into a blur instead of stopping at a line.
 * -------------------------------------------------------------------- */
.composer {
  background: none;
  border-top: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Mirror of the top: opaque page-matched paint over the composer's own
   footprint, fading across the 26px strip that overlaps the thread, with
   the blur running 32px so text softens before the tint takes it. The
   previous build's mask did the opposite — full strength over the
   composer (where there is nothing behind to blur) and near zero over
   the overlap strip — which is why the bottom read as no blur at all. */
/* The same smoothstep ramp as the top, mirrored. See the note above
   `.bar::after` for why the old four-stop version drew faint horizontal
   lines across the screen — this is the surface it was most visible on,
   because the composer sits over the brightest part of the page
   background and a kink in an alpha curve shows most against a gradient. */
.composer::after,
.composer::before {
  -webkit-mask-image: linear-gradient(to top,
      #000 calc(100% - var(--ramp)),
      rgba(0, 0, 0, .9888) calc(100% - var(--ramp) * 0.9375),
      rgba(0, 0, 0, .9570) calc(100% - var(--ramp) * 0.875),
      rgba(0, 0, 0, .9077) calc(100% - var(--ramp) * 0.8125),
      rgba(0, 0, 0, .8438) calc(100% - var(--ramp) * 0.75),
      rgba(0, 0, 0, .7681) calc(100% - var(--ramp) * 0.6875),
      rgba(0, 0, 0, .6836) calc(100% - var(--ramp) * 0.625),
      rgba(0, 0, 0, .5933) calc(100% - var(--ramp) * 0.5625),
      rgba(0, 0, 0, .5000) calc(100% - var(--ramp) * 0.5),
      rgba(0, 0, 0, .4067) calc(100% - var(--ramp) * 0.4375),
      rgba(0, 0, 0, .3164) calc(100% - var(--ramp) * 0.375),
      rgba(0, 0, 0, .2319) calc(100% - var(--ramp) * 0.3125),
      rgba(0, 0, 0, .1562) calc(100% - var(--ramp) * 0.25),
      rgba(0, 0, 0, .0923) calc(100% - var(--ramp) * 0.1875),
      rgba(0, 0, 0, .0430) calc(100% - var(--ramp) * 0.125),
      rgba(0, 0, 0, .0112) calc(100% - var(--ramp) * 0.0625),
      transparent 100%);
  mask-image: linear-gradient(to top,
      #000 calc(100% - var(--ramp)),
      rgba(0, 0, 0, .9888) calc(100% - var(--ramp) * 0.9375),
      rgba(0, 0, 0, .9570) calc(100% - var(--ramp) * 0.875),
      rgba(0, 0, 0, .9077) calc(100% - var(--ramp) * 0.8125),
      rgba(0, 0, 0, .8438) calc(100% - var(--ramp) * 0.75),
      rgba(0, 0, 0, .7681) calc(100% - var(--ramp) * 0.6875),
      rgba(0, 0, 0, .6836) calc(100% - var(--ramp) * 0.625),
      rgba(0, 0, 0, .5933) calc(100% - var(--ramp) * 0.5625),
      rgba(0, 0, 0, .5000) calc(100% - var(--ramp) * 0.5),
      rgba(0, 0, 0, .4067) calc(100% - var(--ramp) * 0.4375),
      rgba(0, 0, 0, .3164) calc(100% - var(--ramp) * 0.375),
      rgba(0, 0, 0, .2319) calc(100% - var(--ramp) * 0.3125),
      rgba(0, 0, 0, .1562) calc(100% - var(--ramp) * 0.25),
      rgba(0, 0, 0, .0923) calc(100% - var(--ramp) * 0.1875),
      rgba(0, 0, 0, .0430) calc(100% - var(--ramp) * 0.125),
      rgba(0, 0, 0, .0112) calc(100% - var(--ramp) * 0.0625),
      transparent 100%);
}
.composer::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -38px;
  --ramp: 38px;
  bottom: 0;
  z-index: -2;
  pointer-events: none;
  background-color: var(--bg);
  background-image: var(--bg-gradient);
  background-attachment: fixed;
}
.composer::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -46px;
  --ramp: 46px;
  bottom: 0;
  z-index: -1;
  pointer-events: none;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* -------------------------------------------------------------------- *
 * 3. NO SCROLLBAR IN THE CONVERSATION, BUT IT STILL SCROLLS
 *
 * `scrollbar-width: none` covers Firefox, `::-webkit-scrollbar` covers the
 * rest. Scrolling, keyboard paging and the scroll-into-view calls all work
 * exactly as before - only the gutter is gone.
 * -------------------------------------------------------------------- */
.thread,
.view,
.drawer-chat-list,
.bottom-sheet {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.thread::-webkit-scrollbar,
.view::-webkit-scrollbar,
.drawer-chat-list::-webkit-scrollbar,
.bottom-sheet::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

/* -------------------------------------------------------------------- *
 * 4. THE METERS BECOME TILES, AND ∞ BECOMES LEGIBLE
 *
 * With the dock slab gone the two meters were floating on nothing, so each
 * gets its own quiet tile to sit in. The percentage was 9.5px, which is
 * survivable for "48%" and not survivable for a single glyph: at that size
 * ∞ reads as a smudge. It gets its own class, a larger size and the line
 * height to carry it without moving the row.
 * -------------------------------------------------------------------- */
.meter-strip {
  gap: 10px;
  padding: 0;
  margin-top: 6px;
}

.meter {
  padding: 7px 10px 8px;
  border-radius: 11px;
  background: rgba(var(--lift-rgb), calc(0.045 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.075 * var(--lift-k)));
  gap: 5px;
  opacity: 1;
  transition: background .18s ease, border-color .18s ease;
}
.meter:hover {
  background: rgba(var(--lift-rgb), calc(0.075 * var(--lift-k)));
  border-color: rgba(var(--lift-rgb), calc(0.13 * var(--lift-k)));
}
.meter:active { opacity: .75; }

.meter-label { font-size: 9.5px; }

.meter-pct {
  font-size: 11px;
  line-height: 1;
}

/* ∞ is one glyph doing the work of three characters. It needs the size. */
.meter-pct.is-unlimited {
  font-size: 16px;
  line-height: .7;
  letter-spacing: 0;
  color: var(--text-bright, var(--text));
  opacity: .92;
}

.meter-expand {
  align-self: center;
  opacity: .7;
}

/* -------------------------------------------------------------------- *
 * 4b. ON A PHONE, THE METERS SHED THE TILE CHROME
 *
 * The tiles above give the two meters a home on a wide screen. On a phone
 * that chrome — a border, a fill and ~15px of vertical padding each — stacked
 * under the pills, the input and the disclaimer, read as a chunky bottom
 * "bar" that pushed the composer nearly a quarter of the way up the screen
 * (measured: composer 193px, 24% of an iPhone, and ~28% once the home-
 * indicator inset is added). Here the meters return to the hairline strip the
 * original design intended (see the meter-strip note in index.html): the same
 * two readouts, tucked into the safe-area band, costing the conversation
 * almost no height. The full per-resource breakdown is one tap away in the
 * fly-out, unchanged. Mobile only — the desktop tiles have the room.
 *
 * Specificity: `#meter-strip .meter` (1,1,0) beats the tile rule's `.meter`
 * (0,1,0), so this wins on a phone without !important.
 * -------------------------------------------------------------------- */
@media (max-width: 560px) {
  #meter-strip { gap: 18px; margin-top: 2px; }
  #meter-strip .meter {
    padding: 0 2px 1px;
    border: 0;
    background: none;
    border-radius: 0;
    opacity: .9;
  }
  #meter-strip .meter:hover { background: none; border-color: transparent; }
  #meter-strip .meter-label { font-size: 8.5px; }
  #meter-strip .meter-pct   { font-size: 9px; }
  #meter-strip .meter-pct.is-unlimited { font-size: 13px; }
  .composer-disclaimer { margin: 3px 0 0; }
  #composer { padding-top: 6px; }
}

/* The header profile picture paints onto the bare initials <span> inside
   the round button. The other three avatar mounts are round and clip the
   artwork themselves; this span had no shape of its own, so a preset
   rendered as a square sitting inside the circle. Fill the button and
   clip round, like every other mount. */
.header-avatar-initials {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* -------------------------------------------------------------------- *
 * 5. THEME TOGGLE — MOVED TO THE ACCOUNT STRIP, BESIDE THE ACCOUNT COG
 *
 * It used to ride beside the Settings row (.drawer-item-pair, kept below for
 * any other caller). It now sits in the account section at the foot of the
 * drawer, next to the card that carries the name and its cog. The section
 * becomes a flex row: the user card takes the space, the toggle is a fixed
 * 40px icon button on the right.
 * -------------------------------------------------------------------- */
.drawer-account-section {
  display: flex;
  align-items: center;
  gap: 8px;
}
.drawer-account-section > .drawer-user-card {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
}
.drawer-account-theme {
  flex: none;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  color: var(--muted);
  cursor: pointer;
  transition: background .16s ease, border-color .16s ease, color .16s ease, transform .1s ease;
}
.drawer-account-theme:hover {
  background: rgba(var(--lift-rgb), calc(0.1 * var(--lift-k)));
  border-color: rgba(var(--lift-rgb), calc(0.16 * var(--lift-k)));
  color: var(--text-bright);
}
.drawer-account-theme:active { transform: scale(0.94); }

/* The old Settings-row pairing, retained for cascade safety — no markup uses
   it after the toggle moved, but leaving the rules costs nothing. */
.drawer-item-pair {
  display: flex;
  align-items: stretch;
  gap: 6px;
}
.drawer-item-pair > .drawer-item {
  flex: 1 1 auto;
  min-width: 0;
}
.drawer-item-icon {
  flex: none;
  width: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  background: rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  color: var(--muted);
  cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.drawer-item-icon:hover {
  background: rgba(var(--lift-rgb), calc(0.1 * var(--lift-k)));
  border-color: rgba(var(--lift-rgb), calc(0.16 * var(--lift-k)));
  color: var(--text-bright);
}
.drawer-item-icon:active { transform: scale(0.94); }

/* -------------------------------------------------------------------- *
 * 6. THE SPEED SHEET IS ONE TILE
 *
 * The sheet chrome no longer draws a panel around a pill; the pill is the
 * panel. The close button moved inside it, which is where the thing it
 * closes actually is.
 * -------------------------------------------------------------------- */
#speed-sheet {
  background: none;
  border: 0;
  box-shadow: none;
  /* ⛔ THE BACKDROP-FILTER IS THE OUTER TILE. Clearing the background and the
     shadow was not enough: a blurred backdrop paints a visible rounded region
     of its own, which is exactly the second box this change exists to remove.
     The pill carries its own blur, so the sheet needs none. */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-radius: 0;
  padding: 0 14px 14px;
}

/* The grip was a handle for a panel that no longer exists. */
#speed-sheet .bottom-sheet-grip { display: none; }

#speed-sheet .quantacode-slider-pill {
  padding: 16px 16px 18px;
  border-radius: 18px;
  background: var(--glass-dock, rgba(var(--lift-rgb), calc(0.06 * var(--lift-k))));
  border: 1px solid rgba(var(--lift-rgb), calc(0.11 * var(--lift-k)));
  box-shadow: 0 18px 48px rgba(var(--shade-rgb), calc(0.5 * var(--shadow-k)));
  backdrop-filter: var(--glass-blur, blur(18px));
  -webkit-backdrop-filter: var(--glass-blur, blur(18px));
}

#speed-sheet .slider-mini-header {
  display: flex;
  align-items: center;
  gap: 10px;
}
#speed-sheet .quantacode-wordmark { font-size: 15px; }
#speed-sheet .slider-gear-name { margin-left: auto; }

.speed-tile-close {
  flex: none;
  width: 28px;
  height: 28px;
  margin-left: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid rgba(var(--lift-rgb), calc(0.09 * var(--lift-k)));
  background: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k)));
  color: var(--muted);
  cursor: pointer;
}
.speed-tile-close:hover {
  color: var(--text-bright);
  background: rgba(var(--lift-rgb), calc(0.11 * var(--lift-k)));
}

.speed-tile-blurb {
  margin: 6px 0 12px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--muted);
}

/* ⛔ iOS FOCUS-ZOOM GUARD. Safari on iOS zooms the whole page when a focused
   field computes below 16px — jarring, and it fights the app's fixed layout
   (modern iOS ignores user-scalable=no, so the viewport tag does not stop it).
   The composer, the image/video prompt, the sign-in fields and the voice prompt
   all sat at 12.5-14.5px. This floors them at 16px on phones only; desktop keeps
   the tighter sizes. Appended last so it wins over the base and the duplicate
   .studio-textarea block regardless of source order. */
@media (max-width: 700px) {
  textarea#input,
  .studio-textarea,
  .key-input,
  .voice-text-prompt-input {
    font-size: 16px;
  }
}

/* ================================================================== *
 * SOVEREIGN MEMORY — the manage view. On-device, portable, un-minable.
 * ================================================================== */
.qmem-overlay {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.qmem-overlay.hidden { display: none; }
.qmem-backdrop {
  position: absolute; inset: 0;
  background: rgba(var(--shade-rgb, 0, 0, 0), 0.6);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.qmem-panel {
  position: relative; z-index: 1;
  width: 100%; max-width: 520px; max-height: 82vh;
  display: flex; flex-direction: column;
  background: var(--panel-solid, rgba(13, 16, 28, 0.98));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(var(--shade-rgb, 0, 0, 0), 0.6);
  overflow: hidden;
}
.qmem-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; padding: 18px 18px 12px;
}
.qmem-title-row { display: flex; gap: 12px; align-items: center; }
.qmem-icon {
  width: 34px; height: 34px; border-radius: 10px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: rgba(56, 189, 248, 0.12); color: var(--cyan);
}
.qmem-icon .ico { width: 18px; height: 18px; }
.qmem-title { font-size: 16px; font-weight: 700; color: var(--text-bright); }
.qmem-subtitle { font-size: 12px; color: var(--muted); margin-top: 2px; max-width: 340px; }
.qmem-x {
  flex: none; background: transparent; border: none; cursor: pointer;
  color: var(--faint); padding: 6px; border-radius: 8px; display: flex; align-items: center;
}
.qmem-x:hover { color: var(--text-bright); background: var(--raised); }
.qmem-x .ico { width: 16px; height: 16px; }
.qmem-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 18px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.qmem-toolbar-label { font-size: 13px; font-weight: 600; color: var(--text); }
.qmem-status { font-size: 12px; color: var(--muted); line-height: 1.5; padding: 6px 18px 0; }
.qmem-list { overflow-y: auto; padding: 8px 12px; flex: 1 1 auto; min-height: 60px; }
.qmem-empty { color: var(--muted); font-size: 13px; line-height: 1.55; padding: 22px 12px; text-align: center; }
.qmem-row { display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: 10px; }
.qmem-row:hover { background: var(--raised); }
.qmem-pin, .qmem-del {
  flex: none; background: transparent; border: none; cursor: pointer;
  color: var(--faint); padding: 5px; border-radius: 7px; display: flex; align-items: center;
}
.qmem-pin .ico, .qmem-del .ico { width: 14px; height: 14px; }
.qmem-pin.on { color: var(--amber); }
.qmem-del:hover { color: var(--red); background: rgba(248, 113, 113, 0.12); }
.qmem-text { flex: 1 1 auto; min-width: 0; font-size: 13.5px; color: var(--text); cursor: text; word-break: break-word; }
.qmem-edit {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: 13.5px;
  background: var(--raised); color: var(--text);
  border: 1px solid var(--line-strong); border-radius: 8px; padding: 6px 8px;
}
.qmem-addrow { display: flex; gap: 8px; padding: 12px 18px 8px; border-top: 1px solid var(--line); }
.qmem-add-input {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: 13.5px;
  background: var(--raised); color: var(--text);
  border: 1px solid var(--line); border-radius: 10px; padding: 9px 12px;
}
.qmem-add-input:focus { outline: none; border-color: var(--line-strong); }
.qmem-add-btn {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 14px; border-radius: 10px; border: none; cursor: pointer;
  background: var(--accent, #38bdf8); color: #04121e; font-weight: 700; font-size: 13px;
}
.qmem-add-btn .ico { width: 14px; height: 14px; }
.qmem-footer { display: flex; align-items: center; gap: 8px; padding: 6px 18px 16px; flex-wrap: wrap; }
.qmem-io-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 9px; cursor: pointer;
  background: transparent; border: 1px solid var(--line-strong);
  color: var(--muted); font-size: 12.5px; font-weight: 600;
}
.qmem-io-btn:hover { color: var(--text-bright); border-color: var(--cyan); }
.qmem-io-btn .ico { width: 13px; height: 13px; }
.qmem-clear {
  margin-left: auto; padding: 7px 12px; border-radius: 9px;
  background: transparent; border: 1px solid rgba(248, 113, 113, 0.4);
  color: var(--red); font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.qmem-clear:hover { background: rgba(248, 113, 113, 0.1); }

/* ==================================================================== *
 * MOBILE (<=899px) — the Android app loads this same live site, so the
 * phone fixes are scoped overrides here. Everything below is gated to
 * phone/overlay widths; the docked desktop layout (>=900px) never matches
 * it and is untouched. From the 2026-08-31 multi-surface mobile audit.
 * ==================================================================== */
@media (max-width: 899px) {

  /* THE TOP-BAR BLUR THAT OVERLAPPED THE HERO EMBLEM. The bar carries two
     frosted-glass pseudo-elements — .bar::before (backdrop-filter: blur(14px),
     ~74px tall) and .bar::after (a top gradient, ~66px). The mobile bar is meant
     to be transparent (its own background is already none), but these bands hang
     ~74px BELOW the bar and their blur bled down over the top crest of the
     emblem — worse once the native status-bar inset pushed the bar lower. Drop
     them on phones; the ☰/＋/profile buttons carry their own backgrounds, so the
     bar stays legible without the band. */
  .bar::before, .bar::after { display: none !important; }

  /* Usage fly-out — it centred with a translateX that the modalIn animation
     cancels, so it ran off the right edge and clipped its top rows. Pin it to
     both gutters and cap the height so every row is reachable. */
  #meter-flyout.meter-flyout {
    left: 14px;
    right: 14px;
    width: auto;
    max-width: none;
    transform: none;
    max-height: 62vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }

  /* Drawer — too much fixed chrome on a short screen collapsed the conversation
     list to height 0 and clipped the footer. Let the whole overlay scroll,
     render the list at natural height, and keep the header reachable. */
  .drawer { overflow-y: auto; overflow-x: hidden; }
  .drawer-history-section { flex: 1 0 auto; }
  .drawer-chat-list { flex: 0 0 auto; overflow-y: visible; }
  .drawer-header { position: sticky; top: 0; z-index: 2; background: var(--bg-deep); }

  /* Chat typography — desktop-scale prose reads too big/loose in a ~360px
     column, and the WebView auto-inflates long answers. Tighten and pin.
     --msg-scale (the text-size setting) is preserved throughout. */
  html, body { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  .msg.model .bubble { font-size: calc(14.75px * var(--msg-scale)); line-height: 1.58; }
  .msg.model .bubble p { margin-bottom: 10px; }
  .msg.user .bubble { font-size: calc(14.5px * var(--msg-scale)); padding-left: 13px; padding-right: 13px; }
  :root { --thread-gap: 15px; }
  :root[data-density="compact"] { --thread-gap: 10px; }

  /* Composer dock — reclaim width so the model name is not truncated to a few
     characters. */
  .composer-control-dock { gap: 6px; }
  .dock-actions { gap: 4px; }
  .dock-chip { padding: 5px 8px; }
  .speed-gear-pill { padding: 6px 9px; }

  /* Settings — full-width selects (were clipped at 148px), wrap long values,
     balance the theme segments, and give every toggle a 44px hit area. */
  .settings-row { flex-wrap: wrap; }
  .settings-row .settings-select { flex: 1 1 100%; width: 100%; max-width: none; order: 2; }
  .settings-row-label, .settings-row-desc { overflow-wrap: anywhere; }
  .settings-row-stack .theme-segmented { display: flex; width: 100%; }
  .settings-row-stack .theme-seg { flex: 1 1 0; text-align: center; padding-left: 10px; padding-right: 10px; }
  .settings-row .switch::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    width: 100%; height: 100%; min-width: 44px; min-height: 44px;
    transform: translate(-50%, -50%); background: transparent;
  }

  /* Bottom sheets (model picker, etc.) — make the body the bounded scroll
     region so every option is reachable, and clamp long descriptions. */
  .sheet { overflow: hidden; }
  .sheet-body { flex: 1 1 auto; min-height: 0; }
  .qm-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

  /* Artifact panel header — icon-only tabs free room for the filename. */
  .artifact-panel-header .art-tab span { display: none; }
  .artifact-panel-header .art-tab { padding: 5px 7px; }
  .artifact-panel-header { gap: 6px; }

  /* Plans modal — trim the heavy frame padding so the cards breathe. */
  .pricing-modal-overlay { padding: 10px; }
  .pricing-modal-panel { padding: 16px !important; border-radius: 16px !important; }
  .pricing-cards-grid { gap: 12px; margin: 14px 0; }
}

/* ------------------------------------------------------------------ *
 * The four inspiration chips on a phone. The wide desktop pills wrapped
 * into a ragged single-file stack at phone width; here they become a 2x2
 * grid of compact cards — icon above label, equal weight, centred — so
 * the welcome reads composed instead of piled.
 * ------------------------------------------------------------------ */
@media (max-width: 700px) {
  .hero-prompt-chips {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 9px;
    max-width: 340px;
    padding: 0 4px;
  }
  .hero-chip {
    flex-direction: column;
    justify-content: center;
    gap: 7px;
    padding: 13px 8px 11px;
    border-radius: 16px;
    font-size: 11.5px;
    letter-spacing: 0.01em;
    white-space: nowrap;
  }
  .hero-chip .chip-ico {
    width: 19px;
    height: 19px;
  }
}

/* ------------------------------------------------------------------ *
 * The drawer on a phone. The desktop rhythm is luxurious; on a small
 * screen it pushed Conversations and the meter below the fold, so the
 * whole menu needed scrolling. Tighten the CHROME — headers, labels,
 * paddings, the meter box — while every row keeps its 44px tap floor.
 * ------------------------------------------------------------------ */
@media (max-width: 700px) {
  .drawer-header { padding: 10px 12px 7px 12px; }
  .drawer-action-row { padding: 7px 12px 4px 12px; }
  .btn-drawer-new { padding: 8px 12px; }
  .drawer-nav { padding: 4px 10px; gap: 1px; }
  .nav-group-label { margin: 6px 0 2px; }
  .drawer-item { padding: 5px 10px; font-size: 12.5px; }
  .drawer-history-section { padding-top: 4px; }
  .drawer-usage-meter-box { padding: 8px 10px; margin: 6px 10px 8px; }
  .usage-meter-label, .usage-meter-val { font-size: 11px; }
  .drawer-footer { padding-top: 4px; padding-bottom: 6px; }
}

/* Second squeeze for the phone drawer: the 44px tap floor is untouchable,
   so the remaining height comes out of the CHROME — header, section labels,
   the meter box and the status pill — until the whole menu, conversations
   and meter fit one screen on a ~740px-tall viewport. */
@media (max-width: 700px) {
  .drawer-header { padding: 8px 12px 6px 12px; }
  .drawer-brand-logo { width: 30px; height: 30px; }
  .nav-group-label { margin: 4px 0 1px; }
  .drawer-item { font-size: 12px; gap: 8px; }
  .drawer-item .ico { width: 16px; height: 16px; }
  .nav-badge { font-size: 10px; padding: 2px 7px; }
  .drawer-usage-meter-box { padding: 6px 9px; margin: 4px 10px 6px; }
  .btn-drawer-upgrade-cta { padding: 6px; font-size: 11px; }
  .drawer-gateway-toggle-wrap { padding: 0 10px 6px; }
  .drawer-gateway-toggle-wrap .drawer-mode-toggle { padding: 5px 8px; }
}

/* -------------------------------------------------------------------- *
 * QUANTA OVERCLOCK™ — THE METER (ported 2026-09-01 from the Quanta Sync
 * IDE's QuantaSlider, Framer Motion → CSS custom properties).
 *
 * Two numbers drive everything: `--i`, the gear's intensity (0.12 Deep ·
 * 0.38 Flow · 0.68 Rapid · 1 QoC™), and `--p`, its position 0..1. app.js
 * sets both on .quantacode-slider-pill; every rule below reads them, so a
 * gear change is one style write and the whole track responds together —
 * glow radius, fill opacity, bar height and tempo, particle speed and
 * brightness. The colour flip is binary on purpose: indigo for the three
 * ordinary gears, the cyan/magenta quantum palette only at QoC™, so the top
 * notch reads as a different STATE, not a slightly bigger number.
 *
 * This block sits at the END of the file so it wins over the three earlier
 * speed-sheet passes without touching them. Reduced motion is handled by the
 * global 1ms rule for every CSS animation; the SMIL wave is not CSS, so it is
 * hidden explicitly below.
 * -------------------------------------------------------------------- */
#speed-sheet .quantacode-slider-pill { --i: 0.38; --p: 0.3333; }

#speed-sheet .slider-track-container {
  position: relative;
  width: 100%;
  height: 56px;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.10);
  background: linear-gradient(180deg, rgba(2, 6, 23, 0.95), rgba(9, 9, 20, 0.98));
  box-shadow: inset 0 0 calc(10px + var(--i) * 18px) rgba(99, 102, 241, calc(0.1 + var(--i) * 0.2));
  transition: border-color 500ms ease, box-shadow 500ms ease;
  display: block;
}
#speed-sheet .quantacode-slider-pill.gear-3 .slider-track-container {
  border-color: rgba(34, 211, 238, 0.5);
  box-shadow: 0 0 28px rgba(34, 211, 238, 0.35), inset 0 0 22px rgba(34, 211, 238, 0.16);
}

/* Spectrum fill, growing to the thumb. */
#speed-sheet .slider-spectrum-fill {
  position: absolute; inset: 0 auto 0 0;
  width: calc(var(--p) * 100%);
  background: linear-gradient(90deg, #3730a3 0%, #6366f1 60%, #818cf8 100%);
  opacity: calc(0.42 + var(--i) * 0.38);
  transition: width 420ms cubic-bezier(0.22, 1, 0.36, 1), opacity 300ms ease, background 300ms ease;
}
#speed-sheet .quantacode-slider-pill.gear-3 .slider-spectrum-fill {
  background: linear-gradient(90deg, #4338ca 0%, #7c3aed 30%, #d946ef 60%, #22d3ee 100%);
}

/* Vertical scan bars — a spectrum analyser reading the speed. 46 of them,
   each with its own hue (set inline by app.js) and a staggered delay. */
#speed-sheet .slider-bars-spectrum {
  position: absolute; inset: 0;
  display: flex; align-items: flex-end;
  gap: 3px; padding: 0 8px 6px;
  pointer-events: none;
}
#speed-sheet .spectrum-bar {
  flex: 1; min-width: 0;
  border-radius: 2px;
  height: 12%;
  opacity: 0.12;
  background: hsl(248 70% 62%);
  box-shadow: none;
  animation: none;
  transition: opacity 300ms ease;
}
#speed-sheet .spectrum-bar.lit {
  opacity: calc(0.5 + var(--i) * 0.5);
  animation: qocBarPulse var(--dur, 1.1s) ease-in-out infinite;
}
#speed-sheet .quantacode-slider-pill.gear-3 .spectrum-bar.lit {
  box-shadow: 0 0 6px currentColor;
}
@keyframes qocBarPulse {
  0%, 100% { height: 18%; }
  50%      { height: calc(28% + var(--i) * 62%); }
}

/* Particles streaming left to right, faster and brighter up the meter. */
#speed-sheet .slider-particles-container {
  position: absolute; inset: 0; overflow: hidden; pointer-events: none;
}
#speed-sheet .slider-particles-container i {
  position: absolute; left: -4%;
  border-radius: 50%;
  background: #c7d2fe;
  box-shadow: 0 0 4px #6366f1;
  opacity: 0;
  animation: qocDrift var(--dur, 2.3s) linear infinite;
}
#speed-sheet .quantacode-slider-pill.gear-3 .slider-particles-container i {
  background: #a5f3fc;
  box-shadow: 0 0 6px #22d3ee;
}
@keyframes qocDrift {
  0%   { left: -4%;  opacity: 0; }
  50%  { opacity: var(--i); }
  100% { left: 104%; opacity: 0; }
}

/* The quantum state — QoC™ only. An interference sweep and a probability
   wave morphing via SMIL (works in every engine; the CSS `d:` form only
   morphs in Chrome 130+). */
#speed-sheet .slider-quantum-wave { position: absolute; inset: 0; pointer-events: none; }
#speed-sheet .slider-quantum-wave.hidden { display: none !important; }
#speed-sheet .quantum-sweep {
  position: absolute; top: 0; bottom: 0; width: 33%; left: -33%;
  background: linear-gradient(90deg, transparent, rgba(165, 243, 252, 0.5), transparent);
  animation: qocSweep 1.5s linear infinite;
}
@keyframes qocSweep { from { left: -33%; } to { left: 100%; } }
#speed-sheet .quantum-wave-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
#speed-sheet .quantum-wave-path {
  fill: none; stroke: #67e8f9; stroke-width: 1.2; stroke-opacity: 0.85;
  animation: none;
}
:root[data-reduce-motion] #speed-sheet .quantum-wave-svg,
:root[data-reduce-motion] #speed-sheet .quantum-sweep { display: none; }
@media (prefers-reduced-motion: reduce) {
  #speed-sheet .quantum-wave-svg, #speed-sheet .quantum-sweep { display: none; }
}

/* The thumb. Travel is inset by its own radius so it never half-vanishes at
   the two stops people reach for most. Pulses and hue-cycles only at QoC™. */
#speed-sheet .slider-thumb {
  position: absolute; top: 50%;
  left: calc(13px + var(--p) * (100% - 26px));
  width: 22px; height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  border: 2px solid #a5b4fc;
  background: rgba(2, 6, 23, 0.9);
  box-shadow: 0 0 10px rgba(99, 102, 241, 0.6);
  transition: left 420ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 300ms ease, border-color 300ms ease;
  z-index: 2; pointer-events: none;
}
#speed-sheet .quantacode-slider-pill.gear-3 .slider-thumb {
  box-shadow: 0 0 18px rgba(34, 211, 238, 0.95);
  animation: qocThumbPulse 1.1s ease-in-out infinite;
}
@keyframes qocThumbPulse {
  0%, 100% { transform: scale(1);    border-color: #22d3ee; }
  50%      { transform: scale(1.18); border-color: #e879f9; }
}
#speed-sheet .slider-real-input { z-index: 10; }

/* Stops are buttons: a click jumps the gear. The selected QoC™ stop wears the
   wordmark's moving spectrum, exactly as the IDE does. */
#speed-sheet .speed-scale { margin-top: 10px; }
#speed-sheet .speed-scale button {
  background: none; border: 0; padding: 2px 4px; cursor: pointer;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 9.5px; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint);
  transition: color 200ms ease;
}
#speed-sheet .speed-scale button:hover { color: var(--muted); }
#speed-sheet .speed-scale button.on { color: #a5b4fc; font-weight: 700; }
#speed-sheet .quantacode-slider-pill.gear-3 .speed-scale button.on { color: #67e8f9; }
#speed-sheet .quantacode-slider-pill.gear-3 .speed-scale button.stop-qoc.on {
  background-image: linear-gradient(90deg, #f87171, #fbbf24, #4ade80, #22d3ee, #818cf8, #e879f9, #f87171);
  background-size: 220% 100%;
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
  animation: rainbowShift 6s linear infinite;
}
#speed-sheet .quantacode-slider-pill.gear-3 .slider-gear-name {
  color: #67e8f9;
  background: rgba(34, 211, 238, 0.12);
  border-color: rgba(34, 211, 238, 0.45);
  box-shadow: 0 0 10px rgba(34, 211, 238, 0.25);
}

/* THE READOUT — the gear has to prove it did something. */
.qoc-hud {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
  margin-top: 12px;
}
.qoc-hud-cell {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 7px 4px 6px;
  border-radius: 10px;
  background: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k)));
  border: 1px solid rgba(var(--lift-rgb), calc(0.08 * var(--lift-k)));
  min-width: 0;
}
.qoc-hud-cell b {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 13px; font-weight: 700; color: var(--text-bright);
  white-space: nowrap;
}
.qoc-hud-cell i {
  font-style: normal; font-size: 8.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint); white-space: nowrap;
}
#speed-sheet .quantacode-slider-pill.gear-3 .qoc-hud-cell b { color: #67e8f9; }
.qoc-hud.fresh .qoc-hud-cell b { animation: qocHudPop 420ms var(--ease-out, ease-out) both; }
@keyframes qocHudPop {
  0%   { transform: translateY(4px); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
@media (max-width: 380px) {
  .qoc-hud { grid-template-columns: repeat(2, 1fr); }
}

/* The dock pill wears the state too: at QoC™ the bolt pulses cyan, so the
   gear is readable without opening the meter. */
.speed-gear-pill.qoc {
  border-color: rgba(34, 211, 238, 0.5);
  box-shadow: 0 0 14px rgba(34, 211, 238, 0.22);
}
.speed-gear-pill.qoc .ico:first-child {
  color: #22d3ee;
  animation: qocBoltPulse 1.1s ease-in-out infinite;
}
@keyframes qocBoltPulse {
  0%, 100% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(34, 211, 238, 0)); }
  50%      { transform: scale(1.15); filter: drop-shadow(0 0 4px rgba(34, 211, 238, 0.9)); }
}

/* -------------------------------------------------------------------- *
 * QoC™ DRAFT-FIRST — the verdict chip. At the fast gear with a cloud model
 * selected, a resident local model answers at once and the cloud model is
 * asked only to verify. Its one-line verdict lands under the reply: a quiet
 * green check when the draft stood, an amber correction when it did not.
 * -------------------------------------------------------------------- */
.qoc-verdict {
  display: inline-flex; align-items: flex-start; gap: 8px;
  margin: 8px 0 2px;
  padding: 6px 10px;
  border-radius: 10px;
  font-size: 12.5px; line-height: 1.4;
  border: 1px solid rgba(var(--lift-rgb), calc(0.10 * var(--lift-k)));
  background: rgba(var(--lift-rgb), calc(0.04 * var(--lift-k)));
  color: var(--muted);
  max-width: 100%;
}
.qoc-verdict.pending { opacity: 0.75; }
.qoc-verdict.pending .qoc-verdict-dot { animation: qocVerdictPulse 1.1s ease-in-out infinite; }
.qoc-verdict.verified { border-color: rgba(52, 211, 153, 0.35); color: var(--emerald, #34d399); }
.qoc-verdict.correction { border-color: rgba(251, 191, 36, 0.45); color: var(--text); background: rgba(251, 191, 36, 0.06); }
.qoc-verdict .qoc-verdict-dot {
  flex: none; width: 8px; height: 8px; margin-top: 5px; border-radius: 50%;
  background: currentColor; box-shadow: 0 0 6px currentColor;
}
.qoc-verdict b { font-weight: 700; margin-right: 4px; }
@keyframes qocVerdictPulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }

/* -------------------------------------------------------------------- *
 * DYNAMIC CHAT — cards and blocks drawn under and inside a reply.
 * Calculator card · tool cards (weather, currency, Wikipedia) · dynamic
 * blocks (mermaid, chart, math) · markdown pipe tables. Every surface reads
 * the same tokens as .code-card so they sit as siblings of a code block.
 * -------------------------------------------------------------------- */
.calc-card {
  margin: 8px 0 2px; padding: 10px 14px;
  border-radius: 12px; border: 1px solid var(--line);
  background: var(--panel);
  max-width: 100%;
}
.calc-card-head {
  display: flex; align-items: center; gap: 6px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  margin-bottom: 6px;
}
.calc-card-head .ico { width: 12px; height: 12px; color: var(--accent); }
.calc-card-head i { font-style: normal; margin-left: auto; color: var(--faint); letter-spacing: .04em; }
.calc-card-body { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-family: var(--font-mono, ui-monospace, monospace); }
.calc-working { font-size: 14px; color: var(--text); }
.calc-equals { font-size: 16px; color: var(--faint); }
.calc-result { font-size: 22px; font-weight: 700; color: var(--text-bright); }

.tool-cards { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 2px; }
.tool-card {
  border-radius: 12px; border: 1px solid var(--line); background: var(--panel);
  padding: 10px 12px; min-width: 0; flex: 1 1 260px; max-width: 100%;
}
.tool-card-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
.tool-card-head span { font-weight: 600; color: var(--text-bright); font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tool-card-head i { font-style: normal; margin-left: auto; font-size: 10.5px; color: var(--faint); letter-spacing: .04em; white-space: nowrap; }
.wx-now { display: flex; align-items: center; gap: 10px; }
.wx-icon { font-size: 30px; line-height: 1; }
.wx-temp { font-size: 30px; font-weight: 700; color: var(--text-bright); letter-spacing: -0.02em; }
.wx-meta { font-size: 12px; color: var(--muted); line-height: 1.35; text-transform: capitalize; }
.wx-days { display: flex; gap: 6px; margin-top: 8px; }
.wx-day { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 6px 4px; border-radius: 8px; background: rgba(var(--lift-rgb), calc(0.05 * var(--lift-k))); font-size: 15px; }
.wx-day b { font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--faint); }
.wx-day i { font-style: normal; font-size: 11px; color: var(--muted); }
.fx-body { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-family: var(--font-mono, ui-monospace, monospace); }
.fx-from { font-size: 14px; color: var(--text); }
.fx-to { font-size: 22px; font-weight: 700; color: var(--text-bright); }
.fx-rate { margin-top: 4px; font-size: 11.5px; color: var(--faint); }
.tool-wiki { display: flex; gap: 12px; align-items: flex-start; }
.wiki-thumb { width: 72px; height: 72px; object-fit: cover; border-radius: 10px; border: 1px solid var(--line); flex: none; max-height: 72px !important; margin: 0 !important; }
.wiki-text { min-width: 0; flex: 1; }
.wiki-desc { font-size: 11.5px; color: var(--faint); text-transform: capitalize; margin: -2px 0 4px; }
.wiki-extract { font-size: 13px; color: var(--text); line-height: 1.5; }
.wiki-link { display: inline-block; margin-top: 6px; font-size: 12px; color: var(--accent); text-decoration: none; }
.wiki-link:hover { text-decoration: underline; }

/* dynamic blocks */
.dyn-block {
  margin: 8px 0; border-radius: 12px; border: 1px solid var(--line); background: var(--panel); overflow: hidden;
}
.dyn-head { display: flex; align-items: center; justify-content: space-between; padding: 5px 10px; background: var(--panel-solid); border-bottom: 1px solid var(--line); }
.dyn-label { font-size: 10.5px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.dyn-view { padding: 12px; overflow-x: auto; }
.dyn-view svg { max-width: 100%; height: auto; display: block; margin: 0 auto; }
.dyn-chart .dyn-view { height: 280px; }
.dyn-math .dyn-view { text-align: center; font-size: 1.05em; }
.dyn-block .dyn-src { display: block; max-height: 260px; overflow: auto; }
.dyn-block.dyn-ready .dyn-src { display: none; }
.dyn-block.dyn-ready.show-source .dyn-src { display: block; border-top: 1px solid var(--line); }
.dyn-block.dyn-error .dyn-view { display: none; }
.dyn-note { padding: 6px 10px; font-size: 11.5px; color: var(--amber, #fbbf24); border-top: 1px solid var(--line); }

/* markdown pipe tables */
.md-table-wrap { overflow-x: auto; margin: 8px 0; -webkit-overflow-scrolling: touch; }
.md-table { border-collapse: collapse; width: 100%; font-size: 13.5px; line-height: 1.45; }
.md-table th, .md-table td { padding: 6px 10px; border: 1px solid var(--line); text-align: left; vertical-align: top; }
.md-table th { background: var(--panel-solid); color: var(--text-bright); font-weight: 600; }
.md-table tbody tr:nth-child(even) td { background: rgba(var(--lift-rgb), calc(0.03 * var(--lift-k))); }

/* ------------------------------------------------------------------ *
   iOS standalone: fill the home-indicator safe area with the app ground.

   The persistent grey strip at the very bottom of the installed PWA (Safari
   AND Chrome, iOS 18) is the safe-area-inset-bottom region showing through as
   a flat band beneath the app. viewport-fit=cover lets us paint into it, so a
   fixed layer pinned to the bottom edge, exactly the inset tall, covers it
   with the same colour as the app floor. Zero height when there is no inset,
   so it is inert everywhere but iOS standalone. pointer-events:none so it
   never eats a tap.

   ⛔ IF THE GREY SURVIVES THIS, the region is not the app's to paint — it is
   the browser's own reserved bar, and no site can remove it.
 * ------------------------------------------------------------------ */
body::after {
  content: "";
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: env(safe-area-inset-bottom);
  background: var(--bg-deep, #06070a);
  z-index: 2147483000;
  pointer-events: none;
}
