/* Genie — Gemini-style design system. Dark by default, light via [data-theme].
   No external assets: the Google Sans stack is progressive (falls back to
   system-ui where the font isn't installed; nothing is fetched). */

:root {
  /* ---- dark palette (default) — Gemini's warm near-black ---- */
  --bg: #131314;
  --bg-elev: #1e1f20;
  --bg-elev-1: #232425;
  --bg-elev-2: #2a2b2d;
  --bg-elev-3: #333537;
  --border: #3a3b3d;
  --border-strong: #5f6063;
  --text: #e3e3e3;
  --text-dim: #c4c7c5;
  --text-faint: #9aa0a6;

  /* Mono accent — white on dark (black on light, see the light palette).
     --on-accent flips the text drawn on accent fills so it stays legible.
     The composed --grad/--grad-h below also flatten to this accent. */
  --accent: #ffffff;
  --accent-2: #ffffff;
  --on-accent: #131314;
  /* Tonal accent wash for selected/active surfaces (e.g. active pill tabs).
     Derived from --accent so it follows the theme (a faint white tint on dark,
     a faint black tint on light) — never a hardcoded hue. */
  --accent-dim: color-mix(in srgb, var(--accent) 14%, transparent);
  /* Selected pill fill for the 聪明钱/Quant tab rows. It LIVES here, not only on
     .sm-root, because the surfaces that use it escape that scope: the Quant pane
     mounts in .plugin-mount and the positions sheet mounts on <body> (a .sm-root
     descendant can't hold a position:fixed scrim — see .sm-sheet-scrim). An
     undefined custom property makes `background` invalid at computed-value time,
     so the ACTIVE tab fell back to transparent while every inactive tab kept its
     --bg-elev-1 pill — the selection read exactly backwards, and the owner tapped
     "Recent activity" believing they were leaving it (2026-07-31). */
  --sm-chip-on: color-mix(in srgb, var(--text) 16%, transparent);
  --grad-a: #4285f4;
  --grad-b: #9b72cb;
  --grad-c: #d96570;
  /* Functional accents (gradient text, progress bars, buttons) flatten to the mono
     accent — white on dark, black on light, theme-aware via var(--accent). The raw
     --grad-a/b/c stops above stay multi-color for the hero card + the two-color
     earn/team charts (which need distinguishable segments). */
  --grad: var(--accent);
  --grad-h: var(--accent);

  /* Text selection — a soft Gemini-blue wash. --grad-a is the same #4285f4 in
     both themes while --bg flips, so this single value stays a medium blue on
     dark and a light periwinkle on light; pairing it with --text keeps the
     selected copy legible. (Before, no ::selection rule existed and the OS
     default rendered the dim hint text dark-on-dark in the light theme.) */
  --selection-bg: color-mix(in srgb, var(--grad-a) 38%, var(--bg));
  --selection-fg: var(--text);

  --user-bubble: #2a3441;
  --user-bubble-border: #38465c;
  --ok: #6dd58c;
  --warn: #f3c969;
  --err: #f28b82;

  /* Smart-money palette — matched to the 预测市场 (predict) screen so the two
     sibling surfaces share one identity: bright green + coral-red. */
  --sm-up: #00D907;
  --sm-down: #FF5A3C;
  /* Ink on an --sm-up fill (CTAs, NEW chip, home badge): near-black on the
     bright dark-theme green, white on the deeper light-theme green. */
  --sm-on-up: #04140b;

  /* Destructive actions (delete / revert / failed). */
  --danger: #f28b82;
  --danger-text: #ffb4ab;
  --danger-bg: #3a1a1d;
  --danger-border: #6e2b2f;

  /* Banner tints. */
  --banner-info-bg: #1b2740;
  --banner-info-fg: #cdddff;
  --banner-info-bd: #2c4170;
  --banner-warn-bg: #3a3015;
  --banner-warn-fg: #f2dca0;
  --banner-warn-bd: #5e4d1f;
  --banner-error-bg: #3a1a1d;
  --banner-error-fg: #ffc9c9;
  --banner-error-bd: #6e2b2f;

  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 8px 28px rgba(0, 0, 0, 0.5);
  --shadow-lg: 0 18px 50px rgba(0, 0, 0, 0.6);

  /* Rounded-geometry scale. --radius-hero/-lg cover the larger Gemini-generous
     surfaces (heroes, modals, big cards) that previously hardcoded 20–28px. */
  --radius-hero: 28px;
  --radius-lg: 20px;
  --radius: 16px;
  --radius-md: 14px;
  --radius-sm: 12px;
  /* Small squircle for icon tiles / badge squares (was a sprawl of 8–11px). */
  --radius-tile: 10px;
  --gap: 16px;
  /* Material 3 "emphasized" easing. */
  --ease: cubic-bezier(0.2, 0, 0, 1);

  /* Type — Gemini's families. Text for body/UI, Flex for display/brand.
     Loaded from Google Fonts in index.html; falls back to the system font. */
  --font-ui: "Google Sans Text", "Google Sans Flex", "Google Sans", Roboto, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-display: "Google Sans Flex", "Google Sans Text", "Google Sans", Roboto, system-ui, sans-serif;
  --font-mono: "Google Sans Code", var(--font-mono);
}

/* ---- light palette — Gemini's soft blue-grey on white ---- */
:root[data-theme="light"] {
  /* Pure-white base so the installed PWA's status-bar strip (black-translucent shows
     the app bg through it) reads white in light theme, matching the app. The elevation
     ramp below was at/under the old grey --bg, so on white it now GAINS definition:
     flat panels (--bg-elev-1) read as a soft grey, inputs (--bg-elev-2/3) as deeper
     grey, and white cards/dialogs (--bg-elev) separate via their shadows/borders. */
  --bg: #ffffff;
  --bg-elev: #ffffff;
  --bg-elev-1: #f1f4f9;
  --bg-elev-2: #e9eef6;
  --bg-elev-3: #dde3ea;
  --border: #dadce0;
  --border-strong: #c4c7c5;
  --text: #1f1f1f;
  --text-dim: #444746;
  --text-faint: #80868b;

  --accent: #000000;
  --accent-2: #000000;
  --on-accent: #ffffff;

  --user-bubble: #d3e3fd;
  --user-bubble-border: #a8c7fa;
  --ok: #1a7f37;
  --warn: #b06000;
  --err: #d93025;

  /* Light theme keeps DARKER greens/reds: #00D907 as text on white fails
     contrast, and smart-money uses the accent for large PnL numbers (unlike
     predict, which uses white text). Coral-shifted red echoes #FF5A3C. */
  --sm-up: #008f04;
  --sm-down: #d9432a;
  --sm-on-up: #ffffff;

  --danger: #d93025;
  --danger-text: #b3261e;
  --danger-bg: #fce8e6;
  --danger-border: #f3b4ad;

  --banner-info-bg: #e8f0fe;
  --banner-info-fg: #1967d2;
  --banner-info-bd: #aecbfa;
  --banner-warn-bg: #fef7e0;
  --banner-warn-fg: #b06000;
  --banner-warn-bd: #fde293;
  --banner-error-bg: #fce8e6;
  --banner-error-fg: #c5221f;
  --banner-error-bd: #f3b4ad;

  --shadow-sm: 0 1px 2px rgba(60, 64, 67, 0.16);
  --shadow-md: 0 4px 18px rgba(60, 64, 67, 0.18);
  --shadow-lg: 0 12px 42px rgba(60, 64, 67, 0.24);
}

* { box-sizing: border-box; }

/* Themed text selection (must follow :root so the tokens resolve). ::selection
   and ::-moz-selection are kept as separate rules — an unknown pseudo in a
   shared selector list drops the whole rule in some engines. */
::selection { background: var(--selection-bg); color: var(--selection-fg); }
::-moz-selection { background: var(--selection-bg); color: var(--selection-fg); }

/* Material Symbols Rounded (Gemini-style menu icons) — SELF-HOSTED (was a Google
   Fonts <link>). The CDN font failed to load on flaky mobile connections and,
   because it uses font-display:block, glyphs fell back to raw ligature text
   ("chevron_right", "more_vert", …) that overflowed every layout. Serving the
   subset from our own origin (same connection as the app itself) removes that
   dependency. The @font-face + the base class below replicate what Google's
   stylesheet used to provide; styles.css then sets our thin optical axes. */
@font-face {
  font-family: "Material Symbols Rounded";
  font-style: normal;
  font-weight: 100 700;
  font-display: block;
  /* ?v=N is LOAD-BEARING: the server serves this file immutable/1y, so a
     changed font MUST change its URL or installed clients keep the old
     glyph set forever (the bell rendered as raw "NOTIFICATIONS" on prod).
     Bump the version every time the woff2 is regenerated. */
  src: url("/material-symbols.woff2?v=2") format("woff2");
}
.material-symbols-rounded {
  font-family: "Material Symbols Rounded";
  font-weight: normal;
  font-style: normal;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-feature-settings: "liga";
  font-feature-settings: "liga";
  -webkit-font-smoothing: antialiased;
  font-size: 20px;
  line-height: 1;
  font-variation-settings: "FILL" 0, "wght" 300, "GRAD" 0, "opsz" 24;
  vertical-align: middle;
  user-select: none;
}
.nav-drawer-icon.material-symbols-rounded { font-size: 22px; }

/* App-wide currency convention: an icon + the number (Power = bolt ⚡, Credits = toll ◉),
   never the spelled-out word. The icon inherits the surrounding color so it reads on any
   surface; it sits slightly larger than the digits and optically aligned. */
.cur { display: inline-flex; align-items: center; gap: 3px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cur .cur-icon { font-size: 1.1em; line-height: 1; }
.cur .cur-val { line-height: 1; }
/* Match the sizes of the bespoke number styles these chips replaced. */
.store-bal-cur .cur { font-size: 17px; font-weight: 700; color: var(--text); }
.store-card-amount .cur { font-size: 26px; font-weight: 700; color: var(--text); }
.store-card-cost .cur { font-size: 15px; font-weight: 700; }
.agentset-stat-cur .cur { font-family: var(--font-display); font-size: 30px; font-weight: 700; color: var(--text); }

html, body {
  margin: 0;
  height: 100%;
}
/* Dark ground behind the body so any sub-pixel height gap never flashes the page's
   default white — a belt for the iOS standalone viewport quirk handled below. */
html { background: var(--bg); }

body {
  background: var(--bg);
  color: var(--text);
  /* Gemini's reading scale: 16px body / 1.5 line-height. */
  font: 16px/1.5 var(--font-ui);
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}

/* Installed to the Home Screen (PWA): with no forcing apple status-bar style, iOS reserves
   the status bar and the web view sits BELOW it (inset). -webkit-fill-available resolves to
   that inset web-view height, filling it with no bottom gap; 100vh is the fallback. Scoped
   to standalone so the browser keeps 100dvh (correct there against Safari's dynamic toolbars). */
@media (display-mode: standalone) {
  body { height: 100vh; height: -webkit-fill-available; }
}

/* ---------------- system notification (Gemini dropdown) ----------------
   A compact, rounded alert that slides down from the top — NOT a full-width
   bar. Centered, capped width, with a kind icon + a dismiss button. */
.banner {
  position: fixed; top: 12px; left: 50%; z-index: 50;
  display: none; align-items: flex-start; gap: 10px;
  width: max-content; max-width: min(460px, calc(100vw - 28px));
  padding: 11px 10px 11px 14px;
  font-size: 13.5px; font-weight: 500; line-height: 1.42; text-align: left;
  border-radius: var(--radius); border: 1px solid transparent;
  box-shadow: var(--shadow-md);
  transform: translate(-50%, -12px); opacity: 0;
  transition: opacity 200ms var(--ease), transform 200ms var(--ease);
}
.banner.show { display: flex; }
.banner.is-in { transform: translate(-50%, 0); opacity: 1; }
.banner-icon { flex: none; font-size: 20px; margin-top: -1px; opacity: 0.95; }
.banner-msg { flex: 1 1 auto; min-width: 0; padding-top: 1px; }
.banner-close {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; margin: -2px 0 0 2px; padding: 0;
  background: none; border: none; cursor: pointer; border-radius: 999px;
  color: inherit; opacity: 0.6; font-size: 18px; transition: opacity 0.15s var(--ease), background 0.15s var(--ease);
}
.banner-close:hover { opacity: 1; background: color-mix(in srgb, currentColor 16%, transparent); }
.banner-action {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  margin: -1px 0 0; padding: 4px 11px;
  background: color-mix(in srgb, currentColor 14%, transparent);
  border: none; border-radius: 999px; cursor: pointer;
  color: inherit; font: inherit; font-weight: 600; font-size: 12.5px; white-space: nowrap;
  transition: background 0.15s var(--ease);
}
.banner-action:hover { background: color-mix(in srgb, currentColor 24%, transparent); }
.banner-action-icon { font-size: 15px; margin-left: -2px; }
.banner-info { background: var(--banner-info-bg); color: var(--banner-info-fg); border-color: var(--banner-info-bd); }
.banner-warn { background: var(--banner-warn-bg); color: var(--banner-warn-fg); border-color: var(--banner-warn-bd); }
.banner-error { background: var(--banner-error-bg); color: var(--banner-error-fg); border-color: var(--banner-error-bd); }
@media (prefers-reduced-motion: reduce) { .banner { transition: opacity 120ms linear; transform: translate(-50%, 0); } }

/* ---------------- floating menu button (no top bar) ---------------- */

/* The hamburger floats over the content, top-left (Gemini sidebar pattern).
   Sits below the session overlay (z 80) so a full-screen mobile session covers
   it; on the home view and desktop split-view it's visible over empty space. */
.nav-fab {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 40;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  background: transparent;
  color: var(--text-dim);
  border: none;
  border-radius: 50%;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.nav-fab:hover { background: var(--bg-elev-2); color: var(--text); }
.nav-fab .material-symbols-rounded { font-size: 24px; }

/* Home loop strip — explicit "you can act now" rows under the earnings widget: a free-pull CTA
   with the exact count, and a card-progress bar that flips to a bold Ready-to-upgrade CTA. */
/* Home hub — a fixed top-right corner cluster on the tasks screen (mirrors the top-left
   hamburger): the star-up progress/CTA + a free-gacha count. Sits in the viewport corner,
   never in the composer's flow. Collapses when there's nothing to act on. */
.home-hub-slot:empty { display: none; }
.home-hub {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 40;
  /* Match the hamburger's 48px box at the same top so the pills' centers line up with it
     (they're only 40px tall) — stays aligned across breakpoints since both share `top`. */
  height: 48px;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* Hidden while a task is open (home chrome collapses to the task list) — matches the
   earn/mascot slots. Every other screen hides it via the .tasks-view display cascade. */
.layout.session-active .home-hub { display: none; }

.hub-pill {
  display: inline-flex; align-items: center; gap: 7px;
  height: 40px; padding: 0 13px 0 11px;
  border: 1px solid var(--border); border-radius: 999px;
  background: color-mix(in srgb, var(--bg-elev-1) 86%, transparent);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: var(--text); cursor: pointer; font: inherit;
  transition: transform 0.12s var(--ease), filter 0.12s var(--ease), background 0.12s var(--ease);
}
.hub-pill:hover { background: var(--bg-elev-2); }
.hub-pill:active { transform: translateY(1px); }
.hub-pill .material-symbols-rounded { font-size: 22px; }
.hub-count { font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: 0.2px; }

/* Free-gacha gift pill — brand gradient so it reads as a reward waiting to be opened. */
.hub-gift {
  border-color: transparent; color: #fff;
  background: linear-gradient(135deg, #0036ff, #c600ff);
  box-shadow: 0 8px 20px -12px rgba(120, 40, 255, 0.9);
  animation: hub-gift-pulse 2.4s var(--ease) infinite;
}
.hub-gift:hover { filter: brightness(1.06); background: linear-gradient(135deg, #0036ff, #c600ff); }
@keyframes hub-gift-pulse {
  0%, 100% { box-shadow: 0 8px 20px -12px rgba(120, 40, 255, 0.9); }
  50%      { box-shadow: 0 8px 26px -10px rgba(160, 60, 255, 1); }
}

/* Star-up in-progress — a compact progress bar toward the next star + the exact fraction, so
   "2/5 towards ★N" reads at a glance. */
.hub-up:not(.is-ready) .hub-up-star { color: #a78bfa; }
.hub-bar {
  flex: none; width: 46px; height: 6px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--text) 15%, transparent);
}
.hub-bar-fill {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, #0036ff, #c600ff); transition: width 0.5s var(--ease);
}

/* Star-up ready — a glowing brand pill that periodically WIGGLES to invite the upgrade tap
   (it's a live CTA: tapping stars the agent up). Rest for most of the cycle, then a quick
   left-right shake with a tiny pop — attention without a constant distraction. */
.hub-up.is-ready {
  border-color: transparent; color: #fff;
  background: linear-gradient(135deg, #6d28d9, #c600ff);
  box-shadow: 0 10px 26px -10px rgba(170, 70, 255, 1);
  transform-origin: center;
  animation: hub-up-wiggle 2.4s var(--ease) infinite;
}
.hub-up.is-ready:hover { filter: brightness(1.07); background: linear-gradient(135deg, #6d28d9, #c600ff); }
.hub-up.is-ready .material-symbols-rounded { color: #fff; }
.hub-up-star { font-size: 13px; font-weight: 800; letter-spacing: 0.2px; }
@keyframes hub-up-wiggle {
  0%, 58%, 100% { transform: rotate(0) scale(1); }
  64% { transform: rotate(-9deg) scale(1.06); }
  70% { transform: rotate(7deg)  scale(1.06); }
  76% { transform: rotate(-6deg) scale(1.04); }
  82% { transform: rotate(4deg)  scale(1.03); }
  88% { transform: rotate(-2deg) scale(1.02); }
  94% { transform: rotate(1deg)  scale(1.01); }
}
@media (prefers-reduced-motion: reduce) {
  .hub-gift, .hub-up.is-ready { animation: none; }
  .hub-bar-fill { transition: none; }
}

/* Mobile: align the hub's top edge with the hamburger (both drop under the safe-area inset). */
@media (max-width: 720px) { .home-hub { top: calc(env(safe-area-inset-top) + 4px); } }

/* Agent chooser dropdown — now the head of the nav drawer (full width). */
.agent-switch { position: relative; }
.agent-switch-btn {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  background: transparent;
  color: var(--text);
  border: none;
  border-radius: 999px;
  padding: 10px 14px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.agent-switch-btn:hover { background: var(--bg-elev-2); }
/* Push the caret to the far right so the pill reads as a full-width selector. */
.agent-switch-btn .agent-switch-caret { margin-left: auto; }
.agent-switch-btn .agent-switch-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent-switch-btn .agent-switch-mark {
  font-size: 18px;
  font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.agent-switch-btn .agent-switch-caret { color: var(--text-faint); font-size: 20px; }

/* Shared dropdown menu surface (hamburger menu + agent list). */
.nav-menu {
  position: absolute;
  top: calc(100% + 8px);
  min-width: 220px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 6px;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
/* Agent menu drops down under the full-width switcher at the drawer head. A long agent list
   must scroll INSIDE the menu instead of running off the bottom of the viewport — cap to the
   viewport height and scroll; keep the "Your agents" head pinned so it stays visible. */
.nav-menu.from-agent {
  left: 0; right: 0;
  max-height: calc(100vh - 84px);
  max-height: calc(100dvh - 84px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.nav-menu.from-agent .nav-menu-head {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-elev);
}
.nav-menu.from-right { right: 0; }
/* Account menu sits at the drawer foot, so it opens upward. */
.nav-menu.from-user { left: 0; right: 0; bottom: calc(100% + 8px); top: auto; }

/* Agent card in the chooser dropdown: name + power/credits/expiry, with a settings
   button. Tall enough to host the stats comfortably. Main area switches agents. */
.agent-card { display: flex; align-items: stretch; gap: 2px; }
.agent-card-main {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
  text-align: left;
  background: transparent;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-sm);
  padding: 11px 8px 11px 12px;
  color: var(--text);
  transition: background 0.15s var(--ease);
}
.agent-card-main:hover { background: var(--bg-elev-2); }
.agent-card.is-current .agent-card-main { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.agent-card-radio { flex: none; font-size: 18px; color: var(--text-dim); }
.agent-card.is-current .agent-card-radio { color: var(--accent); }
.agent-card-info { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 6px; }
.agent-card-name-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
.agent-card-name {
  /* No flex-grow: the star badge should hug the name, not float to the far edge. */
  flex: 0 1 auto; min-width: 0;
  font-size: 14px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Admin-suspended mark — same "bad" red used for a suspended USER elsewhere in admin. */
.agent-card-suspended {
  flex: none; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  padding: 2px 7px; border-radius: 999px;
  color: var(--err); background: color-mix(in srgb, var(--err) 16%, transparent);
}
/* Stats stack on two lines (credits, then days left) — a single row truncates or
   wraps unpredictably as numbers and translations grow. */
.agent-card-stats { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
.agent-card-stat {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.agent-card-stat .material-symbols-rounded { font-size: 15px; color: var(--accent); }
.agent-card-divider { color: var(--text-faint); font-size: 12px; opacity: 0.6; }
.agent-card-sub { font-size: 12px; color: var(--text-faint); white-space: nowrap; }
.agent-card-sub.is-warn { color: var(--warn); }
.agent-card-sub.is-danger { color: var(--danger-text); }
.agent-card-gear {
  flex: none; width: 40px; height: 40px; align-self: center;
  display: grid; place-items: center;
  background: transparent; border: none; cursor: pointer;
  color: var(--text-dim); border-radius: 999px;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.agent-card-gear:hover { background: var(--bg-elev-2); color: var(--text); }
.agent-card-gear .material-symbols-rounded { font-size: 19px; }

/* Star tier pill — a tier-colored "★ N" badge shared across every agent surface
   (picker, dropdown, settings, Earn card) so a high-star agent reads as different at
   a glance. Tint escalates with tier; the buy-agent card uses its own frosted badge.
   See src/web/star.ts. */
.star-pill {
  flex: none; display: inline-flex; align-items: center; gap: 3px;
  padding: 3px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.star-pill-ic { font-size: 13px; font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 20; }
.star-pill--md { font-size: 13px; padding: 4px 11px; }
.star-pill--md .star-pill-ic { font-size: 15px; }
.star-pill--low { color: var(--text-dim); background: var(--bg-elev-3); }
.star-pill--mid { color: var(--warn); background: color-mix(in srgb, var(--warn) 18%, transparent); }
.star-pill--high { color: #fff; background: linear-gradient(135deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c)); }

.user-avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 14px;
  font-weight: 600;
  color: var(--on-accent);
  background: var(--grad);
}

/* Account profile row at the bottom of the drawer. */
.nav-drawer-foot {
  margin-top: auto;
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  padding-top: 8px;
}
.nav-foot-gear { flex: 0 0 auto; }
.user-profile-btn {
  display: flex;
  align-items: center;
  gap: 11px;
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: none;
  border-radius: 999px;
  padding: 8px 10px;
  cursor: pointer;
  color: var(--text);
  text-align: left;
  transition: background 0.15s var(--ease);
}
.user-profile-btn:hover { background: var(--bg-elev-2); }
.user-profile-meta { display: flex; flex-direction: column; min-width: 0; }
.user-profile-name { font-size: 14px; font-weight: 600; }
.user-profile-email {
  font-size: 12px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
.user-profile-email:empty { display: none; }

.nav-menu[hidden] { display: none; }
.nav-menu-head {
  padding: 8px 10px 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-faint);
}
.nav-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: 13.5px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.nav-menu-item:hover { background: var(--bg-elev-2); }
.nav-menu-item .nav-menu-icon { width: 20px; text-align: center; color: var(--text-dim); }
.nav-menu-item.is-current { color: var(--accent); }
.nav-menu-item .nav-menu-sub { margin-left: auto; font-size: 11px; color: var(--text-faint); }
.nav-menu-sep { height: 1px; background: var(--border); margin: 10px 2px; }

/* Hamburger menu — left slide-in drawer overlaying the chat. */
.nav-drawer-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, 0);
  transition: background 0.25s ease;
}
.nav-drawer-overlay.open { background: rgba(0, 0, 0, 0.45); }
.nav-drawer-overlay[hidden] { display: none; }
.nav-drawer {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 300px;
  max-width: 82vw;
  background: var(--bg-elev);
  border: none;
  box-shadow: var(--shadow-lg);
  /* Rounded right edge so the panel reads as a floating sheet, not a full-bleed
     slab (its left edge is off-screen, so only the right corners show). Generous
     radius to match the reference. */
  border-radius: 0 var(--radius-hero) var(--radius-hero) 0;
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateX(-100%);
  transition: transform 0.25s var(--ease);
}
.nav-drawer-overlay.open .nav-drawer { transform: translateX(0); }
/* Head holds the agent switcher; leave room for the floating hamburger above. */
.nav-drawer-head {
  padding: 2px 0 10px;
  margin-bottom: 6px;
}
.nav-drawer-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: 15px;
  padding: 14px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.nav-drawer-item:hover { background: var(--bg-elev-2); }
/* Current screen: the same rounded pill, kept lit so the drawer always shows
   where you are. */
.nav-drawer-item.is-current { background: var(--bg-elev-2); }
.nav-drawer-item.is-current .nav-drawer-icon { color: var(--text); }
.nav-drawer-icon { width: 22px; text-align: center; color: var(--text-dim); }

/* ---------------- Plugins in the drawer + shortcut chips ---------------- */
/* Mid-drawer shelf: the drawer owns scrolling, so this section is ordinary
   document flow—no nested scroller, resize observer, or fade mask. */
.nav-drawer-head,
.nav-drawer-item,
.nav-drawer-foot { flex: none; }
.plugin-section { flex: none; }
.plugin-section-head { padding-top: 2px; }
.plugin-list { display: flex; flex-direction: column; gap: 2px; }
.plugin-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 12px;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.plugin-row:hover { background: var(--bg-elev-2); }
.plugin-row.is-locked { cursor: default; }
.plugin-row.is-locked:hover { background: transparent; }
.plugin-icon { flex: none; width: 22px; text-align: center; color: var(--text-dim); }
.plugin-icon.material-symbols-rounded { font-size: 22px; }
.plugin-icon-glyph { font-size: 18px; line-height: 22px; }
.plugin-row-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.plugin-row-name { font-size: 14px; color: var(--text); }
.plugin-row-tagline {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11px; line-height: 1.35; color: var(--text-faint);
}
.plugin-row-tagline:empty { display: none; }
.plugin-switch { flex: none; display: inline-flex; align-items: center; }
.plugin-lock {
  flex: none;
  font-size: 11px;
  color: var(--text-faint);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 8px;
  white-space: nowrap;
}
.plugin-empty { padding: 7px 12px 9px; font-size: 12px; color: var(--text-faint); }
/* Shortcut chips ride their own row above the composer; the row is absent
   entirely when no enabled plugin has a shortcut. */
.plugin-chips[hidden] { display: none; }
/* A PLUGIN CHIP WHOSE OWN SCREEN IS THE ONE ON THE PAGE. The shortcut row says
   where you ARE, not only where you can go — so the chip inverts while you are
   inside it and goes back the moment you leave. Which chip is lit comes from
   `setActivePluginView` in plugins.ts, which core sets from the view name the
   plugin's own manifest chose; nothing here knows a plugin's name. On tokens, so
   it reads the same in both themes. */
.plugin-chip.is-on {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
  font-weight: 600;
}
.plugin-chip.is-on .plugin-icon { color: inherit; }
.plugin-chip .plugin-icon { width: auto; font-size: 18px; color: inherit; }
.plugin-chip .plugin-icon.material-symbols-rounded { font-size: 18px; }

/* ---------------- Tasks home (Gemini-style) + session slide-in ---------------- */
/* A centered column positioned in the upper-middle: gradient greeting, pill
   composer, then the Recent task list. The whole view scrolls if long. */
.tasks-view {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  background: transparent;
  /* The view itself doesn't scroll — the greeting/composer/recent header is
     pinned and only the task list scrolls (Gemini's home layout). */
  overflow: hidden;
}
.home-center {
  width: 100%;
  max-width: 760px;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0 24px;
}
/* Hero band: occupies the top ~half of the desktop home. The greeting sits in the
   top-middle as the focal point (padding-top) with the composer cluster below it;
   the Recent row and task list follow as siblings underneath. */
.home-header {
  flex: 0 0 auto;
  min-height: 50vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  /* Tight — the mascot is the focal point and sits close to the top. */
  padding-top: 5vh;
  position: relative;
  z-index: 2;
}
.home-greeting,
.home-composer-wrap,
.recent-bar { flex: 0 0 auto; }

/* The greeting as a speech bubble the mascot appears to be saying — a small
   centered tonal pill sitting just under the mascot, with a triangular tail
   (::after) poking up to touch it. Replaces the old full-width gradient
   hero heading now that the mascot itself (not the text) is the focal point. */
.home-greeting {
  position: relative;
  width: fit-content;
  max-width: min(88%, 380px);
  margin: 0 auto 40px;
  text-align: center;
  font-family: var(--font-display);
  font-size: 17px;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: -0.1px;
  color: var(--text);
  background: var(--bg-elev-1);
  padding: 13px 20px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.home-greeting::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -6px;
  width: 12px;
  height: 12px;
  background: var(--bg-elev-1);
  transform: translateX(-50%) rotate(45deg);
}

/* "New task" button — only shown in the split view, where it replaces the hero
   composer (the full composer lives on the full-screen home instead). */
.home-new-task {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: fit-content;
  margin: 4px auto 18px;
  background: var(--bg-elev-2);
  color: var(--text);
  border: none;
  border-radius: 999px;
  padding: 15px 32px 15px 26px;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.home-new-task:hover { background: var(--bg-elev-3); }
.home-new-task .material-symbols-rounded { font-size: 22px; color: var(--text-dim); }

/* ── Home earnings widget (home-earn.ui.ts) ─────────────────────────────────
   A light card with two parts: a MUST-HAVE primary figure (total earned — always
   there) over a DYNAMIC good-to-know note (agent expiring, funds ready, today's
   gain, daily rate…). The number stays put while the note keeps it current, without
   the overload of a full dashboard. Taps through to the Earn screen. */
/* Mascot hero: a large, purely-decorative live mascot centered above the
   greeting (Gemini's centered-orb pattern) — no card chrome, no text.
   ~1/6 of the viewport height, clamped so it stays sane on very short/tall
   screens. */
/* NEGATIVE gap: the canvas renders the ball at ~48% of its own box (the rest
   is jump-arc headroom), leaving ~26% (≈9.6vh at the current 37vh canvas) of
   empty space below the ball. Pull the greeting up through part of that dead
   space — -5.1vh leaves ≈4.5vh of visual air between ball and bubble (1.5x
   the previous gap, per feedback). The canvas overlap is transparent and
   harmless. */
.home-mascot-slot { flex: 0 0 auto; margin: 0 0 clamp(-90px, -5.1vh, -16px); display: flex; justify-content: center; transition: opacity 0.35s ease; }
/* While the splash celebration plays, keep the hero slot empty (one mascot on
   screen at a time); it eases in as the overlay fades — the mascot settles home. */
html.is-app-ready:not(.is-splash-gone):not(.is-splash-skip) .home-mascot-slot { opacity: 0; }
.home-mascot-canvas {
  display: block;
  /* Sized up twice by feedback (1.5x, then 1.5x again) — the mascot IS the
     hero of the tasks screen, and the Recent list is capped at 3 cards to
     make the room. The 2D port frames the ball at ~48% of the canvas
     (jump headroom), so the visible ball is about half these values. */
  width: clamp(158px, 37vh, 470px);
  height: clamp(158px, 37vh, 470px);
  cursor: pointer;
  transition: filter 0.15s var(--ease);
}
.home-mascot-canvas:hover { filter: brightness(1.08); }
/* Static pre-WebGL placeholder (index.html): the canvas footprint, with the flat
   mascot scaled to ~48% to match where the live ball renders inside its frame
   (jump headroom). scale() keeps the layout box, so the header band doesn't jump
   when the live mascot mounts. */
.home-mascot-ph {
  display: block;
  width: clamp(158px, 37vh, 470px);
  height: clamp(158px, 37vh, 470px);
  object-fit: contain;
  transform: scale(0.48);
  animation: mascot-ph-breathe 2s ease-in-out infinite;
}
@keyframes mascot-ph-breathe {
  50% { transform: scale(0.5); opacity: 0.85; }
}
@media (prefers-reduced-motion: reduce) {
  .home-mascot-ph { animation: none; }
}

/* Narrower than the composer column and outlined rather than filled, so the
   stats widget reads as a distinct info card, not another input. */
.home-earn-slot { flex: 0 0 auto; width: 100%; max-width: 560px; margin: 0 auto 44px; }
.home-earn-slot:empty { display: none; } /* collapse when there's nothing to show (or toggled off) */
.home-earn {
  display: flex; flex-direction: column; width: 100%;
  padding: 14px 17px;
  border-radius: var(--radius-lg);
  background: transparent;
  border: 1px solid var(--border);
  cursor: pointer; text-align: left;
  transition: transform 0.13s var(--ease), background 0.15s var(--ease);
}
.home-earn:hover { background: var(--bg-elev-2); }
.home-earn:active { transform: scale(0.992); }
.home-earn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.home-earn-chev { flex: none; font-size: 22px; color: var(--text-faint); }

/* Primary — the must-have earnings figure (always shown). */
.home-earn-primary { display: flex; align-items: center; gap: 12px; }
.home-earn-primary-main { flex: 1 1 auto; min-width: 0; }
.home-earn-label { font-size: 11.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-faint); }
.home-earn-value {
  font-family: var(--font-display); font-size: 27px; font-weight: 700; line-height: 1.1;
  color: var(--text); font-variant-numeric: tabular-nums;
}
/* The running total with today's gain riding alongside it, baseline-aligned so
   the small "Today +$…" tag sits neatly against the big figure. */
.home-earn-value-row { display: flex; align-items: baseline; gap: 10px; margin-top: 2px; flex-wrap: wrap; }
.home-earn-today {
  flex: 0 0 auto;
  font-size: 13px; font-weight: 650; line-height: 1;
  color: var(--ok); font-variant-numeric: tabular-nums;
  padding: 3px 8px; border-radius: 999px;
  background: color-mix(in srgb, var(--ok) 15%, transparent);
  white-space: nowrap;
}

/* Note — the dynamic good-to-know line under a divider. */
.home-earn-note {
  display: flex; align-items: center; gap: 11px;
  margin-top: 13px; padding-top: 13px;
  border-top: 1px solid color-mix(in srgb, var(--text-faint) 16%, transparent);
}
.home-earn-note-ic {
  display: grid; place-items: center; flex: none;
  width: 34px; height: 34px; border-radius: var(--radius-tile);
  background: color-mix(in srgb, var(--accent) 16%, var(--bg-elev-2)); color: var(--accent);
}
.home-earn-note-ic .material-symbols-rounded { font-size: 19px; }
.home-earn-note-body { flex: 1 1 auto; min-width: 0; }
.home-earn-note-title {
  font-size: 13.5px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.home-earn-note-sub {
  font-size: 12px; color: var(--text-faint); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.home-earn-note-action {
  flex: none; border: none; cursor: pointer; border-radius: 999px;
  padding: 7px 14px; font: inherit; font-size: 12.5px; font-weight: 600;
  transition: filter 0.14s var(--ease), background 0.14s var(--ease);
}
.home-earn-note-action.is-primary { background: var(--grad); color: var(--on-accent); }
.home-earn-note-action.is-primary:hover { filter: brightness(1.08); }
.home-earn-note-action.is-warn { background: color-mix(in srgb, var(--warn) 22%, var(--bg-elev-2)); color: var(--warn); }
.home-earn-note-action.is-warn:hover { background: color-mix(in srgb, var(--warn) 32%, var(--bg-elev-2)); }

/* Note tones — the icon chip color. */
.home-earn-note.is-warn .home-earn-note-ic { background: color-mix(in srgb, var(--warn) 18%, var(--bg-elev-2)); color: var(--warn); }
.home-earn-note.is-positive .home-earn-note-ic { background: color-mix(in srgb, var(--ok) 18%, var(--bg-elev-2)); color: var(--ok); }
.home-earn-note.is-info .home-earn-note-ic { background: color-mix(in srgb, var(--accent) 16%, var(--bg-elev-2)); color: var(--accent); }
.home-earn-note.is-neutral .home-earn-note-ic { background: var(--bg-elev-2); color: var(--text-dim); }

/* New-user brand card — a single inviting row, no primary figure. */
.home-earn--brand { flex-direction: row; align-items: center; gap: 13px; }
.home-earn--brand .home-earn-note-ic { width: 40px; height: 40px; background: color-mix(in srgb, var(--grad-b) 22%, var(--bg-elev-2)); color: var(--text); }
.home-earn--brand .home-earn-note-ic .material-symbols-rounded { font-size: 22px; }
.home-earn--brand .home-earn-note-title { font-size: 15px; }

/* Skeleton: a value bar over a note row, reserving the card height on a cold load. */
.home-earn--skeleton { cursor: default; pointer-events: none; }
.home-earn-skel { background: var(--bg-elev-2); border-radius: 8px; animation: home-earn-pulse 1.3s ease-in-out infinite; }
.home-earn-skel-value { width: 42%; height: 26px; }
.home-earn-skel-note {
  display: flex; align-items: center; gap: 11px;
  margin-top: 13px; padding-top: 13px;
  border-top: 1px solid color-mix(in srgb, var(--text-faint) 16%, transparent);
}
.home-earn-skel-ic { width: 34px; height: 34px; border-radius: var(--radius-tile); flex: none; }
.home-earn-skel-line { height: 13px; flex: 1 1 auto; max-width: 58%; }
@keyframes home-earn-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.9; } }
@media (prefers-reduced-motion: reduce) { .home-earn-skel { animation: none; } }

/* ── Home self-improvement notice (home-improve.ui.ts) ──────────────────────
   Part of the stats widget: renders as a divider row INSIDE the earnings card
   (only when the daemon has unseen passes — the slot collapses otherwise).
   Brand-new-user fallback: as a sibling below the "Start earning" row card,
   where the default standalone-card look below applies. */
.home-earn-improve-slot:empty { display: none; }
.home-earn-slot > .home-earn-improve-slot { margin-top: 10px; }
.home-earn .home-earn-improve-slot { margin-top: 0; }
.home-earn .home-improve {
  background: transparent; border-radius: 0; padding: 13px 0 0; margin-top: 13px;
  border-top: 1px solid color-mix(in srgb, var(--text-faint) 16%, transparent);
}
.home-earn .home-improve:hover { background: transparent; }
.home-earn .home-improve:active { transform: none; }
.home-earn .home-improve-ic { width: 34px; height: 34px; }
.home-earn .home-improve-ic .material-symbols-rounded { font-size: 19px; }
.home-improve {
  display: flex; align-items: center; gap: 13px; width: 100%;
  padding: 15px 17px;
  border-radius: var(--radius-lg);
  background: var(--bg-elev-1);
  cursor: pointer; text-align: left;
  transition: transform 0.13s var(--ease), background 0.15s var(--ease);
}
.home-improve:hover { background: var(--bg-elev-2); }
.home-improve:active { transform: scale(0.992); }
.home-improve:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.home-improve-ic {
  display: grid; place-items: center; flex: none;
  width: 40px; height: 40px; border-radius: var(--radius-tile);
  background: color-mix(in srgb, var(--grad-b) 22%, var(--bg-elev-2)); color: var(--text);
}
.home-improve-ic .material-symbols-rounded { font-size: 22px; }
/* Needs-review tint when there are staged (ask-first) changes. */
.home-improve.is-review .home-improve-ic { background: color-mix(in srgb, var(--warn) 18%, var(--bg-elev-2)); color: var(--warn); }
.home-improve-body { flex: 1 1 auto; min-width: 0; }
.home-improve-title {
  font-size: 14px; font-weight: 650; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.home-improve-sub {
  font-size: 12px; color: var(--text-faint); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.home-improve-action {
  flex: none; border: none; cursor: pointer; border-radius: 999px;
  font-size: 12px; font-weight: 600; padding: 6px 13px;
  background: color-mix(in srgb, var(--warn) 22%, var(--bg-elev-2)); color: var(--warn);
}
.home-improve-action:hover { background: color-mix(in srgb, var(--warn) 32%, var(--bg-elev-2)); }
.home-improve-chev { flex: none; font-size: 22px; color: var(--text-faint); }

/* Pill composer — in-flow between the stats widget and the Recent list (per
   feedback; it used to be pinned to the viewport bottom). position:relative
   is load-bearing: the slash palette anchors to this wrap and opens upward. */
.home-composer-wrap {
  position: relative;
  width: 100%;
  z-index: 30;
  margin: 0 0 26px;
}
.home-composer {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--bg-elev-1);
  border: none;
  border-radius: var(--radius-hero);
  /* Spacious, Gemini-style: the input floats in generous padding rather than
     hugging a slim pill. */
  padding: 19px 24px;
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.15s var(--ease);
}
.home-composer:focus-within { box-shadow: var(--shadow-md); }
.home-composer-plus {
  flex: 0 0 auto;
  font-size: 22px;
  line-height: 1;
  color: var(--text-dim);
}
.home-input {
  flex: 1 1 auto;
  resize: none;
  max-height: 200px;
  background: transparent;
  color: var(--text);
  border: none;
  outline: none;
  padding: 4px 0;
  font: inherit;
  font-size: 16px;
  line-height: 1.5;
}
.home-input::placeholder { color: var(--text-faint); }
/* The home palette opens UPWARD — the composer is pinned to the viewport
   bottom now, so a downward drop would run off-screen. Higher-specificity
   selector so it beats the base `.cmd-palette` bottom/left/right (defined
   later in this file). */
.home-composer-wrap .home-palette {
  top: auto;
  bottom: calc(100% + 8px);
  left: 0;
  right: 0;
}

/* Assist chips — a horizontally-scrollable row of shortcut pills above BOTH
   composers (home + session; Gemini's mobile pattern). Tapping one fills the
   composer; 聪明钱 opens its board. Always visible (owner call 2026-07-13). */
.home-chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
  margin: 0 0 10px;
  padding: 2px;
  scroll-snap-type: x proximity;
}
.home-chips::-webkit-scrollbar { display: none; }
.home-chips:empty,
.home-chips.is-hidden { display: none; }
.home-chip {
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 40px;
  padding: 9px 15px;
  border: none;
  border-radius: 999px;
  background: var(--bg-elev-1);
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  font-weight: 550;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background 0.14s var(--ease), transform 0.1s var(--ease);
}
.home-chip:hover { background: var(--bg-elev-2); }
.home-chip:active { transform: scale(0.97); }
.home-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.home-chip .material-symbols-rounded { font-size: 18px; color: var(--text-dim); }
.home-chip .home-chip-emoji { font-size: 15px; line-height: 1; }

/* Recent header: sort dropdown on the left, keyword search on the right. Now a
   sibling of the task list (sits just below the ~50vh hero band on desktop; pins
   beneath the nav on mobile — see the phone media block). */
.recent-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 4px 6px;
}
.recent-sort {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  padding: 4px 6px 4px 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.recent-sort:hover { color: var(--text); }
.recent-caret { font-size: 20px; color: var(--text-faint); }
/* Collapses to a single search icon; clicking it expands the pill (see main.ts). */
.recent-search-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  width: 260px;
  max-width: 55%;
  /* Constant height in both states so expanding/collapsing the search never
     changes the header height (which would make the composer jump). */
  height: 40px;
  margin-left: auto;
  padding: 0 14px;
  overflow: hidden;
  background: var(--bg-elev-1);
  border: none;
  border-radius: 999px;
  transition: width 0.22s var(--ease), padding 0.22s var(--ease),
    background 0.15s var(--ease);
}
/* No stroke — matches the rest of the borderless UI; just a tonal fill on focus. */
.recent-search-wrap:focus-within { background: var(--bg-elev-2); }
.recent-search-ic {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  background: transparent;
  border: none;
  padding: 0;
  color: var(--text-faint);
  cursor: pointer;
}
.recent-search-ic .material-symbols-rounded { font-size: 19px; }
.recent-search {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
}
.recent-search::placeholder { color: var(--text-faint); }

/* Collapsed: just a round icon button; the input is clipped to zero width. */
.recent-search-wrap.is-collapsed {
  width: 40px;
  padding: 0;
  gap: 0;
  background: transparent;
  border-color: transparent;
}
.recent-search-wrap.is-collapsed .recent-search-ic {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  color: var(--text-dim);
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.recent-search-wrap.is-collapsed .recent-search-ic .material-symbols-rounded { font-size: 22px; }
.recent-search-wrap.is-collapsed .recent-search-ic:hover { background: var(--bg-elev-2); color: var(--text); }
.recent-search-wrap.is-collapsed .recent-search {
  width: 0;
  flex: 0 0 0;
  opacity: 0;
  pointer-events: none;
}
/* Relative time tag on a task row (revealed with the kebab on hover/focus). */
.task-time {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.task-row:focus-within .task-time { opacity: 1; }
@media (hover: hover) {
  .task-row:hover .task-time { opacity: 1; }
}
.tasks-list {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* The composer sits in-flow above the list now — just a small tail gap. */
  padding-bottom: 28px;
  gap: 12px;
}
.tasks-empty {
  color: var(--text-faint);
  padding: 20px 4px;
  font-size: 13.5px;
}
/* Inline "Retry" link in the task-fetch error state (see renderTasksError). */
.tasks-retry {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--text);
  cursor: pointer;
  text-decoration: underline;
}
.tasks-retry:hover { opacity: 0.7; }
/* Loading indicator shown while the task list is first fetched. */
.tasks-loading {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--text-faint);
  padding: 18px 4px;
  font-size: 13.5px;
}
.spinner {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
/* Skeleton placeholders for the Recent task list while it loads. The list lives in
   the agent's sandbox, so a cold fetch can take many seconds; shimmer rows shaped
   like real task rows read as "loading" instead of a blank screen. */
.tasks-skeleton { display: flex; flex-direction: column; }
.task-skel-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 20px 14px 20px 18px;
}
.task-skel-main {
  display: flex;
  flex-direction: column;
  gap: 9px;
  flex: 1 1 auto;
  min-width: 0;
}
.skel {
  background: var(--bg-elev-2);
  border-radius: 7px;
  animation: skel-pulse 1.3s ease-in-out infinite;
  animation-delay: var(--skel-d, 0s);
}
.skel-dot { width: 22px; height: 22px; flex: 0 0 auto; border-radius: 50%; }
.skel-line { height: 11px; border-radius: 6px; }
.skel-line--title { max-width: 100%; }
.skel-line--sub { width: 32%; height: 9px; }
@keyframes skel-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 0.85; }
}
@media (prefers-reduced-motion: reduce) {
  .skel { animation: none; opacity: 0.5; }
}
/* Taller, calmer task cards with a relaxed rhythm (Gemini). Background stays
   transparent — elevation comes from hover only, not a resting fill. */
.task-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: var(--radius-hero);
  padding: 20px 14px 20px 18px;
  cursor: pointer;
  width: 100%;
  transition: background 0.12s var(--ease);
}
@media (hover: hover) {
  .task-row:hover { background: var(--bg-elev-1); }
}
.task-row-main {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  flex: 1 1 auto;
}
@media (hover: hover) {
  .task-row:hover .task-title { color: var(--accent); }
}
/* Kebab on a task row: hidden until the row is hovered/focused or its menu is open. */
.task-kebab {
  flex: 0 0 auto;
  opacity: 0;
  width: 30px;
  height: 30px;
  font-size: 18px;
  line-height: 1;
  border-radius: 50%;
  transition: opacity 0.12s ease, background 0.12s ease;
}
.task-row:focus-within .task-kebab,
.task-kebab[aria-expanded="true"] { opacity: 1; }
@media (hover: hover) {
  .task-row:hover .task-kebab { opacity: 1; }
}
/* Touch devices have no hover to reveal these, and a sticky :hover would force a
   double-tap (first tap highlights the row, second opens it). Show the time + kebab
   always so a single tap opens the task. */
@media (hover: none) {
  .task-time { opacity: 1; }
  .task-kebab { opacity: 1; }
}
.task-pin-mark { color: var(--text-faint); margin-right: 4px; font-size: 16px; }
.task-title {
  font-size: 15px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
.task-subtitle {
  font-size: 13px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
/* Live-state chip on a task row (working / needs your reply / interrupted). Only
   rendered while the state is known (agent warm) — idle tasks carry no chip. */
.task-state {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 500;
  border-radius: 999px;
  padding: 2px 10px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  white-space: nowrap;
}
.task-state::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.task-state-working { color: var(--accent); }
.task-state-working::before { animation: task-state-pulse 1.4s ease-in-out infinite; }
.task-state-needs_input { color: var(--warn, #e0a63d); }
.task-state-stopped { color: var(--text-faint); }
@keyframes task-state-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

.session-overlay {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(0, 0, 0, 0);
  transition: background 0.25s ease;
}
.session-overlay.open { background: rgba(0, 0, 0, 0.4); }
.session-overlay[hidden] { display: none; }
.session-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 760px;
  max-width: 100%;
  border: none;
  border-left: 1px solid transparent;
  border-radius: 0;
  box-shadow: var(--shadow-lg);
  transform: translateX(100%);
  transition: transform 0.25s var(--ease);
}
.session-overlay.open .session-panel { transform: translateX(0); }
.session-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid transparent;
  /* Sit above the transcript so its fade mask paints over the scrolling text. */
  position: relative;
  z-index: 2;
  background: var(--bg-elev);
}
/* Gemini-style fade: text scrolling up under the title bar dissolves into the bg.
   Kept SHORT (owner 2026-07-29: "reduce the drop shadow from the nav bar") — at
   40px it read as a heavy drop shadow on the client surfaces, which don't scroll
   text under the bar at all; 12px is enough to soften the edge for the ones that
   do. */
.session-bar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 12px;
  background: linear-gradient(to bottom, var(--bg-elev), transparent);
  pointer-events: none;
}
.session-title {
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
/* Status pill (Gemini "Complete" / "Working") between the title and the kebab. */
.session-status {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-dim);
  background: var(--bg-elev-2);
  border-radius: 999px;
  padding: 4px 12px;
  white-space: nowrap;
}
.session-status[hidden] { display: none; }
.session-status.working { color: var(--accent); }
.session-menu-btn {
  flex: 0 0 auto;
  font-size: 20px;
  line-height: 1;
}
/* Back chevron: only on mobile/narrow, where the session is a full-screen overlay
   (no task list beside it). On desktop the split-view already shows the list.
   Selector is `.session-bar .session-back` (specificity 0,2,0) so it beats the
   later `.icon-btn { display: grid }` rule the button also matches. */
.session-back { flex: 0 0 auto; }
.session-back svg { display: block; }
.session-bar .session-back { display: none; }
@media (max-width: 899px) {
  .session-bar .session-back { display: inline-flex; align-items: center; justify-content: center; }
}
/* …unless back means "the screen you came from" rather than "the task list":
   a deep-linked sub-screen (Quant → asset detail) hides its own inner back row
   because the header carries one, so on desktop the chevron IS the only exit
   (owner 2026-07-29). main.ts sets .has-back with the override. */
.session-bar.has-back .session-back { display: inline-flex; align-items: center; justify-content: center; }

/* Kebab dropdown menu (task rename / pin / delete / close) ----------------- */
.kebab-menu {
  position: fixed;
  z-index: 200;
  min-width: 184px;
  padding: 6px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.kebab-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 9px 12px;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.kebab-item:hover { background: var(--bg-elev-1); }
.kebab-item.danger { color: var(--danger); }
.kebab-ic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  color: var(--text-faint);
}
.kebab-ic .material-symbols-rounded { font-size: 18px; }
.kebab-item.danger .kebab-ic { color: var(--danger); }
.kebab-sep {
  height: 1px;
  margin: 6px 4px;
  background: var(--border);
}

/* Desktop split-view: opening a task slides the list into a left column and the
   session becomes the right pane (instead of a full-screen modal overlay). */
/* Gemini-style edge handle: a slim pill anchored just inside the session
   pane's left edge at mid-height, so it stays in the same spot relative to
   the pane whether the task list is expanded or folded. Only meaningful in
   the desktop split view — hidden everywhere else (mobile session is already
   full-screen). */
.session-edge-toggle { display: none; }
@media (min-width: 900px) {
  .tasks-view {
    transition:
      flex-basis 0.3s var(--ease),
      max-width 0.3s var(--ease),
      opacity 0.25s var(--ease);
  }
  .layout.session-active .session-edge-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    left: 6px;
    top: 50%;
    transform: translateY(-50%);
    width: 16px;
    height: 52px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-elev-2);
    color: var(--text-dim);
    cursor: pointer;
    z-index: 5;
    opacity: 0.75;
    transition: opacity 0.15s var(--ease), background 0.15s var(--ease);
  }
  .layout.session-active .session-edge-toggle:hover {
    opacity: 1;
    background: var(--bg-elev-3);
    color: var(--text);
  }
  .layout.session-active .session-edge-toggle .material-symbols-rounded {
    font-size: 16px;
  }
  /* Full-width mode (Gemini-style expand): collapse the left task column so the
     session pane takes the whole viewport. Toggled by the edge handle. */
  .layout.session-active.session-wide .tasks-view {
    flex-basis: 0%;
    max-width: 0;
    min-width: 0;
    opacity: 0;
    pointer-events: none;
    border-right: none;
  }
  /* With the left column gone the session pane owns the whole viewport, so the
     floating hamburger has nothing left to float over: it sat right against the
     back arrow and the title, and the bar had to be padded 64px to dodge it
     (owner 2026-07-29: "should not be here visibly blocking"). Hide it and give
     the padding back — this is the same rule a full-screen mobile session already
     follows, where the overlay covers the FAB outright. The drawer stays one tap
     away: the edge handle restores the split, and the FAB with it. */
  body:has(.layout.session-active.session-wide) .nav-fab {
    display: none;
  }
  .layout.session-active {
    justify-content: flex-start;
  }
  .layout.session-active .tasks-view {
    flex: 0 0 40%;
    max-width: 560px;
    align-items: stretch;
    border-right: 1px solid transparent;
    /* The column itself doesn't scroll — only the task list inside it does. */
    overflow: hidden;
  }
  /* Split the left column into a fixed header (New task + Recent/search) and a
     scrollable task list below — like Gemini's sidebar. */
  .layout.session-active .home-center {
    max-width: none;
    width: 100%;
    height: 100%;
    min-height: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
  }
  /* No 1/3 band in the narrow split column — the header is just the compact
     New-task button + Recent/search, anchored at the top. */
  .layout.session-active .home-header { min-height: 0; justify-content: flex-start; padding-top: 0; }
  .layout.session-active .home-new-task { display: flex; flex: 0 0 auto; margin: 28px auto 18px; }
  .layout.session-active .recent-bar { flex: 0 0 auto; margin: 0 0 4px; padding: 0 28px; }
  .layout.session-active .tasks-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 28px 24px;
  }
  /* The big hero greeting is a home-screen flourish; hide it in the narrow
     split-view column. The hero composer collapses to the "New task" button. */
  .layout.session-active .home-greeting { display: none; }
  .layout.session-active .home-composer-wrap { display: none; }
  /* The earnings widget is a home-screen flourish too — hide it in the narrow column. */
  .layout.session-active .home-earn-slot { display: none; }
  .layout.session-active .home-mascot-slot { display: none; }
  /* Session becomes an in-flow right pane (not a fixed full-screen modal). */
  .layout.session-active .session-overlay {
    position: relative;
    inset: auto;
    flex: 1 1 0;
    min-width: 0;
    background: transparent;
    z-index: 1;
  }
  .layout.session-active .session-panel {
    position: relative;
    width: 100%;
    height: 100%;
    max-width: none;
    border-left: 1px solid transparent;
    box-shadow: none;
  }
}
.model-indicator {
  font-size: 12.5px;
  color: var(--text-dim);
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  white-space: nowrap;
  max-width: 50vw;
  overflow: hidden;
  text-overflow: ellipsis;
}
.model-indicator.no-model { color: var(--warn); border-color: var(--banner-warn-bd); }

.icon-btn {
  /* The box's size as a VALUE, not just a number: a row that wants its glyph —
     rather than its tap target — to land on the content edge has to be able to
     work out the difference, and typing 40 again somewhere else is how the two
     drift. See `.m3-toprow-actions.pull-glyph`. */
  --icon-btn-size: 40px;
  background: transparent;
  color: var(--text-dim);
  border: none;
  border-radius: 50%;
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  font-size: 17px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.icon-btn:hover { background: var(--bg-elev-2); color: var(--text); }

/* ---------------- layout ---------------- */

.layout {
  flex: 1 1 auto;
  display: flex;
  justify-content: center;
  min-height: 0;
  overflow: hidden;
}

.chat-col {
  display: flex;
  flex-direction: column;
  min-height: 0;
  width: 100%;
  max-width: 860px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

/* ---------------- transcript ---------------- */

.transcript {
  flex: 1 1 auto;
  overflow-y: auto;
  /* Bottom padding halved with the fade (owner 2026-08-03: "reduce the
     shadow area by 50%") — still clears the 16px gradient at rest. */
  padding: 24px 24px 22px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  scroll-behavior: smooth;
}

/* ---- conversational message rows (Gemini layout) ----
   User: a right-aligned tonal bubble, no avatar, no label.
   Assistant: full-width text with just the gradient "spark" to its left. */

.msg {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

.msg-avatar {
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 20px;
  font-weight: 650;
  line-height: 1;
  user-select: none;
}
/* Assistant pfp: a filled circular avatar with the Genie spark. Solid color so it
   ALWAYS paints — the old transparent `background-clip:text` spark could render blank
   if the gradient/clip didn't paint, leaving the reply with no avatar at all. */
.msg-assistant .msg-avatar {
  background: var(--bg-elev-2);
  color: var(--accent);
  font-size: 25px;
  font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24;
}
/* The live mascot avatar (main.ts's shared, reparented canvas) — the one
   assistant row it's currently attached to swaps its icon avatar for this.
   The canvas renders the ball at ~48% of its own box (jump headroom), so at
   100% the pfp read tiny; per feedback it's 1.5x — done by overflowing the
   canvas out of the SAME 42px layout slot (negative margins) so message
   indentation stays identical across rows. The visible ball lands at ~30px,
   still inside the slot; only jump/celebrate arcs spill past it, over
   transparent background. */
.msg-avatar.msg-avatar-mascot { background: transparent; overflow: visible; }
.msg-avatar-mascot .msg-avatar-mascot-canvas { width: 150%; height: 150%; margin: -25%; }
.msg-avatar-mascot-canvas { display: block; width: 100%; height: 100%; }

/* No sender labels — the bubble (user) and spark (assistant) carry the role. */
.msg-sender { display: none; }

/* User turn → right-aligned tonal bubble. */
.msg-user { justify-content: flex-end; }
.msg-user .msg-avatar { display: none; }
.msg-user .msg-content {
  flex: 0 1 auto;
  align-items: flex-end;
  max-width: 82%;
}
.msg-user .msg-body {
  background: var(--user-bubble);
  color: var(--text);
  padding: 11px 17px;
  border-radius: var(--radius-lg);
  width: fit-content;
  max-width: 100%;
}

/* Assistant turn → airy, full-width reading column. */
.msg-assistant .msg-content { line-height: 1.7; }

.msg-content {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.msg-body {
  font-size: 16px;
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-wrap: anywhere;
  position: relative;
}
.msg-body.busy {
  /* Reserve space so the absolutely-positioned working dot never overlaps the
     last line of streamed text. */
  padding-bottom: 14px;
}
.msg-body.busy::after {
  /* The body holds BLOCK-level children (.bubble-thinking, .bubble-text,
     .bubble-activity), so an inline-block dot would drop onto its own line.
     Anchor it to the body (which is position: relative) so it reads as a
     steady "working" indicator at the bottom-left in every phase — including
     agent_start when .bubble-text is still empty. */
  content: "";
  position: absolute;
  left: 0;
  bottom: 2px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 0.25; transform: scale(0.8); }
  50% { opacity: 1; transform: scale(1.1); }
}

/* "Genie is working" indicator: a pulsing dot + a cycling playful line, shown in
   the gap before the first output so model latency reads as activity, not a hang. */
.working-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 0;
  color: var(--text-dim);
  font-size: 14px;
  font-style: normal;
}
.working-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 1s ease-in-out infinite;
}
.working-label { animation: workingFade 2.6s ease-in-out infinite; }
@keyframes workingFade {
  0% { opacity: 0.45; }
  12% { opacity: 0.95; }
  88% { opacity: 0.95; }
  100% { opacity: 0.45; }
}
/* Suppress the bare busy dot while the richer indicator is up (avoid two dots). */
.msg-body.busy:has(.working-indicator)::after { display: none; }

.bubble-text { white-space: pre-wrap; }
.bubble-text:empty { display: none; }

/* ---------------- markdown (assistant body) ---------------- */

/* Rendered markdown owns its own block spacing, so the pre-wrap whitespace used
   for plain streamed text is turned off here. */
.markdown { white-space: normal; }
.markdown:empty { display: none; }

/* Collapse outer margins so a bubble doesn't gain leading/trailing whitespace. */
.markdown > :first-child { margin-top: 0; }
.markdown > :last-child { margin-bottom: 0; }

.markdown p { margin: 0 0 8px; }

.markdown h1,
.markdown h2,
.markdown h3 {
  margin: 14px 0 6px;
  line-height: 1.3;
  font-weight: 650;
}
.markdown h1 { font-size: 19px; }
.markdown h2 { font-size: 17px; }
.markdown h3 { font-size: 15px; color: var(--text-dim); }

.markdown ul,
.markdown ol {
  margin: 6px 0 8px;
  padding-left: 22px;
}
.markdown li { margin: 2px 0; }
.markdown li::marker { color: var(--text-faint); }

.markdown blockquote {
  margin: 8px 0;
  padding: 2px 0 2px 11px;
  border-left: 3px solid var(--border);
  color: var(--text-dim);
}

/* GFM tables (md.ts): themed like the view tables, scrolling in their own
   wrapper so a wide table never stretches the chat bubble. */
.markdown .md-table-wrap {
  margin: 8px 0;
  overflow-x: auto;
}
.markdown table {
  border-collapse: collapse;
  font-size: 13px;
  min-width: 60%;
}
.markdown th,
.markdown td {
  padding: 5px 10px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}
.markdown th {
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
}
.markdown tr:last-child td { border-bottom: none; }
.markdown th.md-right,
.markdown td.md-right { text-align: right; }
.markdown th.md-center,
.markdown td.md-center { text-align: center; }

.markdown strong { font-weight: 650; }
.markdown em { font-style: italic; }

.markdown code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1px 5px;
}

.markdown pre {
  margin: 8px 0;
  padding: 11px 13px;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-x: auto;
}
.markdown pre code {
  display: block;
  background: none;
  border: none;
  padding: 0;
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre;
  color: var(--text);
}

.markdown a {
  color: var(--accent);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.markdown a:hover { text-decoration: underline; }

/* Collapsible "process" block — folded one-line summary the user can unfold to
   see the streamed reasoning + tool activity (Gemini pattern). Folded by default. */
.process { margin: 0 0 12px; }
.process-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: none;
  padding: 3px 0;
  cursor: pointer;
  color: var(--text-dim);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  transition: color 0.15s var(--ease);
}
.process-toggle:hover { color: var(--text); }
.process-caret {
  font-size: 18px;
  color: var(--text-faint);
  transition: transform 0.2s var(--ease);
}
.process.open .process-caret { transform: rotate(90deg); }
.process-detail {
  display: none;
  margin-top: 10px;
}
/* `hidden` is the collapse authority. The explicit !important defeats any
   theme/mobile flex declaration added later, the same class of cascade bug
   that exposed full reasoning in the transcript. */
.process-detail[hidden] { display: none !important; }
.process.open .process-detail:not([hidden]) { display: block; }

/* Process timeline (Gemini-style): reasoning + tool steps as nodes on a rail. */
.process-timeline {
  position: relative;
  padding: 2px 0;
}
/* The vertical rail connecting node icons. */
.process-timeline::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 12px;
  bottom: 12px;
  width: 1.5px;
  background: var(--border);
}
.tl-step {
  display: grid;
  grid-template-columns: 24px 1fr;
  column-gap: 12px;
  align-items: start;
}
.tl-icon {
  position: relative;
  z-index: 1;
  grid-column: 1;
  display: grid;
  place-items: center;
  width: 24px;
  height: 22px;
  color: var(--text-faint);
  background: var(--bg-elev); /* mask the rail so the icon reads as a node */
  font-size: 18px;
  font-variation-settings: "FILL" 0, "wght" 300, "GRAD" 0, "opsz" 20;
}
.tl-icon.ok { color: var(--ok); }
.tl-icon.failed { color: var(--err); }
.tl-body {
  grid-column: 2;
  min-width: 0;
  padding-bottom: 12px;
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--text-dim);
  word-wrap: break-word;
  overflow-wrap: anywhere;
}
.tl-line { margin: 0; }
/* Tool nodes: name reads a touch stronger than reasoning lines. */
.tl-step.tl-tool .tl-line {
  color: var(--text);
  font-weight: 500;
}
/* Compact markdown inside reasoning nodes — proper numbered lists, code, breaks. */
.tl-body.markdown p { margin: 0 0 5px; }
.tl-body.markdown ol,
.tl-body.markdown ul { margin: 3px 0 5px; padding-left: 22px; }
.tl-body.markdown li { margin: 1px 0; }
.tl-body.markdown li::marker { color: var(--text-faint); }
.tl-body.markdown h1,
.tl-body.markdown h2,
.tl-body.markdown h3 { font-size: 13.5px; margin: 5px 0 2px; color: var(--text-dim); }

/* Streamed reasoning — Gemini style: small, muted, NOT italic. */
.bubble-thinking {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-dim);
  white-space: pre-wrap;
  border-left: 2px solid var(--border);
  padding-left: 12px;
  margin: 0;
}

.bubble-activity {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}
.tool-chip {
  font-size: 11.5px;
  padding: 3px 11px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev-1);
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.tool-chip::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-faint);
}
.tool-chip.pending::before { background: var(--accent); animation: pulse 1s ease-in-out infinite; }
.tool-chip.ok::before { background: var(--ok); }
.tool-chip.failed { color: var(--danger-text); border-color: var(--danger-border); }
.tool-chip.failed::before { background: var(--err); }

/* ---------------- composer ---------------- */

/* Positioning context so the slash palette can float ABOVE the composer. */
.composer-wrap {
  flex: 0 0 auto;
  position: relative;
}
/* Quant is full-bleed (owner 2026-08-03): the composer — and the HUD row
   under it (Chill · credits · Top up) and the context hint, which read as one
   section — slides off while that screen is up; a downward swipe brings it
   back (quant.ts owns the class). IN FLOW both ways: a position:fixed
   variant shoved the whole column up past the top edge on the phone, so the
   hide is a max-height collapse + slide, which cannot move anything above
   it. The 40vh ceiling only exists to give the transition endpoints. */
.composer-wrap,
.usage-hud,
.context-hint {
  transition: transform 0.26s ease, opacity 0.18s ease, max-height 0.26s ease;
  max-height: 40vh;
}
/* ═══ A SHEET THAT SAYS IT COVERS THE COMPOSER REALLY COVERS IT ═══
   Owner, 2026-08-26, on the trade sheet: *"for the screen it should be an overlay even on top of
   the text composer area so it should cover on top of the composer"*. Under a modal these three
   are inert anyway, and a half-dimmed composer showing through a full-height sheet reads as a
   live control the sheet is sitting on. Only for a sheet that asks (`.sm-sheet-over`) — every
   other sheet still ends above the composer, which is the older rule and still the right one. */
body:has(.sm-sheet-scrim.sm-sheet-over) .composer-wrap,
body:has(.sm-sheet-scrim.sm-sheet-over) .usage-hud,
body:has(.sm-sheet-scrim.sm-sheet-over) .context-hint { visibility: hidden; }
/* Gemini-style fade: a gradient mask above the composer so transcript text
   dissolves into the panel background instead of being hard-cut at the edge. */
.composer-wrap::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  /* 16px, was 32 (owner 2026-08-03: "reduce the shadow area by 50%"). */
  height: 16px;
  background: linear-gradient(to top, var(--bg-elev), transparent);
  pointer-events: none;
}

/* Gemini-style centered "island" composer: a rounded pill, max-width, centered in
   the panel, floating above the usage HUD (no full-width top border). Roomier
   padding than a typical chat input — Gemini's composer reads spacious, not
   cramped around the text. */
/* ═══ THE COMPOSER'S COLUMN, DECLARED ONCE (owner ruling 2026-08-17) ═══
   The Smart Money pill sat at the far LEFT of the chat area while the composer
   was centred in its own 768px column — measured at 1500px: the chips row
   spanned 890px from x=585, the composer 768px from x=646, so the pill missed
   the composer's left edge by 110px and read as belonging to nothing.
   Everything that must line up with the composer now shares THIS rule, so the
   width is one number with several readers rather than a number copied about.
   Change it here and the pill moves with it — which is the point, and what the
   suite pins. */
.composer,
.session-chips {
  width: 100%;
  max-width: 768px;
  margin-left: auto;
  margin-right: auto;
}
.composer {
  flex: 0 0 auto;
  display: flex;
  gap: 12px;
  align-items: flex-end;
  padding: 18px 16px 18px 26px;
  border: none;
  border-radius: var(--radius-hero);
  background: var(--bg-elev-1);
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.15s var(--ease);
}
/* No stroke, no focus highlight — elevation comes from the shadow alone. */
.composer:focus-within { box-shadow: var(--shadow-md); }
.composer-plus {
  flex: 0 0 auto;
  font-size: 23px;
  line-height: 1;
  color: var(--text-dim);
  align-self: center;
}
.composer-wrap { padding: 9px 24px 22px; }

/* Credit readout — shares the usage-hud row with the context/cost labels. */
.credit-balance {
  flex: 0 0 auto;
  display: inline-flex; /* centers the coin glyph + number on the row's vertical axis */
  align-items: center;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  white-space: nowrap;
}
.credit-balance.low { color: var(--warn); }
.credit-balance[hidden] { display: none; } /* display:inline-flex above beats the UA [hidden] rule */

/* Weekly allowance bar (docs/WEEKLY_ALLOWANCE.md) — replaces the raw balance while
   the subscription allowance is active: progress through the week, not a draining
   number. Full detail (credits used/granted, wallet) lives in the tooltip. */
.allowance-hud {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-left: auto; /* takes over the balance's right-alignment while it's hidden */
  font-size: 11.5px;
  line-height: 1; /* shrink text boxes to glyph height so flex centering is optical, not box math */
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  white-space: nowrap;
}
.allowance-hud[hidden] { display: none; }
.allowance-track {
  width: 64px;
  height: 6px;
  border-radius: 999px;
  background: var(--bg-elev-3);
  overflow: hidden;
}
.allowance-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.3s var(--ease);
}
.allowance-hud.warn .allowance-fill { background: var(--warn); }
.allowance-pct { color: var(--text); font-weight: 500; }
.allowance-hud.warn .allowance-pct { color: var(--warn); }
.allowance-reset { color: var(--text-dim); opacity: 0.75; }

/* Session savings pill — a summary line at the bottom of the newest reply
   ("−12% credits saved"). Plain words, no glyph; percentage, not a raw credit
   count; the number flips up odometer-style when the session rate changes. */
.savings-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px; /* leading spaces inside the label spans collapse — gap owns spacing */
  margin-top: 10px;
  padding: 3px 11px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ok) 14%, var(--bg-elev-2));
  color: var(--ok);
  font-size: 11.5px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  user-select: none;
}
.savings-pill-pre:empty,
.savings-pill-post:empty { display: none; }
.savings-pill { line-height: 1.25; } /* one shared line box → labels + digits share an optical center */
.savings-pill-num {
  position: relative;
  display: inline-block;
  overflow: hidden;
  height: 1.25em;
}
/* block, not inline-block: baseline alignment inside the clipped window pushed
   the digits ~2px below the labels' center. A block child fills from the top. */
.savings-pill-digit { display: block; line-height: 1.25; }
.savings-pill-digit.flip-in { animation: savings-flip-in 0.35s var(--ease); }
.savings-pill-digit.flip-out {
  position: absolute;
  left: 0;
  top: 0;
  animation: savings-flip-out 0.35s var(--ease) forwards;
}
@keyframes savings-flip-in {
  from { transform: translateY(115%); }
  to { transform: translateY(0); }
}
@keyframes savings-flip-out {
  from { transform: translateY(0); opacity: 1; }
  to { transform: translateY(-115%); opacity: 0; }
}

.credit-topup {
  flex: 0 0 auto;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--accent);
  background: var(--bg-elev-2);
  border: none;
  cursor: pointer;
  padding: 4px 13px;
  border-radius: 999px;
  transition: background 0.15s var(--ease);
}
.credit-topup:hover { background: var(--bg-elev-3); }
.credit-topup:focus { outline: none; }
.credit-topup:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Receipts sheet — recent credit_ledger movements, opened by tapping the HUD
   balance or the weekly bar. Transparency on demand, no ambient taxi meter. */
.receipts-backdrop {
  position: fixed;
  inset: 0;
  z-index: 12000;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Side + bottom insets so the sheet FLOATS with margins (Gemini style), never
     edge-to-edge; also clears the phone's home indicator. */
  padding: 0 12px calc(12px + env(safe-area-inset-bottom, 0px));
}
.receipts-sheet {
  width: min(560px, 100%);
  max-height: 62vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-elev-1, var(--bg));
  /* Fully rounded — a floating card, not a docked panel. */
  border-radius: 28px;
  padding: 20px 20px 12px;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.45);
}
.receipts-title {
  font-size: 14px;
  font-weight: 650;
  margin-bottom: 10px;
}
.receipts-list { overflow-y: auto; font-size: 13px; }
.receipt-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 2px;
  border-bottom: 1px solid var(--bg-elev-2);
}
.receipt-row:last-child { border-bottom: none; }
.receipt-ic { font-size: 18px; color: var(--text-dim); }
.receipt-text { flex: 1 1 auto; min-width: 0; }
.receipt-label {
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.receipt-time { font-size: 11px; color: var(--text-dim); }
.receipt-amt {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.receipt-amt.pos { color: var(--ok); }

/* The HUD money elements now open the receipts sheet. */
.credit-balance, .allowance-hud { cursor: pointer; }

/* Long-conversation hint — a quiet line above the HUD, not a scary banner. */
.context-hint {
  padding: 4px 24px 0;
  font-size: 11.5px;
  color: var(--warn);
  opacity: 0.9;
  white-space: normal;
}
.context-hint[hidden] { display: none; }

/* Budget-guard select (Settings → Agent) — matches the switch rows' scale. */
.agentset-select {
  flex: 0 0 auto;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--bg-elev-2);
  border: 1px solid var(--bg-elev-3);
  border-radius: 8px;
  padding: 5px 8px;
}

/* Composer-HUD Effort toggle — a compact pill that flips Chill ⇄ YOLO. Chill reads
   muted (the safe default); YOLO lights up to signal the agent is in autonomous mode. */
.effort-hud {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-faint);
  background: var(--bg-elev-2);
  border: none;
  cursor: pointer;
  padding: 3px 11px;
  border-radius: 999px;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.effort-hud:hover { background: var(--bg-elev-3); }
.effort-hud:focus { outline: none; }
.effort-hud:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.effort-hud.is-yolo { color: var(--on-accent); background: var(--accent); }

/* ---------------- slash command palette ---------------- */

.cmd-palette {
  position: absolute;
  left: 24px;
  right: 24px;
  max-width: 768px;
  margin: 0 auto;
  bottom: calc(100% + 2px);
  z-index: 50;
  max-height: 320px;
  overflow-y: auto;
  padding: 6px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  -webkit-overflow-scrolling: touch;
}
.cmd-palette[hidden] { display: none; }

/* "↓ New messages" — appears when the transcript's auto-follow is suppressed
   because the user scrolled up mid-stream. Tapping snaps back to the bottom and
   it hides once the user is there. Mirrors .cmd-palette's anchor: absolute,
   centered, floating just above the composer. */
.scroll-resume {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 10px);
  z-index: 40;
  margin: 0 auto;
  width: fit-content;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px 7px 11px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text);
  font: 500 13px/1 system-ui, -apple-system, sans-serif;
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.scroll-resume .material-symbols-rounded { font-size: 18px; }
.scroll-resume.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .scroll-resume { transition: opacity 0.18s ease; transform: none; }
}

.cmd-group {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-faint);
  padding: 6px 8px 4px;
}

.cmd-item {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid transparent;
}
.cmd-item.selected {
  background: var(--bg-elev-2);
  border-color: var(--accent);
}
.cmd-name {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
}
.cmd-desc {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12.5px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cmd-empty {
  padding: 9px 10px;
  font-size: 12.5px;
  color: var(--text-faint);
  font-style: italic;
}
.composer-input {
  flex: 1 1 auto;
  resize: none;
  max-height: 200px;
  background: transparent;
  color: var(--text);
  border: none;
  border-radius: 0;
  padding: 11px 0;
  align-self: center;
  font: inherit;
  font-size: 16.5px;
  line-height: 1.55;
  outline: none;
}
.composer-input::placeholder { color: var(--text-faint); }

/* Icon-only round button. Gemini-style: a monochrome light/dark circle (not the
   brand gradient); the stop state just swaps the arrow glyph for a square. */
.composer-send {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  background: var(--text);
  color: var(--bg);
  border: none;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  transition: filter 0.15s, transform 0.1s var(--ease);
}
.composer-send .material-symbols-rounded {
  font-size: 22px;
  font-variation-settings: "FILL" 0, "wght" 500, "GRAD" 0, "opsz" 24;
}
.composer-send:hover { filter: brightness(0.9); }
.composer-send:active { transform: scale(0.94); }
.composer-send:focus { outline: none; }
.composer-send:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Empty + idle: drop the button out of layout (used by the home composer). */
.composer-send.is-hidden { display: none; }
/* …except on the home composer, where we keep the button's box RESERVED (just
   invisible) so the pill's height and the input's width don't jump the instant
   the user starts typing and the send arrow appears. */
.home-composer .composer-send.is-hidden { display: grid; visibility: hidden; }

/* Idle + empty (Gemini): a plain mic, not the filled circle. */
.composer-send.is-mic {
  background: transparent;
  color: var(--text-dim);
}
.composer-send.is-mic:hover { background: var(--bg-elev-2); color: var(--text); filter: none; }
.composer-send.is-listening { color: var(--accent); animation: pulse 1.1s ease-in-out infinite; }

/* Processing: same monochrome button, square glyph (set in JS). */
.composer-send.is-stop {
  background: var(--text);
  color: var(--bg);
}
.composer-send.is-stop:hover { filter: brightness(0.9); }
.composer-send.is-stop .material-symbols-rounded {
  font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24;
}

/* ---------------- usage HUD (statusline under composer) ---------------- */

/* Clean status line, no hard divider — blends into the panel like the rest of
   the UI. Deliberately minimal: effort toggle · credits left · Top up (token/
   context/cost telemetry lives in the admin panel, not user chrome). The Top-up
   pill rides the far edge so the strip still spans the composer. */
.usage-hud {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 2px 24px 12px;
  background: transparent;
  font-family: var(--font-ui);
}
/* Chill anchors the left edge; the credits chip + Top-up pill ride together on the
   right (credits immediately left of the button it feeds). */
.usage-hud .credit-balance { margin-left: auto; }

/* ---------------- surfaces (inline in transcript) ---------------- */

.surface-card {
  flex-shrink: 0; /* prevent transcript flex from collapsing the surface */
  align-self: stretch;
  /* Inset to align with the assistant content column (avatar width + row gap),
     so generated surfaces read as part of the assistant's turn. */
  margin-left: 56px;
  display: flex;
  flex-direction: column;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.surface-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 6px 14px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
}
.surface-title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Header icon buttons (pin, collapse) — dim until hover, accent when active. */
.surface-btn {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text-dim);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.surface-btn:hover { opacity: 1; background: var(--bg-elev-1); }
.surface-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.surface-btn.active { opacity: 1; color: var(--accent); }
/* Refresh in flight: spin until the next render frame lands for this surface. */
.surface-refresh.is-refreshing {
  opacity: 1;
  color: var(--accent);
  animation: surface-refresh-spin 1s linear infinite;
}
@keyframes surface-refresh-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .surface-refresh.is-refreshing { animation: none; }
}
.surface-pin.active { font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24; }

/* Pinned: the card sticks to the top of the transcript so an interactive surface (a game,
   a live dashboard) stays visible while chat streams below. CSS-only — the card keeps its
   DOM slot, so an iframe surface never reloads. The FRAME (not the whole card) is height-
   capped, so a tall surface scrolls INTERNALLY and chat keeps a readable strip below. */
.surface-card.pinned {
  position: sticky;
  top: 6px;
  z-index: 6;
  box-shadow: var(--shadow-lg);
}
.surface-card.pinned .surface-frame { max-height: min(62dvh, 900px); }
/* Phones: the desktop pinned cap clips an interactive surface's bottom controls
   (a game's action bar ends up below the fold, reachable only by scrolling inside
   the frame). Give pinned iframes most of the viewport — chat still peeks below,
   and fullscreen is one tap away. */
@media (max-width: 720px) {
  .surface-card.pinned .surface-frame { max-height: min(78dvh, 900px); }
}

/* Fullscreen: the card takes over the viewport IN PLACE — CSS-only, like pinning,
   because re-parenting an iframe reloads its srcdoc and resets the game. Declared
   after .pinned so position:fixed wins when both classes are set. The frame drops
   every height cap (including the JS px height from content reports) and flexes to
   fill under the header. */
.surface-card.fullscreen {
  position: fixed;
  inset: 0;
  z-index: 90;
  margin: 0;
  border: 0;
  border-radius: 0;
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}
.surface-card.fullscreen .surface-frame {
  flex: 1 1 auto;
  height: auto !important;
  max-height: none !important;
}
/* Desktop: the nav hamburger is a fixed FAB in the ROOT stacking context, so it
   paints over the fullscreen card's top-left corner — inset the header so the
   title doesn't run under it. (A full-width SESSION solves the same collision by
   hiding the FAB instead; a card can't, because the FAB is still the only nav
   while a card is open over the split view.) Mobile needs none: the session
   overlay (z 80) covers the FAB there. */
@media (min-width: 721px) {
  .surface-card.fullscreen .surface-header { padding-left: 64px; }
}

/* Collapse: hide the body via display:none — an iframe keeps its state, so pausing a
   game/dashboard never resets it. */
.surface-card.collapsed .surface-frame,
.surface-card.collapsed .surface-view { display: none; }
.surface-frame {
  display: block;
  width: 100%;
  height: 340px;
  border: 0;
  background: transparent;
}

/* render_ui state: "loading" — a subtle in-progress cue on the card header,
   whether the surface has real (partial) content yet or is a bare skeleton. */
.surface-card.surface-loading .surface-header {
  animation: surface-header-pulse 1.3s ease-in-out infinite;
}
@keyframes surface-header-pulse {
  0%, 100% { opacity: 0.65; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .surface-card.surface-loading .surface-header { animation: none; }
}

/* ---------------- structured view surfaces (chart/table) ---------------- */
/* Rendered in-page by views.ts from a data spec — no iframe. Series palette +
   semantic colors are defined here so the canvas renderer can read them. */
.surface-view {
  /* Vibrant trading-desk palette (matches the approved report mockup): gold-first
     series (the classic EMA look), crypto-desk green/red for candles. */
  --view-c1: #e8c447;
  --view-c2: #6fa8ff;
  --view-c3: #c77dff;
  --view-c4: #5ad8cc;
  --view-c5: #ff9f6e;
  --view-up: #16c784;
  --view-down: #ea3943;
  padding: 12px 14px 14px;
}
.view-stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.view-stack-horizontal {
  flex-direction: row;
  flex-wrap: wrap;
}
.view-stack-horizontal > .view-stack-item {
  flex: 1 1 0;
  min-width: 140px;
}
.view-grid {
  display: grid;
  gap: 12px;
}
.view-stack-item,
.view-grid-item {
  min-width: 0;
}
/* Bare loading placeholder (no view/html yet) — an instant "something is
   coming" shape instead of a blank card for the whole wait. */
.view-skeleton-line,
.view-skeleton-block {
  background: var(--bg-elev-2);
  border-radius: 6px;
  animation: view-skeleton-pulse 1.3s ease-in-out infinite;
}
.view-skeleton-line {
  height: 13px;
  width: 38%;
  margin-bottom: 10px;
}
.view-skeleton-line.long {
  width: 62%;
}
.view-skeleton-block {
  height: 120px;
  width: 100%;
}
@keyframes view-skeleton-pulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 0.9; }
}
@media (prefers-reduced-motion: reduce) {
  .view-skeleton-line,
  .view-skeleton-block { animation: none; }
}
.view-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 8px;
}
.view-caption {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 8px;
}
.view-chart-wrap {
  position: relative;
  width: 100%;
}
.view-canvas {
  display: block;
  width: 100%;
}
/* Interactive chart: crosshair on hover, grabbing while panning. touch-action
   pan-y lets the page scroll vertically while horizontal drag / pinch are ours. */
.view-chart-wrap .view-canvas {
  cursor: crosshair;
  touch-action: pan-y;
}
.view-chart-wrap .view-canvas.is-panning {
  cursor: grabbing;
}
.view-chart-tip {
  position: absolute;
  z-index: 3;
  pointer-events: none;
  background: var(--bg-elev-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 9px;
  font-size: 11.5px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
}
.view-chart-tip-h {
  font-weight: 600;
  margin-bottom: 2px;
  color: var(--text-dim);
}
.view-chart-tip-r {
  color: var(--text);
}
/* Dataset switcher (e.g. timeframes) above an interactive chart. */
.view-chart-tabs {
  display: flex;
  gap: 4px;
  margin: 2px 0 6px;
}
.view-chart-tab {
  appearance: none;
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 3px 10px;
  font: 500 11.5px/1.4 inherit;
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  cursor: pointer;
}
.view-chart-tab:hover {
  color: var(--text);
}
.view-chart-tab:focus-visible {
  outline: 2px solid var(--accent, #8ab4f8);
  outline-offset: 1px;
}
.view-chart-tab.is-active {
  background: var(--bg-elev-2);
  border-color: var(--border);
  color: var(--text);
}
/* Series legend: one toggleable chip per line (click = show/hide, scale re-fits). */
.view-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 4px;
  margin: 0 0 4px;
}
.view-chart-key {
  appearance: none;
  background: none;
  border: 0;
  border-radius: 6px;
  padding: 2px 8px 2px 6px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: 500 11px/1.4 inherit;
  font-family: inherit;
  color: var(--text-dim);
  cursor: pointer;
}
.view-chart-key:hover {
  color: var(--text);
  background: var(--bg-elev-1);
}
.view-chart-key:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.view-chart-key-dot {
  width: 8px;
  height: 8px;
  border-radius: 3px;
  flex: 0 0 auto;
}
.view-chart-key.is-off {
  opacity: 0.45;
}
.view-chart-key.is-off .view-chart-key-name {
  text-decoration: line-through;
}
.view-table-wrap {
  width: 100%;
  overflow-x: auto;
}
.view-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.view-table th,
.view-table td {
  padding: 6px 10px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.view-table th {
  font-weight: 600;
  color: var(--text-dim);
  font-size: 12px;
}
.view-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.view-table tbody tr:last-child td {
  border-bottom: 0;
}

/* card */
.view-card-headline {
  font-size: 30px;
  font-weight: 650;
  line-height: 1.1;
  color: var(--text);
  letter-spacing: -0.01em;
}
.view-arrow {
  font-size: 0.6em;
  vertical-align: middle;
}
.view-card-subhead {
  font-size: 13px;
  color: var(--text-dim);
  margin-top: 2px;
}
.view-card-rows {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.view-card-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}
.view-card-label {
  color: var(--text-dim);
}
.view-card-value {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* kpi grid */
.view-kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}
.view-kpi {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}
.view-kpi-label {
  font-size: 11.5px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.view-kpi-value {
  font-size: 20px;
  font-weight: 600;
  color: var(--text);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
.view-kpi-delta {
  font-size: 12px;
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

/* list */
.view-list {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: viewlist;
}
.view-list[class] {
  /* keep zero default padding for both ol/ul */
}
ol.view-list {
  counter-reset: viewlist;
}
.view-list-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13.5px;
}
.view-list-item:last-child {
  border-bottom: 0;
}
ol.view-list .view-list-main::before {
  counter-increment: viewlist;
  content: counter(viewlist) ". ";
  color: var(--text-dim);
}
.view-list-main {
  min-width: 0;
}
.view-list-text {
  color: var(--text);
}
.view-list-secondary {
  color: var(--text-dim);
  margin-left: 8px;
  font-size: 12.5px;
}
.view-list-value {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* form */
.view-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.view-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.view-field-label {
  font-size: 12px;
  color: var(--text-dim);
}
.view-input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.view-input:focus {
  outline: none;
  border-color: var(--accent);
}
textarea.view-input {
  min-height: 64px;
  resize: vertical;
}
.view-checkbox {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  align-self: flex-start;
}
.view-form-status {
  font-size: 12px;
  color: var(--text-dim);
  min-height: 14px;
}

/* buttons */
.view-buttons-text {
  font-size: 13.5px;
  color: var(--text);
  margin-bottom: 8px;
}
.view-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.view-btn {
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text);
  cursor: pointer;
  transition: filter 0.12s, opacity 0.12s;
}
.view-btn:hover:not(:disabled) {
  filter: brightness(1.12);
}
.view-btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #06121f;
}
.view-btn.danger {
  background: #f28b82;
  border-color: #f28b82;
  color: #2a0a08;
}
.view-btn.selected {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.view-btn:disabled {
  opacity: 0.55;
  cursor: default;
}
.view-form .view-btn {
  align-self: flex-start;
}

/* ---------------- explorer (interactive filter/search/sort/drill) ---------------- */
.view-explorer {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.view-exp-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
}
.view-exp-search {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 13px;
  padding: 7px 11px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text);
}
.view-exp-search:focus {
  outline: none;
  border-color: var(--border-strong);
}
.view-exp-count {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
  white-space: nowrap;
}
.view-exp-facets {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}
.view-exp-facet-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.view-exp-facet-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.view-exp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.view-exp-chip {
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text-dim);
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s, color 0.14s;
}
.view-exp-chip:hover {
  border-color: var(--border-strong);
  color: var(--text);
}
.view-exp-chip.active {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--text);
}
.view-exp-summary {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 10px);
  background: var(--bg-elev-1);
}
.view-exp-summary-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.view-exp-summary-body {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}
.view-exp-donut {
  flex: 0 0 auto;
}
.view-exp-donut.interactive {
  cursor: pointer;
}
.view-exp-summary-body .view-exp-bars {
  flex: 1 1 220px;
  min-width: 200px;
}
.view-exp-bars {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.view-exp-bar-row {
  display: grid;
  grid-template-columns: minmax(64px, 24%) 1fr auto;
  align-items: center;
  gap: 10px;
  transition: background 0.12s, opacity 0.2s;
}
.view-exp-bar-row.clickable {
  cursor: pointer;
  border-radius: 6px;
  padding: 2px 4px;
  margin: 0 -4px;
}
.view-exp-bar-row.clickable:hover {
  background: var(--bg-elev-2);
}
.view-exp-bar-row.active {
  background: var(--accent-dim);
}
.view-exp-bar-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: middle;
}
.view-exp-bar-name {
  font-size: 12px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.view-exp-bar-track {
  height: 8px;
  border-radius: 999px;
  background: var(--bg-elev-3);
  overflow: hidden;
}
.view-exp-bar-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--view-c1, #6ea8fe);
  transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.view-exp-bar-val {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  min-width: 40px;
  text-align: right;
}
.view-exp-map-wrap {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 10px);
  overflow: hidden;
}
.view-exp-map {
  display: block;
  width: 100%;
  cursor: crosshair;
}
/* Standalone `map` view wrapper — hosts the canvas + the Google Maps chip. */
.view-map-wrap {
  position: relative;
}
.view-map-wrap .view-canvas {
  cursor: pointer;
}
/* Google Maps handoff chip, overlaid top-right of every map card. A plain link
   (no SDK/key/third-party script); href resolves to the filtered pins on tap. */
.view-map-glink {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-elev);
  opacity: 0.92;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}
.view-map-glink:hover {
  opacity: 1;
  background: var(--bg-elev-1);
}
.view-map-glink .material-symbols-rounded {
  font-size: 13px;
}
.view-exp-table-wrap {
  overflow-x: auto;
}
/* Row-windowing sentinel: extends the rendered window on tap (and auto-fires
   via IntersectionObserver as it scrolls into view). */
.view-exp-more-row td { padding: 0; }
.view-exp-more {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 12.5px;
  cursor: pointer;
}
.view-exp-more:hover { color: var(--text); }
.view-exp-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.view-exp-table th {
  position: sticky;
  top: 0;
  text-align: left;
  font-weight: 600;
  color: var(--text-dim);
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elev);
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
}
.view-exp-table th:hover {
  color: var(--text);
}
.view-exp-table th.num {
  text-align: right;
}
.view-exp-sort-ind {
  color: var(--accent);
  font-size: 11px;
}
.view-exp-table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.view-exp-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.view-exp-row {
  cursor: pointer;
  transition: background 0.12s;
}
.view-exp-row:hover {
  background: var(--bg-elev-1);
}
.view-exp-row.expanded {
  background: var(--accent-dim);
}
.view-exp-row.hl {
  background: var(--accent-dim);
}
.view-exp-row.expanded td {
  border-bottom-color: transparent;
}
.view-exp-tag {
  display: inline-block;
  font-size: 11px;
  font-weight: 500;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev-2);
  color: var(--text-dim);
}
.view-exp-tag.clickable {
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.view-exp-tag.clickable:hover {
  border-color: var(--border-strong);
  color: var(--text);
}
.view-exp-tag.on {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--text);
}
.view-exp-detail-row td {
  padding: 0 10px 12px;
  background: var(--accent-dim);
}
.view-exp-detail {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 6px 18px;
  padding: 4px 4px 2px;
}
.view-exp-detail-pair {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
/* Actions footer inside the drill-in: drill chip + Google Maps link on one
   full-width row, aligned and vertically centered. */
.view-exp-detail-actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 2px;
}
.view-exp-detail-actions .view-exp-maplink { margin-top: 0; }
.view-exp-detail-actions .view-act { margin-top: 0; }
.view-exp-detail-key {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.view-exp-maplink {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--link, #8ab4f8);
  text-decoration: none;
}
.view-exp-maplink:hover {
  text-decoration: underline;
}
.view-exp-maplink .material-symbols-rounded {
  font-size: 14px;
}

.view-exp-detail-val {
  font-size: 13px;
  color: var(--text);
}

/* Tappable follow-up chip (docs/TAPPABLE_ACTIONS.md): one tap = one agent turn. */
.view-act {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  align-self: center;
  margin-top: 4px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: none;
  font-size: 12px;
  font-weight: 600;
  color: var(--link, #8ab4f8);
  cursor: pointer;
  white-space: nowrap;
}
.view-act:hover { background: var(--bg-elev-1); }
.view-act:disabled { opacity: 0.5; cursor: default; }
.view-act .material-symbols-rounded { font-size: 13px; }
.view-list-item .view-act { margin-left: 8px; }
.view-kpi .view-act { margin-top: 6px; }

/* ---------------- gallery (filterable image grid + lightbox) ---------------- */
.view-gallery {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.view-gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
}
.view-gallery-tile {
  margin: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  background: var(--bg-elev-1);
  cursor: pointer;
  transition: border-color 0.14s, transform 0.14s;
}
.view-gallery-tile:hover {
  border-color: var(--border-strong);
  transform: translateY(-2px);
}
.view-gallery-thumb {
  aspect-ratio: 1 / 1;
  background: var(--bg-elev-2);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.view-gallery-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.view-gallery-tile.broken .view-gallery-thumb::after {
  content: "🖼";
  font-size: 26px;
  opacity: 0.4;
}
.view-gallery-tile.broken .view-gallery-img {
  display: none;
}
.view-gallery-cap {
  padding: 8px 10px;
}
.view-gallery-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.view-gallery-sub {
  font-size: 11px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.view-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.82);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.view-lightbox-box {
  position: relative;
  max-width: 92vw;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
}
.view-lightbox-img {
  max-width: 92vw;
  max-height: 78vh;
  object-fit: contain;
  border-radius: 8px;
  background: var(--bg-elev);
}
.view-lightbox-meta {
  text-align: center;
  color: var(--text);
}
.view-lightbox-title {
  font-size: 15px;
  font-weight: 600;
}
.view-lightbox-sub {
  font-size: 13px;
  color: var(--text-dim);
  margin-top: 2px;
}
.view-lightbox-link {
  font-size: 13px;
  color: var(--accent);
  text-decoration: none;
}
.view-lightbox-close {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.view-lightbox-close:hover {
  background: var(--bg-elev-2);
}

/* ---------------- web-search sources (Path 2) ---------------- */
.sources-panel {
  /* Align under the assistant content column, matching surface cards. */
  margin: 10px 0 2px;
  padding: 10px 14px;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.sources-head {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 6px;
}
.sources-list {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sources-list li {
  font-size: 13px;
  line-height: 1.45;
}
.sources-list a.src-link {
  color: var(--accent);
  text-decoration: none;
}
.sources-list a.src-link:hover {
  text-decoration: underline;
}
.sources-list .src-host {
  margin-left: 6px;
  color: var(--text-faint);
  font-size: 11.5px;
}

/* ---------------- settings modal ---------------- */

.settings-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
  display: grid;
  place-items: center;
  padding: 20px;
  /* Above the auth/agent gate (.auth-overlay z 1000) so the buy-agent modal,
     reachable from the agent picker, layers on top of it. */
  z-index: 1100;
}
.settings-panel {
  width: min(560px, 100%);
  max-height: 90dvh;
  overflow-y: auto;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.settings-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg-elev);
  /* Above ALL scrolling content — the gacha card's animated transform and the
     buy-modal value card (z-index 3, rides over the hero deck) would otherwise
     paint over this sticky bar. */
  z-index: 4;
}
.settings-titlebar h2 { margin: 0; font-size: 17px; }
.settings-close {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px;
  background: transparent;
  border: none;
  border-radius: 999px;
  color: var(--text-dim);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
.settings-close:hover { color: var(--text); background: var(--bg-elev-2); }

/* ---------------- in-app confirm / prompt dialog (confirm.ui.ts) ----------------
   Tonal Gemini replacement for window.confirm()/prompt(); centered, --ease motion. */
.confirm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
  display: grid;
  place-items: center;
  padding: 20px;
  z-index: 1400;
}
.confirm-dialog {
  width: min(400px, 100%);
  background: var(--bg-elev);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 24px;
  animation: confirm-in 0.2s var(--ease);
}
@keyframes confirm-in {
  from { opacity: 0; transform: scale(0.96) translateY(8px); }
  to { opacity: 1; transform: none; }
}
.confirm-title { font-family: var(--font-display); font-size: 19px; font-weight: 600; color: var(--text); line-height: 1.3; }
.confirm-msg { margin-top: 8px; font-size: 14px; line-height: 1.5; color: var(--text-dim); }
.confirm-input {
  width: 100%; margin-top: 16px;
  background: var(--bg-elev-2); border: none; border-radius: var(--radius-sm);
  padding: 12px 14px; font: inherit; font-size: 15px; color: var(--text);
  outline: 2px solid transparent; transition: outline-color 0.15s var(--ease);
}
.confirm-input::placeholder { color: var(--text-faint); }
.confirm-input:focus { outline-color: color-mix(in srgb, var(--accent) 50%, transparent); }
.confirm-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 22px; }
.confirm-btn {
  border: none; border-radius: 999px; padding: 11px 20px; min-height: 44px;
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: background 0.15s var(--ease), filter 0.15s var(--ease);
}
.confirm-btn-cancel { background: var(--bg-elev-2); color: var(--text); }
.confirm-btn-cancel:hover { background: var(--bg-elev-3); }
.confirm-btn-ok { background: var(--accent); color: var(--on-accent); }
.confirm-btn-ok:hover { filter: brightness(1.08); }
.confirm-btn-danger { background: var(--danger); color: var(--on-accent); }
.confirm-btn-danger:hover { filter: brightness(1.08); }

.settings-section {
  padding: 18px 20px;
  border-bottom: 1px solid var(--border);
}
.settings-section:last-child { border-bottom: none; }
/* Admin panels: each section is its own Gemini card (elevated surface, rounded, gapped)
   instead of a divider-separated block, so sections read as distinct — matches the
   settings-view treatment but without its narrow width cap (admin holds wide tables/charts). */
.admin-body .settings-section {
  background: var(--bg-elev-1);
  border: none;
  border-radius: var(--radius-lg);
  padding: 28px 32px;
  margin: 0 0 20px;
}
.admin-body .settings-section:last-child { margin-bottom: 0; }
@media (max-width: 640px) {
  .admin-body .settings-section { padding: 22px 20px; }
}
.settings-section h3 { margin: 0 0 12px; font-size: 14px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; }

.settings-radios { display: flex; gap: 20px; margin-bottom: 14px; font-size: 14px; }

/* Billing modal (M6) — reuses .settings-overlay/.settings-panel/.settings-section. */
.billing-body { max-height: 70vh; overflow-y: auto; }
/* Buy-agent: let the panel itself own the scroll (90dvh) so the popup runs as tall
   as it can and content rarely needs scrolling. No nested scroll region. */
.buy-agent-body { max-height: none; overflow: visible; }
.billing-note { padding: 24px 20px; color: var(--text-faint); font-size: 14px; }
/* One-line heads-up under the buy CTA (on-chain): payment comes from your wallet. */
.buy-hint { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; text-align: center; margin-top: 2px; }
/* Trial terms at the reinvest checkout — amber, icon + text, matching the yield card's trial
   banner so the "unlock at first fee" deal reads the same at the point of decision. */
.buy-hint--trial {
  display: flex; align-items: center; gap: 8px; text-align: left; color: var(--text);
  margin-top: 8px; padding: 9px 11px; border-radius: var(--radius-md);
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 12%, var(--bg-elev-1));
}
.buy-hint--trial .material-symbols-rounded { flex: none; font-size: 18px; color: var(--warn); }

/* Buy-agent titlebar: groups the "New agent" heading with the reinvest "Locked liquidity"
   badge so the funding source reads as a header-level fact, not fine print. */
.buy-title-group { display: flex; align-items: center; gap: 10px; min-width: 0; }
.buy-locked-badge {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap;
  background: color-mix(in srgb, var(--grad-b) 18%, var(--bg-elev-1)); color: var(--text);
}
.buy-locked-badge .material-symbols-rounded { font-size: 14px; color: var(--grad-b); }

/* Funding modal (self-custodial deposit) — same Gemini design language as the Wallet
   Receive modal: wallet-modal panel (24px radius, display-font title), wallet QR +
   address + white pill, with a guided amount card / network / withdrawal checklist.
   The 90dvh panel owns the scroll (wallet-modal-body has no inner clip). */
/* wallet-modal sets overflow:visible (its modals are short); the funding modal is
   taller, so restore the panel's own 90dvh scroll (3-class beats .wallet-modal). */
.settings-panel.wallet-modal.fund-panel { overflow-y: auto; }
.fund-body { display: flex; flex-direction: column; gap: 14px; }
.fund-body .wallet-qr,
.fund-body .wallet-receive-addr,
.fund-body .wallet-modal-intro,
.fund-body .auth-btn { margin: 0; }
.fund-card { background: var(--bg-elev-1); border-radius: var(--radius); padding: 16px 18px; }
.fund-card-label {
  font-size: 12px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--text-dim);
}
.fund-amount { text-align: center; }
.fund-amount-big {
  font-family: var(--font-display); font-size: 30px; font-weight: 700; line-height: 1.1;
  color: var(--text); font-variant-numeric: tabular-nums; margin-top: 7px;
}
.fund-amount-sub { font-size: 13px; color: var(--text-faint); margin-top: 7px; }
.fund-guide { display: flex; flex-direction: column; gap: 12px; }
.fund-guide-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.fund-net-value { font-size: 14px; font-weight: 600; color: var(--text); }
.fund-steps {
  margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 7px;
  font-size: 13.5px; line-height: 1.5; color: var(--text-dim);
}
.fund-warn { font-size: 12.5px; color: var(--warn); line-height: 1.5; text-align: center; }
.fund-reassure { font-size: 12.5px; color: var(--text-faint); line-height: 1.55; text-align: center; }
.fund-status { font-size: 13px; color: var(--text-faint); text-align: center; }
.billing-balance { font-size: 14px; color: var(--text); margin-bottom: 12px; }
.billing-balance.billing-warn { color: var(--warn); }
.billing-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
.billing-buy {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 12px;
  background: var(--bg-elev-2);
  color: var(--text);
  border: none;
  border-radius: 999px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  transition: background 0.12s var(--ease);
}
.billing-buy:hover:not(:disabled) { background: var(--bg-elev-3); }
.billing-buy:disabled { opacity: 0.5; cursor: default; }
.billing-grid-1 { grid-template-columns: 1fr; }
.settings-radios label { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }

/* ------------------------------------------------------------------ */
/* Buy-agent screen (Gemini design)                                    */
/* ------------------------------------------------------------------ */
.buy-agent { display: flex; flex-direction: column; gap: 15px; }
.buy-agent .settings-label { margin-top: 0; }

/* Buy-agent modal (owner redesign v4): a single-column DROP STAGE at every width —
   hero deck, money block, configure, CTA, fine print — centered on one 430px axis. */
.settings-panel.buy-agent-panel { width: min(620px, 100%); }
.buy-agent { position: relative; }
/* Theatrical vignette behind the stage (static — motion lives in the mascot/rays). */
.buy-agent::before {
  content: "";
  position: absolute;
  inset: -10px;
  pointer-events: none;
  background: radial-gradient(56% 32% at 50% 20%, rgba(80, 40, 255, 0.12), transparent 70%);
}
.buy-agent-grid { position: relative; display: flex; flex-direction: column; align-items: center; gap: 20px; }
.buy-agent-card-col { display: flex; flex-direction: column; gap: 18px; width: 100%; align-items: center; }
.buy-agent-form-col { display: flex; flex-direction: column; gap: 16px; width: min(430px, 100%); min-width: 0; }

/* Pre-sheet loading veil: while the catalog/quote load, the overlay holds only a
   shimmering mystery card (aurora drift + a soft pulse around the Genie mark) — the
   New-agent sheet then appears fully formed instead of opening half-empty. */
.buy-loading-stage { display: grid; place-items: center; pointer-events: none; }
.buy-card-skel {
  width: min(58vw, 216px); aspect-ratio: 5 / 7; border-radius: var(--radius-lg);
  display: grid; place-items: center; color: #fff;
  background: linear-gradient(125deg, var(--grad-a), var(--grad-b) 42%, var(--grad-c) 72%, var(--grad-a) 100%);
  background-size: 240% 240%;
  box-shadow: 0 24px 48px -16px rgba(0, 0, 0, 0.6);
  animation: buy-card-aurora 4.5s ease-in-out infinite, buy-skel-pulse 1.6s ease-in-out infinite;
}
.buy-card-skel .material-symbols-rounded { font-size: 46px; filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.4)); animation: buy-skel-spark 1.6s ease-in-out infinite; }
@keyframes buy-skel-pulse { 0%, 100% { transform: scale(0.98); } 50% { transform: scale(1); } }
@keyframes buy-skel-spark { 0%, 100% { transform: scale(1); opacity: 0.85; } 50% { transform: scale(1.18); opacity: 1; } }
/* The aurora drift these cards animate with — referenced by the gacha mystery back
   too, which previously pointed at keyframes that were never defined. */
@keyframes buy-card-aurora {
  0%, 100% { background-position: 0% 30%; }
  50% { background-position: 100% 70%; }
}
@media (prefers-reduced-motion: reduce) { .buy-card-skel, .buy-card-skel .material-symbols-rounded { animation: none; } }

/* Hero card — the Genie mascot (Three.js/WebGL orb) on a calm dark back (a
   portrait collectible card). The card lives on a "stage" so entrance
   (scale/fade) and the floating idle animation don't fight over `transform`
   (they're on different elements). */
/* No perspective/preserve-3d here: a 3D context forces the card + ghost fan into
   separate compositor layers, and WebKit updates those a frame late inside an
   overflow scroller — the deck visibly trailed the rest of the sheet during
   momentum scroll on iOS. Nothing uses 3D transforms (hover is filter-only). */
.buy-card-stage {
  width: 100%;
  max-width: 264px;
  margin: 0 auto;
  animation: buy-card-in 0.6s var(--ease) both;
}
.buy-card {
  position: relative;
  width: 100%;
  aspect-ratio: 5 / 7;
  max-height: 370px;
  margin: 0 auto;
  padding: 14px 14px 16px;
  border-radius: var(--radius-lg);
  border: 1.5px solid rgba(255, 255, 255, 0.14);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* Calm dark backdrop — the mascot's own rim glow is the color story here, so
     the card behind it stays quiet instead of competing for attention. */
  background: radial-gradient(120% 90% at 50% 8%, #1d1e26, var(--bg-elev-1) 60%, #0c0c10 100%);
  animation:
    buy-card-float 6s ease-in-out infinite,
    buy-card-glow 4.5s ease-in-out infinite;
  transition: filter 0.25s var(--ease);
}
/* Static top gloss for depth. */
.buy-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 75% at 22% 0%, rgba(255, 255, 255, 0.12), transparent 55%);
  pointer-events: none;
}
/* Prismatic ray burst behind the mascot (the star-upgrade sheet's backdrop recipe:
   near-black card base + burst at half opacity). The WebGL2 canvas fills the host;
   the card's overflow:hidden clips it to the rounded corners. */
.buy-card-burst {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Golden/purple ray field (owner call). A coherent two-hue burst (set in
     billing.ui.ts) reads far calmer than the old rainbow, so it can sit at a
     visible opacity while the mascot's rim glow stays the focal point. */
  opacity: 0.42;
}
.buy-card-mascot {
  position: absolute;
  top: 8%;
  left: 0;
  width: 100%;
  height: 68%;
  display: block;
  cursor: pointer;
}
/* Head row: NAME (left) + star-tier pill (right), riding above the mascot canvas. */
.buy-card-head {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  color: #fff;
}
/* Foot: the price alone, centred, dressed as a swing tag. The drop-shadow lives HERE
   (not on the tag) because clip-path on the tag would clip its own box-shadow. */
.buy-card-foot {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
  color: #fff;
  filter: drop-shadow(0 5px 12px rgba(0, 0, 0, 0.45));
}
.buy-card-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.35);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Price tag: a light ticket with a pointed left end and a punched string-hole, so the
   number reads as THE PRICE — echoing the light CTA button — not as another data pill. */
.buy-card-price {
  position: relative;
  flex: 0 0 auto;
  font-size: 13.5px;
  font-weight: 700;
  white-space: nowrap;
  color: #14151a;
  background: linear-gradient(180deg, #ffffff, #e8eaf1);
  padding: 6px 14px 6px 24px;
  border-radius: 3px 8px 8px 3px;
  clip-path: polygon(14px 0, 100% 0, 100% 100%, 14px 100%, 0 50%);
}
.buy-card-price::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 50%;
  width: 5px;
  height: 5px;
  transform: translateY(-50%);
  border-radius: 50%;
  background: rgba(20, 21, 26, 0.55); /* the punched hole */
}
/* Star-tier badge in the head row — a frosted dark pill so the star reads on the
   bright card (the generic tier-colored .star-pill would clash with it). */
.buy-card-star {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 4px 10px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.28); color: #fff;
  font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums;
  backdrop-filter: blur(2px);
}
.buy-card-star-ic { font-size: 15px; font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 20; }
/* Hover: brighten + saturate (not transform — that's owned by the float animation). */
.buy-card-stage:hover .buy-card { filter: brightness(1.08) saturate(1.12); }
/* Light theme (owner call: the card must look BRIGHT, not a dark slab). The burst
   canvas is opaque (webgl2 alpha:false — black bg + rays), so invert+hue-rotate
   turns it into a white burst with the same ray hues; the card ground and head
   ink flip to match. */
:root[data-theme="light"] .buy-card {
  background: radial-gradient(120% 90% at 50% 8%, #ffffff, #eef0f8 60%, #e2e6f3 100%);
  border-color: rgba(25, 28, 48, 0.12);
}
:root[data-theme="light"] .buy-card-burst { filter: invert(1) hue-rotate(180deg); opacity: 0.45; }
:root[data-theme="light"] .buy-card-head { color: #14151a; }
:root[data-theme="light"] .buy-card-name { text-shadow: 0 1px 8px rgba(255, 255, 255, 0.5); }
:root[data-theme="light"] .buy-card-reroll { background: rgba(20, 21, 26, 0.08); }

@keyframes buy-card-in {
  from { opacity: 0; transform: translateY(16px) scale(0.93); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes buy-card-float {
  0%, 100% { transform: translateY(0) rotate(-0.4deg); }
  50% { transform: translateY(-8px) rotate(0.4deg); }
}
@keyframes buy-card-glow {
  0%, 100% { box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), 0 0 0 rgba(120, 90, 220, 0); }
  50% { box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55), 0 0 34px rgba(120, 90, 220, 0.45); }
}
@media (prefers-reduced-motion: reduce) {
  .buy-card-stage,
  .buy-card { animation: none; }
}

/* Tier / supply progress */
.buy-tier { display: flex; flex-direction: column; gap: 8px; }
.buy-tier-head { display: flex; align-items: baseline; justify-content: space-between; font-size: 13px; }
.buy-tier-label { color: var(--text-dim); }
.buy-tier-count { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.buy-progress { height: 8px; border-radius: 999px; background: var(--bg-elev-2); overflow: hidden; }
.buy-progress-fill {
  height: 100%;
  min-width: 6px;
  border-radius: 999px;
  background: var(--grad);
  transition: width 0.3s var(--ease);
}
.buy-tier-sub { font-size: 12px; color: var(--text-faint); }

/* Earn pitch (billing.ui.ts yieldPreview). Gold hero number (count-up) + always-visible,
   color-coded value lines. Calm, Gemini-organized: a soft static gold wash, no animation,
   no horizontal scroll — the value reads at a glance. Cash-math detail sits in the
   "How payouts work" disclosure. */
.buy-earn--hype {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px 18px;
  border: 1px solid color-mix(in srgb, #ffd166 26%, var(--border));
  border-radius: var(--radius-lg, 18px);
  background:
    radial-gradient(130% 120% at 0% 0%, color-mix(in srgb, #ffd166 10%, transparent), transparent 55%),
    var(--bg-elev-1);
}
.buy-earn-ic { font-size: 20px; flex: 0 0 auto; }
.buy-earn-kicker {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 600;
  color: color-mix(in srgb, #ffd166 60%, var(--text));
}
.buy-earn-kicker .buy-earn-ic { font-size: 18px; color: #ffd166; }
.buy-earn-badge {
  margin-left: auto;
  font-size: 11px; font-weight: 700;
  padding: 2px 8px; border-radius: 999px;
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 18%, transparent);
}
.buy-earn-bignum { display: flex; align-items: baseline; gap: 10px; }
.buy-earn-bignum-val {
  font-size: 38px; font-weight: 800; line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.buy-earn-bignum-unit { display: flex; flex-direction: column; line-height: 1.25; }
.buy-earn-bignum-unit-day { font-size: 14px; font-weight: 600; color: var(--text-dim); }
.buy-earn-bignum-unit-mo { font-size: 12px; color: var(--text-faint); }
/* Always-visible color-coded value lines (no scroll, nothing hidden). */
.buy-earn-values { display: flex; flex-direction: column; gap: 10px; }
.buy-earn-v { display: flex; align-items: flex-start; gap: 11px; }
.buy-earn-v-ic { font-size: 22px; flex: 0 0 auto; margin-top: 1px; }
.buy-earn-v-main { display: flex; flex-direction: column; gap: 1px; }
.buy-earn-v-title { font-size: 14px; font-weight: 700; color: var(--text); }
.buy-earn-v-sub { font-size: 12px; line-height: 1.35; color: var(--text-faint); }
.buy-earn-rows { display: flex; flex-direction: column; gap: 8px; padding-top: 6px; }
.buy-earn-row { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.buy-earn-row .buy-earn-ic { font-size: 18px; color: var(--text-faint); }
.buy-earn-row-label { color: var(--text-dim); flex: 1 1 auto; }
.buy-earn-row-val { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Fields */
.buy-field { display: flex; flex-direction: column; gap: 7px; }

/* Coupon: input + Apply button, with a validity message below. */
.buy-coupon-row { display: flex; gap: 8px; align-items: stretch; }
.buy-coupon-row .settings-input { flex: 1 1 auto; }
.buy-coupon-apply {
  flex: 0 0 auto;
  padding: 0 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
  color: var(--text);
  font: inherit; font-size: 14px; font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease), opacity 0.15s var(--ease);
}
.buy-coupon-apply:hover:not(:disabled) { background: var(--bg-elev-3); }
.buy-coupon-apply:disabled { opacity: 0.55; cursor: default; }
.buy-coupon-msg { font-size: 12.5px; line-height: 1.4; text-align: center; margin-top: 6px; }
.buy-coupon-msg.is-ok { color: var(--ok); }
.buy-coupon-msg.is-err { color: var(--warn); }
/* Original price struck through next to the discounted one on the hero card. */
.buy-card-price-was { text-decoration: line-through; opacity: 0.6; font-weight: 500; margin-right: 7px; }

/* Quantity: quick chips + numeric stepper */
.buy-qty { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.buy-qty-chips { display: flex; gap: 7px; }
.buy-chip {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px;
  padding: 8px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev-1);
  color: var(--text-dim);
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.buy-chip:hover { border-color: var(--accent); }
.buy-chip.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.buy-chip.is-danger { color: var(--err); }
.buy-chip.is-danger:hover { border-color: var(--err); background: color-mix(in srgb, var(--err) 14%, transparent); }
.buy-stepper {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  padding: 3px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elev-1);
}
.buy-step-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s;
}
.buy-step-btn:hover:not(:disabled) { background: var(--bg-elev-2); }
.buy-step-btn:disabled { opacity: 0.35; cursor: default; }
.buy-qty-input {
  width: 44px;
  text-align: center;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  -moz-appearance: textfield;
}
.buy-qty-input::-webkit-outer-spin-button,
.buy-qty-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Implicit consent (owner call): a quiet caption under the CTA; it shakes on press
   as the "you just agreed to this" beat. */
/* Consent line inside the gradient CTA — quiet white, normal weight against the
   800-weight price row it inherits from the button. */
.buy-consent { display: block; text-align: center; font-size: 11.5px; font-weight: 600; color: rgba(255, 255, 255, 0.78); }
.buy-cta .buy-terms-link { cursor: pointer; }
.buy-consent.is-shake { animation: buy-consent-shake 0.4s ease; }
@keyframes buy-consent-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}
.buy-terms-link { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.buy-terms-link:hover { color: var(--text); }

/* CTA — always armed (implicit consent), brand gradient use #3, shimmer sweep.
   Live price leads; the struck MSRP total + −N% chip trail on the right. */
.buy-cta-group { display: flex; flex-direction: column; gap: 8px; }
.buy-cta {
  position: relative;
  overflow: hidden;
  width: 100%;
  padding: 13px 22px 10px;
  border: none;
  border-radius: 999px;
  background: linear-gradient(100deg, #0036ff, #7b2bff 55%, #c600ff);
  color: #fff;
  font-size: 16.5px;
  font-weight: 800;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  box-shadow: 0 14px 40px rgba(90, 30, 230, 0.4);
}
.buy-cta-main { display: flex; align-items: center; justify-content: center; gap: 10px; }
.buy-cta:hover:not(:disabled) {
  /* .billing-buy:hover swaps in a flat elev-3 at higher specificity — re-pin the gradient. */
  background: linear-gradient(100deg, #0036ff, #7b2bff 55%, #c600ff);
  filter: brightness(1.07);
}
.buy-cta:disabled { opacity: 0.7; }
.buy-cta::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 70px;
  left: -90px;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.35), transparent);
  animation: buy-cta-sweep 2.8s ease-in-out infinite;
}
@keyframes buy-cta-sweep {
  0%, 55% { left: -90px; }
  85%, 100% { left: 115%; }
}
.buy-cta-was { text-decoration: line-through; opacity: 0.55; font-weight: 600; font-size: 0.78em; }
.buy-cta-off { background: rgba(255, 255, 255, 0.18); border-radius: 999px; padding: 3px 10px; font-size: 0.68em; font-weight: 800; }
@media (prefers-reduced-motion: reduce) { .buy-cta::after { animation: none; display: none; } }

.settings-fields { display: flex; flex-direction: column; gap: 7px; }
.settings-label { font-size: 12.5px; color: var(--text-dim); margin-top: 6px; }

.settings-input, .settings-textarea {
  width: 100%;
  background: var(--bg-elev-1);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  font: inherit;
  font-size: 14px;
  outline: none;
}
.settings-input:focus, .settings-textarea:focus { border-color: var(--accent); }
.settings-textarea { resize: vertical; font-family: var(--font-mono); font-size: 12.5px; }

/* Warn-only cash-safety notice under the settings Save button (the save still succeeds). */
.settings-cash-warning {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--banner-warn-bg);
  color: var(--banner-warn-fg);
  border: 1px solid var(--banner-warn-bd);
  font-size: 12.5px;
  line-height: 1.5;
  display: grid;
  gap: 4px;
}
.settings-cash-warning strong { font-weight: 700; }

/* ── Admin → Danger zone: live-money master switches (yield & payments) ─────── */
/* Red-accented nav item so the destructive panel reads as set-apart at a glance. */
.admin-nav-item.is-danger .admin-nav-icon { color: var(--danger-text); }
.admin-nav-item.is-danger.is-active .admin-nav-label,
.admin-nav-item.is-danger.is-active .admin-nav-icon { color: var(--danger-text); }
.admin-danger-banner {
  display: flex; gap: 10px; align-items: flex-start;
  margin-bottom: 20px; padding: 16px 22px;
  border-radius: var(--radius-md);
  background: var(--danger-bg); color: var(--danger-text);
  border: 1px solid var(--danger-border);
  font-size: 13px; line-height: 1.5;
}
.admin-danger-banner .material-symbols-rounded { font-size: 20px; flex: none; }
.admin-danger-banner strong { font-weight: 700; }
.danger-toggle {
  border: 1px solid var(--danger-border);
  background: color-mix(in srgb, var(--danger-bg) 55%, var(--bg-elev-1));
  border-radius: var(--radius-lg);
  padding: 24px 28px; display: grid; gap: 12px;
}
.danger-toggle + .danger-toggle { margin-top: 18px; }
@media (max-width: 640px) {
  .danger-toggle { padding: 18px 20px; }
}
.danger-toggle-title { font-size: 15px; font-weight: 700; color: var(--text); }
.danger-toggle-desc { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; margin-top: 3px; }
.danger-seg { display: flex; gap: 8px; flex-wrap: wrap; }
.danger-opt {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 18px; border-radius: var(--radius-md); cursor: pointer;
  background: var(--bg-elev-1); border: none; color: var(--text-dim);
  font: inherit; font-size: 13.5px; font-weight: 600;
  box-shadow: inset 0 0 0 1.5px transparent;
  transition: color 0.15s var(--ease), background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.danger-opt:hover { color: var(--text); background: var(--bg-elev-2); }
.danger-opt:disabled { opacity: 0.55; cursor: default; }
/* Selected state: neutral for Off, amber for partial (admins only), red for fully Live. */
.danger-opt.is-active {
  color: var(--text); background: var(--bg-elev-2);
  box-shadow: inset 0 0 0 1.5px var(--border);
}
.danger-opt.is-active.is-partial {
  color: var(--banner-warn-fg); background: var(--banner-warn-bg);
  box-shadow: inset 0 0 0 1.5px var(--banner-warn-bd);
}
.danger-opt.is-active.is-live {
  color: #fff; background: var(--danger);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--danger) 60%, transparent);
}
.danger-status { font-size: 12.5px; color: var(--text-dim); min-height: 16px; }
.danger-status.is-ok { color: var(--ok); }
.danger-status.is-warn { color: var(--banner-warn-fg); }
.danger-status.is-err { color: var(--danger-text); }

.settings-actions { display: flex; gap: 10px; margin-top: 16px; }
.settings-inline-row { display: flex; gap: 8px; }
.settings-inline-row .settings-input { flex: 1 1 auto; }

.settings-btn {
  background: var(--bg-elev-2);
  color: var(--text);
  border: none;
  border-radius: 999px;
  padding: 10px 20px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 550;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s var(--ease);
}
.settings-btn:hover { background: var(--bg-elev-3); }
.settings-btn.primary {
  background: var(--grad);
  color: var(--on-accent);
  border: none;
  font-weight: 650;
}
.settings-btn.primary:hover { filter: brightness(1.08); }

.settings-status { font-size: 13px; margin-top: 10px; min-height: 18px; color: var(--text-dim); }
.settings-status.ok { color: var(--ok); }
.settings-status.err { color: var(--err); }

/* Language picker (International settings) — selectable cards, one per language. */
.settings-lang-options { display: flex; flex-direction: column; gap: 8px; max-width: 420px; }
.settings-lang-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 13px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev-1);
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.settings-lang-option:hover { background: var(--bg-elev-2); }
.settings-lang-option.is-active { border-color: var(--accent); background: var(--bg-elev-2); }
.settings-lang-name { flex: 1 1 auto; font-weight: 500; }
.settings-lang-check { font-size: 20px; color: var(--accent, #6b9fff); opacity: 0; }
.settings-lang-option.is-active .settings-lang-check { opacity: 1; }

.skill-list { list-style: none; margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.skill-empty { color: var(--text-faint); font-size: 13px; }
.skill-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 11px;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.skill-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.skill-name { font-size: 13.5px; font-weight: 600; }
.skill-desc { font-size: 12px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.skill-del {
  flex: 0 0 auto;
  background: transparent;
  color: var(--danger-text);
  border: 1px solid var(--danger-border);
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 38px; padding: 6px 14px;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.skill-del:hover { background: var(--danger-bg); }
/* Neutral counterpart to .skill-del for non-destructive row actions (e.g. Edit). */
.skill-edit {
  flex: 0 0 auto;
  background: var(--bg-elev-2);
  color: var(--text);
  border: none;
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 38px; padding: 6px 14px;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.skill-edit:hover { background: var(--bg-elev-3); }

/* ---------------- self-improvement section ---------------- */

.settings-hint {
  margin: 0 0 14px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim);
}

/* Composer-shortcut editor (admin → Home screen): one row per pill — auto emoji,
   name input, prompt textarea, remove. */
.chip-editor-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 12px;
}
.chip-editor-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.chip-editor-emoji {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  font-size: 20px;
  line-height: 1;
  background: var(--bg-elev-1);
  border-radius: 12px;
}
.chip-editor-fields {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.chip-editor-prompt {
  min-height: 40px;
  resize: vertical;
}
.chip-editor-remove {
  flex: 0 0 auto;
  width: 32px;
  height: 40px;
  border: none;
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  font-size: 15px;
  border-radius: 10px;
}
.chip-editor-remove:hover {
  background: var(--bg-elev-1);
  color: var(--text);
}
.chip-editor-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.chip-editor-add {
  background: var(--bg-elev-1);
  color: var(--text);
}

/* Master + per-pass toggles share this row layout: checkbox + label/hint stack. */
.settings-toggle {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 7px 0;
  cursor: pointer;
}
.settings-toggle input[type="checkbox"] {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 2px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.settings-toggle input[type="checkbox"]:disabled { cursor: default; }
.settings-toggle-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.settings-toggle-label { font-size: 14px; color: var(--text); }
.settings-toggle-hint { font-size: 12px; color: var(--text-faint); }

/* Per-pass toggles are indented under the master switch and dim when it's off. */
.settings-subtoggles {
  display: flex;
  flex-direction: column;
  margin-left: 26px;
  padding-left: 12px;
  border-left: 1px solid var(--border);
}
.settings-subtoggles.disabled {
  opacity: 0.45;
  pointer-events: none;
}

.improve-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.improve-empty { color: var(--text-faint); font-size: 13px; font-style: italic; }

.improve-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 11px;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.improve-item.reverted { opacity: 0.55; }
.improve-item.reverted .improve-summary { text-decoration: line-through; }

.improve-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.improve-summary {
  font-size: 13.5px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.improve-sub {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.improve-time {
  font-size: 11.5px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.improve-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.improve-phase {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-dim);
  background: var(--bg-elev-2);
}
/* Phase tags are mono tonal chips — distinguished by their text label, not color
   (reflect/dreaming/curator inherit the base; manual reads quieter). */
.improve-phase.phase-manual { color: var(--text-faint); }
.improve-kinds {
  font-size: 11.5px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.improve-revert {
  flex: 0 0 auto;
  background: transparent;
  color: var(--danger-text);
  border: 1px solid var(--danger-border);
  border-radius: 999px;
  padding: 5px 13px;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition: background 0.15s;
}
.improve-revert:hover { background: var(--danger-bg); }
.improve-revert:disabled { opacity: 0.5; cursor: default; }

/* Pending-approval card (Material/Gemini): content stacked, then one aligned action
   footer — a neutral outline Reject (dismissive, left) and a filled primary Approve
   (affirmative, right), both equal-height pill icon+label buttons. Replaces the old
   mismatched plain-text Approve + red outline Reject. */
.approval-card {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 11px;
  padding: 13px 14px;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.approval-actions { display: flex; justify-content: flex-end; gap: 8px; }
.approval-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  height: 34px; padding: 0 15px; border-radius: 999px;
  border: 1px solid transparent;
  font: inherit; font-size: 13px; font-weight: 600; line-height: 1; cursor: pointer;
  transition: background 0.15s var(--ease), filter 0.15s var(--ease), color 0.15s var(--ease), transform 0.1s var(--ease);
}
.approval-btn .material-symbols-rounded { font-size: 17px; }
.approval-btn:disabled { opacity: 0.5; cursor: default; }
.approval-btn--reject { background: transparent; color: var(--text-dim); border-color: var(--border); }
.approval-btn--reject:not(:disabled):hover { background: var(--bg-elev-2); color: var(--text); }
.approval-btn--approve { background: var(--accent); color: var(--on-accent); }
.approval-btn--approve:not(:disabled):hover { filter: brightness(1.06); }
.approval-btn:not(:disabled):active { transform: scale(0.97); }

.improve-reverted-tag {
  flex: 0 0 auto;
  font-size: 11px;
  font-style: italic;
  color: var(--text-faint);
}

/* ---------------- shared toast (toast.ui.ts) ---------------- */
/* Above sheets/modals/confirm dialogs so an action taken inside an overlay (e.g. the
   admin Details sheet) still surfaces its confirmation. Mirrors .sched-toast. */
.genie-toast {
  /* Centered via inset-0 + auto margins, NOT left:50%: a fixed box at left:50%
     shrink-to-fits into the space right of center (HALF the viewport), so long
     messages wrapped into a skinny multi-line blob on phones. fit-content sizes
     to the text; max-width caps it; the 16px insets are the phone edge gutter. */
  position: fixed; left: 16px; right: 16px; bottom: calc(28px + env(safe-area-inset-bottom, 0px));
  margin: 0 auto; width: fit-content; max-width: 560px;
  transform: translateY(6px);
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 20px; border-radius: 999px;
  background: var(--bg-elev-3); color: var(--text);
  font-size: 13.5px; line-height: 1.35;
  opacity: 0; pointer-events: none; z-index: 20000;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.32);
  transition: opacity var(--ease) 160ms, transform var(--ease) 160ms;
}
.genie-toast.is-shown { opacity: 1; transform: translateY(0); }
.genie-toast.is-error { background: var(--danger-bg); color: var(--danger); }
.genie-toast-icon { font-size: 18px; color: var(--ok); }
.genie-toast.is-error .genie-toast-icon { color: var(--danger); }
/* THE TOAST'S ONE ACTION (owner 2026-08-17: the "Learned" receipt became a toast
   and its Undo rides on it). The snackbar itself stays click-through — that is
   what `pointer-events: none` above is for — so the button re-enables pointer
   events for ITSELF, and ONLY while the toast is actually shown: a hidden toast
   is still in the DOM at opacity 0, and an invisible live button over the
   composer would be a trap. 44px tall for the same reason the idea cards are. */
.genie-toast-action {
  flex: none; margin: -4px -8px -4px 4px; padding: 10px 12px; min-height: 44px;
  display: inline-flex; align-items: center;
  appearance: none; background: none; border: none; border-radius: 999px;
  font: inherit; font-weight: 650; line-height: 1; color: var(--accent, #4c8dff);
  cursor: pointer;
}
.genie-toast.is-shown .genie-toast-action { pointer-events: auto; }
.genie-toast-action:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
.genie-toast-action:disabled { opacity: 0.5; cursor: default; }
.genie-toast.is-error .genie-toast-action { color: var(--danger); }

/* ---------------- toast ---------------- */

/* (legacy .toast removed — main.ts now uses the shared .genie-toast snackbar) */

/* ---------------- slide-up options modal (ui_request) ---------------- */

.modal-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
  background: rgba(0, 0, 0, 0);
  z-index: 300;
  transition: background 0.25s ease;
}
.modal-overlay.show {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px);
}

.modal-sheet {
  width: 100%;
  max-width: 860px;
  max-height: 80dvh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px 20px calc(20px + env(safe-area-inset-bottom, 0px));
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: var(--shadow-lg);
  transform: translateY(100%);
  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.modal-overlay.show .modal-sheet { transform: translateY(0); }

.modal-question {
  font-size: 15px;
  color: var(--text);
}
.modal-question p:first-child { margin-top: 0; }

/* The facts under the question. The sheet covers the chat, so this is what the
   user decides on — quieter than the question, scrolls if long, never taller
   than a third of the screen so the options stay reachable. */
.modal-context {
  margin-top: 6px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-dim);
  max-height: 32vh;
  overflow-y: auto;
}
.modal-context p:first-child { margin-top: 0; }
.modal-context p:last-child { margin-bottom: 0; }

.modal-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.modal-option {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  text-align: left;
  background: var(--bg-elev-1);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  font: inherit;
  font-size: 14.5px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.modal-option:hover,
.modal-option.selected {
  background: var(--bg-elev-2);
  border-color: var(--accent);
}
.modal-option:focus-visible { outline: none; border-color: var(--accent); }
.modal-option-num {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.modal-option.selected .modal-option-num {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.modal-option-num:empty { display: none; }
.modal-option-text { flex: 1 1 auto; min-width: 0; }

.modal-input {
  width: 100%;
  resize: none;
  max-height: 160px;
  background: var(--bg-elev-1);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  font: inherit;
  font-size: 14.5px;
  line-height: 1.45;
  outline: none;
}
.modal-input:focus { border-color: var(--accent); }
.modal-input::placeholder { color: var(--text-faint); }

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
.modal-btn {
  background: var(--bg-elev-2);
  color: var(--text);
  border: none;
  border-radius: 999px;
  padding: 10px 20px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 550;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.modal-btn:hover { background: var(--bg-elev-3); }
.modal-btn.primary {
  background: var(--grad);
  color: var(--on-accent);
  border: none;
  font-weight: 650;
}
.modal-btn.primary:hover { filter: brightness(1.08); }

@media (prefers-reduced-motion: reduce) {
  .modal-sheet { transition: none; }
  .modal-overlay { transition: none; }
}

/* ---------------- responsive ---------------- */

@media (max-width: 860px) {
  .model-indicator { max-width: 38vw; }
}

@media (max-width: 520px) {
  /* --- Phone home: the WHOLE column scrolls as one (mascot → bubble → earnings
     scroll away as you browse), and only the Recent row pins — right beneath the
     floating hamburger — so task browsing stays anchored (Gemini's mobile home).

     Shared gutter so the earnings card and the task list are the same width and
     centered, and their content aligns on a single left edge (gutter + 16px). --- */

  /* The single scroll container is .home-center now (not just the list), so the
     hero scrolls with everything else. .tasks-view is just the clipping frame;
     the fixed composer + top scrim escape it via position:fixed. The gutter var
     is scoped here so every descendant (card, Recent bar, list) shares it. */
  .tasks-view { overflow: hidden; }
  .home-center {
    --home-gutter: 16px;
    padding: 0;
    overflow-y: auto;
    /* Never scroll sideways — the assist-chip row scrolls within its own box, so
       the page itself must not pick up a stray horizontal scroll. */
    overflow-x: hidden;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  /* Opaque top strip: as the hero and task rows scroll up they dissolve behind
     the floating hamburger, above the pinned Recent bar. Sits under the hamburger
     (z 40) but over the scrolling content. */
  .home-center::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: calc(env(safe-area-inset-top) + 52px);
    background: var(--bg);
    z-index: 20;
    pointer-events: none;
  }

  /* The hero is now just the top of the scroll — no min-height, no pin. Mascot
     rides high; its transparent canvas headroom tucks under the top scrim. */
  .home-header { min-height: 0; padding-top: calc(env(safe-area-inset-top) + 4.5vh); z-index: 2; }

  /* Hamburger floats top-left over the scrim. */
  .nav-fab { top: calc(env(safe-area-inset-top) + 4px); }

  /* Even vertical rhythm below the ball: bubble → earnings → Recent step 24 / 24,
     and the ball-to-bubble gap is tightened to ~16 by pulling the bubble up
     through more of the canvas' below-ball headroom. No slack band, so a third
     task row clears the fold. */
  .home-mascot-canvas { width: clamp(150px, 37vh, 330px); height: clamp(150px, 37vh, 330px); }
  .home-mascot-ph { width: clamp(150px, 37vh, 330px); height: clamp(150px, 37vh, 330px); }
  .home-mascot-slot { margin: 0 0 clamp(-78px, -6.5vh, -26px); }
  .home-greeting {
    margin: 0 auto 24px;
    padding: 11px 18px;
    font-size: 15.5px;
    line-height: 1.35;
  }

  /* Earnings card and task list share the gutter → same width, centered. */
  .home-earn-slot { width: auto; margin: 0 var(--home-gutter) 24px; }
  .home-earn { padding: 14px 16px; }

  /* Recent row: pinned beneath the top scrim as the hero scrolls away. Solid fill
     with a short fade below so task rows dissolve as they pass under it. Its label
     lines up with the task titles (gutter + 16); its search icon with the kebabs. */
  .recent-bar {
    position: sticky;
    top: calc(env(safe-area-inset-top) + 52px);
    z-index: 15;
    margin: 0;
    padding: 6px var(--home-gutter) 8px calc(var(--home-gutter) + 16px);
    background: var(--bg);
  }
  .recent-bar::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 14px;
    background: linear-gradient(to bottom, var(--bg), transparent);
    pointer-events: none;
  }
  /* ≥44px hit area for the sort dropdown. */
  .recent-sort { min-height: 44px; }

  /* The list flows in the shared scroll (no inner scroll of its own) and clears
     the docked composer at the bottom (composer + assist chips + safe area). */
  .tasks-list {
    flex: 0 0 auto;
    overflow: visible;
    padding: 0 var(--home-gutter);
    padding-bottom: calc(150px + env(safe-area-inset-bottom));
    gap: 4px;
  }
  /* Title aligns with the earnings label (gutter + 16); kebab hugs the right. */
  .task-row { padding: 16px 6px 16px 16px; }
  .task-title { font-size: 16px; }
  .task-subtitle { font-size: 13.5px; }
  /* Always-visible on touch (see the hover:none rule); ≥44px for the kebab. */
  .task-time { font-size: 12.5px; }
  .task-kebab { width: 44px; height: 44px; font-size: 20px; }

  /* Docked composer, thumb-zone: anchored to the viewport bottom with a scrim so
     list rows dissolve beneath it. The assist-chip row rides just above the pill. */
  .home-composer-wrap {
    position: fixed;
    left: 12px;
    right: 12px;
    bottom: 0;
    width: auto;
    margin: 0;
    padding: 10px 0 calc(10px + env(safe-area-inset-bottom));
    background: linear-gradient(to top, var(--bg) 72%, transparent);
  }
  .home-composer { padding: 18px 18px 18px 22px; border-radius: var(--radius-hero); gap: 14px; }
  .home-composer-plus { font-size: 26px; }
  .home-input { font-size: 16.5px; line-height: 1.55; }

  /* Search + drawer/menu rows: ≥44–48px hit areas. */
  .recent-search-wrap { height: 44px; }
  .recent-search-wrap.is-collapsed,
  .recent-search-wrap.is-collapsed .recent-search-ic { width: 44px; height: 44px; }
  .nav-drawer-item { min-height: 52px; }
  .nav-menu-item { min-height: 44px; }

  .brand-name { display: none; }
  /* Tighter side margins on a phone so the chat + composer use the full width
     (matches the home composer's 12px inset). */
  .transcript { gap: 14px; padding-left: 12px; padding-right: 12px; }
  .composer-wrap { padding-left: 12px; padding-right: 12px; }
  .msg { gap: 9px; }
  .msg-avatar { width: 36px; height: 36px; font-size: 17.5px; }
  .surface-card { margin-left: 45px; }
  .cmd-palette { left: 8px; right: 8px; max-height: 50dvh; }
  .cmd-desc { font-size: 12px; }
}

/* ------------------------------------------------------------------ */
/* Auth overlay (login / invite gate)                                  */
/* ------------------------------------------------------------------ */

.auth-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: radial-gradient(1200px 600px at 50% -10%, var(--bg-elev) 0%, var(--bg) 60%);
}

.auth-card {
  position: relative;
  width: 100%;
  max-width: 380px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 28px 26px 24px;
  box-shadow: var(--shadow-lg);
}
.auth-close {
  position: absolute;
  top: 12px;
  right: 12px;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.auth-close:hover { background: var(--bg-elev-2); color: var(--text); }
.auth-close .material-symbols-rounded { font-size: 20px; }

.auth-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 20px;
}
.auth-mark {
  font-size: 30px;
  font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24;
  /* The first impression — the one sanctioned use of the signature blue→purple→pink
     gradient on the brand mark (mirrors the profile avatar hero). */
  background: linear-gradient(135deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.auth-brand-name { font-family: var(--font-display); font-size: 17px; font-weight: 600; letter-spacing: 0.2px; }

.auth-title { font-family: var(--font-display); font-size: 24px; font-weight: 600; line-height: 1.2; margin-bottom: 6px; }
.auth-sub { color: var(--text-dim); font-size: 13.5px; line-height: 1.45; margin-bottom: 18px; }

/* Loading / wallet-finalize state: indeterminate spinner + label (the wallet
   setup can take several seconds — never a static line that reads as hung). */
.auth-loading {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 14px 0 6px; color: var(--text-dim); font-size: 13.5px; text-align: center;
}
.auth-spinner {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid var(--bg-elev-3); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .auth-spinner { animation-duration: 2.4s; } }

.auth-body { display: flex; flex-direction: column; gap: 10px; }

.auth-input {
  width: 100%;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 15px;
  min-height: 44px;
  padding: 13px;
  outline: none;
}
.auth-input:focus { border-color: var(--accent); }

.auth-btn {
  width: 100%;
  min-height: 44px;
  background: var(--grad);
  color: var(--on-accent);
  border: none;
  border-radius: 999px;
  font-size: 14.5px;
  font-weight: 600;
  padding: 13px;
  cursor: pointer;
  transition: filter 0.15s ease, opacity 0.15s ease;
}
.auth-btn:hover { filter: brightness(1.08); }
.auth-btn:disabled { opacity: 0.55; cursor: default; }
/* Secondary / ghost button (e.g. "Skip for now" in the delegation step). */
.auth-btn-ghost {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
}
.auth-btn-ghost:hover { filter: none; opacity: 1; color: var(--text); border-color: var(--border-strong); }

/* Send success summary (wallet-send island) — our own post-send breakdown, replacing
   Privy's gas-only "Total". */
.send-summary {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 4px 0 14px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev-1);
}
.send-summary-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 14px; }
.send-summary-label { color: var(--text-dim); }
.send-summary-value { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
/* The Total row sits below a divider and reads a touch larger — it's the headline number. */
.send-summary-total { margin-top: 4px; padding-top: 10px; border-top: 1px solid var(--border); font-size: 15px; }
.send-summary-total .send-summary-label { color: var(--text); font-weight: 600; }
.send-summary-link {
  display: inline-block;
  margin-bottom: 16px;
  color: var(--accent);
  font-size: 13.5px;
  text-decoration: none;
}
.send-summary-link:hover { text-decoration: underline; }

/* Info modal (profile / wallet / team) — reuses .auth-overlay / .auth-card. */
.info-card { min-width: 320px; }
.info-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13.5px;
}
.info-row-label { color: var(--text-dim); flex: 0 0 auto; }
.info-row-value {
  color: var(--text);
  text-align: right;
  word-break: break-all;
  font-family: var(--font-mono);
  font-size: 12.5px;
}
.info-card .auth-sub { margin-top: 14px; margin-bottom: 16px; }

.auth-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 12.5px;
  padding: 4px;
  cursor: pointer;
  align-self: flex-start;
}
.auth-link-icon { font-size: 16px; }
.auth-link:hover { color: var(--text); }
.auth-link:disabled { opacity: 0.5; cursor: default; }
.auth-link:disabled:hover { color: var(--text-dim); }

.auth-err {
  margin-top: 14px;
  color: var(--err);
  font-size: 12.5px;
  line-height: 1.4;
}

/* Agent picker (reuses .auth-overlay / .auth-card) */
.agent-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
}
.agent-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 13px 15px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.agent-row:hover {
  border-color: var(--accent);
  background: var(--bg-elev-2);
}
.agent-name { font-size: 14.5px; font-weight: 600; }
.agent-meta { font-size: 12px; color: var(--text-dim); display: inline-flex; align-items: center; gap: 8px; }

/* ── Schedules screen (standalone view, not a modal) ────────────────────────
   Lives in #schedules-view inside .layout; shown when .layout.schedules-active
   replaces the home (tasks) view. Reuses .settings-btn/.settings-input/.settings-*. */
.schedules-view {
  display: none;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.layout.schedules-active .tasks-view,
.layout.brain-active .tasks-view,
.layout.skills-active .tasks-view,
.layout.earn-active .tasks-view,
.layout.earnings-active .tasks-view,
.layout.store-active .tasks-view,
.layout.wallet-active .tasks-view,
.layout.profile-active .tasks-view,
.layout.team-active .tasks-view,
.layout.settings-active .tasks-view,
.layout.admin-active .tasks-view,
.layout.partner-active .tasks-view { display: none; }
.layout.schedules-active #schedules-view { display: block; }
.layout.brain-active #brain-view { display: block; }
.layout.skills-active #skills-view { display: block; }
.layout.earn-active #earn-view { display: block; }
/* Earn screen: on desktop it scrolls wholesale like the other screens (full
   headline + subtitle at the top). On mobile (≤720) it switches to a pinned
   header + inner scroll (see the earnings media block below). */
.layout.earnings-active #earnings-view { display: block; }
.layout.store-active #store-view { display: block; }
.layout.wallet-active #wallet-view { display: block; }
.layout.profile-active #profile-view { display: block; }
.layout.team-active #team-view { display: block; }
.layout.settings-active #settings-view { display: block; }
.layout.admin-active #admin-view { display: block; }
.layout.partner-active #partner-view { display: block; }

/* Partner screen (Agent Sales System). Reuses .sched-* / .earn-tile primitives. */
.partner-tier {
  margin: 8px 0 4px;
  padding: 18px 20px;
  border: none;
  border-radius: var(--radius);
  background: var(--bg-elev-1);
}
.partner-tier-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.partner-tier-name { font-size: 1.15rem; font-weight: 650; color: var(--text); }
.partner-tier-rates { font-size: 0.85rem; color: var(--text-dim); }
.partner-tier-next { margin-top: 8px; font-size: 0.85rem; color: var(--text-faint); }
.partner-list { display: flex; flex-direction: column; gap: 2px; }
.partner-section-label { margin: 16px 0 6px; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint); }
.partner-row {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px; border-radius: var(--radius-sm);
  transition: background 0.12s var(--ease);
}
.partner-row:hover { background: var(--bg-elev-1); }
.partner-row-main { flex: 1 1 auto; font-weight: 500; color: var(--text); }
.partner-row-sub { color: var(--text-dim); font-size: 0.85rem; }
.partner-row-date { color: var(--text-faint); font-size: 0.8rem; min-width: 90px; text-align: right; }
.partner-amount { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 110px; text-align: right; color: var(--text); }
.partner-amount.is-pos { color: var(--ok); }
.partner-amount.is-neg { color: var(--text-dim); }

/* Admin screen: categorized sidebar + content panels. */
.admin-layout {
  display: flex;
  gap: 28px;
  max-width: 1100px;
  margin: 0 auto;
  padding: 56px 24px 96px;
  align-items: flex-start;
}
.admin-nav {
  position: sticky;
  top: 56px;
  flex: 0 0 220px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.admin-nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  border: none;
  border-radius: var(--radius-tile);
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.admin-nav-item { border-radius: 999px; }
.admin-nav-item:hover { background: var(--bg-elev-1); color: var(--text); }
.admin-nav-item.is-active {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  font-weight: 600;
}
.admin-nav-item.is-active .admin-nav-icon { color: var(--accent); }
.admin-nav-icon { font-size: 20px; }
.admin-nav-label { flex: 1 1 auto; }
/* Unread-improvements badge on a nav tab (Self-improvement). Accent pill with a
   count; sits at the row's trailing edge on desktop, floats on the icon on mobile. */
.admin-nav-badge {
  flex: none; min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--accent); color: var(--on-accent);
  font-size: 11px; font-weight: 700; line-height: 1;
}
/* Transparent wrapper on desktop (items stay direct flex children of the vertical
   sidebar); becomes the horizontal scroller only in the mobile bottom-bar rule. */
.admin-nav-scroll { display: contents; }
.admin-content { flex: 1 1 auto; min-width: 0; }
/* Settings/Admin panels reuse .billing-body for spacing but are NOT a modal —
   drop the modal's `max-height: 70vh; overflow-y: auto` so the panel grows with its
   content and the page (#…-view) scrolls naturally, instead of clipping the list
   with dead space below. */
.admin-body { max-height: none; overflow: visible; }
.admin-panel-head { margin-bottom: 18px; }
.admin-panel-head .sched-headline { margin: 0 0 4px; }
.admin-panel-head .sched-subtitle { margin: 0; }

@media (max-width: 720px) {
  /* The sidebar becomes a bottom-anchored tab bar (icon + name per tab). It fills
     the width when there are few items (Settings, 3) and scrolls horizontally when
     there are many (Admin, 12). Content gets bottom padding so it isn't hidden
     behind the fixed bar. */
  /* Column layout: stretch content to full width (the base align-items:flex-start
     would otherwise shrink each panel to its content width). */
  .admin-layout { flex-direction: column; padding: 0 16px 88px; align-items: stretch; }
  .admin-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 50;
    flex-basis: auto;
    width: 100%;
    padding: 6px 4px;
    padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
    /* Read as a distinct elevated bar (the old --surface-0 fallback was nearly the
       page bg, so the bar vanished and content looked hard-"cut off" against it). */
    background: var(--bg-elev);
    border-top: 1px solid var(--border);
    /* The inner .admin-nav-scroll does the horizontal scrolling; the nav itself must
       NOT clip, so its ::before fade can paint above the bar. */
    overflow: visible;
  }
  /* Fade above the bar so scrolling content dissolves into it instead of slicing flat
     at the top edge. Absolute + bottom:100% pins it directly above the fixed bar — no
     magic numbers — and the gradient ends on the bar's own color for a seamless join. */
  .admin-nav::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: 28px;
    background: linear-gradient(to top, var(--bg-elev), transparent);
    pointer-events: none;
  }
  /* Right-edge fade hinting more tabs to scroll to — only when the bar actually
     overflows (the JS toggles .is-scrollable via a ResizeObserver), so the 3-tab
     Settings bar doesn't get a misleading fade. Fixes "couldn't find Pricing/Grants"
     (admin tabs 7-12 were scrolled off with no affordance). */
  .admin-nav.is-scrollable::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    width: 24px;
    background: linear-gradient(to right, transparent, var(--bg-elev));
    pointer-events: none;
    z-index: 1;
  }
  .admin-nav-scroll {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 0;
    width: 100%;
    overflow-x: auto;
    scrollbar-width: none; /* hide the scrollbar; the bar still scrolls by touch */
  }
  .admin-nav-scroll::-webkit-scrollbar { display: none; }
  /* Gemini / Material-3 nav bar: the active indicator is a pill behind the ICON
     (not the whole tab — that read as a wide, short slab with dead space), with the
     label below. Tabs are evenly distributed at a comfortable height. */
  /* flex: 1 0 auto — grow to fill the bar when there are few tabs (Settings, 3), but
     never shrink below a comfortable min-width when there are many (Admin, 12), so the
     bar scrolls horizontally instead of cramming every tab to ~38px. */
  .admin-nav-item {
    flex: 1 0 auto;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    width: auto;
    min-width: 64px;
    padding: 8px 6px 7px;
    border-radius: 0;
    background: transparent;
    text-align: center;
    font-size: 0.72rem;
    line-height: 1.15;
  }
  .admin-nav-item:hover { background: transparent; }
  .admin-nav-item.is-active { background: transparent; color: var(--text); font-weight: 400; }
  .admin-nav-icon {
    display: grid;
    place-items: center;
    width: 48px;
    height: 30px;
    border-radius: 999px;
    font-size: 21px;
    color: var(--text-dim);
    transition: background 0.15s var(--ease), color 0.15s var(--ease);
  }
  .admin-nav-item.is-active .admin-nav-icon {
    background: color-mix(in srgb, var(--accent) 22%, transparent);
    color: var(--accent);
  }
  .admin-nav-label { display: block; flex: 0 0 auto; white-space: nowrap; color: var(--text-faint); }
  .admin-nav-item.is-active .admin-nav-label { color: var(--text); font-weight: 600; }
}

/* Mobile fit for the rest of the account & settings screens (the nav above is
   handled separately). Without these, label/value rows, the admin data tables, the
   partner dashboard, and the billing grid overflow or crush at ~390px. */
@media (max-width: 720px) {
  /* Label+value rows (Profile, Team, Admin Sales, Connected agents): the label was
     flex:0 0 auto and the row never wrapped, so a long label or trailing action
     buttons pushed past the edge. Let it wrap and let the label shrink. */
  .info-row { flex-wrap: wrap; }
  .info-row-label { flex: 1 1 auto; min-width: 0; }
  .admin-row-actions, .profile-wallet-actions { margin-left: auto; }

  /* Admin data tables: give the table a real min-width so the .dt-wrap's
     overflow-x:auto actually scrolls horizontally instead of crushing 6 columns
     into ~358px. Tighten cell padding to claw back a little room. */
  .dt-table { min-width: 620px; }
  .dt-table th, .dt-table td { padding: 10px 12px; }

  /* Billing modal: stack the 2-up credit/pass purchase buttons to a single column
     so "50 USDT → 100,000"-style labels don't wrap raggedly at ~150px. */
  .billing-grid { grid-template-columns: 1fr; }

  /* Partner / Sales (and Earn) dashboard: 4 stat tiles wrap to 2×2 instead of being
     squeezed to ~76px each; ledger rows drop their fixed date/amount min-widths so
     the main column keeps room (and can wrap). */
  .earn-dash { flex-wrap: wrap; }
  .earn-tile { flex: 1 1 calc(50% - 6px); }
  .partner-row { flex-wrap: wrap; }
  .partner-row-main { min-width: 0; }
  .partner-row-date,
  .partner-amount { min-width: 0; }
}

/* Mobile nav-bar headers: across every screen the headline becomes a left-aligned
   title in a ~56px top bar sitting just to the right of the floating hamburger
   (.nav-fab, fixed top:12, 48px, z-40), vertically centered with it; the subtitle
   is hidden and content scrolls UNDER the pinned bar. The bar stays under the
   hamburger on the z-axis (z-30) so the menu stays tappable, and paints the page
   bg so scrolling content disappears cleanly. Covers .sched-intro (Profile / Team /
   Partner / Schedules / Skills / Store / XP) and .admin-panel-head (Admin / Settings).
   Earnings (.earnings-topbar) and Tasks (.home-greeting) already do this themselves. */
@media (max-width: 720px) {
  /* Center the hamburger in the 56px bar so it lines up with the title. */
  .nav-fab { top: calc(env(safe-area-inset-top) + 4px); }
  /* Kill the desktop top padding on mobile (.layout > … specificity beats the base
     `.sched-center { padding: 72px … }` defined later in the file). */
  .layout .sched-center { padding-top: 0; }

  .admin-panel-head,
  .sched-intro {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    min-height: calc(56px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) 16px 0 68px; /* left pad clears the hamburger */
    background: var(--bg);
  }
  /* Break each header out of its container's side padding so the bar spans full
     width and the title lands ~60px from the viewport edge (tight to the
     hamburger), matching the Earnings/Tasks bars. */
  .sched-intro { margin: 0 -24px 8px; }       /* .sched-center has 24px side padding */
  .admin-panel-head { margin: 0 -16px 8px; }  /* .admin-layout has 16px side padding */
  .admin-panel-head .sched-headline,
  .sched-intro .sched-headline {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    letter-spacing: -0.2px;
    line-height: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .admin-panel-head .sched-subtitle,
  .sched-intro .sched-subtitle { display: none; }
}

/* Breathing room above an action button that follows a field group, so the
   Save/Grant/Create button never sits flush against the input above it. */
.admin-body .admin-save-row { margin-top: 16px; }

/* Overview stat tiles. */
.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 10px;
  margin-bottom: 24px;
}
.admin-stat {
  padding: 18px 20px;
  border: none;
  border-radius: var(--radius);
  background: var(--bg-elev-1);
  transition: background 0.12s var(--ease);
}
.admin-stat:hover { background: var(--bg-elev-2); }
.admin-stat-value { font-size: 1.7rem; font-weight: 650; letter-spacing: -0.5px; color: var(--text); font-variant-numeric: tabular-nums; }
.admin-stat-label { margin-top: 4px; font-size: 0.8rem; color: var(--text-faint); }
/* Money-safety anomaly badge: a non-zero count is a warning the owner should notice at a glance. */
.admin-stat-alert { background: color-mix(in srgb, var(--danger, #e5484d) 14%, var(--bg-elev-1)); box-shadow: inset 0 0 0 1px var(--danger, #e5484d); }
.admin-stat-alert .admin-stat-value { color: var(--danger, #e5484d); }
.admin-inline-action { margin-bottom: 14px; max-width: 280px; }

/* CRM sales dashboard: section sub-heads, tier bars, daily-GMV sparkline. */
#admin-view .admin-subhead {
  margin: 22px 0 8px;
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}
.crm-tier-row { display: flex; align-items: center; gap: 12px; padding: 9px 0; }
.crm-tier-name { flex: 0 0 96px; font-size: 13.5px; color: var(--text-dim); }
.crm-tier-count { flex: 0 0 auto; min-width: 36px; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text); }
.crm-bar { flex: 1 1 auto; height: 8px; border-radius: 999px; background: var(--bg-elev-2); overflow: hidden; }
.crm-bar-fill { height: 100%; border-radius: 999px; background: var(--grad); min-width: 2px; transition: width 0.3s var(--ease); }
/* Daily-GMV bar chart + time-frame selector. */
.crm-chart-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.crm-range { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.crm-range .buy-chip { min-width: 0; min-height: 36px; padding: 6px 12px; font-size: 12.5px; }
.crm-range-custom { margin-top: 8px; }
.crm-range-custom.is-collapsed { display: none; }
.crm-date {
  background: var(--bg-elev-1);
  color: var(--text);
  border: none;
  border-radius: 999px;
  padding: 6px 12px;
  font: inherit;
  font-size: 12.5px;
  outline: none;
  color-scheme: light dark; /* native picker adapts to the active theme */
}
.crm-date:focus { background: var(--bg-elev-2); }
.crm-date-sep { color: var(--text-faint); }
/* Inline label + control, e.g. the drill-down sheet's Agent picker — lighter-weight
   than a full admin-subhead eyebrow since it sits directly beside its control. */
.crm-field-row { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.crm-field-label { flex: 0 0 auto; font-size: 0.8rem; color: var(--text-faint); }
.crm-field-row .dt-filter { flex: 1 1 auto; }
.crm-chart-wrap {
  position: relative;
  margin-top: 8px;
  padding: 14px 14px 10px;
  border-radius: var(--radius);
  background: var(--bg-elev-1);
}
.crm-chart {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 150px;
}
/* Y-axis gridlines (max down to 0), appended as the last child of .crm-chart so they sit
   ABOVE the bars — each line's value label carries its own background chip, so a tall bar
   passing behind never blots out the scale. Replaces a single top-right max figure with the
   full intermediate scale. `pointer-events: none` keeps it out of the tooltip's hit-testing
   (see chart.ui.ts's attachChartTooltip, which reads the column beneath via elementFromPoint). */
.crm-chart-grid {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  pointer-events: none;
}
.crm-chart-gridline { position: relative; border-top: 1px dashed color-mix(in srgb, var(--border-strong) 70%, transparent); }
.crm-chart-gridline-label {
  position: absolute;
  right: 0;
  top: 0;
  transform: translateY(-50%);
  background: var(--bg-elev-1);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 10px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.crm-chart-col {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  align-items: flex-end;
  cursor: pointer;
}
.crm-chart-bar {
  width: 100%;
  border-radius: 3px 3px 0 0;
  background: var(--grad);
  min-height: 2px;
  opacity: 0.88;
  transition: opacity 0.12s var(--ease);
}
.crm-chart-col:hover .crm-chart-bar { opacity: 1; }
/* Stacked cost chart (admin-models.ui.ts): segments fill a column bottom-up. */
.crm-chart-stack { display: flex; flex-direction: column-reverse; width: 100%; height: 100%; }
.crm-chart-seg { width: 100%; min-height: 0; opacity: 0.9; }
.crm-chart-col:hover .crm-chart-seg { opacity: 1; }

/* --- Admin model routing chain + catalog picker + cost legend (admin-models.ui.ts) --- */
.mdl-chain { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
.mdl-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-elev-2); }
.mdl-rank { flex: 0 0 auto; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; border-radius: 999px; background: var(--bg-elev-2); color: var(--text-dim); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
.mdl-main { flex: 1 1 auto; min-width: 0; }
.mdl-name { font-weight: 600; color: var(--text); overflow-wrap: anywhere; line-height: 1.3; }
.mdl-meta { font-size: 12px; color: var(--text-dim); display: flex; gap: 10px; flex-wrap: wrap; margin-top: 2px; }
.mdl-actions { flex: 0 0 auto; display: inline-flex; gap: 2px; }
.mdl-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: none; background: transparent; color: var(--text-dim); border-radius: 8px; cursor: pointer; }
.mdl-iconbtn:hover:not(:disabled) { background: var(--bg-elev-3); color: var(--text); }
.mdl-iconbtn:disabled { opacity: 0.3; cursor: default; }
.mdl-iconbtn.is-danger:hover { color: var(--err); }
.mdl-iconbtn .material-symbols-rounded { font-size: 20px; }
.mdl-tag { flex: 0 0 auto; font-size: 11px; color: var(--text-faint); }
.mdl-picker { border: 1px solid var(--border); border-radius: 12px; margin-top: 8px; overflow: hidden; background: var(--bg-elev-2); }
.mdl-pick-search { width: 100%; box-sizing: border-box; border: none; border-bottom: 1px solid var(--border); background: transparent; color: var(--text); padding: 10px 12px; font: inherit; font-size: 14px; }
.mdl-pick-search:focus { outline: none; }
.mdl-pick-list { max-height: 300px; overflow: auto; }
.mdl-pick-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--border); }
.mdl-pick-row:last-child { border-bottom: none; }
.mdl-pick-row:hover { background: var(--bg-elev-2); }
.mdl-score { flex: 0 0 auto; min-width: 34px; text-align: center; padding: 2px 6px; border-radius: 6px; font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok); }
.mdl-score.is-none { background: var(--bg-elev-2); color: var(--text-faint); }
.mdl-legend { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 10px; font-size: 12px; }
.mdl-legend-item { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); }
.mdl-swatch { width: 10px; height: 10px; border-radius: 2px; flex: 0 0 auto; }
.mdl-recon { display: flex; gap: 18px; flex-wrap: wrap; margin: 8px 0 2px; font-size: 13px; color: var(--text-dim); }
.mdl-recon strong { color: var(--text); font-variant-numeric: tabular-nums; }
.crm-chart-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
/* Hover (mouse) / tap (touch) value readout — see chart.ui.ts's attachChartTooltip.
   Positioned relative to .crm-chart-wrap (its offset parent), just above the pointed-at
   column; hidden until a pointer lands on a column. */
.crm-chart-tooltip {
  position: absolute;
  z-index: 2;
  transform: translateX(-50%);
  white-space: pre-line;
  text-align: center;
  background: var(--bg-elev-2);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  font-size: 11.5px;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
  box-shadow: var(--shadow-md);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.1s var(--ease);
}
.crm-chart-tooltip.is-visible { opacity: 1; visibility: visible; }

/* Sales / admin grouped lists as Gemini tonal cards (GMV overview, partners,
   payouts): a single rounded surface with hairline row dividers, no outer stroke. */
#admin-view .admin-card,
#admin-view .admin-list {
  background: var(--bg-elev-1);
  border-radius: var(--radius);
  padding: 2px 16px;
  margin-top: 10px;
}
#admin-view .admin-card .info-row,
#admin-view .admin-list .info-row { padding: 13px 0; }
#admin-view .admin-card .info-row:last-child,
#admin-view .admin-list .info-row:last-child { border-bottom: none; }
/* A muted empty-state line ("No payouts yet.") shouldn't look like a card. */
#admin-view .admin-list > .admin-note:only-child { padding: 14px 0; }

/* Partner/payout row action buttons → compact tonal pills (not full-width). */
#admin-view .admin-row-actions .billing-buy {
  width: auto;
  padding: 7px 16px;
  background: var(--bg-elev-2);
  border: none;
  font-size: 0.82rem;
}
#admin-view .admin-row-actions .billing-buy:hover:not(:disabled) { background: var(--bg-elev-3); }

/* Mascot test panel: centered WebGL preview + expression-trigger chips. */
.admin-mascot-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 32px 16px 24px;
  background: var(--bg-elev-1);
  border-radius: var(--radius);
  margin-top: 10px;
}
.admin-mascot-stage { width: 220px; height: 220px; }
.admin-mascot-canvas { display: block; width: 100%; height: 100%; cursor: pointer; }
.admin-mascot-chips { justify-content: center; }

/* Primary CTAs in admin (Save, Enroll, Grant, Create, Run rebate) → spark gradient. */
#admin-view .billing-grid-1 > .billing-buy {
  background: var(--grad);
  color: var(--on-accent);
  border: none;
  font-weight: 600;
  padding: 13px 16px;
}
#admin-view .billing-grid-1 > .billing-buy:hover:not(:disabled) { background: var(--grad); filter: brightness(1.06); }
#admin-view .billing-grid-1 > .billing-buy:disabled { opacity: 0.5; }

/* Admin data table (search / sort / filter / paginate) — Gemini: borderless tonal
   fills, rounded pills, elevation via fill not stroke. */
.dt-toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.dt-search { display: flex; align-items: center; gap: 8px; flex: 1 1 240px; min-width: 200px;
  padding: 9px 14px; border: none; border-radius: 999px;
  background: var(--bg-elev-1); }
.dt-search .material-symbols-rounded { font-size: 19px; color: var(--text-faint); }
.dt-search-input { flex: 1 1 auto; border: none; background: transparent; color: var(--text); font: inherit; outline: none; }
.dt-filter {
  padding: 9px 14px; border: none; border-radius: 999px;
  background: var(--bg-elev-1); color: var(--text); font: inherit; font-size: 0.9rem; cursor: pointer;
  transition: background 0.12s var(--ease);
}
.dt-filter:hover { background: var(--bg-elev-2); }
.dt-wrap { overflow-x: auto; border: none; border-radius: var(--radius); background: var(--bg-elev-1); }
.dt-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.dt-table th, .dt-table td { text-align: left; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.dt-table tbody tr:last-child td { border-bottom: none; }
.dt-table thead th { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint); font-weight: 600; white-space: nowrap; }
.dt-table tbody tr { transition: background 0.1s var(--ease); }
.dt-table tbody tr:hover { background: var(--bg-elev-2); }
.dt-table td.is-num, .dt-table th.is-num { text-align: right; font-variant-numeric: tabular-nums; }
/* Trial-monitor: flag likely sybil farms (many trials, zero conversions). */
.admin-trial-tbl tbody tr.is-suspect td:first-child { color: var(--danger-text); font-weight: 600; }
.admin-trial-tbl tbody tr.is-suspect { background: var(--danger-bg); }
/* Cap a column's width (e.g. a long email) so it doesn't crowd out the more
   important numeric columns; full value is still available via title on hover. */
.dt-col-narrow { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-th-btn { display: inline-flex; align-items: center; gap: 4px; background: none; border: none; color: inherit; font: inherit;
  text-transform: inherit; letter-spacing: inherit; cursor: pointer; padding: 0; }
.dt-th-btn:hover { color: var(--text); }
.dt-table th.is-sorted { color: var(--text); }
.dt-sort-ind { font-size: 0.8rem; color: var(--accent); }
.dt-msg { text-align: center; padding: 28px 14px; color: var(--text-faint); }
.dt-actions-col { width: 1%; }
.dt-actions { text-align: right; white-space: nowrap; }
.dt-row-actions { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dt-action {
  display: inline-flex; align-items: center; justify-content: center; min-height: 36px;
  padding: 6px 14px; border: none; border-radius: 999px;
  background: var(--bg-elev-2); color: var(--text); font: inherit; font-size: 0.82rem; cursor: pointer;
  transition: background 0.12s var(--ease);
}
.dt-action:hover:not(:disabled) { background: var(--bg-elev-3); }
.dt-action:disabled { opacity: 0.5; cursor: default; }
.dt-action-danger { color: var(--err); }
.dt-action-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--err) 16%, transparent); }
.dt-link { color: var(--accent); text-decoration: none; }
.dt-link:hover { text-decoration: underline; }
.dt-mono { font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 0.82rem; color: var(--text-dim); }
/* Copyable code token (e.g. a user's invite code): tonal pill, fill-based, accent-tint confirm. */
.dt-code {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.8rem; letter-spacing: 0.02em;
  padding: 3px 10px; border: none; border-radius: 999px;
  background: var(--bg-elev-2); color: var(--text-dim); cursor: pointer;
  transition: background 0.12s var(--ease), color 0.12s var(--ease);
}
.dt-code:hover { background: var(--bg-elev-3); color: var(--text); }
.dt-code.is-copied { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); }
.dt-badges { display: inline-flex; gap: 5px; flex-wrap: wrap; }
.dt-badge { padding: 3px 10px; border-radius: 999px; font-size: 0.76rem; font-weight: 500; }
.dt-badge-good { background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok); }
.dt-badge-bad { background: color-mix(in srgb, var(--err) 18%, transparent); color: var(--err); }
.dt-badge-info { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }
.dt-badge-muted { background: var(--bg-elev-3); color: var(--text-dim); }
.dt-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; }
.dt-summary { font-size: 0.85rem; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.dt-page-btns { display: flex; gap: 6px; }
.dt-page-btn {
  display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px;
  border: none; border-radius: 50%; background: var(--bg-elev-1);
  color: var(--text); cursor: pointer; transition: background 0.12s var(--ease);
}
.dt-page-btn:hover:not(:disabled) { background: var(--bg-elev-2); }
.dt-page-btn:disabled { opacity: 0.35; cursor: default; }
.dt-page-btn .material-symbols-rounded { font-size: 18px; }

/* Coupon create form — labeled fields, each with an inline "?" help toggle that expands a
   detailed explanation card. Gemini-style: calm tonal surfaces, generous spacing, soft
   rounded geometry, restrained accents. */
.coupon-form { gap: 16px; }
.coupon-field { display: flex; flex-direction: column; gap: 6px; }
.coupon-field-head { display: flex; align-items: center; gap: 6px; min-height: 18px; }
.coupon-field-label { margin-top: 0; font-weight: 500; letter-spacing: 0.1px; }
.coupon-info {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 1px; flex: 0 0 auto;
  border: none; border-radius: 50%; background: transparent;
  color: var(--text-faint); cursor: pointer; line-height: 1;
  font-size: 16px; /* Material symbol glyph size (overrides the 20px base) */
  transition: color 0.15s ease, background 0.15s ease;
}
.coupon-info:hover { color: var(--text); background: var(--bg-elev-2); }
.coupon-info:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.coupon-info.is-active { color: var(--accent); background: var(--accent-dim); }
.coupon-help {
  display: none;
  margin-top: 2px;
  font-size: 12px; line-height: 1.55; color: var(--text-dim);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
}
.coupon-help.is-open { display: block; animation: coupon-help-in 0.16s ease; }
@keyframes coupon-help-in { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }

/* Coupons: multi-select table + bulk action bar */
.coupon-table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 0.85rem; }
.coupon-table th {
  text-align: left; padding: 7px 8px; white-space: nowrap;
  color: var(--text-faint); font-weight: 600; font-size: 0.74rem; letter-spacing: 0.02em;
  border-bottom: 1px solid var(--border);
}
.coupon-table td { padding: 9px 8px; border-bottom: 1px solid var(--border); color: var(--text); vertical-align: middle; }
.coupon-table tbody tr:last-child td { border-bottom: none; }
.coupon-table tbody tr:hover td { background: var(--bg-elev-2); }
.coupon-th-check { width: 1%; white-space: nowrap; }
.coupon-th-check input { cursor: pointer; vertical-align: middle; }
.coupon-code { font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-weight: 600; letter-spacing: 0.04em; }
.coupon-row-actions { width: 1%; text-align: right; }
.coupon-copy {
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; cursor: pointer; padding: 3px; border-radius: 6px;
  color: var(--text-dim); font-size: 18px; line-height: 1;
}
.coupon-copy:hover { color: var(--text); background: var(--bg-elev-2); }
.coupon-bulk-bar { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.coupon-bulk-count { margin-right: auto; color: var(--text-dim); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.billing-buy.is-compact { width: auto; padding: 6px 14px; font-size: 0.85rem; }
.billing-buy.is-danger { background: var(--danger); color: var(--on-accent); }
/* Re-assert the red on hover: the base .billing-buy:hover rule is more specific and
   would otherwise repaint it neutral gray exactly as the user goes to click. */
.billing-buy.is-danger:hover:not(:disabled) { background: var(--danger); filter: brightness(1.08); }

/* Admin sales section helpers. */
.admin-list { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
.admin-row-actions { display: flex; gap: 6px; margin-left: 12px; }
.admin-note { color: var(--text-faint); font-size: 0.85rem; padding: 8px 2px; }
.admin-warn {
  padding: 12px 14px; margin-bottom: 10px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--warn) 16%, transparent); border: none;
  color: var(--text); font-size: 0.85rem;
}

.sched-center {
  max-width: 760px;
  margin: 0 auto;
  padding: 72px 24px 96px;
}

/* Intro */
.sched-headline {
  font-family: "Google Sans Flex", "Google Sans Text", Roboto, system-ui, sans-serif;
  font-size: 32px;
  font-weight: 400;
  letter-spacing: -0.2px;
  margin: 0 0 10px;
}
.sched-subtitle {
  font-size: 14.5px;
  color: var(--text-dim);
  line-height: 1.55;
  margin: 0;
  max-width: 620px;
}

/* Create actions */
.sched-cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin: 26px 0 8px; }
.sched-cta {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 18px; border-radius: 999px;
  border: none; background: var(--bg-elev-2);
  color: var(--text); font: inherit; font-size: 14px; font-weight: 500; cursor: pointer;
  transition: background var(--ease) 150ms, filter var(--ease) 150ms;
}
.sched-cta:hover { background: var(--bg-elev-3); }
.sched-cta .material-symbols-rounded { font-size: 20px; }
.sched-cta-primary { background: var(--grad); color: var(--on-accent); font-weight: 600; }
.sched-cta-primary:hover { filter: brightness(1.08); background: var(--grad); }

/* Empty state */
.sched-empty { padding: 48px 8px; text-align: center; color: var(--text-dim); }
.sched-empty p { margin: 4px 0; }
.sched-empty-sub { font-size: 13px; color: var(--text-faint); }

/* Grouped list of cards */
.sched-group-label {
  font-size: 13px; font-weight: 600; color: var(--text);
  margin: 30px 0 12px;
}
.sched-cards { display: flex; flex-direction: column; gap: 12px; }
.sched-card {
  position: relative;
  background: var(--bg-elev-1);
  border-radius: var(--radius);
  padding: 18px 20px;
  cursor: pointer;
  transition: background var(--ease) 150ms;
}
.sched-card:hover { background: var(--bg-elev-2); }
.sched-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Keep the title/meta clear of the absolutely-positioned kebab. */
.sched-card-info { padding-right: 40px; }
.sched-card-title { font-size: 15px; font-weight: 500; color: var(--text); }
.sched-card-when { font-size: 13.5px; color: var(--text-dim); margin-top: 4px; }
.sched-card-last { font-size: 13px; color: var(--text-faint); margin-top: 2px; }
.sched-card-last.is-err { color: var(--danger); }

/* Overflow ("⋯") button + popover menu — Run / Edit / Pause / Delete (Gemini style) */
.sched-kebab {
  position: absolute; top: 14px; right: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 999px;
  background: transparent; border: none; color: var(--text-dim); cursor: pointer;
  transition: background var(--ease) 120ms, color var(--ease) 120ms;
}
.sched-kebab:hover { background: var(--bg-elev-3); color: var(--text); }
.sched-kebab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sched-kebab .material-symbols-rounded { font-size: 20px; }

.sched-menu {
  position: fixed; z-index: 1200; min-width: 188px;
  background: var(--bg-elev-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 6px; box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column;
}
.sched-menu-item {
  display: flex; align-items: center; gap: 12px;
  background: transparent; border: none; color: var(--text);
  font-size: 14px; text-align: left; padding: 9px 12px; border-radius: var(--radius-tile); cursor: pointer;
  transition: background var(--ease) 120ms;
}
.sched-menu-item:hover { background: var(--bg-elev-3); }
.sched-menu-item .material-symbols-rounded { font-size: 20px; color: var(--text-dim); }
.sched-menu-item.is-danger { color: var(--danger); }
.sched-menu-item.is-danger .material-symbols-rounded { color: var(--danger); }
.sched-menu-sep { height: 1px; background: var(--border); margin: 6px 6px; }

/* Create / edit form */
.sched-form { display: flex; flex-direction: column; gap: 12px; margin-top: 18px; }
.sched-form-head { font-size: 16px; font-weight: 600; margin-bottom: 4px; }
.sched-field { display: flex; flex-direction: column; gap: 5px; }
.sched-field[hidden] { display: none; }
.sched-objective { min-height: 78px; font-family: inherit; font-size: 14px; }
/* Tonal select — drop native OS chrome, draw a Gemini chevron instead. */
.sched-select {
  appearance: none; -webkit-appearance: none;
  padding-right: 38px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%239aa0a6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 18px;
}
.sched-select::-ms-expand { display: none; }

/* Toast for run-now / errors — fixed to the viewport since there's no modal panel.
   z-index above EVERYTHING, including the confetti canvas (10000) and welcome-overlay
   (9999) — verified live that a gacha-win toast's ~3.2s window overlaps the confetti
   burst's ~5s window for most realistic dismiss timings, and confetti (being decorative,
   full-screen, z-index 10000) was rendering ON TOP of the toast for that whole overlap.
   bottom offset adds env(safe-area-inset-bottom) — every OTHER bottom-pinned element in
   this app (composer, home header, nav) already accounts for it; this one didn't, so on a
   real phone with the browser's bottom toolbar/home-indicator showing, the toast could be
   rendering fine (confirmed via headless testing at every viewport) while sitting fully
   behind that OS chrome — invisible despite being "there." */
.sched-toast {
  position: fixed; left: 50%; bottom: calc(28px + env(safe-area-inset-bottom, 0px)); transform: translate(-50%, 6px);
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 16px; border-radius: 999px;
  background: var(--bg-elev-3); color: var(--text);
  font-size: 13px; opacity: 0; pointer-events: none; z-index: 20000;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.32);
  transition: opacity var(--ease) 160ms, transform var(--ease) 160ms;
}
.sched-toast.is-shown { opacity: 1; transform: translate(-50%, 0); }
.sched-toast.is-error { background: var(--danger-bg); color: var(--danger); }
/* Optional leading icon (see flash()'s iconName param) — same size/role as the top banner's
   info/warn/error glyph. */
.sched-toast .material-symbols-rounded { font-size: 16px; flex: none; color: var(--text-dim); }
.sched-toast.is-error .material-symbols-rounded { color: var(--danger); }

/* ── Skills screen (standalone view; Marketplace | Installed) ────────────────
   Reuses .sched-center/.sched-intro/.sched-headline/.sched-subtitle/.sched-empty/
   .sched-toast and the .sched-menu popover. Gemini-style: pill controls, tonal
   surfaces, an expandable search mirroring the recent-tasks search. */

/* Controls row: sort chip on the left, expandable search pinned right. */
.skills-controls { display: flex; align-items: center; gap: 10px; min-height: 40px; margin-bottom: 16px; }
.skills-sort-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-elev-1); border: none; cursor: pointer;
  color: var(--text); font-size: 13.5px; font-weight: 500;
  height: 40px; padding: 0 8px 0 16px; border-radius: 999px;
  transition: background var(--ease) 120ms;
}
.skills-sort-chip:hover { background: var(--bg-elev-2); }
.skills-chip-label { color: var(--text-faint); font-weight: 400; }
.skills-chip-arrow { color: var(--text-dim); font-size: 13px; }
.skills-sort-chip .material-symbols-rounded { font-size: 20px; color: var(--text-dim); margin-left: -2px; }

.skills-sort-menu { min-width: 210px; }
.skills-menu-check { width: 20px; display: inline-flex; justify-content: center; flex: none; }
.skills-menu-check .material-symbols-rounded { font-size: 18px; color: var(--accent); }
.skills-sort-menu .sched-menu-item { gap: 8px; }

/* Expandable search — same behavior as the recent-tasks search. */
.skills-search-wrap {
  display: flex; align-items: center; gap: 6px; flex: 0 0 auto;
  width: 260px; max-width: 60%; height: 40px; margin-left: auto; padding: 0 14px;
  overflow: hidden; background: var(--bg-elev-1); border: none; border-radius: 999px;
  transition: width 0.22s var(--ease), padding 0.22s var(--ease), background 0.15s var(--ease);
}
.skills-search-wrap:focus-within { background: var(--bg-elev-2); }
.skills-search-ic {
  flex: 0 0 auto; display: grid; place-items: center;
  background: transparent; border: none; padding: 0; color: var(--text-faint); cursor: pointer;
}
.skills-search-ic .material-symbols-rounded { font-size: 19px; }
.skills-search {
  flex: 1 1 auto; min-width: 0; background: transparent; border: none; outline: none;
  color: var(--text); font: inherit; font-size: 13.5px;
}
.skills-search::placeholder { color: var(--text-faint); }
.skills-search-wrap.is-collapsed { width: 40px; padding: 0; gap: 0; background: transparent; }
.skills-search-wrap.is-collapsed .skills-search-ic {
  width: 40px; height: 40px; border-radius: 50%; color: var(--text-dim);
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.skills-search-wrap.is-collapsed .skills-search-ic .material-symbols-rounded { font-size: 22px; }
.skills-search-wrap.is-collapsed .skills-search-ic:hover { background: var(--bg-elev-2); color: var(--text); }
.skills-search-wrap.is-collapsed .skills-search { width: 0; flex: 0 0 0; opacity: 0; pointer-events: none; }

/* Cards */
.skills-cards { display: flex; flex-direction: column; gap: 10px; }

.skills-card {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  background: var(--bg-elev-1); border-radius: var(--radius-lg); padding: 16px 20px; cursor: pointer;
  transition: background var(--ease) 150ms;
}
.skills-card:hover { background: var(--bg-elev-2); }
.skills-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.skills-card-info { min-width: 0; flex: 1; }
.skills-card-title { font-size: 15px; font-weight: 600; color: var(--text); }
/* "Update available" chip beside an installed skill's name — accent-tinted so a
   stale skill reads at a glance without shouting. */
.skills-badge-update {
  display: inline-block; margin-left: 8px; padding: 2px 8px; vertical-align: 1px;
  border-radius: 999px; font-size: 11px; font-weight: 600;
  color: var(--accent); background: var(--accent-dim);
}
.skills-card-desc {
  font-size: 13px; color: var(--text-dim); margin-top: 4px; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.skills-stats { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 9px; font-size: 12px; color: var(--text-faint); }
.skills-stats > span { position: relative; }
.skills-stats > span + span::before {
  content: "·"; position: absolute; left: -8px; color: var(--text-faint);
}
.skills-rate { display: inline-flex; align-items: center; gap: 3px; }
.skills-rate .material-symbols-rounded { font-size: 14px; }

.skills-action {
  flex: none; align-self: center; white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
  background: var(--accent); color: var(--on-accent); border: none;
  border-radius: 999px; padding: 9px 20px; font-size: 13px; font-weight: 600; cursor: pointer;
  transition: filter var(--ease) 120ms, background var(--ease) 120ms, color var(--ease) 120ms;
}
.skills-action:hover { filter: brightness(1.08); }
.skills-action:disabled { cursor: default; }
.skills-action.is-installed {
  background: transparent; color: var(--text-faint);
  box-shadow: inset 0 0 0 1px var(--border); filter: none;
}
.skills-action.is-danger {
  background: transparent; color: var(--text-dim); box-shadow: inset 0 0 0 1px var(--border); filter: none;
}
.skills-action.is-danger:hover { color: var(--danger); box-shadow: inset 0 0 0 1px var(--danger); background: var(--danger-bg); }

/* Skill detail modal — a centered popup. Reuses the .settings-overlay backdrop,
   but centers with flexbox (grid place-items can mis-place a single auto-sized
   child). A column layout caps the height and lets the description scroll while
   the header, KPIs, and action stay pinned. */
.skills-modal-overlay { display: flex; align-items: center; justify-content: center; }
.skills-sheet {
  display: flex;
  flex-direction: column;
  width: min(460px, 100%);
  max-height: 86dvh;
  background: var(--bg-elev);
  border-radius: var(--radius-lg);
  padding: 24px;
  box-shadow: var(--shadow-lg);
  transform: scale(0.96);
  opacity: 0;
  transition: transform 0.18s var(--ease), opacity 0.18s var(--ease);
}
.skills-sheet.is-open { transform: scale(1); opacity: 1; }
.skills-sheet-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.skills-sheet-title { margin: 0; font-size: 20px; font-weight: 600; line-height: 1.25; }
.skills-sheet-sub { font-size: 13px; color: var(--text-dim); margin-top: 6px; flex: none; }
.skills-kpis { display: flex; gap: 10px; margin: 20px 0; flex: none; }
.skills-kpis[hidden] { display: none; }
.skills-kpi { flex: 1; background: var(--bg-elev-1); border-radius: var(--radius); padding: 14px 10px; text-align: center; }
.skills-kpi-value { font-size: 18px; font-weight: 600; color: var(--text); }
.skills-kpi-label { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.5px; margin-top: 5px; }
/* The description takes the remaining height and scrolls when long. */
.skills-sheet-desc {
  flex: 1 1 auto; min-height: 64px; overflow-y: auto;
  font-size: 14px; color: var(--text); line-height: 1.6; margin: 4px 0 20px;
  padding-right: 4px;
}
.skills-sheet-actions { display: flex; flex: none; }
.skills-sheet-action { width: 100%; text-align: center; padding: 12px; font-size: 14px; }

/* ── Earn screen (standalone view) ──────────────────────────────────────────
   Reuses .sched-center/.sched-intro/.sched-headline/.sched-subtitle/.sched-region/
   .sched-empty/.sched-toast and the .tabs-primary underline tabs. */
.earn-jackpot {
  margin: 18px 0 14px;
  padding: 22px 24px;
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 26%, var(--bg-elev-1)), var(--bg-elev-1));
  border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
}
.earn-jackpot-head { display: flex; align-items: center; justify-content: space-between; }
.earn-jackpot-label { font-size: 12.5px; font-weight: 600; letter-spacing: 0.6px; text-transform: uppercase; color: var(--text-dim); }
.earn-jackpot-edit { background: transparent; border: none; color: var(--text-dim); cursor: pointer; padding: 4px; border-radius: 999px; }
.earn-jackpot-edit:hover { color: var(--text); background: var(--bg-elev-2); }
.earn-jackpot-edit .material-symbols-rounded { font-size: 18px; }
.earn-jackpot-value { display: flex; align-items: baseline; gap: 8px; margin-top: 6px; }
.earn-jackpot-xp { font-size: 40px; font-weight: 700; line-height: 1; color: var(--text); }
.earn-jackpot-unit { font-size: 16px; font-weight: 600; color: var(--accent); }
.earn-jackpot-editor { display: flex; flex-direction: column; gap: 6px; }

.earn-dash { display: flex; gap: 12px; margin-bottom: 6px; }
.earn-tile { flex: 1; background: var(--bg-elev-1); border-radius: var(--radius); padding: 16px; text-align: center; }
.earn-tile-value { font-size: 22px; font-weight: 700; color: var(--text); }
.earn-tile-label { font-size: 12px; color: var(--text-faint); margin-top: 4px; }

.earn-bounties { display: flex; flex-direction: column; gap: 12px; }
.earn-new { align-self: flex-start; }
.earn-bounty-list { display: flex; flex-direction: column; gap: 10px; }
.earn-bounty {
  display: flex; align-items: center; gap: 14px;
  background: var(--bg-elev-1); border-radius: var(--radius); padding: 14px 16px;
}
.earn-bounty-icon {
  flex: none; width: 46px; height: 46px; border-radius: var(--radius-sm); overflow: hidden;
  display: grid; place-items: center; background: var(--bg-elev-2); color: var(--accent);
}
.earn-bounty-icon img { width: 100%; height: 100%; object-fit: cover; }
.earn-bounty-icon .material-symbols-rounded { font-size: 26px; }
.earn-bounty-meta { min-width: 0; flex: 1; }
.earn-bounty-titlerow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.earn-bounty-name { font-size: 15px; font-weight: 600; color: var(--text); }
.earn-bounty-reward {
  font-size: 11.5px; font-weight: 700; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent); padding: 2px 8px; border-radius: 999px;
}
.earn-bounty-desc {
  font-size: 13px; color: var(--text-dim); margin-top: 3px; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.earn-bounty-sub { font-size: 12px; color: var(--text-faint); margin-top: 4px; }
.earn-bounty-actions { flex: none; display: flex; align-items: center; gap: 6px; }
.earn-icon-btn {
  width: 40px; height: 40px; border-radius: 999px; flex: none; cursor: pointer;
  background: transparent; border: none; color: var(--text-dim);
  display: inline-grid; place-items: center; transition: background var(--ease) 120ms, color var(--ease) 120ms;
}
.earn-icon-btn:hover { background: var(--bg-elev-2); color: var(--text); }
.earn-icon-btn .material-symbols-rounded { font-size: 19px; }
.earn-icon-btn.is-danger:hover { color: var(--danger); background: var(--danger-bg); }
.skills-action.is-ghost { background: transparent; color: var(--text-faint); box-shadow: inset 0 0 0 1px var(--border); filter: none; }

.earn-ranks { display: flex; flex-direction: column; gap: 22px; }
.earn-rank-title { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); margin-bottom: 8px; }
.earn-rank-list { display: flex; flex-direction: column; gap: 4px; }
.earn-rank-row {
  display: flex; align-items: center; gap: 14px;
  padding: 11px 14px; border-radius: var(--radius-sm); background: var(--bg-elev-1);
}
.earn-rank-row.is-me { background: color-mix(in srgb, var(--accent) 14%, var(--bg-elev-1)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent); }
.earn-rank-pos { font-size: 13px; font-weight: 700; color: var(--text-dim); width: 40px; flex: none; }
.earn-rank-name { flex: 1; min-width: 0; font-size: 14px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.earn-rank-xp { font-size: 13px; font-weight: 600; color: var(--text-dim); flex: none; }

/* Congrats notif — slides up from the bottom when a reward lands. */
.earn-congrats {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 140%);
  z-index: 1300; display: flex; align-items: center; gap: 14px;
  background: var(--bg-elev-2); border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
  border-radius: var(--radius-lg); padding: 14px 18px; box-shadow: var(--shadow-lg);
  max-width: min(440px, calc(100vw - 32px)); transition: transform 0.34s var(--ease);
}
.earn-congrats.is-shown { transform: translate(-50%, 0); }
.earn-congrats-emoji { font-size: 28px; line-height: 1; color: var(--accent); }
.earn-congrats-title { font-size: 15px; font-weight: 700; color: var(--text); }
.earn-congrats-sub { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.earn-congrats-view {
  margin-left: 6px; flex: none; background: var(--accent); color: var(--on-accent); border: none;
  border-radius: 999px; padding: 8px 14px; font-size: 13px; font-weight: 600; cursor: pointer;
}

/* Earn: season end text + Ranks sub-tabs (Agents | Team) */
.earn-jackpot-ends { font-size: 12px; color: var(--text-dim); }
.earn-subtabs { display: flex; gap: 8px; margin-bottom: 16px; }
.earn-subtab {
  display: inline-flex; align-items: center; justify-content: center; min-height: 40px;
  background: var(--bg-elev-1); border: none; cursor: pointer;
  color: var(--text-dim); font-size: 13px; font-weight: 500;
  padding: 8px 16px; border-radius: 999px;
  transition: color var(--ease) 120ms, background var(--ease) 120ms;
}
.earn-subtab:hover { color: var(--text); }
.earn-subtab.is-active { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-elev-1)); }

/* ── Two clear levels of navigation ────────────────────────────────────────
   Primary: the Activity | Ranks section tabs render as bold underline tabs (not
   pills) so they're unmistakably the top level. Secondary: the in-section filters
   (All/Yield/… and the Ranks sub-tabs) stay as small, quiet pills. */
/* Material-3 / Gemini primary tabs: text labels on a full-width rail (the divider),
   with a rounded active indicator that sits on the rail. */
.tabs-primary { gap: 6px; margin: 16px 0 16px; border-bottom: 1px solid var(--border); }
.tabs-primary .earn-subtab {
  position: relative; align-items: flex-end; min-height: 40px;
  background: none; border-radius: 0;
  padding: 11px 14px 9px; font-size: 14.5px; font-weight: 600; letter-spacing: 0.2px;
  color: var(--text-dim);
}
.tabs-primary .earn-subtab:first-child { padding-left: 2px; }
.tabs-primary .earn-subtab:hover { background: none; color: var(--text); }
.tabs-primary .earn-subtab.is-active { background: none; color: var(--accent); }
/* Active indicator: a rounded bar that spans the label (inset matches the side
   padding) and sits on the divider rail just below it. */
.tabs-primary .earn-subtab.is-active::after {
  content: ""; position: absolute; left: 14px; right: 14px; bottom: 0; height: 3px;
  border-radius: 3px 3px 0 0; background: var(--accent);
}
.tabs-primary .earn-subtab:first-child.is-active::after { left: 2px; }

/* ── User-level Earn screen (earnings.ui.ts) ───────────────────────────────
   Reward-pool overview + sub-dashboard tiles + Leaderboards/Details tabs.
   Reuses .earn-dash/.earn-tile, .earn-subtab, .earn-rank-* primitives.
   Desktop: a normal centered column that scrolls wholesale (headline + subtitle
   like the other screens). Mobile: a pinned header + inner scroll (media block). */
.earnings-screen { display: block; }
/* Desktop header: big headline + subtitle, matching .sched-intro. */
.earnings-topbar {
  width: 100%; max-width: 760px; margin: 0 auto;
  padding: 72px 24px 4px;
}
.earnings-topbar-title {
  margin: 0 0 10px;
  font-family: "Google Sans Flex", "Google Sans Text", Roboto, system-ui, sans-serif;
  font-size: 32px; font-weight: 400; letter-spacing: -0.2px; line-height: 1.1;
}
.earnings-subtitle {
  margin: 0; max-width: 620px;
  font-size: 14.5px; color: var(--text-dim); line-height: 1.55;
}
/* One scroll column: giveaway + hero + actions all scroll together (nothing pinned). */
.earnings-scroll {
  width: 100%; max-width: 760px; margin: 0 auto;
  padding: 0 24px 96px;
}
.earnings-jackpot-slot { margin: 12px 0 16px; }
.earnings-hero-slot { margin-bottom: 6px; }

/* Mobile: compact pinned top bar (title beside the hamburger); everything below scrolls
   together in one region, the tabs sticking just under the top bar. */
@media (max-width: 720px) {
  .layout.earnings-active #earnings-view { display: flex; flex-direction: column; overflow: hidden; }
  .earnings-screen { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .earnings-topbar {
    flex: 0 0 auto;
    display: flex; align-items: center;
    max-width: none; margin: 0;
    min-height: calc(56px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) 16px 0 68px; /* left pad clears the wider hamburger */
    background: var(--bg);
  }
  .earnings-topbar-title { margin: 0; font-size: 18px; font-weight: 600; line-height: 1; }
  .earnings-subtitle { display: none; }
  .earnings-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; max-width: none; padding: 0 16px 96px; }
}

.earnings-overview {
  background: var(--bg-elev-1);
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  margin-bottom: 12px;
}
.earnings-overview-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.earnings-overview-label {
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--text-dim);
}
.earnings-overview-expiry {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--text-dim);
}
.earnings-overview-expiry .material-symbols-rounded { font-size: 16px; }
.earnings-pool {
  font-family: var(--font-display);
  font-size: 42px; font-weight: 700; line-height: 1.05; margin-top: 10px;
  background: var(--grad-h); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.earnings-progress { margin-top: 18px; }
.earnings-progress-track {
  height: 10px; border-radius: 999px; background: var(--bg-elev-3); overflow: hidden;
}
.earnings-progress-bar {
  height: 100%; width: 0; border-radius: 999px; background: var(--grad-h);
  transition: width 0.7s var(--ease);
}
.earnings-progress-meta {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-top: 9px; font-size: 12.5px; color: var(--text-dim);
}
.earnings-progress-meta strong { color: var(--text); font-weight: 600; }

/* Keep all 4 stat tiles on one line (override the shared 2×2 wrap rule); the
   value font scales down on narrow phones so "$3,847.55" still fits at 4-up. */
.earn-dash.earnings-dash { flex-wrap: nowrap; gap: 8px; }
.earnings-dash .earn-tile { flex: 1 1 0; min-width: 0; padding: 15px 8px; }
.earnings-dash .earn-tile-value {
  font-size: clamp(14px, 4.2vw, 22px);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.earnings-dash .earn-tile-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* "How yield works" primer — a foldable card directly under the yield dashboard. */
.yield-how {
  margin: 10px 0 4px; border-radius: var(--radius-lg);
  border: 1px solid var(--border); background: var(--surface-2, rgba(255, 255, 255, 0.03));
}
.yield-how-summary {
  display: block; cursor: pointer; list-style: none;
  padding: 12px 14px; font-size: 13.5px; font-weight: 700; color: var(--text);
}
.yield-how-summary::-webkit-details-marker { display: none; }
.yield-how-bar { display: flex; align-items: center; gap: 8px; }
.yield-how-title { flex: 1 1 auto; min-width: 0; text-align: left; }
.yield-how-summary .material-symbols-rounded { font-size: 18px; color: var(--accent); }
/* Chevron rotates when open. */
.yield-how-summary .material-symbols-rounded:last-child { color: var(--text-dim); transition: transform 0.18s var(--ease); }
.yield-how[open] .yield-how-summary .material-symbols-rounded:last-child { transform: rotate(180deg); }
.yield-how-list { list-style: none; margin: 0; padding: 0 14px 12px; display: flex; flex-direction: column; gap: 10px; }
.yield-how-row { display: flex; align-items: flex-start; gap: 10px; font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }
.yield-how-row .material-symbols-rounded { font-size: 17px; color: var(--accent); flex: none; margin-top: 1px; }

/* Leaderboard amount (reuses .earn-rank-row layout, prominent value column). */
.earnings-rank-amount {
  flex: none; font-size: 14px; font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Detail list rows (yield / referral). */
.earnings-list { display: flex; flex-direction: column; gap: 4px; }
.earnings-row {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; border-radius: var(--radius-sm); background: var(--bg-elev-1);
}
.earnings-row-main { flex: 1 1 auto; min-width: 0; }
.earnings-row-title {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 14px; font-weight: 500; color: var(--text);
}
.earnings-row-sub { font-size: 12px; color: var(--text-faint); margin-top: 3px; }
.earnings-type {
  font-size: 11px; font-weight: 600; color: var(--text-dim);
  background: var(--bg-elev-2); padding: 2px 8px; border-radius: 999px;
}
.earnings-row-amount {
  font-size: 14px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums; text-align: right;
}
.earnings-row-amount.is-pos { color: var(--ok); }
.earnings-row-date { font-size: 11.5px; color: var(--text-faint); text-align: right; margin-top: 3px; }

/* ── Store screen (store.ui.ts) ────────────────────────────────────────────
   Active-agent balance strip + two bundle grids (Power, Credits). Reuses the
   .sched-center scaffold and .skills-action button. */
/* Store header: the title and the wallet HUD share ONE row (so the HUD is always
   level with "Store"), with the subtitle on the line below. On mobile the row fills
   the sticky bar and centers with the floating hamburger. */
.store-intro-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex: 1 1 auto; min-width: 0; }
.store-intro-row .sched-headline { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Wallet HUD: Power + Credits grouped in one bordered pill. */
.store-wallet {
  flex: none; display: inline-flex; align-items: center; gap: 10px;
  padding: 6px 12px; border-radius: 999px;
  background: var(--bg-elev-1); border: 1px solid var(--border);
}
.store-wallet .cur { font-size: 14px; font-weight: 600; color: var(--text); }
.store-wallet-sep { width: 1px; height: 14px; background: var(--border-strong); }

.store-balance {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  background: var(--bg-elev-1); border-radius: var(--radius); padding: 14px 18px; margin: 4px 0 22px;
}
.store-balance-agent { font-size: 14px; font-weight: 600; color: var(--text); }
.store-balance-pills { display: flex; gap: 22px; }
.store-bal { display: flex; align-items: center; gap: 9px; }
.store-bal .material-symbols-rounded { font-size: 22px; color: var(--accent); }
.store-bal-value { font-size: 17px; font-weight: 700; color: var(--text); line-height: 1.1; font-variant-numeric: tabular-nums; }
.store-bal-label { font-size: 11.5px; color: var(--text-faint); }

.store-section { margin-top: 26px; }
.store-section-title {
  font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-dim); margin: 0 0 4px;
}
.store-section-hint { font-size: 12.5px; color: var(--text-faint); margin: 0 0 14px; line-height: 1.5; }

.store-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
.store-card {
  position: relative;
  display: flex; flex-direction: column; gap: 8px;
  background: var(--bg-elev-1); border-radius: var(--radius); padding: 16px;
}
/* Best-value card: accent ring + a faint accent wash so the promotion reads at a glance. */
.store-card.is-best {
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev-1));
}
.store-badge {
  position: absolute; top: 12px; right: 12px;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.3px; color: var(--on-accent);
  background: var(--accent); border-radius: 999px; padding: 2px 9px; white-space: nowrap;
}
.store-card-name { font-size: 13px; font-weight: 600; color: var(--text-dim); }
/* Reserve room under the absolute "Best value" badge so a long name can't slide beneath it. */
.store-card.is-best .store-card-name { max-width: calc(100% - 88px); }
/* Per-bundle discount / bonus chip — its own line (never crowds the price+Buy row), nowrap. */
.store-card-save {
  align-self: flex-start;
  font-size: 11px; font-weight: 700; white-space: nowrap; line-height: 1.3;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border-radius: 999px; padding: 2px 9px;
}
.store-card-save.is-best { color: var(--on-accent); background: var(--accent); }
.store-card-amount { display: flex; align-items: baseline; gap: 6px; }
.store-card-amount .material-symbols-rounded { font-size: 22px; color: var(--accent); align-self: center; }
.store-card-amount > span:nth-of-type(1) { font-size: 26px; font-weight: 700; color: var(--text); line-height: 1; font-variant-numeric: tabular-nums; }
.store-card-unit { font-size: 13px; font-weight: 600; color: var(--text-faint); }
.store-card-sub { font-size: 12px; color: var(--text-faint); }
/* Price ABOVE a full-width button (two lines) — in the narrow 2-column grid a
   side-by-side row squeezes/misaligns the button (esp. with CJK labels). */
.store-card-foot { display: flex; flex-direction: column; align-items: stretch; gap: 10px; margin-top: auto; padding-top: 6px; }
.store-card-price { font-size: 16px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
/* Price shown as the locked-earnings chip: shrink to its content, don't stretch to the
   card-foot's full width (the foot is a stretch column). */
.store-card-foot .earn-lock-chip { align-self: flex-start; }
.store-card-cost { display: inline-flex; align-items: center; gap: 3px; }
.store-card-cost .material-symbols-rounded { font-size: 17px; color: var(--accent); }
.store-buy { width: 100%; align-self: stretch; padding: 11px 18px; min-height: 44px; }

/* ── Top-up modal (topup.ui.ts) — one job: allocate Credits pool → agent.
   Gemini language: a centered balance hero, quick-amount chips, one primary
   action. Reuses .settings-overlay/.settings-panel + .buy-chip + .auth-btn. ── */
.settings-panel.topup-panel { width: min(440px, 100%); }
.topup-body { padding: 8px 22px 24px; }

/* Hero: the pool balance, in the app's balance-card treatment (mirrors .wallet-hero). */
.topup-hero {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 28px 20px; margin: 4px 0 22px;
  border-radius: var(--radius-hero);
  background:
    radial-gradient(125% 80% at 50% -10%, color-mix(in srgb, var(--grad-b) 16%, transparent), transparent 62%),
    var(--bg-elev-1);
  text-align: center;
}
.topup-hero-label {
  font-family: var(--font-display);
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.1px;
  color: var(--text-faint);
}
.topup-hero-num { display: inline-flex; align-items: center; gap: 8px; }
.topup-hero-num .cur-icon { font-size: 32px; color: var(--accent); }
.topup-hero-num .cur-val {
  font-family: var(--font-display);
  font-size: 46px; font-weight: 500; line-height: 1.02; letter-spacing: -1.4px;
  font-variant-numeric: tabular-nums; color: var(--accent);
}

.topup-label { font-size: 13px; font-weight: 600; color: var(--text-dim); margin: 0 0 10px; }
.topup-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.topup-input {
  width: 100%; box-sizing: border-box; padding: 12px 14px; margin-bottom: 16px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--bg-elev-1); color: var(--text);
  font-size: 16px; font-variant-numeric: tabular-nums;
}
.topup-input:focus { outline: none; border-color: var(--border-strong); }
.topup-primary { width: 100%; }
.topup-foot { text-align: center; font-size: 12.5px; color: var(--text-faint); margin: 14px 0 0; }
.topup-empty { text-align: center; font-size: 14px; color: var(--text-dim); margin: 2px 0 16px; }

.topup-note { padding: 14px 2px 0; font-size: 12.5px; color: var(--text-dim); line-height: 1.5; text-align: center; }
.topup-note.is-error { color: var(--err); }

/* ── Wallet screen (portfolio · receive · send) — Gemini-style ─────────────── */
/* Fill the .sched-center column so the hero card / actions / asset rows share the
   exact left+right edges of the headline + subtitle (no indented, off-balance card). */
.wallet-body { width: 100%; margin: 12px 0 0; }

/* Hero: the headline number rendered in Gemini's signature blue→purple→pink
   gradient (Google Sans Flex display face), on a softly gradient-lit card. */
.wallet-hero {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 36px 20px 30px; margin: 6px 0 22px;
  border-radius: var(--radius-hero);
  background:
    radial-gradient(125% 80% at 50% -10%, color-mix(in srgb, var(--grad-b) 16%, transparent), transparent 62%),
    var(--bg-elev-1);
  text-align: center;
}
.wallet-hero-label {
  font-family: var(--font-display);
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.1px;
  color: var(--text-faint);
}
.wallet-hero-total {
  font-family: var(--font-display);
  font-size: 56px; font-weight: 500; line-height: 1.02; letter-spacing: -1.6px;
  font-variant-numeric: tabular-nums;
  /* Mono accent: white on dark, black on light (matches the app's greeting). */
  color: var(--accent);
}
.wallet-address {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 4px; min-height: 40px;
  background: var(--bg-elev-2); border: none; border-radius: 999px; padding: 8px 16px;
  color: var(--text-dim); font: inherit; font-size: 13px; cursor: pointer;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.wallet-address:hover { color: var(--text); background: var(--bg-elev-3); }
.wallet-address .material-symbols-rounded { font-size: 16px; }
.wallet-degraded { font-size: 12px; color: var(--warn); margin-top: 6px; }
/* Network badge — quiet/neutral on mainnet, a loud warning on any test network. */
.wallet-net {
  display: inline-flex; align-items: center; gap: 5px; align-self: center; margin-top: 10px;
  padding: 3px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 600; letter-spacing: 0.2px;
  background: var(--bg-elev-2); color: var(--text-faint);
}
.wallet-net.is-test {
  background: color-mix(in srgb, var(--warn) 18%, var(--bg-elev-2)); color: var(--warn);
}
.wallet-net .material-symbols-rounded { font-size: 15px; }
/* Receive-screen test-network warning — sits above the address/QR. */
.wallet-receive-warn {
  display: flex; align-items: flex-start; gap: 8px; margin: 0 0 12px;
  padding: 11px 13px; border-radius: var(--radius-md); line-height: 1.5; font-size: 13px;
  background: color-mix(in srgb, var(--warn) 14%, var(--bg-elev-1)); color: var(--text);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
}
.wallet-receive-warn .material-symbols-rounded { font-size: 19px; color: var(--warn); flex: none; }

/* Manual refresh — a subtle icon button in the hero's top-right corner. */
.wallet-refresh {
  position: absolute; top: 14px; right: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
  background: transparent; color: var(--text-faint);
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.wallet-refresh:hover { background: var(--bg-elev-2); color: var(--text); }
.wallet-refresh:disabled { cursor: default; }
.wallet-refresh .material-symbols-rounded { font-size: 20px; }
/* Earnings shortcut pinned to the balance card (top-left, mirrors refresh top-right). */
.wallet-hero-earn {
  position: absolute; top: 14px; left: 14px;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 11px 6px 9px; border-radius: 999px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 35%, transparent);
  color: var(--text-dim);
  font: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.wallet-hero-earn:hover { background: var(--bg-elev-2); color: var(--text); }
.wallet-hero-earn .material-symbols-rounded { font-size: 16px; }
.wallet-refresh.is-spinning .material-symbols-rounded { animation: wallet-spin 0.8s linear infinite; }
@keyframes wallet-spin { to { transform: rotate(360deg); } }

/* Receive / Send — Gemini tonal pills. */
.wallet-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 28px; }
.wallet-action {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 15px 16px; border-radius: 999px; border: none; cursor: pointer;
  background: var(--bg-elev-1); color: var(--text); font: inherit; font-size: 15px; font-weight: 600;
  transition: background 0.15s var(--ease), transform 0.15s var(--ease);
}
.wallet-action:hover { background: var(--bg-elev-2); }
.wallet-action:active { transform: scale(0.985); }
.wallet-action .material-symbols-rounded { font-size: 20px; color: var(--text-dim); }

/* Asset list — Gemini list rows with a tonal hover. */
.wallet-tokens { display: flex; flex-direction: column; margin: 0 -8px; }
.wallet-token {
  display: flex; align-items: center; gap: 13px; padding: 12px 12px; border-radius: var(--radius);
  transition: background 0.15s var(--ease);
}
.wallet-token:hover { background: var(--bg-elev-1); }
.wallet-token-logo { width: 38px; height: 38px; border-radius: 50%; flex: none; object-fit: cover; background: var(--bg-elev-2); }
.wallet-token-logo-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 600; color: var(--text-dim);
}
.wallet-token-main { flex: 1 1 auto; min-width: 0; }
.wallet-token-symbol { font-size: 15px; font-weight: 600; color: var(--text); }
.wallet-token-name { font-size: 12.5px; color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wallet-token-right { text-align: right; flex: none; }
.wallet-token-usd { font-size: 15px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.wallet-token-bal { font-size: 12.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.wallet-token-empty { font-size: 13.5px; color: var(--text-faint); padding: 16px 12px; }
.wallet-empty { color: var(--text-faint); }
.wallet-spam-toggle {
  background: none; border: none; color: var(--text-dim); font: inherit; font-size: 13px;
  cursor: pointer; padding: 12px; margin: 4px -8px 0; text-align: left; border-radius: var(--radius-sm);
}
.wallet-spam-toggle:hover { color: var(--text); background: var(--bg-elev-1); }

/* Activity (in/out history) — rows are explorer links; incoming amounts are green,
   settled-withdrawal arrivals get an accent highlight. */
.wallet-tabs { margin-top: 22px; }
.wallet-tab-content { min-height: 80px; }
.wallet-activity { display: flex; flex-direction: column; margin: 0 -8px; }
.wallet-act-row {
  display: flex; align-items: center; gap: 13px; padding: 11px 12px; border-radius: var(--radius);
  text-decoration: none; color: inherit; transition: background 0.15s var(--ease);
}
.wallet-act-row:hover { background: var(--bg-elev-1); }
.wallet-act-icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 36px; height: 36px; border-radius: 50%; overflow: hidden;
  background: var(--bg-elev-2); color: var(--text-dim); font-size: 19px;
}
.wallet-act-main { flex: 1 1 auto; min-width: 0; }
.wallet-act-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 14.5px; font-weight: 600; color: var(--text);
}
.wallet-act-badge {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
  color: var(--ok); background: color-mix(in srgb, var(--ok) 16%, transparent);
  padding: 2px 7px; border-radius: 999px;
}
.wallet-act-badge.is-pending { color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); }
.wallet-act-badge.is-failed { color: var(--danger); background: color-mix(in srgb, var(--danger) 16%, transparent); }
/* Activity subcategory chips — All | Withdrawals. */
.wallet-act-filters { display: flex; gap: 6px; margin: 2px 0 10px; }
.wallet-act-filter {
  border: 1px solid var(--border); background: transparent; color: var(--text-dim);
  font: inherit; font-size: 12px; font-weight: 600;
  padding: 4px 12px; border-radius: 999px; cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.wallet-act-filter:hover { color: var(--text); background: var(--bg-elev-1); }
.wallet-act-filter.is-active {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.wallet-act-sub { font-size: 12.5px; color: var(--text-faint); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wallet-act-right { text-align: right; flex: none; }
.wallet-act-amount { font-size: 14.5px; font-weight: 700; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.wallet-act-amount.is-pos { color: var(--ok); }
.wallet-act-date { font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
/* Settled earnings withdrawal landing — the highlighted "withdraw received" row. */
.wallet-act-row.is-withdrawal { background: color-mix(in srgb, var(--ok) 9%, transparent); }
.wallet-act-row.is-withdrawal:hover { background: color-mix(in srgb, var(--ok) 14%, transparent); }
.wallet-act-row.is-withdrawal .wallet-act-icon { background: color-mix(in srgb, var(--ok) 18%, var(--bg-elev-1)); color: var(--ok); }

/* Receive modal: QR + address. */
.wallet-qr { display: flex; align-items: center; justify-content: center; margin: 18px 0; }
.wallet-qr-img { width: 232px; height: 232px; image-rendering: pixelated; border-radius: var(--radius-lg); background: #fff; padding: 14px; box-shadow: var(--shadow-md); }
.wallet-qr-loading { font-size: 13px; color: var(--text-faint); padding: 46px 0; }
.wallet-receive-addr { background: var(--bg-elev-1); border-radius: var(--radius-md); padding: 13px 16px; margin-bottom: 12px; word-break: break-all; text-align: center; }
.wallet-receive-addr code { font-size: 13px; color: var(--text-dim); font-family: var(--font-mono); letter-spacing: 0.2px; }

/* Send modal: labeled fields (inputs reuse .auth-input / button reuses .auth-btn). */
.wallet-field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 16px; }
.wallet-field-label { font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.wallet-field-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.wallet-send-hint { font-size: 12px; color: var(--warn); margin: -6px 0 14px; min-height: 16px; line-height: 1.5; }

/* Custom asset dropdown — a dark, themed replacement for the native <select>, whose
   option popup renders in the OS (light) theme and can't be styled. */
.wallet-asset-wrap { position: relative; }
.wallet-asset-trigger {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--bg-elev-1); color: var(--text); font: inherit; font-size: 15px; cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.wallet-asset-trigger:hover { background: var(--bg-elev-2); }
.wallet-asset-label { flex: 1 1 auto; text-align: left; }
.wallet-asset-chev { font-size: 22px; color: var(--text-faint); }
.wallet-asset-logo { width: 24px; height: 24px; border-radius: 50%; flex: none; object-fit: cover; background: var(--bg-elev-2); font-size: 11px; }
.wallet-asset-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 6;
  padding: 6px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--bg-elev-2); box-shadow: var(--shadow-lg);
  max-height: 244px; overflow-y: auto;
}
.wallet-asset-option {
  width: 100%; display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; border-radius: var(--radius-sm); border: none; background: none; color: var(--text);
  font: inherit; cursor: pointer; text-align: left; transition: background 0.12s var(--ease);
}
.wallet-asset-option:hover { background: var(--bg-elev-3); }
.wallet-asset-opt-main { flex: 1 1 auto; min-width: 0; }
.wallet-asset-opt-sym { font-size: 14px; font-weight: 600; }
.wallet-asset-opt-bal { font-size: 12px; color: var(--text-faint); }
.wallet-asset-check { font-size: 19px; color: var(--accent); opacity: 0; flex: none; }
.wallet-asset-option.is-selected .wallet-asset-check { opacity: 1; }

/* Quick-amount chips. */
.wallet-amount-chips { display: flex; gap: 6px; }
.wallet-chip {
  display: inline-flex; align-items: center; justify-content: center; min-height: 40px;
  padding: 8px 16px; border-radius: 999px; border: none; cursor: pointer;
  background: var(--bg-elev-2); color: var(--text-dim); font: inherit; font-size: 12px; font-weight: 600;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.wallet-chip:hover { background: var(--bg-elev-3); color: var(--text); }

/* Wallet modals (Receive / Send) — shared Gemini chrome so both read identically:
   softer corners than the default settings panel, a borderless title, and even body
   padding so the QR / fields / address / buttons all align to the same gutters. */
/* overflow:visible so the asset dropdown menu can float past the body/panel edges
   (wallet modals are short, so they never need the panel's own scroll). */
.settings-panel.wallet-modal { border-radius: var(--radius-lg); overflow: visible; }
.settings-panel.wallet-modal .settings-titlebar { border-bottom: none; padding: 18px 22px 6px; }
.settings-panel.wallet-modal .settings-titlebar h2 { font-family: var(--font-display); font-weight: 500; }
.wallet-modal-body { padding: 4px 22px 22px; max-height: none; overflow: visible; }
.wallet-modal-intro { font-size: 14px; color: var(--text-dim); line-height: 1.55; margin: 2px 0 8px; }
/* The primary modal button (Copy address / Confirm send) is the same white pill in both. */
.wallet-modal-body .auth-btn { margin-top: 4px; }

/* ── Settings → Appearance: light/dark segmented toggle ────────────────────── */
.theme-toggle-row { display: flex; gap: 10px; margin-top: 8px; }
.theme-opt {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 20px; border-radius: var(--radius-md); cursor: pointer;
  background: var(--bg-elev-1); border: none; color: var(--text-dim);
  font: inherit; font-size: 14px; font-weight: 600;
  box-shadow: inset 0 0 0 1.5px transparent;
  transition: color 0.15s var(--ease), background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.theme-opt .material-symbols-rounded { font-size: 20px; }
.theme-opt:hover { color: var(--text); background: var(--bg-elev-2); }
.theme-opt.is-active {
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev-1));
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent);
}

/* Settings → Agent: inline input + action button rows. */
.settings-row { display: flex; gap: 8px; align-items: center; margin-top: 6px; }
.settings-row .settings-input { flex: 1 1 auto; min-width: 0; }
.settings-row .skills-action { flex: none; }

/* ── Settings → Agent panel (Gemini-style cards) ───────────────────────────── */
.agentset { display: flex; flex-direction: column; gap: 14px; max-width: 560px; }
.agentset-card { background: var(--bg-elev-1); border-radius: var(--radius-lg); padding: 20px 22px; }
.agentset-label {
  font-size: 12px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 14px;
}

/* advanced web search toggle */
.agentset-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.agentset-toggle-text { min-width: 0; }
.agentset-toggle-row .agentset-label { margin-bottom: 4px; }
.agentset-hint { font-size: 12.5px; line-height: 1.4; color: var(--text-faint); text-transform: none; letter-spacing: 0; font-weight: 400; }
.agentset-switch {
  appearance: none; -webkit-appearance: none; flex: 0 0 auto; position: relative;
  width: 44px; height: 26px; border-radius: 999px; cursor: pointer;
  background: var(--bg-elev-2); border: 1px solid var(--border); transition: background 0.15s var(--ease);
}
.agentset-switch::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--text-dim); transition: transform 0.15s var(--ease), background 0.15s var(--ease);
}
.agentset-switch:checked { background: var(--accent); border-color: var(--accent); }
.agentset-switch:checked::after { transform: translateX(18px); background: var(--bg); }
.agentset-switch:disabled { opacity: 0.6; cursor: default; }

.agentset-name-row { display: flex; gap: 10px; align-items: center; }
.agentset-name,
.agentset-input {
  flex: 1 1 auto; min-width: 0;
  background: var(--bg-elev-2); border: none; border-radius: var(--radius-sm);
  padding: 12px 14px; color: var(--text); font: inherit; outline: none;
  transition: box-shadow 0.15s var(--ease);
}
.agentset-name { font-size: 16px; font-weight: 600; }
.agentset-input { font-size: 14px; }
.agentset-name::placeholder,
.agentset-input::placeholder { color: var(--text-faint); }
.agentset-name:focus,
.agentset-input:focus { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent); }

/* identity card: label + the agent's star tier, side by side */
.agentset-label-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.agentset-label--inline { margin-bottom: 0; }

/* Yield section: star + price/yield/unlock tiles, a one-line takeaway, and an
   expandable "How yield works" explainer. Values come from GET /api/agents. */
.agentset-ystats { display: flex; flex-wrap: wrap; gap: 8px; }
.agentset-ystat {
  flex: 1 1 0; min-width: 110px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  background: var(--bg-elev-2); border-radius: var(--radius-sm); padding: 12px 8px;
}
.agentset-ystat-val { font-size: 16px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agentset-ystat-label { font-size: 11px; font-weight: 600; color: var(--text-dim); }
.agentset-ystat-sub { font-size: 10px; color: var(--text-faint); }
.agentset-yhint { font-size: 12px; color: var(--text-dim); margin-top: 12px; line-height: 1.45; }
.agentset-ydisc { margin-top: 12px; }
.agentset-ydisc-sum {
  font-size: 13px; font-weight: 600; color: var(--text-dim); cursor: pointer;
  list-style: none; display: inline-flex; align-items: center; gap: 6px; padding: 8px 0;
}
.agentset-ydisc-sum::-webkit-details-marker { display: none; }
.agentset-ydisc-chev { font-size: 18px; transition: transform 0.15s var(--ease); }
.agentset-ydisc[open] .agentset-ydisc-chev { transform: rotate(90deg); }
.agentset-ydisc-list { margin: 8px 0 8px; padding-left: 20px; display: flex; flex-direction: column; gap: 12px; }
.agentset-ydisc-list li { font-size: 13px; color: var(--text-dim); line-height: 1.65; }
.agentset-ydisc-list li::marker { color: var(--text-faint); }

.agentset-save,
.agentset-btn {
  flex: none; cursor: pointer; border: none; border-radius: 999px;
  background: var(--bg-elev-2); color: var(--text);
  font: inherit; font-size: 14px; font-weight: 600; padding: 11px 22px;
  transition: background 0.15s var(--ease), filter 0.15s var(--ease);
}
.agentset-save:hover,
.agentset-btn:hover { background: var(--bg-elev-3); }
.agentset-btn-primary { background: var(--accent); color: var(--on-accent); }
.agentset-btn-primary:hover { background: var(--accent); filter: brightness(1.06); }
.agentset-btn-sm { font-size: 12.5px; padding: 7px 16px; }
/* Star-up CTA: wears the brand gradient (the feature's color on the Earn screen). */
.agentset-btn-starup {
  color: #fff;
  background: linear-gradient(120deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c));
  box-shadow: 0 6px 18px -10px color-mix(in srgb, var(--grad-b) 90%, transparent);
}
.agentset-btn-starup:hover {
  background: linear-gradient(120deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c));
  filter: brightness(1.08);
}

/* Gemini-style settings list rows (Yield economics, Credits, Subscription). */
.agentset-rows { margin-top: 4px; }
.agentset-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 0; }
.agentset-row + .agentset-row,
.agentset-rowgroup + .agentset-rowgroup { border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
.agentset-row-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.agentset-row-label { font-size: 14px; font-weight: 500; color: var(--text); }
.agentset-row-sub { font-size: 12px; color: var(--text-faint); display: flex; align-items: center; gap: 3px; flex-wrap: wrap; }
.agentset-row-side { flex: none; display: flex; align-items: center; gap: 10px; }
.agentset-row-val { font-size: 14.5px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
.agentset-row-val.is-warn { color: var(--warn); }
.agentset-row-val.is-danger { color: var(--danger-text); }
/* Collapsed-by-default controls under a row (top-up field, month picker). [hidden]
   must beat the blocks' own display values. */
.agentset-subctl { display: flex; flex-direction: column; gap: 10px; padding: 2px 0 12px; }
.agentset-rowgroup > .agentset-field { margin: 2px 0 12px; }
.agentset-subctl[hidden], .agentset-field[hidden] { display: none; }
/* Upgrade block: x/y counter pill + card-set progress bar (earn-screen language). */
.agentset-upgrade { border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
.agentset-upgrade .uset-actions { display: flex; justify-content: flex-end; margin-top: 16px; }
.agentset-upgrade .agentset-hint { margin-top: 12px; }
.agentset-prog {
  flex: none; font-size: 12.5px; font-weight: 800; font-variant-numeric: tabular-nums;
  color: var(--text); background: var(--bg-elev-2); border-radius: 999px; padding: 5px 12px;
}
.agentset-progbar { height: 6px; border-radius: 999px; background: var(--bg-elev-2); overflow: hidden; margin: 1px 0 4px; }
.agentset-progbar-fill {
  height: 100%; border-radius: 999px; min-width: 6px;
  background: linear-gradient(90deg, var(--grad-a), var(--grad-b));
  transition: width 0.3s var(--ease);
}

.agentset-id-row { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.agentset-id {
  font-family: var(--font-mono); font-size: 12px; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.agentset-copy {
  flex: none; display: grid; place-items: center; cursor: pointer;
  background: transparent; border: none; color: var(--text-faint);
  padding: 4px; border-radius: var(--radius-tile); transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.agentset-copy:hover { background: var(--bg-elev-2); color: var(--text); }
.agentset-copy .material-symbols-rounded { font-size: 16px; }

.agentset-stat { display: flex; align-items: baseline; gap: 8px; }
.agentset-stat-value {
  font-family: var(--font-display); font-size: 32px; font-weight: 700; line-height: 1;
  color: var(--text); font-variant-numeric: tabular-nums;
}
.agentset-stat-unit { font-size: 14px; font-weight: 600; color: var(--text-faint); }
.agentset-meta { font-size: 12.5px; color: var(--text-faint); margin-top: 8px; }
.agentset-field { display: flex; gap: 10px; align-items: center; margin-top: 14px; }

.agentset-status { font-family: var(--font-display); font-size: 24px; font-weight: 600; color: var(--text); }
.agentset-status-expired { color: var(--danger-text); }
.agentset-status-none { color: var(--text-faint); }
.agentset-months { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px 0 16px; }
.agentset-chip {
  cursor: pointer; border: none; border-radius: 999px;
  background: var(--bg-elev-2); color: var(--text-dim);
  font: inherit; font-size: 13px; font-weight: 600; padding: 8px 16px;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.agentset-chip:hover { color: var(--text); }
.agentset-chip.is-active { color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, var(--bg-elev-2)); }
.agentset-buy-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.agentset-cost { font-size: 13px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* User settings panels in the same Gemini card style as the Agent panel. Scoped to
   the settings screen so admin/profile forms (which also use .settings-section) are
   untouched. Cards: International, Spending, Appearance, Self-improvement. */
#settings-view .settings-section {
  background: var(--bg-elev-1);
  border: none;
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  margin: 0 0 14px;
  max-width: 560px;
}
#settings-view .settings-section h3,
#settings-view .settings-label {
  margin: 0 0 14px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-dim);
}
/* Tonal (borderless) inputs — the Gemini field style. Shared by the Settings
   screen and the Schedules create/edit form (both use .settings-input/-textarea)
   so neither falls back to the legacy 1px-border + white-on-focus outline. */
#settings-view .settings-input,
#settings-view .settings-textarea,
#schedules-view .settings-input,
#schedules-view .settings-textarea {
  background: var(--bg-elev-2);
  border: none;
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  transition: box-shadow 0.15s var(--ease);
}
#settings-view .settings-input:focus,
#settings-view .settings-textarea:focus,
#schedules-view .settings-input:focus,
#schedules-view .settings-textarea:focus {
  border: none;
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent);
}
/* Language rows + theme toggle sit a touch below the card label. */
#settings-view .settings-lang-options,
#settings-view .theme-toggle-row { margin-top: 4px; }
#settings-view .settings-hint { margin-top: 10px; }

/* User settings cards (International / Spending / Appearance), Gemini style — same
   card language as the Agent panel, self-contained (no inherited .settings-* quirks). */
.uset { display: flex; flex-direction: column; gap: 14px; max-width: 560px; }
.uset-card { background: var(--bg-elev-1); border-radius: var(--radius-lg); padding: 20px 22px; }
/* Memory / Instructions / Persona editor: each textarea sits in its own uset-card section */
.uset-card > .settings-textarea { margin-top: 10px; background: var(--bg-elev-2); }
/* Memory graph view (docs/SECOND_BRAIN.md): 2D force-directed canvas of the topic wiki. */
.memory-graph-canvas {
  display: block; width: 100%; height: 320px; margin-top: 10px;
  background: var(--bg-elev-2); border: 1px solid var(--border); border-radius: 10px;
  touch-action: none; /* let pointer-drag work without the page panning */
}
.memory-graph-empty { margin-top: 10px; }
.memory-graph-legend {
  display: flex; flex-wrap: wrap; gap: 16px; margin-top: 10px;
  font-size: 11px; color: var(--text-faint);
}
.mg-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.mg-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); display: inline-block; }
.mg-line { width: 16px; height: 0; border-top: 2px solid var(--text-faint); display: inline-block; }
.mg-inferred { border-top-style: dashed; border-top-color: var(--accent); }
/* Brain screen — layout comes from the shared .sched-center/.sched-intro/.sched-headline/
   .sched-subtitle convention (see renderBrain); only the canvas + empty message are specific. */
.memory-graph-canvas.brain-canvas { height: min(62vh, 600px); margin-top: 16px; }
/* Positioned box the canvas + floating legend + empty-state share (see renderBrain).
   On mobile it goes full-bleed and the legend/detail overlay it. */
.brain-graph { position: relative; }
.brain-empty { margin: 40px 0; text-align: center; }
/* The sheet backdrop is mobile-only; inert on desktop. */
.brain-backdrop { display: none; }
/* Detail panel: the "open note" for a clicked topic node. Class-driven (not the
   `hidden` attr) so the mobile sheet can transition; on desktop it's an inline card
   shown only once a node is opened. */
.brain-detail {
  display: none; margin-top: 14px; padding: 14px 16px;
  background: var(--bg-elev-2); border: 1px solid var(--border); border-radius: 12px;
}
.brain-detail.open { display: block; }
.brain-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.brain-detail-title { font-size: 16px; font-weight: 600; letter-spacing: 0.1px; }
.brain-detail-close {
  background: none; border: none; color: var(--text-dim); font-size: 13px; line-height: 1;
  cursor: pointer; padding: 4px 6px; border-radius: 6px;
}
.brain-detail-close:hover { color: var(--text); background: color-mix(in srgb, var(--text) 10%, transparent); }
/* Each topic note renders as its own block (a lead line / a sentence-per-line body /
   an arrow-timeline stepped list), separated by a hairline, so a long note reads as
   structure instead of a wall of text. */
.brain-detail-list { margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.brain-note { display: flex; flex-direction: column; gap: 7px; }
.brain-note + .brain-note { border-top: 1px solid var(--border); padding-top: 14px; }
.brain-note strong { color: var(--text); font-weight: 600; }
.brain-line { margin: 0; font-size: 14px; line-height: 1.6; color: var(--text-dim); }
.brain-steps { list-style: none; margin: 2px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.brain-step {
  position: relative; margin: 0; padding-left: 18px;
  font-size: 13px; line-height: 1.55; color: var(--text-dim);
}
.brain-step::before {
  content: ""; position: absolute; left: 2px; top: 6px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
}
/* Connector line down the timeline. */
.brain-step:not(:last-child)::after {
  content: ""; position: absolute; left: 4.5px; top: 13px; bottom: -10px; width: 1px; background: var(--border);
}
.brain-detail-muted { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--text-faint); }

/* Brain lenses + activity Stream (docs/BRAIN_UX.md P1+P3). The lens row reuses the
   Material-3/Gemini primary-tab primitives (.earn-subtabs.tabs-primary — labels on a
   divider rail, rounded indicator). The Brain screen's SIGNATURE is the brand
   (mascot-ring) gradient: it fills the active-lens indicator, rings "just learned"
   map nodes (canvas, memoryGraph.ts), and borders an expanded Stream receipt.
   Everything else stays the app's monochrome system. */
:root {
  --brand-a: #0036ff;
  --brand-b: #c600ff;
}
.brain-lenses.tabs-primary { margin-top: 4px; }
.brain-lenses .earn-subtab.is-active { color: var(--text); }
.brain-lenses .earn-subtab.is-active::after {
  background: linear-gradient(90deg, var(--brand-a), var(--brand-b));
}
/* Map status chip — floats over the graph like the legend. */
.brain-mapstat {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 2;
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  font-size: 11px; color: var(--text-dim); pointer-events: none;
  background: color-mix(in srgb, var(--bg-elev-2) 85%, transparent);
  border: 1px solid var(--border); border-radius: 999px; padding: 4px 13px;
}
.brain-mapstat-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-a), var(--brand-b));
}
.brain-stream { display: flex; flex-direction: column; gap: 8px; max-width: 640px; margin-top: 10px; }
/* Author `display:flex` defeats the hidden attribute (UA [hidden] rule loses) — the
   lens switcher and the expand/collapse bodies all rely on it. */
.brain-lens-view[hidden],
.brain-ev-body[hidden],
.brain-group-rows[hidden] { display: none; }
.brain-day {
  font-size: 11px; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); margin: 10px 2px 2px;
}
/* The permanent transparent border keeps the card from shifting 1px when the
   expanded (.is-open) gradient border swaps in. */
.brain-ev {
  background: var(--bg-elev); border: 1px solid transparent; border-radius: 14px;
  padding: 12px 14px; transition: background var(--ease) 120ms;
}
.brain-ev.is-open {
  background:
    linear-gradient(var(--bg-elev), var(--bg-elev)) padding-box,
    linear-gradient(135deg,
      color-mix(in srgb, var(--brand-a) 65%, transparent),
      color-mix(in srgb, var(--brand-b) 65%, transparent)) border-box;
}
.brain-ev-head {
  display: flex; align-items: flex-start; gap: 11px; width: 100%;
  background: none; border: none; padding: 0; font: inherit; color: inherit; text-align: left;
}
button.brain-ev-head { cursor: pointer; }
.brain-ev-head.is-link:hover .brain-ev-main b { color: var(--text); }
.brain-ev-go { color: var(--text-faint); font-size: 17px; line-height: 1; margin-top: 3px; }
.brain-ev-ico {
  flex: none; width: 30px; height: 30px; border-radius: 10px; background: var(--bg-elev-2);
  display: grid; place-items: center; font-size: 15px;
}
.brain-ev-main { flex: 1; min-width: 0; }
.brain-ev-main b { display: block; font-size: 14px; font-weight: 600; line-height: 1.4; }
.brain-ev-main small { color: var(--text-faint); font-size: 12px; }
.brain-kind {
  flex: none; font-size: 10.5px; color: var(--text-dim); background: var(--bg-elev-2);
  border-radius: 6px; padding: 2px 8px; margin-top: 4px;
}
.brain-ev.reverted .brain-ev-main b { text-decoration: line-through; opacity: 0.6; }
.brain-ev-body { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
.brain-ev-actions { display: flex; align-items: center; gap: 8px; }
.brain-ev-actions button {
  font: inherit; font-size: 12px; font-weight: 600; border: none; border-radius: 999px;
  padding: 6px 14px; cursor: pointer; background: var(--bg-elev-2); color: var(--text-dim);
}
.brain-ev-actions button:hover:not(:disabled) { color: var(--text); }
.brain-reverted-tag { font-size: 11px; color: var(--text-faint); }
.brain-commit { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--text-faint); }
.brain-diff {
  margin: 0; font-family: var(--font-mono); font-size: 11px; line-height: 1.55;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; overflow: auto; max-height: 260px; white-space: pre;
}
.brain-diff .diff-add { color: var(--ok); }
.brain-diff .diff-del { color: var(--danger); }
.brain-diff .diff-meta { color: var(--text-faint); }
/* A collapsed dream night's sub-entries, indented under the group head. */
.brain-group-rows {
  display: flex; flex-direction: column; gap: 6px; margin-top: 10px;
  border-left: 2px solid var(--border); padding-left: 10px;
}
.brain-group-rows .brain-ev { background: var(--bg-elev-2); }
.brain-group-rows .brain-ev .brain-ev-ico { background: var(--bg-elev-3); }

/* Brain on mobile: the graph goes full-bleed under the 56px title bar, the
   topic/linked/inferred legend floats over it as a widget, and tapping a node slides
   its note up as a bottom sheet (matching the app's other slide-up modals). The
   .sched-intro keeps its 24px-negating side margin, so we keep the center's side
   padding and break the graph out to full width instead of zeroing it. */
@media (max-width: 720px) {
  .layout .brain-center { padding-bottom: 0; }
  .brain-center .sched-intro { margin-bottom: 0; }
  .brain-graph {
    margin: 0 -24px; /* full-bleed past .sched-center's 24px side padding */
    /* 56px title bar + ~52px lens rail (docs/BRAIN_UX.md P1) above the graph. */
    height: calc(100vh - 108px - env(safe-area-inset-top));
    height: calc(100dvh - 108px - env(safe-area-inset-top));
  }
  .brain-lenses.tabs-primary { margin: 4px 0 8px; }
  /* The legend owns the top-right corner on mobile; the status chip sits at the bottom. */
  .brain-mapstat { top: auto; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
  /* Compound selectors beat the .memory-graph-canvas / .memory-graph-legend bases. */
  .brain-canvas.memory-graph-canvas {
    height: 100%; margin: 0; border: none; border-radius: 0; background: var(--bg);
  }
  .brain-legend.memory-graph-legend {
    position: absolute; top: 12px; right: 12px; z-index: 2; margin: 0;
    flex-direction: column; gap: 8px; padding: 9px 11px; border-radius: 12px;
    background: color-mix(in srgb, var(--bg-elev-2) 82%, transparent);
    border: 1px solid var(--border);
    -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  }
  .brain-empty {
    position: absolute; inset: 0; margin: 0; padding: 0 32px;
    display: flex; align-items: center; justify-content: center;
  }
  /* Tap-to-dismiss backdrop. */
  .brain-backdrop {
    display: block; position: fixed; inset: 0; z-index: 50;
    background: color-mix(in srgb, #000 45%, transparent);
    opacity: 0; visibility: hidden;
    transition: opacity 200ms var(--ease), visibility 200ms var(--ease);
  }
  .brain-backdrop.visible { opacity: 1; visibility: visible; }
  /* The note as a bottom sheet: always laid out (display:block) but slid off-screen
     until .open, so it can animate in. */
  .brain-detail {
    display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 51;
    margin: 0; max-height: 72vh; overflow-y: auto;
    padding: 18px 18px calc(18px + env(safe-area-inset-bottom));
    border: 1px solid var(--border); border-bottom: none;
    border-radius: 18px 18px 0 0;
    transform: translateY(101%); visibility: hidden;
    transition: transform 260ms var(--ease), visibility 260ms var(--ease);
  }
  .brain-detail.open { transform: translateY(0); visibility: visible; }
  /* Grabber affordance. */
  .brain-detail::before {
    content: ""; display: block; width: 36px; height: 4px; border-radius: 999px;
    background: var(--border); margin: -4px auto 12px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .brain-backdrop, .brain-detail { transition: none; }
}

/* ── Brain v2 — fullscreen immersive brain-scape (docs/BRAIN_UX.md §5) ─────────
   The Map IS the Brain screen: a fixed, edge-to-edge constellation with floating
   glass chrome (no nav bar over the map — owner rule). Follows the app theme (owner
   2026-07-14, "Light constellation"): dark = deep space; light overrides are at the
   end of this block + the canvas recolors itself (brainScape.ts).
   z-index 25: under the hamburger FAB (40) and the nav drawer; the hidden column
   layout sits behind. */
.brain-immerse { position: fixed; inset: 0; z-index: 25; overflow: hidden; background: #0b0a10; }
.brain-scape { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; }
/* Bottom HUD stack — replay pill centered ON TOP of the status pane, caption at the
   very bottom (owner). Normal flow inside, so nothing overlaps at any card height. */
.brain-hud-stack {
  position: absolute; left: 16px; right: 16px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 14px);
  z-index: 4; display: flex; flex-direction: column; align-items: center; gap: 10px;
  pointer-events: none;
}
.brain-hud-stack > * { pointer-events: auto; }
.brain-hud-bottom {
  width: 100%; text-align: center; pointer-events: none;
  font-size: 11px; color: rgba(200, 190, 230, 0.5);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Lens switcher — bare floating TEXT at top center (owner rule: NOT a nav bar,
   no capsule, no background — like the reference HUD's "CORE · IDLE" labels).
   The active lens is white with a thin gradient underline (the one sanctioned
   gradient use for the lens indicator); the inactive one is a faint whisper. */
.brain-dock {
  /* Below the hamburger row (FAB: top 12/safe+4, 48px tall) — the lens words must
     never share the app bar's line (owner). */
  position: absolute; top: calc(env(safe-area-inset-top, 0px) + 74px); left: 50%;
  /* width:max-content: an abs-positioned box at left:50% shrink-wraps to HALF the
     screen — with four dock words the CJK labels wrapped vertically on phones. */
  transform: translateX(-50%); z-index: 4; display: flex; gap: 30px;
  width: max-content; white-space: nowrap;
}
@media (max-width: 480px) {
  .brain-dock { gap: 20px; }
}
.brain-dock button {
  position: relative; border: none; background: none; cursor: pointer;
  font-weight: 600; font-size: 13px; line-height: 1; padding: 6px 2px 9px;
  color: rgba(190, 185, 215, 0.5); letter-spacing: 0.02em;
  transition: color 0.2s var(--ease);
}
.brain-dock button:hover { color: rgba(226, 220, 245, 0.85); }
.brain-dock button.is-active { color: #fff; }
.brain-dock button.is-active::after {
  content: ""; position: absolute; left: 15%; right: 15%; bottom: 0; height: 2px;
  border-radius: 2px; background: linear-gradient(90deg, var(--brand-a), var(--brand-b));
}
/* The one-sentence status card — floats above the dock, recedes once read. */
.brain-status {
  align-self: flex-start;
  max-width: 330px; text-align: left; cursor: pointer;
  border: none; border-radius: 24px; padding: 14px 16px 12px; color: #e8e8ea;
  font: inherit; font-family: inherit;
  background: color-mix(in srgb, #15141b 62%, transparent);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  animation: brain-hud-recede 1.2s ease 4.5s forwards;
}
.brain-status:hover, .brain-status:focus-visible { animation: none; opacity: 1; }
@keyframes brain-hud-recede { to { opacity: 0.45; } }
.brain-status-s1 { font-size: 14.5px; line-height: 1.45; font-weight: 600; }
.brain-status-s2 { margin-top: 6px; font-size: 12px; line-height: 1.6; color: #a9a9b4; }
.brain-status-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px;
  vertical-align: 1px; background: linear-gradient(135deg, var(--brand-a), var(--brand-b));
}
.brain-status-s3 {
  display: flex; align-items: center; gap: 10px; margin-top: 10px; padding-top: 9px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.brain-status-s3 span { font-size: 11.5px; color: #8f8f9a; }
.brain-status-s3 b { color: #cfcfd8; font-weight: 650; }
.brain-pace { display: flex; align-items: flex-end; gap: 2.5px; height: 18px; }
.brain-pace i { width: 5px; border-radius: 2px; background: #3a3a42; min-height: 3px; }
.brain-pace i.hi { background: linear-gradient(180deg, var(--brand-b), var(--brand-a)); }
/* Day zero — the expectant sky (passive: no buttons, no asks). */
.brain-newborn { position: absolute; left: 24px; right: 24px; top: 36%; text-align: center; pointer-events: none; color: #e8e8ea; }
.brain-newborn b { display: block; font-size: 16px; font-weight: 650; margin-bottom: 8px; }
.brain-newborn p { margin: 0 auto; max-width: 300px; font-size: 13px; line-height: 1.6; color: rgba(210, 205, 230, 0.75); }
/* Stream + Search lenses — overlays over the DIMMED scape (owner, 2026-07-13): no
   grey glass, just darkness; the components float on the still-live sky. Anchored
   rows (widgets, tabs, input, footer) with one dedicated scroller in between. */
.brain-stream-panel {
  position: absolute; inset: 0; z-index: 3; overflow: hidden;
  display: flex; flex-direction: column;
  background: color-mix(in srgb, #05050a 62%, transparent);
  padding: calc(env(safe-area-inset-top, 0px) + 132px) 16px 0;
}
.brain-stream-panel[hidden] { display: none; }
/* Anchored status widgets (owner: "more like widgets"). */
.brain-widgets {
  flex: none; width: 100%; max-width: 640px; margin: 0 auto 10px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px;
}
.brain-widgets:empty { display: none; }
.brain-widget { border-radius: 20px; background: var(--bg-elev); padding: 12px 14px; min-width: 0; }
.brain-widget-title {
  font-weight: 600; font-size: 11px; letter-spacing: 0.6px; text-transform: uppercase;
  color: var(--text-faint); margin-bottom: 8px;
}
.brain-widget-body {
  margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--text-dim); min-width: 0;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
}
.brain-widget-stats { display: flex; gap: 18px; }
.brain-widget-stat { display: flex; flex-direction: column; gap: 1px; }
.brain-widget-stat b { font-size: 20px; font-weight: 650; line-height: 1.15; font-variant-numeric: tabular-nums; }
.brain-widget-stat small { font-size: 11px; color: var(--text-faint); }
.brain-widget .brain-recap-btn { margin-top: 8px; }
/* Anchored day tabs — horizontal scroll; only the records below scroll vertically. */
.brain-daytabs {
  flex: none; width: 100%; max-width: 640px; margin: 0 auto; padding: 2px 0 10px;
  display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none;
}
.brain-daytabs::-webkit-scrollbar { display: none; }
.brain-daytab {
  flex: none; border: none; border-radius: 999px; background: var(--bg-elev); cursor: pointer;
  padding: 7px 14px; font: inherit; font-size: 12px; font-weight: 600; color: var(--text-dim);
}
.brain-daytab:hover { background: var(--bg-elev-1); }
.brain-daytab.is-active { background: var(--bg-elev-2); color: var(--text); }
.brain-stream-scroll {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 4px 0 calc(env(safe-area-inset-bottom, 0px) + 40px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 24px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 24px);
}
.brain-stream-panel .brain-stream { max-width: 640px; margin: 0 auto; }
.brain-recap-btn {
  font: inherit; font-size: 12px; font-weight: 600; border: none; border-radius: 999px;
  padding: 8px 16px; cursor: pointer; background: var(--bg-elev-3); color: var(--text);
}
.brain-prov { margin-top: 12px; }
.brain-ev-diffhost { margin-top: 10px; }
.brain-ev-diffhost:empty { display: none; }
.brain-about-note { margin-top: 4px; font-size: 12px; }
/* System objects (Skills / Rules / Persona / Export) — quiet pills at the bottom of
   the owner-portrait sheet (owner: the search lens shows the input and nothing else). */
.brain-about-utils { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
/* Sheets inside the immersive shell are bottom sheets on EVERY size (they slide
   over the scape); these two-class rules out-rank both the desktop base and the
   mobile media-query versions above. */
.brain-immerse .brain-backdrop {
  display: block; position: fixed; inset: 0; z-index: 50;
  background: color-mix(in srgb, #000 45%, transparent);
  opacity: 0; visibility: hidden;
  transition: opacity 200ms var(--ease), visibility 200ms var(--ease);
}
.brain-immerse .brain-backdrop.visible { opacity: 1; visibility: visible; }
.brain-immerse .brain-detail {
  display: block; position: fixed; left: 50%; bottom: 0; z-index: 51;
  width: 100%; max-width: 640px; transform: translate(-50%, 101%);
  margin: 0; max-height: 72vh; max-height: 72dvh; overflow-y: auto;
  padding: 18px 18px calc(18px + env(safe-area-inset-bottom, 0px));
  background: var(--bg-elev); border: none; border-radius: 24px 24px 0 0;
  visibility: hidden;
  transition: transform 260ms var(--ease), visibility 260ms var(--ease);
}
.brain-immerse .brain-detail.open { transform: translate(-50%, 0); visibility: visible; }
.brain-immerse .brain-detail::before {
  content: ""; display: block; width: 36px; height: 4px; border-radius: 999px;
  background: var(--border); margin: -4px auto 12px;
}
/* Brain Search lens (owner, 2026-07-13) — Google grammar over the dimmed sky:
   ONE clear input, a compact dropdown of top hits while typing, footer pills at
   the bottom edge. The dim + flex-column shell comes from .brain-stream-panel. */
.brain-lib-filterwrap { flex: none; width: 100%; max-width: 640px; margin: 4px auto 0; }
.brain-lib-filter {
  width: 100%; border: none; border-radius: 999px; padding: 11px 18px;
  /* 16px is the iOS no-auto-zoom floor: Safari zooms the whole page when a focused
     input's font is smaller (owner hit this on phone). Google's box is 16px too. */
  background: var(--bg-elev-1); color: var(--text); font-size: 16px; line-height: 1.4; outline: none;
}
.brain-lib-filter:focus { background: var(--bg-elev-2); }
.brain-lib-filter::placeholder { color: var(--text-faint); }
/* The dropdown — top hits only; the sky spotlight holds the full result set. */
.brain-suggest {
  margin-top: 8px; border-radius: 20px; background: var(--bg-elev); padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
}
.brain-suggest[hidden] { display: none; }
.brain-suggest-row {
  display: flex; align-items: baseline; gap: 10px; min-width: 0; text-align: left; cursor: pointer;
  border: none; border-radius: 14px; background: transparent; padding: 10px 12px;
  font: inherit; color: var(--text);
}
.brain-suggest-row:hover { background: var(--bg-elev-1); }
.brain-suggest-row b { font-size: 13.5px; font-weight: 600; white-space: nowrap; }
.brain-suggest-row small {
  font-size: 11.5px; color: var(--text-faint);
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.brain-suggest-none { margin: 8px 12px; }
.brain-suggest-more { margin: 4px 12px 8px; font-size: 11px; color: var(--text-faint); }
.brain-lib-util {
  border: none; border-radius: 999px; background: var(--bg-elev); cursor: pointer;
  padding: 8px 14px; font: inherit; font-size: 12px; font-weight: 600; color: var(--text-dim);
}
.brain-lib-util:hover { background: var(--bg-elev-1); color: var(--text); }

/* Review bell (B3) — a dock word like the lenses, sitting right of Search
   (owner, 2026-07-13). The .brain-dock button rule styles the text; this only
   lays out the count badge beside it. */
.brain-bell { display: inline-flex; align-items: center; gap: 6px; }
.brain-bell-badge {
  min-width: 16px; height: 16px; border-radius: 999px; padding: 0 4px;
  display: inline-grid; place-items: center;
  font-weight: 700; font-size: 10px; background: var(--bg-elev-2); color: var(--text);
}

/* Section labels shared by the Library list head and the sheets. */
.brain-sec-title {
  margin: 10px 2px 2px; font-weight: 600; font-size: 11px; line-height: 1.4; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--text-faint);
}

/* Topic sheet: quiet properties line + connection rows with their reasons. */
.brain-topic-meta {
  margin: -4px 0 10px; font-weight: 500; font-size: 11px; line-height: 1.4; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--text-faint);
}
.brain-conn {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; cursor: pointer;
  border: none; border-radius: 14px; background: var(--bg-elev-1); padding: 10px 14px;
  font: inherit; color: var(--text); margin-top: 6px;
}
.brain-conn:hover { background: var(--bg-elev-2); }
.brain-conn-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.brain-conn-main b { font-size: 14px; font-weight: 600; }
.brain-conn-main small { font-size: 12px; line-height: 1.45; color: var(--text-faint); }
.brain-conn-type {
  margin-left: auto; flex: none; font-weight: 600; font-size: 11px; line-height: 1; letter-spacing: 0.3px;
  color: var(--text-muted); background: var(--bg-elev-2); border-radius: 999px; padding: 5px 9px;
}

/* Suggested connections (C3) — approve/dismiss rows inside a recap-style card. */
.brain-sugg {
  display: flex; align-items: center; gap: 10px;
  border-radius: 14px; background: var(--bg-elev-1); padding: 10px 12px; margin-top: 8px;
}
.brain-sugg-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.brain-sugg-main b { font-size: 13.5px; font-weight: 600; }
.brain-sugg-main small { font-size: 12px; line-height: 1.45; color: var(--text-faint); }
.brain-sugg-acts { margin-left: auto; display: flex; gap: 6px; flex: none; }
.brain-sugg-btn {
  border: none; border-radius: 999px; padding: 7px 12px; cursor: pointer;
  font-weight: 600; font-size: 12px; line-height: 1;
  background: var(--bg-elev-2); color: var(--text-muted);
}
.brain-sugg-btn.is-yes { background: var(--text); color: var(--bg); }
.brain-sugg-btn:hover:not(:disabled) { filter: brightness(1.1); }
.brain-sugg-btn:disabled { opacity: 0.5; cursor: default; }

/* Growth replay (B4) — quiet centered pill riding the bottom HUD stack. */
.brain-replay {
  border: none; background: none; cursor: pointer; padding: 4px 8px;
  font-weight: 600; font-size: 12px; line-height: 1; color: var(--text-faint);
}
.brain-replay:hover { color: var(--text); }

/* Owner-curated tag (B5) — quiet trust signal on hand-edited notes. */
.brain-curated-tag { margin-left: auto; font-size: 11px; color: var(--text-faint); }

/* Forgotten (superseded) notes — struck through, quiet; Delete forever lives only here. */
.brain-note-forgotten { opacity: 0.75; }
.brain-forgot-text { text-decoration: line-through; color: var(--text-faint); }
.brain-forgot-tag { font-size: 11px; color: var(--text-faint); }

/* Per-note actions + inline editor in the topic sheet. */
.brain-note-acts { display: flex; gap: 12px; margin-top: 7px; }
.brain-note-btn {
  border: none; background: none; padding: 0; cursor: pointer;
  font-weight: 600; font-size: 12px; line-height: 1.4; color: var(--text-faint);
}
.brain-note-btn:hover:not(:disabled) { color: var(--text); }
.brain-note-btn.is-save { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.brain-note-btn.is-danger:hover:not(:disabled) { color: var(--danger); }
.brain-note-btn:disabled { opacity: 0.5; cursor: default; }
.brain-note-edit {
  width: 100%; min-height: 64px; resize: vertical; border: none; border-radius: 12px;
  background: var(--bg-elev-2); color: var(--text); padding: 10px 12px;
  font-size: 14px; line-height: 1.5; outline: none;
}
.brain-doc { white-space: pre-wrap; font-size: 12.5px; }

/* Chat memory receipt chips (docs/BRAIN_UX.md §5 B2) — tonal, quiet, inline. */
.chat-memchip-row { display: flex; justify-content: center; padding: 2px 0; }
.chat-memchip-row.is-link { cursor: pointer; }
.chat-memchip {
  display: inline-flex; align-items: center; gap: 4px; max-width: 86%;
  border-radius: 999px; padding: 7px 14px; font-size: 12px; line-height: 1.4;
  background: var(--bg-elev-1); color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-memchip-row.is-link:hover .chat-memchip { background: var(--bg-elev-2); color: var(--text); }
.chat-memchip-undo {
  border: none; background: none; padding: 0; margin: 0; cursor: pointer;
  font-weight: 600; font-size: 12px; line-height: 1.4; color: var(--text);
  text-decoration: underline; text-underline-offset: 2px;
}
.chat-memchip-undo:disabled { opacity: 0.5; cursor: default; }

/* Interrupted-reply marker: same quiet chip shape, warn-tinted so a dead-end reply
 * is explained in place (the error banner is transient). Wraps on phones — the
 * sentence + Retry must never ellipsize into an unexplained "回复中…". */
.chat-interrupt-chip {
  background: color-mix(in srgb, var(--warn) 12%, var(--bg-elev-1));
  color: var(--text);
  white-space: normal; text-align: center;
}
.session-status.interrupted { color: var(--warn); }

/* The hamburger FAB keeps contrast over the dark scape in both themes. */
body:has(.layout.brain-active) .nav-fab {
  color: #e4e4e4;
  background: color-mix(in srgb, #1c1b23 60%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
@media (max-width: 720px) {
  .brain-status { align-self: stretch; max-width: none; }
}
@media (prefers-reduced-motion: reduce) {
  .brain-status { animation: none; }
  .brain-immerse .brain-backdrop, .brain-immerse .brain-detail { transition: none; }
}

/* Light theme (owner: "Light constellation") — the Brain screen follows the app
   theme. Dark keeps the deep-space look verbatim; light rides the app neutrals so
   the screen isn't a dark slab in a light app. The canvas recolors itself
   (brainScape.ts reads data-theme); these rules carry the fixed backdrop, the lens
   scrim, and the HUD text/chrome that was hardcoded light-on-dark. The brand-gradient
   accents (lens underline, status dot, fresh-node ring) stay — they read on both. */
:root[data-theme="light"] .brain-immerse { background: var(--bg); }
:root[data-theme="light"] .brain-stream-panel {
  background: color-mix(in srgb, var(--bg) 58%, transparent);
}
:root[data-theme="light"] .brain-status {
  background: color-mix(in srgb, var(--bg-elev) 72%, transparent);
  color: var(--text);
}
:root[data-theme="light"] .brain-status-s2 { color: var(--text-dim); }
:root[data-theme="light"] .brain-status-s3 { border-top-color: var(--border); }
:root[data-theme="light"] .brain-status-s3 span { color: var(--text-faint); }
:root[data-theme="light"] .brain-status-s3 b { color: var(--text); }
:root[data-theme="light"] .brain-pace i { background: var(--bg-elev-3); }
:root[data-theme="light"] .brain-dock button { color: var(--text-faint); }
:root[data-theme="light"] .brain-dock button:hover { color: var(--text-dim); }
:root[data-theme="light"] .brain-dock button.is-active { color: var(--text); }
:root[data-theme="light"] .brain-hud-bottom { color: var(--text-faint); }
:root[data-theme="light"] .brain-newborn { color: var(--text); }
:root[data-theme="light"] .brain-newborn p { color: var(--text-dim); }
/* The FAB glass flips to light over the now-light scape. */
:root[data-theme="light"] body:has(.layout.brain-active) .nav-fab {
  color: var(--text);
  background: color-mix(in srgb, var(--bg-elev) 70%, transparent);
}

.uset-label {
  margin: 0 0 14px; font-size: 12px; font-weight: 600; letter-spacing: 0.5px;
  text-transform: uppercase; color: var(--text-dim);
}
.uset-options { display: flex; flex-direction: column; gap: 8px; }
.uset-option {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; padding: 14px 16px; cursor: pointer; text-align: left;
  background: var(--bg-elev-2); border: none; border-radius: var(--radius-md);
  color: var(--text); font: inherit; font-size: 15px; font-weight: 500;
  transition: background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.uset-option:hover { background: var(--bg-elev-3); }
.uset-option.is-active {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev-2));
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 50%, transparent);
}
.uset-option-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uset-option-check { flex: none; font-size: 20px; color: var(--accent); opacity: 0; }
.uset-option.is-active .uset-option-check { opacity: 1; }
.uset-status { font-size: 12.5px; color: var(--text-faint); line-height: 1.5; margin-top: 12px; }
.uset-status.err { color: var(--danger-text); }
.uset-field {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg-elev-2); border-radius: var(--radius-sm); padding: 0 14px;
  transition: box-shadow 0.15s var(--ease);
}
.uset-field:focus-within { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent); }
.uset-prefix { flex: none; color: var(--text-faint); font-size: 15px; }
.uset-input {
  flex: 1 1 auto; min-width: 0; background: transparent; border: none; outline: none;
  color: var(--text); font: inherit; font-size: 15px; padding: 12px 0;
}
.uset-input::placeholder { color: var(--text-faint); }

/* Self-improvement: Gemini toggle switches + indented sub-toggles + tonal history. */
.uset-toggle { display: flex; align-items: center; gap: 14px; padding: 11px 0; cursor: pointer; }
.uset-toggle-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.uset-toggle-label { font-size: 14px; font-weight: 500; color: var(--text); }
.uset-toggle-hint { font-size: 12px; color: var(--text-faint); line-height: 1.45; }
.uset-switch-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.uset-switch {
  flex: none; position: relative; width: 42px; height: 24px; border-radius: 999px;
  background: var(--bg-elev-3); transition: background 0.18s var(--ease);
}
.uset-switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--text-dim);
  transition: transform 0.18s var(--ease), background 0.18s var(--ease);
}
.uset-switch-input:checked + .uset-switch { background: var(--accent); }
.uset-switch-input:checked + .uset-switch::after { transform: translateX(18px); background: var(--on-accent); }
.uset-switch-input:focus-visible + .uset-switch { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
.uset-subtoggles { margin-left: 4px; padding-left: 16px; border-left: 2px solid var(--border); }
.uset-subtoggles.is-disabled { opacity: 0.45; pointer-events: none; }
.uset-actions { margin-top: 14px; }

/* Per-target apply mode: row (label/hint left, segmented control right) + the
   Gemini segmented control itself (tonal track, accent-filled active pill). */
.uset-apply { display: flex; flex-direction: column; }
.uset-apply.is-disabled { opacity: 0.45; pointer-events: none; }
.uset-apply-row {
  display: flex; align-items: center; gap: 16px; padding: 11px 0;
}
.uset-apply-row + .uset-apply-row { border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
.uset-seg {
  flex: none; display: inline-flex; align-items: center; gap: 2px;
  background: var(--bg-elev-3); border-radius: 999px; padding: 3px;
}
.uset-seg-btn {
  appearance: none; border: none; cursor: pointer; font: inherit;
  font-size: 12.5px; font-weight: 500; line-height: 1; white-space: nowrap;
  color: var(--text-faint); background: transparent;
  padding: 7px 14px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  transition: background 0.18s var(--ease), color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
/* M3-style leading check on the selected segment — slides in with the tonal fill. */
.uset-seg-btn::before {
  content: "check"; font-family: "Material Symbols Rounded"; font-size: 14px; line-height: 1;
  max-width: 0; opacity: 0; overflow: hidden;
  transition: max-width 0.18s var(--ease), opacity 0.18s var(--ease);
}
.uset-seg-btn:hover { color: var(--text); }
.uset-seg-btn.is-active {
  /* Soft tonal thumb (iOS/Gemini style) instead of a full-contrast accent fill. */
  background: color-mix(in srgb, var(--accent) 16%, var(--bg-elev-2));
  color: var(--text);
  box-shadow: 0 1px 3px color-mix(in srgb, #000 25%, transparent);
}
.uset-seg-btn.is-active::before { max-width: 14px; opacity: 1; }
/* Narrow screens: the label column and the segmented control can't share a row
   (the label squeezes to one word per line) — stack them, control full-width. */
@media (max-width: 540px) {
  .uset-apply-row { flex-direction: column; align-items: stretch; gap: 10px; }
  .uset-apply-row .uset-seg { display: flex; width: 100%; }
  .uset-apply-row .uset-seg-btn { flex: 1 1 0; text-align: center; }
}
.uset-seg-btn:focus-visible {
  outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

.uset .improve-list { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 0; padding: 0; list-style: none; }
.uset .improve-item { background: var(--bg-elev-2); border: none; border-radius: var(--radius-md); padding: 13px 15px; }

/* ── Account screens (Profile / Team / Settings / Admin) ─────────────────────
   These reuse the modal section look (.settings-section / .billing-*) inside the
   standalone screen scaffold (.sched-center). Neutralize the modal-only chrome
   (side padding, inner scroll caps) so sections align with the intro. */
/* NOTE: #admin-view is intentionally excluded — the admin panel renders each
   .settings-section as a padded card (see `.admin-body .settings-section`), so it
   must NOT get this edge-to-edge, zero-horizontal-padding modal treatment. */
#profile-view .settings-section,
#team-view .settings-section,
#settings-view .settings-section {
  padding: 22px 0;
  border-bottom: 1px solid var(--border);
}
#profile-view .settings-section:last-child,
#team-view .settings-section:last-child,
#settings-view .settings-section:last-child { border-bottom: none; }
#admin-view .billing-body { max-height: none; overflow: visible; }
#admin-view .billing-note { padding: 16px 0; }

/* Copy button beside an info-row value/code (Profile + Team). */
.profile-copy {
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--text-faint); cursor: pointer;
  min-width: 40px; min-height: 40px; padding: 2px; border-radius: 999px; margin-left: 2px;
  transition: color var(--ease) 120ms, background var(--ease) 120ms;
}
.profile-copy:hover { color: var(--text); background: var(--bg-elev-2); }
.profile-copy .material-symbols-rounded { font-size: 16px; }

/* ── Profile screen (profile.ui.ts) — Gemini identity hero + tonal tiles ──────
   A lettered avatar on the signature blue→purple→pink gradient (email + role
   pill) over a softly gradient-lit card (mirrors .wallet-hero), then a column of
   tonal detail tiles for the copyable user id / invite code / wallet address. */
.profile-hero-slot { margin: 8px 0 6px; }
.profile-hero {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 13px;
  padding: 34px 20px 28px;
  border-radius: var(--radius-hero);
  background:
    radial-gradient(125% 80% at 50% -10%, color-mix(in srgb, var(--grad-b) 18%, transparent), transparent 62%),
    var(--bg-elev-1);
  text-align: center;
}
.profile-avatar {
  display: grid; place-items: center;
  width: 88px; height: 88px; border-radius: 50%;
  font-family: var(--font-display);
  font-size: 38px; font-weight: 500; line-height: 1;
  color: #fff;
  background: linear-gradient(135deg, var(--grad-a), var(--grad-b) 52%, var(--grad-c));
  box-shadow: 0 8px 26px color-mix(in srgb, var(--grad-b) 40%, transparent);
}
.profile-hero-identity {
  display: flex; flex-direction: column; align-items: center; gap: 3px; max-width: 100%;
}
.profile-hero-email {
  font-family: var(--font-display);
  font-size: 20px; font-weight: 500; letter-spacing: -0.2px;
  color: var(--text); word-break: break-all; max-width: 100%;
}
.profile-hero-sub { font-size: 13px; color: var(--text-faint); word-break: break-all; }
.profile-role-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 13px 5px 10px; border-radius: 999px;
  background: var(--bg-elev-2); color: var(--text-dim);
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.2px;
}
.profile-role-pill .material-symbols-rounded { font-size: 16px; }
.profile-role-pill.is-admin {
  background: color-mix(in srgb, var(--grad-b) 24%, var(--bg-elev-2)); color: var(--text);
}

/* Tonal detail tiles. */
.profile-detail-list { display: flex; flex-direction: column; gap: 8px; }
.profile-detail {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; border-radius: var(--radius);
  background: var(--bg-elev-1);
  transition: background 0.15s var(--ease);
}
.profile-detail:hover { background: var(--bg-elev-2); }
/* Editable tile (Your name): the whole row is the tap target, not just the pencil. */
.profile-detail.is-editable { cursor: pointer; }
.profile-detail.is-editable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.profile-detail.is-editable:has(.profile-name-input) { cursor: default; }
.profile-detail-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: none; border-radius: var(--radius-tile);
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-elev-2)); color: var(--text-dim);
}
.profile-detail-icon .material-symbols-rounded { font-size: 20px; }
.profile-detail-main { flex: 1 1 auto; min-width: 0; }
.profile-detail-label {
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--text-faint);
}
.profile-detail-value { font-size: 14.5px; color: var(--text); margin-top: 2px; word-break: break-all; }
.profile-detail-value.is-mono { font-family: var(--font-mono); font-size: 13px; }
.profile-detail-sub { font-size: 12px; margin-top: 3px; }
.profile-detail-sub.is-ok { color: var(--ok); }
.profile-detail-sub.is-muted { color: var(--text-faint); }
.profile-detail-copy {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
  background: transparent; color: var(--text-faint);
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.profile-detail-copy:hover { color: var(--text); background: var(--bg-elev-3); }
.profile-detail-copy:disabled { opacity: 0.5; cursor: default; }
.profile-detail-copy .material-symbols-rounded { font-size: 18px; }
.profile-detail-copy.is-primary { color: var(--accent); }
.profile-detail-copy.is-primary:hover:not(:disabled) {
  color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent);
}

/* Editable display-name tile. */
.profile-detail-value.is-placeholder { color: var(--text-faint); }
.profile-name-actions { display: inline-flex; align-items: center; gap: 4px; flex: none; }
.profile-name-input {
  width: 100%; margin-top: 3px; box-sizing: border-box;
  font-family: inherit; font-size: 14.5px; color: var(--text);
  background: var(--bg-elev-3); border: none; border-radius: 8px; padding: 7px 10px;
}
.profile-name-input::placeholder { color: var(--text-faint); }
.profile-name-input:focus {
  outline: none;
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 45%, transparent);
}

/* Pending agent wallet approvals. */
.profile-actions-hint { margin: 14px 0 0; }
.profile-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.profile-action-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 14px; border-radius: var(--radius); background: var(--bg-elev-1);
}
.profile-action-what { font-size: 13.5px; color: var(--text); min-width: 0; word-break: break-word; }
.profile-action-btns { flex: none; display: inline-flex; gap: 8px; }
.profile-action-btns .skills-action { padding: 6px 14px; font-size: 12.5px; }

/* Team invite codes + create action. */
.team-code { font-family: var(--font-mono); font-size: 13.5px; color: var(--text); }
.team-actions { margin-top: 14px; }

/* Team screen — Gemini referral-count hero + tonal cards (matches profile/earn),
   replacing the old hard-bordered settings-form look. */
.team-hero {
  max-width: 560px;
  background: var(--bg-elev-1);
  border-radius: var(--radius-lg);
  padding: 22px 22px 20px;
  margin-bottom: 14px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 10%, transparent);
}
.team-hero-value { font-family: var(--font-display); font-size: 44px; font-weight: 700; line-height: 1.05; color: var(--text); }
.team-hero-label { font-size: 12.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-dim); margin-top: 2px; }
.team-hero-sub { display: inline-flex; align-items: center; gap: 6px; margin-top: 14px; font-size: 13px; color: var(--text-faint); }
.team-hero-sub .material-symbols-rounded { font-size: 17px; }
/* Tonal cards + borderless rows for the Team screen (standalone view + embedded tab). */
#team-view .settings-section,
.team-embed .settings-section {
  background: var(--bg-elev-1);
  border: none;
  border-radius: var(--radius-lg);
  padding: 18px 20px;
  margin: 0 0 14px;
  max-width: 560px;
}
#team-view .settings-section h3,
.team-embed .settings-section h3 {
  margin: 0 0 6px; font-size: 12px; font-weight: 600;
  letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-dim);
}
#team-view .info-row,
.team-embed .info-row { border-bottom: none; padding: 11px 0; }
#team-view .info-row + .info-row,
.team-embed .info-row + .info-row { border-top: 1px solid var(--border); }

/* ===================================================================
   Earn screen redesign (earnings.ui.ts) — hero + referral/yield sheets.
   Reuses .earnings-topbar/.earnings-scroll/.earnings-overview/.earn-tile/
   .earn-rank-* and adds the hero, gauges, invite, team, market, search +
   chips below. The Activity feed + Leaderboard are embedded inside the
   Referral/Yield sheets (not a top-level tab), so they never duplicate the
   Wallet screen's own Activity tab. */

/* Hero: total earned + breakdown + the cash-out/reinvest pockets, all one card — the
   pockets ARE the total, just split spendable-now vs locked, not a separate surface. */
.earnings-hero {
  background: var(--bg-elev-1);
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  display: flex; flex-direction: column; justify-content: center;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent);
}
.earnings-hero-label {
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.6px;
  text-transform: uppercase; color: var(--text-dim);
}
.earnings-hero-total {
  font-family: var(--font-display);
  font-size: 44px; font-weight: 700; line-height: 1.05; margin-top: 6px;
  background: var(--grad-h); -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* Source breakdown (Referral / Yield) — TIER 3: quiet navigation into the detail sheets,
   not headline stats. Deliberately recessive (flat, muted, label-led) so it doesn't fight
   the hero total above it or the action pockets below it. */
.earnings-hero-breakdown { display: flex; gap: 8px; margin-top: 14px; }
.earnings-bd-chip {
  flex: 1; display: flex; align-items: center; gap: 8px;
  background: transparent; border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 8px 8px 8px 11px; min-width: 0;
  cursor: pointer; text-align: left; font: inherit; color: inherit;
  transition: background var(--ease) 130ms, transform var(--ease) 130ms;
}
.earnings-bd-chip:hover { background: var(--bg-elev-1); }
.earnings-bd-chip:active { transform: scale(0.985); }
.earnings-bd-main { min-width: 0; display: flex; flex-direction: column-reverse; }
.earnings-bd-chip .earnings-bd-chev { margin-left: auto; flex: none; font-size: 18px; color: var(--text-faint); }
.earnings-bd-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none; color: var(--text-faint);
}
.earnings-bd-icon .material-symbols-rounded { font-size: 17px; }
/* Label leads (normal weight), amount is the small secondary line. */
.earnings-bd-label { font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.earnings-bd-amount { font-size: 11.5px; font-weight: 600; color: var(--text-faint); font-variant-numeric: tabular-nums; margin-top: 1px; }

/* Withdraw / claim CTA row. */
.earnings-claim {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border);
}
.earnings-claim-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.earnings-claim-label { font-size: 11.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.5px; }
.earnings-claim-amount { font-size: 20px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.earnings-claim-pending { font-size: 11.5px; color: var(--warn); }
.earnings-claim-btn {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  border: none; cursor: pointer; border-radius: 999px; padding: 11px 20px;
  font-family: var(--font-ui); font-size: 14px; font-weight: 600;
  background: var(--grad); color: var(--on-accent);
  transition: filter var(--ease) 140ms, opacity var(--ease) 140ms;
}
.earnings-claim-btn:hover:not(:disabled) { filter: brightness(1.08); }
.earnings-claim-btn:disabled { opacity: 0.45; cursor: default; }
.earnings-claim-btn .material-symbols-rounded { font-size: 18px; }

/* ── Genesis Giveaway hero (top of the Earn screen) ─────────────────────────
   The ONE card that breaks the calm mono palette. Deep restrained violet gradient
   (premium depth), a warm champagne glow + glassy highlight for richness (not just
   text), and ONE accent — gold, reserved strictly for the prize money. */
.earn-jackpot-promo {
  position: relative; overflow: hidden;
  border-radius: var(--radius-lg); padding: 22px 22px 20px;
  display: flex; flex-direction: column; gap: 2px;
  color: #fff;
  background:
    radial-gradient(135% 120% at 14% 0%, rgba(255, 255, 255, 0.22) 0%, transparent 52%),
    linear-gradient(140deg, #2e1065 0%, #5b21b6 34%, #7c3aed 64%, #9d2d8f 100%);
  background-size: 135% 120%, 240% 240%;
  background-position: 0% 0%, 0% 50%;
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 20px 48px -18px rgba(46, 16, 101, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.16);
  animation: earn-jackpot-flow 20s ease-in-out infinite;
}
/* Diagonal shine sweep. */
.earn-jackpot-promo::before {
  content: ""; position: absolute; top: -25%; left: -75%;
  width: 45%; height: 150%; pointer-events: none; z-index: 1;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.35), transparent);
  transform: skewX(-18deg);
  animation: earn-jackpot-shine 6s var(--ease) 1.4s infinite;
}
/* Warm champagne glow in the lower-right — depth + a quiet tie to the gold money. */
.earn-jackpot-promo::after {
  content: ""; position: absolute; right: -15%; bottom: -28%;
  width: 66%; height: 78%; pointer-events: none; z-index: 0;
  background: radial-gradient(circle at center, rgba(255, 206, 120, 0.22) 0%, transparent 62%);
}
.earn-jackpot-promo > * { position: relative; z-index: 2; }
@keyframes earn-jackpot-shine { 0% { left: -75%; } 24% { left: 135%; } 100% { left: 135%; } }
@keyframes earn-jackpot-flow {
  0%, 100% { background-position: 0% 0%, 0% 50%; }
  50% { background-position: 0% 0%, 100% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .earn-jackpot-promo { animation: none; }
  .earn-jackpot-promo::before { animation: none; }
}
.earn-jackpot-promo-head { position: relative; display: flex; align-items: center; gap: 12px; }
.earn-jackpot-promo-label {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 700; letter-spacing: 0.7px; text-transform: uppercase; color: rgba(255, 244, 230, 0.78);
}
/* The headline IS the money → metallic champagne gold (with a soft drop shadow for
   depth, so it reads as an object, not flat text). The only big gold on the card. */
.earn-jackpot-promo-amount {
  position: relative; font-family: var(--font-display);
  font-size: clamp(42px, 12vw, 56px); font-weight: 800; line-height: 1.02; margin: 6px 0 2px;
  background: linear-gradient(135deg, #fff6da 0%, #ffe2a0 44%, #f4b95a 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 2px 14px rgba(0, 0, 0, 0.28));
}
.earn-jackpot-promo .earnings-progress { position: relative; margin-top: 14px; }
.earn-jackpot-promo .earnings-progress-track { background: rgba(255, 255, 255, 0.22); }
.earn-jackpot-promo .earnings-progress-bar { background: linear-gradient(90deg, rgba(255, 255, 255, 0.85), #ffffff); }
.earn-jackpot-promo .earnings-progress-meta { color: rgba(255, 255, 255, 0.75); }
.earn-jackpot-promo .earnings-progress-meta strong { color: #fff; }

/* Premium CTA — one clean solid-white button: benefit line + the code in a single
   violet accent, a quiet copy glyph, and a soft breathing halo. No tile, no chip. */
.earn-jackpot-cta {
  position: relative; display: flex; align-items: center; gap: 12px; width: 100%;
  margin-top: 16px; padding: 12px 16px; border-radius: var(--radius-md); cursor: pointer;
  background: #fff; border: none; color: #1a1430; font: inherit; text-align: left;
  box-shadow: 0 8px 22px -12px rgba(0, 0, 0, 0.5);
  transition: transform var(--ease) 130ms, filter var(--ease) 130ms;
  animation: earn-cta-pulse 3s ease-in-out infinite;
}
.earn-jackpot-cta:hover { filter: brightness(0.98); }
.earn-jackpot-cta:active { transform: scale(0.99); }
@keyframes earn-cta-pulse {
  0%, 100% { box-shadow: 0 8px 22px -12px rgba(0, 0, 0, 0.5), 0 0 0 0 rgba(255, 255, 255, 0); }
  50% { box-shadow: 0 8px 22px -12px rgba(0, 0, 0, 0.5), 0 0 0 4px rgba(255, 255, 255, 0.16); }
}
@media (prefers-reduced-motion: reduce) { .earn-jackpot-cta { animation: none; } }
.earn-jackpot-cta-main { min-width: 0; flex: 1 1 auto; }
.earn-jackpot-cta-title { font-size: 14.5px; font-weight: 700; color: #1a1430; letter-spacing: -0.1px; }
.earn-jackpot-cta-code { display: flex; align-items: baseline; gap: 7px; margin-top: 2px; }
.earn-jackpot-cta-code-label { font-size: 10.5px; font-weight: 600; letter-spacing: 0.4px; text-transform: uppercase; color: rgba(26, 20, 48, 0.5); }
.earn-jackpot-cta-code-val { font-family: var(--font-mono); font-size: 14px; font-weight: 700; letter-spacing: 1.5px; color: #5b21b6; }
.earn-jackpot-cta-go { flex: none; display: inline-flex; align-items: center; color: #5b21b6; }
.earn-jackpot-cta-go .material-symbols-rounded { font-size: 20px; }

/* Deadline badge — quiet date chip under the amount (no days-left pill). */
.earn-jackpot-deadline {
  position: relative; display: inline-flex; align-items: center; gap: 7px; align-self: flex-start;
  margin-top: 10px; padding: 6px 12px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.2); border: 1px solid rgba(255, 255, 255, 0.2);
  font-size: 12.5px; color: rgba(255, 255, 255, 0.92);
}
.earn-jackpot-deadline .material-symbols-rounded { font-size: 17px; color: rgba(255, 255, 255, 0.78); }
.earn-jackpot-deadline-date { font-weight: 600; }
.earn-jackpot-deadline.is-urgent { border-color: rgba(255, 209, 92, 0.55); }
.earn-jackpot-deadline.is-urgent .earn-jackpot-deadline-date { color: #ffd98a; }

/* "How it works" disclosure — admin-configured conditions + prize tiers + note. */
.earn-jackpot-how-wrap { position: relative; margin-top: 14px; }
.earn-jackpot-how-btn {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 0;
  background: none; border: none; cursor: pointer; font: inherit;
  font-size: 12px; font-weight: 600; color: rgba(255, 255, 255, 0.78);
}
.earn-jackpot-how-btn:hover { color: rgba(255, 255, 255, 0.95); }
.earn-jackpot-how-btn .material-symbols-rounded { font-size: 17px; }
.earn-jackpot-how-chev { transition: transform 0.2s var(--ease); }
.earn-jackpot-how-btn.is-open .earn-jackpot-how-chev { transform: rotate(180deg); }
.earn-jackpot-how { margin-top: 8px; }
.earn-jackpot-how.is-collapsed { display: none; }
.earn-jackpot-how-cap {
  font-size: 11px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase;
  color: rgba(255, 255, 255, 0.78); margin: 10px 0 6px;
}
.earn-jackpot-how-rule { margin: 0 0 4px; font-size: 12.5px; line-height: 1.55; color: rgba(255, 255, 255, 0.86); }
.earn-jackpot-conditions { display: flex; flex-direction: column; gap: 5px; }
.earn-jackpot-condition { display: flex; align-items: flex-start; gap: 7px; font-size: 12.5px; line-height: 1.4; color: rgba(255, 255, 255, 0.92); }
.earn-jackpot-condition .material-symbols-rounded { font-size: 17px; color: #ffe08a; flex: none; margin-top: 0.5px; }
.earn-jackpot-prizes { display: flex; flex-direction: column; gap: 9px; }
.earn-jackpot-prize {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; border-radius: var(--radius-md); background: rgba(0, 0, 0, 0.22);
}
/* Big champagne-gold prize figures — the exciting part. */
.earn-jackpot-prize-amt {
  flex: none; min-width: 96px;
  font-family: var(--font-display); font-weight: 800; font-size: 25px;
  line-height: 1; letter-spacing: -0.5px; font-variant-numeric: tabular-nums;
  background: linear-gradient(135deg, #fff6da 0%, #ffe2a0 44%, #f4b95a 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
}
.earn-jackpot-prize-main { min-width: 0; }
.earn-jackpot-prize-label { font-size: 13px; font-weight: 700; color: #fff; }
.earn-jackpot-prize-desc { font-size: 12px; line-height: 1.45; color: rgba(255, 255, 255, 0.86); margin-top: 2px; }
.earn-jackpot-note { margin: 10px 0 0; font-size: 11.5px; line-height: 1.45; color: rgba(255, 255, 255, 0.74); }

/* ── Referral / yield slide-in detail sheet ────────────────────────────────
   Opened from the total-earned card's breakdown buttons. Slides in from the
   right (matches the session panel); full-width on mobile. */
.earn-sheet-overlay {
  position: fixed; inset: 0; z-index: 1100; display: flex; justify-content: flex-end;
  background: rgba(0, 0, 0, 0); transition: background 0.26s var(--ease);
}
.earn-sheet-overlay.is-open { background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(2px); }
.earn-sheet {
  display: flex; flex-direction: column;
  width: min(480px, 100%); height: 100%;
  background: var(--bg-elev); box-shadow: var(--shadow-lg);
  transform: translateX(100%); transition: transform 0.28s var(--ease);
}
.earn-sheet-overlay.is-open .earn-sheet { transform: translateX(0); }
/* Admin "Details" sheet only (admin-bi.ui.ts) — wide enough on desktop to hold a real
   data table (7 metric columns + a 4-button per-row action cell) without the actions
   collapsing into a vertical stack; the earnings screen's own sheet usage is untouched
   (stays 480px-capped). */
@media (min-width: 721px) {
  .earn-sheet.is-wide { width: min(1240px, 96vw); }
}
.earn-sheet-bar {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 12px 14px; border-bottom: 1px solid var(--border); background: var(--bg-elev);
}
/* Title takes the middle so any trailing element (e.g. Power) pins to the right. */
.earn-sheet-title { flex: 1 1 auto; min-width: 0; font-size: 17px; font-weight: 600; color: var(--text); }
/* Back chevron, top-left (replaces the close X). */
.earn-sheet-back {
  flex: none; width: 36px; height: 36px; border-radius: 999px; cursor: pointer; border: none;
  background: transparent; color: var(--text-dim); display: inline-grid; place-items: center;
  transition: background var(--ease) 120ms, color var(--ease) 120ms;
}
.earn-sheet-back:hover { background: var(--bg-elev-2); color: var(--text); }
.earn-sheet-back .material-symbols-rounded { font-size: 24px; }
.earn-sheet-power { flex: none; display: inline-flex; align-items: center; }
.earn-sheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 18px; }
/* First section heading inside a sheet shouldn't carry the big top margin. */
.earn-sheet-body > div > .earnings-section-head:first-child { margin-top: 4px; }
/* Admin "Details" sheet tab strip (Overview/Activity/Agents) — reuses .buy-chip's
   pill-toggle look verbatim, just needs its own row layout. */
.earn-sheet-tabs { display: flex; gap: 6px; margin-bottom: 16px; }

/* Yield-agent subscription progress ring (days left out of the 30-day window). */
/* ── Yield tab: compact Power chip + taller agent cards ───────────────────── */
/* Agents-section header meta: "N agents | ⚡ 4,961". */
.yield-agents-meta { display: inline-flex; align-items: center; gap: 9px; }
.yield-meta-sep { width: 1px; height: 12px; background: var(--border-strong); flex: none; }
/* Power balance as a ⚡ + number chip (the word "Power" is never shown). Taps → Store. */
.yield-power-chip {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  border: none; background: none; font: inherit; padding: 2px 2px;
  font-size: 13px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums;
  transition: color var(--ease) 120ms;
}
.yield-power-chip:hover { color: var(--accent); }
.yield-power-chip .material-symbols-rounded { font-size: 17px; color: #f0b94a; }
.yield-power-chip.is-empty { color: var(--accent); font-weight: 600; }

/* One tall card per owned agent. */
.yield-cards { display: flex; flex-direction: column; gap: 10px; }
.yield-card {
  display: flex; flex-direction: column; gap: 12px;
  padding: 14px 16px 12px; border-radius: var(--radius); background: var(--bg-elev-1);
  box-shadow: inset 0 0 0 1px var(--border);
}
.yield-card.is-lapsed { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 40%, transparent); }
.yield-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.yield-card-name { font-size: 15.5px; font-weight: 600; color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* (the star tier renders via the shared .star-pill — see src/web/star.ts) */
/* (b) the three numbers that matter */
.yield-card-stats { display: flex; gap: 8px; }
.yield-card-stat { flex: 1 1 0; min-width: 0; background: var(--bg-elev-2); border-radius: var(--radius-sm); padding: 10px 8px; text-align: center; }
.yield-card-stat-value { font-size: 16px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.yield-card-stat-label { font-size: 11px; color: var(--text-faint); margin-top: 3px; }
/* Earn-back progress (revenue model v2 §5): gross generated vs purchase price. */
.yield-payback { display: flex; flex-direction: column; gap: 5px; }
.yield-payback-row { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 12px; }
.yield-payback-label { font-weight: 600; color: var(--text-dim); }
.yield-payback.is-done .yield-payback-label { color: var(--ok); }
.yield-payback-nums { color: var(--text-faint); font-variant-numeric: tabular-nums; }
.yield-payback-track { height: 5px; border-radius: 999px; background: var(--bg-elev-2); overflow: hidden; }
.yield-payback-fill { height: 100%; width: 0; border-radius: inherit; background: linear-gradient(90deg, #0036ff, #c600ff); transition: width 0.6s ease; }
.yield-payback.is-done .yield-payback-fill { background: var(--ok); }
/* Appreciation line: new-agent tranche price vs what this one cost. */
.yield-apprec { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ok); }
.yield-apprec .material-symbols-rounded { font-size: 16px; }
/* (c) time left + extend */
.yield-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 11px; border-top: 1px solid var(--border); }
.yield-card-status { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: var(--text-dim); }
.yield-card-status .material-symbols-rounded { font-size: 18px; }
.yield-card-status.is-lapsed { color: var(--warn); }
/* Extend CTA: two segments — "+30 days" | "39 ⚡" — split by a divider. */
.yield-card-extend {
  flex: none; display: inline-flex; align-items: stretch; cursor: pointer;
  border: none; border-radius: 999px; overflow: hidden; font: inherit; font-size: 13px; font-weight: 700;
  background: var(--grad); color: var(--on-accent); transition: filter var(--ease) 130ms;
}
.yield-card-extend:hover { filter: brightness(1.06); }
.yield-card-extend-gain { display: inline-flex; align-items: center; padding: 9px 12px; }
.yield-card-extend-cost {
  display: inline-flex; align-items: center; gap: 3px; padding: 9px 13px 9px 11px;
  border-left: 1px solid color-mix(in srgb, var(--on-accent) 22%, transparent);
  font-variant-numeric: tabular-nums;
}
.yield-card-extend-cost .material-symbols-rounded { font-size: 17px; }

/* Tile icons (referral/yield dashboards). */
.earn-tile-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; margin: 0 auto 6px; border-radius: var(--radius-tile);
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-elev-1)); color: var(--accent);
}
.earn-tile-icon .material-symbols-rounded { font-size: 16px; }

/* Section labels + meta. */
.earnings-section-label { font-size: 12.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-dim); }
.earnings-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 22px 0 10px; }
.earnings-section-meta { font-size: 12px; color: var(--text-faint); }

/* Reward-pool label + tier-name with leading icons (scoped so partner.ui is untouched). */
.earnings-screen .earnings-overview-label,
.earnings-screen .partner-tier-name { display: inline-flex; align-items: center; gap: 7px; }
.earnings-screen .partner-tier { margin-bottom: 12px; }

/* Tier qualification gauges. */
.earnings-gauges { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
.earnings-gauge-head { display: flex; align-items: baseline; justify-content: space-between; font-size: 12.5px; color: var(--text-dim); margin-bottom: 6px; }
.earnings-gauge-num { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.earnings-mini-track { height: 8px; }

/* Invite-code card. */
.earnings-invite { background: var(--bg-elev-1); border-radius: var(--radius); padding: 16px 18px; margin-top: 14px; }
.earnings-invite-main { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.earnings-invite-label { font-size: 12px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.5px; }
.earnings-invite-code { display: flex; align-items: center; gap: 6px; }
.earnings-invite-code .team-code { font-size: 18px; letter-spacing: 1px; }
.earnings-invite-hint { font-size: 12.5px; color: var(--text-dim); margin-top: 8px; line-height: 1.5; }

/* List-row leading icon + negative amounts. */
.earnings-row-icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 36px; height: 36px; border-radius: var(--radius-tile);
  background: var(--bg-elev-2); color: var(--text-dim);
}
.earnings-row-icon .material-symbols-rounded { font-size: 19px; }
/* Qualified daily-pool row: green check. */
.earnings-row-icon.is-qualified { background: color-mix(in srgb, var(--ok) 18%, var(--bg-elev-1)); color: var(--ok); }
.earnings-row-amount.is-neg { color: var(--text-faint); }

/* Explainer notes. */
.earnings-note { font-size: 12.5px; color: var(--text-dim); background: var(--bg-elev-1); border-radius: var(--radius-sm); padding: 12px 14px; margin: 12px 0; line-height: 1.5; }
.earnings-note-split { display: flex; flex-direction: column; gap: 4px; }
.earnings-note strong { color: var(--text); }

/* ====================== Node-tier badges & referral upgrades ===============
   A cohesive metal family, premium escalating: Regular (graphite) → Light (silver)
   → Master (gold) → Super (polished black-diamond). Identities fixed across themes. */
.tier-badge {
  position: relative; flex: none; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center;
  color: #1b1b1b;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), inset 0 0 0 1px rgba(255, 255, 255, 0.08), 0 3px 10px rgba(0, 0, 0, 0.3);
}
.tier-badge .material-symbols-rounded { position: relative; z-index: 1; font-variation-settings: "FILL" 1; }
.tier-badge-shine {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0) 48%);
  mix-blend-mode: screen;
}
.tier-badge--lg { width: 54px; height: 54px; border-radius: var(--radius); }
.tier-badge--lg .material-symbols-rounded { font-size: 28px; }
.tier-badge--sm { width: 38px; height: 38px; border-radius: var(--radius-tile); }
.tier-badge--sm .material-symbols-rounded { font-size: 20px; }
.tier-badge--regular { background: linear-gradient(145deg, #9aa3af, #646b78); color: #f3f4f6; }
.tier-badge--light { background: linear-gradient(145deg, #e8edf3, #a7b2c0); color: #2b333f; }
.tier-badge--master { background: linear-gradient(145deg, #f6d98b, #d2a23c); color: #4a360f; }
.tier-badge--super { background: linear-gradient(150deg, #2c3242, #0f1219); color: #dbe6ff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), inset 0 0 0 1px rgba(150, 180, 255, 0.2), 0 3px 14px rgba(70, 110, 200, 0.4);
}
.tier-badge--super .material-symbols-rounded { text-shadow: 0 0 10px rgba(140, 180, 255, 0.7); }

/* Current-tier hero block: card + the "All tiers" benefits disclosure. */
.tier-block { display: flex; flex-direction: column; }
.tier-card { display: flex; flex-direction: column; }
.tier-card-top { display: flex; align-items: flex-start; gap: 14px; }
.tier-card-id { flex: 1 1 auto; min-width: 0; }
.tier-card-name { font-family: var(--font-display); font-size: 1.25rem; font-weight: 650; color: var(--text); line-height: 1.12; }
.tier-card .partner-tier-rates { margin-top: 3px; }
.tier-card--light { background: linear-gradient(180deg, color-mix(in srgb, #aeb8c4 10%, var(--bg-elev-1)), var(--bg-elev-1)); }
.tier-card--master { background: linear-gradient(180deg, color-mix(in srgb, #f2c14e 12%, var(--bg-elev-1)), var(--bg-elev-1)); }
.tier-card--super {
  background: linear-gradient(180deg, color-mix(in srgb, #6c8bff 14%, var(--bg-elev-1)), var(--bg-elev-1));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent);
}
/* Clear pill button on the badge row that opens the all-tiers comparison. */
.tier-benefits-btn {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 11px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  font: inherit; font-size: 12px; font-weight: 600;
}
.tier-benefits-btn:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.tier-benefits-btn .material-symbols-rounded { font-size: 15px; }
.tier-learn-chev { transition: transform 0.2s var(--ease); }
.tier-benefits-btn.is-open .tier-learn-chev { transform: rotate(180deg); }
/* In-card performance dashboard (3 stats) + invite code. */
.tier-divider { height: 1px; background: color-mix(in srgb, var(--text-faint) 16%, transparent); margin: 16px 0; }
.tier-perf { display: flex; gap: 8px; }
.tier-perf-stat { flex: 1 1 0; min-width: 0; text-align: center; }
.tier-perf-icon { color: var(--text-faint); }
.tier-perf-icon .material-symbols-rounded { font-size: 18px; }
.tier-perf-value { font-family: var(--font-display); font-size: clamp(15px, 4.6vw, 20px); font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tier-perf-label { font-size: 11px; color: var(--text-faint); margin-top: 3px; line-height: 1.3; }
.tier-invite {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid color-mix(in srgb, var(--text-faint) 16%, transparent);
}
.tier-invite-label { font-size: 12px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.5px; }
.tier-invite-code { display: flex; align-items: center; gap: 8px; }
.tier-invite-code .team-code { font-size: 17px; letter-spacing: 1px; }

/* All-tiers benefits ladder (revealed by "Compare all tiers"). */
.tier-ladder { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
.tier-ladder.is-collapsed { display: none; }
.tier-rung {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: var(--radius-md); background: var(--bg-elev-1); border: 1px solid transparent;
}
.tier-rung:not(.is-current):not(.is-achieved) { opacity: 0.78; }
.tier-rung.is-current {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev-1));
}
.tier-rung-main { flex: 1 1 auto; min-width: 0; }
.tier-rung-name { display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--text); }
.tier-rung-req { font-size: 12px; color: var(--text-faint); margin-top: 3px; }
.tier-rung-tag {
  font-size: 11px; font-weight: 700; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent); padding: 1px 8px; border-radius: 999px;
}
.tier-rung-mark { display: inline-flex; }
.tier-rung-mark .material-symbols-rounded { font-size: 16px; }
.tier-rung-mark.is-done { color: var(--ok); }
.tier-rung-mark.is-locked { color: var(--text-faint); }
.tier-rung-rates { display: flex; gap: 14px; flex: none; }
.tier-rung-rate { text-align: right; line-height: 1.12; }
.tier-rung-rate strong { display: block; font-size: 15px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.tier-rung-rate span { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint); }

/* ===== Daily dividend pool: one cohesive bounded section ===== */
.pool-section { background: var(--bg-elev-1); border-radius: var(--radius-lg); overflow: hidden; }
.pool-hero {
  position: relative; overflow: hidden; padding: 20px 22px;
  background: linear-gradient(150deg, color-mix(in srgb, var(--accent-2) 22%, var(--bg-elev-2)), var(--bg-elev-2) 72%);
}
.pool-hero-glow {
  position: absolute; top: -40%; right: -18%; width: 260px; height: 260px; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 32%, transparent), transparent 70%); pointer-events: none;
}
.pool-hero-label {
  position: relative; display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-dim);
}
.pool-hero-label .material-symbols-rounded { font-size: 18px; }
.pool-hero-amount {
  position: relative; font-family: var(--font-display); font-size: 40px; font-weight: 700; line-height: 1.05; margin-top: 8px;
  background: var(--grad-h); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.pool-hero-sub { position: relative; font-size: 12.5px; color: var(--text-dim); margin-top: 6px; }
.pool-block { padding: 14px 16px 2px; }
.pool-block-cap { font-size: 11.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-faint); margin-bottom: 10px; }
.pool-rows { display: flex; flex-direction: column; gap: 6px; }
.pool-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg-elev-2); }
.pool-row-icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 32px; height: 32px; border-radius: var(--radius-tile); background: var(--bg-elev-3); color: var(--text-faint);
}
.pool-row-icon .material-symbols-rounded { font-size: 18px; }
/* Qualified vs locked is carried by the check/lock icon shape, not colour — keep it
   monochrome so the section reads as one calm surface. */
.pool-row-icon.is-qualified { background: color-mix(in srgb, var(--accent) 16%, var(--bg-elev-2)); color: var(--accent); }
.pool-row-main { flex: 1 1 auto; min-width: 0; }
.pool-row-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 14px; font-weight: 500; color: var(--text); }
.pool-row .earnings-type { background: var(--bg-elev-3); }
.pool-row-sub { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
.pool-row-fig { text-align: right; flex: none; }
/* Pool amounts are figures, not signed ledger movements — keep them neutral text. */
.pool-row-amount { font-size: 14px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.pool-row-note { font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
.pool-dash { display: flex; gap: 8px; }
.pool-stat { flex: 1 1 0; min-width: 0; background: var(--bg-elev-2); border-radius: var(--radius-sm); padding: 13px 10px; text-align: center; }
/* "Today" is the live figure — emphasise it with the neutral theme accent, not colour. */
.pool-stat.is-accent { background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev-2)); }
.pool-stat-value {
  font-size: clamp(15px, 4.4vw, 21px); font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pool-stat-label { font-size: 11.5px; color: var(--text-faint); margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* "How it works" disclosure — rules + requirements, collapsed by default. */
.pool-learn-wrap { padding: 8px 16px 14px; }
.pool-learn {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 2px;
  background: none; border: none; cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--accent);
}
.pool-learn:hover { opacity: 0.85; }
.pool-learn-chev { font-size: 18px; transition: transform 0.2s var(--ease); }
.pool-learn.is-open .pool-learn-chev { transform: rotate(180deg); }
.pool-details.is-collapsed { display: none; }
.pool-details { margin-top: 8px; }
.pool-details p { font-size: 12px; color: var(--text-dim); line-height: 1.55; margin: 0 0 8px; }
.pool-details p:last-child { margin-bottom: 0; }

/* "Earnings over time" card — reuses .crm-chart* design with stacked Stream A/B bars. */
/* Extra top room so the max gridline's label clears the section header above it. */
.earn-chart-wrap { padding-top: 22px; }
.earn-chart-head { margin: 22px 0 0; }
.earn-chart-head .earnings-section-label { margin-bottom: 0; }
.earn-bar {
  width: 100%; min-height: 2px; display: flex; flex-direction: column;
  border-radius: 3px 3px 0 0; overflow: hidden; opacity: 0.9; transition: opacity 0.12s var(--ease);
}
.crm-chart-col:hover .earn-bar { opacity: 1; }
.earn-bar-seg { width: 100%; }
/* Two income streams distinguished by accent weight, not by hue — keeps the chart in
   the same monochrome system as the rest of the screen. */
.earn-bar-seg.is-commission { background: color-mix(in srgb, var(--accent) 72%, transparent); }
.earn-bar-seg.is-rebate { background: color-mix(in srgb, var(--accent) 34%, transparent); }
.earn-chart-legend { display: flex; gap: 16px; margin-top: 8px; font-size: 11.5px; color: var(--text-dim); }
.earn-legend-dot { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 6px; vertical-align: middle; }
.earn-legend-dot.is-commission { background: color-mix(in srgb, var(--accent) 72%, transparent); }
.earn-legend-dot.is-rebate { background: color-mix(in srgb, var(--accent) 34%, transparent); }

/* 3-layer team, grouped into collapsible Layer 1/2/3 sections. */
.team-layers { display: flex; flex-direction: column; gap: 10px; }
.team-layer { background: var(--bg-elev-1); border-radius: var(--radius-md); overflow: hidden; }
.team-layer-head {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px 14px; background: transparent; border: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
}
/* Layer badges are just labels (the row also says "Layer 1/2/3") — one neutral
   treatment for all, no per-layer colour coding. */
.team-layer-pill {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 30px; height: 30px; border-radius: var(--radius-tile); font-weight: 700; font-size: 14px;
  color: var(--text); background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev-2));
}
.team-layer-id { flex: 1 1 auto; min-width: 0; }
.team-layer-name { font-weight: 600; color: var(--text); }
.team-layer-meta { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
.team-layer-chev { font-size: 22px; color: var(--text-faint); transition: transform 0.2s var(--ease); }
.team-layer-body { display: flex; flex-direction: column; gap: 4px; padding: 0 10px 10px; }
.team-layer-body.is-collapsed { display: none; }
.team-layer-more { font-size: 12px; color: var(--text-faint); padding: 8px 6px; }

/* Yield market / buy. */
.earnings-market { background: var(--bg-elev-1); border-radius: var(--radius); padding: 16px 18px; margin-top: 4px; }
.earnings-market-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.earnings-market-grid { display: flex; gap: 10px; margin-bottom: 14px; }
.earnings-mini { flex: 1; background: var(--bg-elev-2); border-radius: var(--radius-sm); padding: 10px 12px; text-align: center; min-width: 0; }
.earnings-mini-value { font-size: 15px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.earnings-mini-label { font-size: 11px; color: var(--text-faint); margin-top: 2px; }
.earnings-buy-btn { width: 100%; justify-content: center; display: inline-flex; align-items: center; gap: 7px; }
.earnings-buy-btn .material-symbols-rounded { font-size: 18px; }

/* Ranks: "you're #N" banner. */
.earnings-rank-you {
  font-size: 13px; font-weight: 600; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev-1));
  border-radius: var(--radius-sm); padding: 10px 14px; margin-bottom: 8px;
}

/* Error-state retry. */
.earnings-retry-wrap { margin-top: 16px; display: flex; justify-content: center; }

/* ===== Earn: two-pocket rail + reinvest & gacha ===========================
   Builds on the Gemini tokens. The reinvest/gacha surfaces are the one place the
   multi-colour gradient stops (--grad-a/b/c) earn their keep — a "premium pull" accent
   reserved for the locked-funded growth actions, so they read as special vs the calm
   monochrome of the rest of the screen. Rarity hues: common=neutral, rare=blue,
   epic=purple, legendary=gold. */
/* align-items: start (not the grid default stretch) — Cash out can grow taller once its
   withdrawal-progress fold is open without forcing Reinvest to stretch to match. */
.earn-pockets { display: grid; grid-template-columns: 1fr 1fr; align-items: stretch; gap: 10px; margin-top: 14px; }
.earn-pocket {
  display: flex; flex-direction: column; gap: 3px;
  background: var(--bg-elev-2); border-radius: var(--radius-md); padding: 13px 14px 14px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 8%, transparent);
}
.earn-pocket--reinvest {
  background:
    linear-gradient(150deg, color-mix(in srgb, var(--grad-a) 16%, transparent), color-mix(in srgb, var(--grad-b) 16%, transparent)),
    var(--bg-elev-2);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--grad-b) 32%, transparent);
}
/* Row form for Cash out's head, so the Wallet cross-link pill can sit opposite the label
   instead of stacking under it. Reinvest has no pill, so it just uses .earn-pocket-head
   alone (inline-flex, sized to content). */
.earn-pocket-head-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.earn-pocket-head {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.4px; text-transform: uppercase; color: var(--text-dim);
}
.earn-pocket-head .material-symbols-rounded { font-size: 16px; }
/* Cross-link pill between the two USDT surfaces (Earn ⇄ Wallet) — same shape as the
   Wallet screen's own corner shortcut, so the bridge reads the same from both sides. */
.earn-wallet-pill {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px 4px 7px; border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  font: inherit; font-size: 11px; font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.earn-wallet-pill:hover { background: var(--bg-elev-1); color: var(--text); }
.earn-wallet-pill .material-symbols-rounded { font-size: 14px; }
/* In-flight withdrawal count riding the pill — the request list itself lives in the
   Wallet's Activity → Withdrawals view the pill opens. */
.earn-wallet-pill-badge {
  min-width: 15px; height: 15px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  font-size: 9.5px; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--warn); background: color-mix(in srgb, var(--warn) 18%, transparent);
}
.earn-pocket-amount { font-size: 22px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; line-height: 1.1; }
.earn-pocket-sub { font-size: 11px; color: var(--text-faint); margin-bottom: 10px; min-height: 14px; }
.earn-pocket-btn {
  margin-top: auto; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; padding: 10px 12px; border: none; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 650;
  background: var(--accent); color: var(--on-accent);
  transition: transform var(--ease) 130ms, opacity var(--ease) 130ms, box-shadow var(--ease) 130ms;
}
.earn-pocket-btn .material-symbols-rounded { font-size: 18px; }
.earn-pocket-btn:not(:disabled):hover { opacity: 0.92; }
.earn-pocket-btn:not(:disabled):active { transform: scale(0.97); }
.earn-pocket-btn:disabled { opacity: 0.4; cursor: default; }
.earn-pocket-btn--alt {
  background: linear-gradient(120deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c)); color: #fff;
  box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--grad-b) 80%, transparent);
}

/* The locked-balance chip pinned top-right in the Reinvest sheet bar. */
.earn-lock-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px; border-radius: 999px;
  font-size: 13px; font-weight: 650; font-variant-numeric: tabular-nums;
  background: color-mix(in srgb, var(--grad-b) 18%, var(--bg-elev-1)); color: var(--text);
}
.earn-lock-chip .material-symbols-rounded { font-size: 15px; color: var(--grad-b); }

/* Reinvest sheet — portrait store-cards (the locked balance lives in the top-bar chip). One
   accent hue PER CARD keeps the palette cohesive (Gemini) while the tier ramp + top-tier glow
   bring the gamified hype. Tier ramp: silver → blue → purple → gold; the Genie agent card is
   the hero and wears the brand gradient (the one multi-colour accent on the screen). */
.reinvest-tab { display: flex; flex-direction: column; }
.reinvest-agent-section { margin-top: 6px; }
/* auto-fit (not auto-fill) — .pack-grid now only wraps the single Buy-a-Genie card, and
   auto-fill reserves as many minmax tracks as geometrically fit regardless of item count,
   so a lone card sat in one ~half-width track with an invisible empty second track eating
   the rest of the row. auto-fit collapses that empty track to 0, letting the card's 1fr
   track take the full row — this is what was silently shrinking it below the pack-rail's
   full-width cards. */
/* 2/3 width, matching .pack-rail .pack-card below so the Buy-a-Genie card and the pack
   cards stay the same size as each other. */
.pack-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); max-width: 66.6%; }
/* Card packs — a horizontal rail, not a wrapped grid: the most-promoted (top-tier) pack
   leads leftmost, the rest are a swipe away. No bleed/negative-margin trick — the rail
   stays inset exactly like .store-grid above it, so cards line up at the same edges
   (a bled rail computed its 100%-width cards against a slightly different box, which is
   what was throwing the two cards off by a few px). */
.pack-rail {
  display: flex; gap: 12px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.pack-rail::-webkit-scrollbar { display: none; }
/* 2/3 of the rail's width — matches .pack-grid's cap on the Buy-a-Genie card above (same
   fraction of the same container width, so both stay the same size). The remaining third
   (minus the gap) peeks the next card at the edge, inviting a swipe. */
.pack-rail .pack-card { flex: 0 0 66.6%; scroll-snap-align: start; }
.pack-card {
  --pack: #9aa0a6; gap: 8px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pack) 22%, transparent);
}
.pack-card--t1 { --pack: #9aa0a6; }
.pack-card--t2 { --pack: #38bdf8; }
.pack-card--t3 { --pack: #c084fc; }
.pack-card--t4 { --pack: #ffce5a; }
/* Top tier = the jackpot pack: brighter ring + a soft outer glow. */
.pack-card.is-top { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--pack) 65%, transparent), 0 0 34px -14px var(--pack); }
.pack-emblem {
  display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--pack) 20%, var(--bg-elev-2)); color: var(--pack);
}
.pack-emblem .material-symbols-rounded { font-size: 22px; }
.pack-name { font-size: 14px; font-weight: 700; color: var(--text); }
/* "N-card pack" — signals the booster-pack hand size under the pack name. */
.pack-slots { font-size: 11px; font-weight: 600; color: var(--gtc-accent, var(--accent)); letter-spacing: 0.2px; margin-top: -1px; }
.pack-badge { background: var(--pack); color: #0a0b0d; }
/* Featured odds — dim text with tier-hue dots (no per-rarity colour rainbow). */
/* Free-pull grants — gift chips above the store, each opens its pack for free. Brand-gradient
   so they read as a reward, distinct from the frosted paid pack cards. */
.gacha-grants { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.gacha-grant {
  display: inline-flex; align-items: center; gap: 9px; padding: 8px 12px; border: 0; cursor: pointer;
  border-radius: 14px; color: #fff; text-align: left;
  background: linear-gradient(135deg, #0036ff, #c600ff);
  box-shadow: 0 6px 16px -8px rgba(120, 40, 255, 0.7);
  transition: transform 0.12s ease, filter 0.12s ease;
}
.gacha-grant:hover { filter: brightness(1.06); }
.gacha-grant:active { transform: translateY(1px); }
.gacha-grant:disabled { opacity: 0.55; cursor: default; filter: none; transform: none; }
.gacha-grant-ic { display: inline-flex; }
.gacha-grant-ic .material-symbols-rounded { font-size: 22px; }
.gacha-grant-body { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.gacha-grant-title { font-size: 12.5px; font-weight: 800; }
.gacha-grant-reason { font-size: 10.5px; font-weight: 600; opacity: 0.85; }
.gacha-grant-open {
  margin-left: 4px; font-size: 11px; font-weight: 800; letter-spacing: 0.3px; text-transform: uppercase;
  background: rgba(255, 255, 255, 0.2); border-radius: 999px; padding: 3px 9px;
}
.pack-odds { display: flex; flex-wrap: wrap; gap: 4px 10px; margin: 0 0 2px; font-size: 11.5px; color: var(--text-dim); }
.pack-odd { display: inline-flex; align-items: center; gap: 5px; }
.pack-odd-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--pack); }
.pack-odd-pct { color: var(--text-faint); font-variant-numeric: tabular-nums; }
/* Tier-tinted CTA: a calm tint for entry tiers, a solid fill on the top tier (extra pop). */
.pack-card .store-buy {
  background: color-mix(in srgb, var(--pack) 20%, var(--bg-elev-2)); color: var(--pack);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pack) 42%, transparent);
}
.pack-card.is-top .store-buy { background: var(--pack); color: #0a0b0d; box-shadow: none; }
.pack-card .store-buy:disabled { opacity: 0.5; }
/* The Genie agent card — the hero purchase, so it wears the brand gradient.
   Scoped to .pack-card so it can't leak into the agent-switcher dropdown,
   whose rows also use the .agent-card class. */
.pack-card.agent-card { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--grad-b) 34%, transparent); }
.pack-card.agent-card .pack-emblem { background: linear-gradient(135deg, var(--grad-a), var(--grad-b), var(--grad-c)); color: #fff; }
.pack-card.agent-card .store-buy { background: linear-gradient(120deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c)); color: #fff; box-shadow: none; }
.agent-card .store-buy:disabled { opacity: 0.5; }
.agent-tag { font-size: 11.5px; color: var(--text-faint); line-height: 1.4; margin: 0 0 2px; }

/* Upgrade card collection (Wallet screen's Cards tab) — a fixed 2-up grid of collectible
   tiles styled like the gacha reveal card itself (rarity-tinted border, dark radial
   interior, a glowing circular icon as the "art"), just shrunk down and static — so a card
   in your collection visually matches the moment you won it, not a generic list row.
   Fixed 2 columns per the design brief (not auto-fit) — a lone tile stays half-width,
   left-aligned, rather than stretching to fill the row. */
/* minmax(0,1fr) — NOT plain 1fr — so the columns can shrink below the cards' min-content
   instead of blowing the grid wider than its container (which let the mobile Wallet scroll
   sideways). The page then only ever scrolls vertically. */
.card-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
@media (max-width: 720px) { .card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.card-tile {
  --card-accent: #9aa0a6;
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 7px;
  /* Same proportions as the actual gacha reveal card (.gacha-spinner is 300×416) — width
     comes from the 3-up grid column, height follows the ratio (grows past it if content
     needs more, never shrinks below it), so a tile reads as a scaled-down replica, not a
     generic square box. */
  aspect-ratio: 300 / 416;
  padding: 16px 10px 12px;
  border-radius: var(--radius-lg);
  border: 2px solid transparent;
  background:
    radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--card-accent) 20%, #0a0b0d), #0a0b0d 78%) padding-box,
    linear-gradient(140deg, var(--card-accent), color-mix(in srgb, var(--card-accent) 55%, #000) 50%, var(--card-accent)) border-box;
  box-shadow: 0 14px 28px -14px rgba(0, 0, 0, 0.6), 0 0 22px -12px var(--card-accent);
}
.card-tile--mid { --card-accent: #ffce5a; }
.card-tile--high { --card-accent: #c084fc; }
.card-tile-head { position: absolute; top: 10px; left: 11px; right: 11px; display: flex; align-items: center; justify-content: space-between; }
.card-tile-rank { font-size: 10.5px; font-weight: 800; letter-spacing: 1px; color: var(--card-accent); }
.card-tile-gem { display: inline-flex; color: var(--card-accent); filter: drop-shadow(0 0 4px var(--card-accent)); }
.card-tile-gem .material-symbols-rounded { font-size: 13px; }
/* Progress-toward-next-star chip in the tile head — a target for the collection. */
.card-tile-prog {
  font-size: 10px; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: 0.2px;
  color: var(--card-accent); background: color-mix(in srgb, var(--card-accent) 15%, transparent);
  border-radius: 999px; padding: 2px 7px;
}
.card-tile-prog.is-ready { color: #fff; background: var(--card-accent); }
.card-tile-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; margin-top: 6px; border-radius: 50%; color: #fff;
  background: color-mix(in srgb, var(--card-accent) 18%, rgba(0, 0, 0, 0.5));
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--card-accent) 55%, transparent), 0 0 18px -4px var(--card-accent);
}
.card-tile-icon .material-symbols-rounded { font-size: 26px; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5)); }
/* Upgrade card art: the same upgrade.svg used in the gacha reveal, scaled for the collectible
   tile (a mini replica of the reveal card) instead of the old glyph-in-a-circle. */
.card-tile-art { display: block; width: 120px; height: 120px; margin: 6px 0 2px; filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.5)); }
.card-tile-count { font-size: 12.5px; font-weight: 700; color: rgba(255, 255, 255, 0.82); }
/* Same explainer copy as the reveal card / old list row — always visible, not a hover-only
   title attribute, so what the card is FOR doesn't require discovering a tooltip. */
.card-tile-desc { font-size: 11.5px; line-height: 1.42; text-align: center; color: rgba(255, 255, 255, 0.64); }
/* The general outcome of an upgrade — accent-tinted so it reads as the "why". */
.card-tile-result { font-size: 11.5px; line-height: 1.42; text-align: center; font-weight: 600; margin-top: 5px; color: color-mix(in srgb, var(--card-accent) 82%, #fff); }
.card-tile-prog {
  font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums; color: rgba(255, 255, 255, 0.62);
  background: rgba(255, 255, 255, 0.1); border-radius: 999px; padding: 3px 10px;
}
.card-tile-prog.is-ready { color: #0a0b0d; background: var(--card-accent); font-weight: 800; }

/* Gacha card (Wallet → Cards) — an openable .card-tile mirroring the gacha reveal's card BACK
   (the mystery card + white Superpower wordmark). Each PACK TIER gets its own colour palette via
   --pack, matching the Store/Reinvest pack-card hues (t1 silver → t4 gold): a tier-hued shimmer
   over a deep base + a tier glow, so a Cosmic pack reads distinctly from a free pull at a glance. */
.card-tile--gacha {
  --pack: #9aa0a6; /* default = Free pull / tier 1 */
  cursor: pointer; font: inherit; text-align: center;
  -webkit-appearance: none; appearance: none;
  border: 2px solid color-mix(in srgb, var(--pack) 60%, transparent);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--pack) 58%, #16102a),
    color-mix(in srgb, var(--pack) 30%, #100a1e) 54%,
    color-mix(in srgb, var(--pack) 64%, #1d1436) 100%);
  background-size: 200% 200%;
  animation: buy-card-aurora 9s ease-in-out infinite;
  box-shadow: 0 14px 28px -14px rgba(0, 0, 0, 0.6), 0 0 26px -12px var(--pack);
  transition: transform 0.12s var(--ease), filter 0.12s var(--ease);
}
/* Per-tier palette (same hues as .pack-card--t1..t4 in the Store/Reinvest packs). */
.card-tile--gacha.gacha-t1 { --pack: #9aa0a6; }
.card-tile--gacha.gacha-t2 { --pack: #38bdf8; }
.card-tile--gacha.gacha-t3 { --pack: #c084fc; }
.card-tile--gacha.gacha-t4 { --pack: #ffce5a; box-shadow: 0 14px 28px -14px rgba(0, 0, 0, 0.6), 0 0 34px -10px var(--pack); }
.card-tile--gacha:hover { filter: brightness(1.06); }
.card-tile--gacha:active { transform: translateY(1px); }
.card-tile--gacha:disabled { opacity: 0.55; cursor: default; }
.card-tile--gacha .card-tile-rank { color: #fff; }
.card-tile--gacha .card-tile-gem { color: #fff; filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.6)); }
/* Superpower wordmark emblem — matches .gacha-back-emblem / .gacha-back-logo on the reveal. */
.gacha-tile-emblem {
  display: flex; align-items: center; justify-content: center;
  width: 62%; aspect-ratio: 1 / 1; border-radius: 50%;
  background: radial-gradient(circle, rgba(0, 0, 0, 0.12) 30%, transparent 66%);
}
.gacha-tile-logo { width: 92%; opacity: 0.94; filter: brightness(0) invert(1) drop-shadow(0 0 12px rgba(255, 255, 255, 0.35)); }
/* "What's inside" loot overview — tiny frosted chips under the emblem teasing the drop CATEGORIES
   (upgrade cards / Credits / rare agent). Keep the emblem full-size (the wordmark is the hero); the
   loot chips tuck under it. */
.card-tile--gacha:has(.gacha-loot) .gacha-tile-emblem { width: 64%; }
/* Card layout: emblem+loot form one bottom-aligned block at the top, the price sits vertically
   centred, and the CTA pins to the bottom — so price + CTA land at the SAME height on every card no
   matter how many loot chips a pack shows. */
.card-tile--gacha { justify-content: space-between; }
.gacha-tile-body {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 8px; width: 100%; padding-top: 14px;
}
.gacha-loot { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px 5px; padding: 0 2px; }
.gacha-loot-chip {
  display: inline-flex; align-items: center; gap: 2.5px;
  font-size: 9px; font-weight: 700; line-height: 1; white-space: nowrap;
  color: rgba(255, 255, 255, 0.92);
  background: rgba(0, 0, 0, 0.28); border-radius: 999px; padding: 2.5px 7px 2.5px 5px;
}
.gacha-loot-chip .material-symbols-rounded { font-size: 11px; opacity: 0.95; }
/* Fungible stack count (×N) — white + bold so a stacked pack reads at a glance, like the star-up ×N. */
.card-tile--gacha .card-tile-count { color: #fff; font-weight: 800; font-size: 15px; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); }
/* Store version: the Power price sits where the ×N stack count does on a Wallet card. */
.gacha-tile-price { color: #fff; font-weight: 800; font-size: 15px; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); }
.gacha-tile-price .cur-icon { font-size: 17px; }
/* Reinvest version: a lock + $ price (opened with locked earnings, not Power). */
.gacha-tile-price--lock { display: inline-flex; align-items: center; gap: 4px; }
.gacha-tile-price--lock .material-symbols-rounded { font-size: 14px; opacity: 0.85; }
/* Reinvest sheet: the gacha packs wrap in a 2-col grid (like the Shop on mobile) — the sheet is
   ≤480px, so 2-up keeps the cards a good size (2+1 for the three packs). */
.reinvest-pack-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

/* Store currency card (Power / Credits) — SAME layout as the Gacha card: an absolute head
   (name + a currency gem, top-right), the amount as the centred hero (where the Gacha logo
   sits), then a chip + price + white CTA. Reuses the .card-tile gradient/glow, themed via
   --card-accent (Power = electric blue, Credits = gold). */
.card-tile--store {
  cursor: pointer; font: inherit; -webkit-appearance: none; appearance: none;
  container-type: inline-size; /* size the hero amount to the card width (cqw) so long numbers never truncate */
  transition: transform 0.12s var(--ease), filter 0.12s var(--ease);
}
.card-tile--store.is-top { box-shadow: 0 14px 28px -14px rgba(0, 0, 0, 0.6), 0 0 36px -10px var(--card-accent); }
.card-tile--store:hover { filter: brightness(1.07); }
.card-tile--store:active { transform: translateY(1px); }
.card-tile--store .card-tile-rank { color: rgba(255, 255, 255, 0.72); }
.card-tile--store .card-tile-gem { color: var(--card-accent); filter: drop-shadow(0 0 4px color-mix(in srgb, var(--card-accent) 55%, transparent)); }
/* "Best value" sits BOTTOM-CENTER, in the empty band below the centred CTA — top-right (image
   22/24) collided with the pack name. A ribbon-style pill anchored to the card's lower edge. */
.card-tile--store .store-badge {
  top: auto; right: auto; bottom: 10px; left: 50%; transform: translateX(-50%);
  background: var(--card-accent); color: #0a0b0d;
}
/* Hero occupies the EXACT footprint of the gacha card's emblem (62% width, 1:1) — same box, so
   the price + Buy below it land at the identical positions as the gacha card's. */
.store-hero { width: 62%; aspect-ratio: 1 / 1; margin: 0 auto; display: grid; place-items: center; }
/* The amount is a single unbroken unit sized to the card width — big on desktop, shrinking on
   narrow cards so even ◉3,000 fits without truncating. */
.store-hero .cur { font-weight: 800; color: #fff; white-space: nowrap; max-width: 100%; }
.store-hero .cur-val { font-size: min(44px, 21cqw); line-height: 1; }
.store-hero .cur-icon { font-size: min(30px, 15cqw); color: var(--card-accent); }
.card-tile--store .store-price { color: #fff; }
.card-tile--store .store-price .cur { color: #fff; }
.card-tile--store .store-price .cur-icon { color: var(--card-accent); font-size: 15px; }
/* Discount / bonus chip floats at TOP-CENTER (in the empty band under the name row), out of the
   flow so it never shifts the number/button. */
.store-chip {
  position: absolute; top: 34px; left: 50%; transform: translateX(-50%);
  font-size: 11px; font-weight: 800; white-space: nowrap;
  color: color-mix(in srgb, var(--card-accent) 82%, #fff);
  background: color-mix(in srgb, var(--card-accent) 16%, transparent);
  border-radius: 999px; padding: 3px 11px;
}
.card-tile-open {
  margin-top: 6px; font-size: 12px; font-weight: 800; letter-spacing: 0.3px;
  padding: 6px 18px; border-radius: 999px;
  color: #1a1030; background: #fff; box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.5);
}
@media (prefers-reduced-motion: reduce) { .card-tile--gacha { animation: none; } }

/* Star-up strip on a yield agent card — ONE loud number (the relative yield jump) with
   the star target under it and an x/x cards pill. The same strip in both states: lit
   (gradient, tappable) when a full card set is held, muted .is-locked otherwise. */
.yield-card-starup {
  display: flex; align-items: center; gap: 11px; width: 100%; margin-top: 10px;
  padding: 10px 12px; border: none; border-radius: var(--radius-md); cursor: pointer; font: inherit; text-align: left; color: #fff;
  background: linear-gradient(120deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c));
  box-shadow: 0 6px 18px -10px color-mix(in srgb, var(--grad-b) 90%, transparent);
  transition: transform var(--ease) 130ms, filter var(--ease) 130ms;
}
.yield-card-starup:hover { filter: brightness(1.06); }
.yield-card-starup:active { transform: scale(0.985); }
.starup-emblem {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
  border-radius: 50%; background: rgba(255, 255, 255, 0.18);
}
.starup-emblem .material-symbols-rounded { font-size: 18px; }
.starup-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.starup-headline { font-size: 16.5px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15; letter-spacing: 0.1px; }
.starup-sub { font-size: 11px; font-weight: 500; opacity: 0.85; margin-top: 1px; }
/* Card progress fill under the headline — thin, so "almost there" reads at a glance. */
.starup-bar {
  margin-top: 5px; height: 4px; border-radius: 999px; overflow: hidden;
  background: rgba(0, 0, 0, 0.24);
}
.starup-bar-fill {
  display: block; height: 100%; border-radius: 999px;
  background: rgba(255, 255, 255, 0.9); transition: width 0.5s ease;
}
.starup-bar.is-full .starup-bar-fill { background: #fff; box-shadow: 0 0 8px rgba(255, 255, 255, 0.7); }
.yield-card-starup.is-locked .starup-bar { background: color-mix(in srgb, var(--text) 8%, transparent); }
.yield-card-starup.is-locked .starup-bar-fill { background: color-mix(in srgb, var(--text) 34%, transparent); box-shadow: none; }
/* x/x cards pill — frosted dark so it reads on the gradient; dimmer when locked. */
.starup-prog {
  flex: none; font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums;
  color: #fff; background: rgba(0, 0, 0, 0.28); border-radius: 999px; padding: 4px 10px;
}
/* Locked variant: the full card set isn't held yet — same layout, non-interactive,
   flat elevation; the pill shows how far along the set is. */
.yield-card-starup.is-locked {
  cursor: default; color: var(--text-dim);
  background: var(--bg-elev-2); box-shadow: none;
}
.yield-card-starup.is-locked:hover { filter: none; }
.yield-card-starup.is-locked:active { transform: none; }
.yield-card-starup.is-locked .starup-emblem { background: color-mix(in srgb, var(--text) 9%, transparent); color: var(--text-faint); }
.yield-card-starup.is-locked .starup-sub { color: var(--text-faint); opacity: 1; }
.yield-card-starup.is-locked .starup-prog {
  color: var(--text-dim); background: color-mix(in srgb, var(--text) 9%, transparent);
}

/* Trial banner (v53): reinvest/gacha agents earn LOCKED scrip only until the first fee is
   paid. Warm amber "gated — unlock me" treatment, deliberately distinct from the brand-
   gradient star-up strip so it reads as a state to resolve, not a reward. The whole strip is
   the subscribe CTA (paying the fee clears the trial → cashable USDT + weekly credits switch on). */
.yield-trial {
  display: flex; align-items: center; gap: 11px; width: 100%; margin-top: 10px;
  padding: 10px 12px; border-radius: var(--radius-md); cursor: pointer; font: inherit; text-align: left;
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 12%, var(--bg-elev-1));
  transition: transform var(--ease) 130ms, filter var(--ease) 130ms;
}
.yield-trial:hover { filter: brightness(1.04); }
.yield-trial:active { transform: scale(0.985); }
.yield-trial-emblem {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
  border-radius: 50%; background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn);
}
.yield-trial-emblem .material-symbols-rounded { font-size: 18px; }
.yield-trial-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.yield-trial-headline { font-size: 14px; font-weight: 750; line-height: 1.15; color: var(--text); }
.yield-trial-sub { font-size: 11px; font-weight: 500; color: var(--text-dim); margin-top: 1px; }
/* Power cost to unlock — a gradient pill mirroring the extend CTA's spend affordance. */
.yield-trial-cost {
  flex: none; display: inline-flex; align-items: center; gap: 3px; font-size: 12.5px; font-weight: 800;
  font-variant-numeric: tabular-nums; color: var(--on-accent); border-radius: 999px; padding: 5px 11px;
  background: var(--grad);
}
.yield-trial-cost .material-symbols-rounded { font-size: 15px; }

/* Upline (sponsor) card on the Referral tab. */
.upline-card { display: flex; align-items: center; gap: 12px; background: var(--bg-elev-1); border-radius: var(--radius-md); padding: 13px 15px; margin-bottom: 12px; }
.upline-avatar {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-elev-2)); color: var(--accent);
}
.upline-avatar .material-symbols-rounded { font-size: 21px; }
.upline-main { min-width: 0; flex: 1; }
.upline-label { font-size: 11px; font-weight: 600; letter-spacing: 0.4px; text-transform: uppercase; color: var(--text-faint); }
.upline-name { font-size: 14.5px; font-weight: 650; color: var(--text); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upline-tag { flex: none; font-size: 11px; font-weight: 600; color: var(--text-dim); padding: 3px 10px; border-radius: 999px; background: var(--bg-elev-2); }

/* ===== Gacha reveal: swipe-up → flashy spin/flip → prize (the dopamine moment) ===== */
.gacha-stage {
  /* Above the reinvest/yield slide-in sheet (z 1100) + nav menus (1300/1400) — the reveal
     is a full-screen takeover moment, nothing should sit over it. */
  position: fixed; inset: 0; z-index: 1500; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
  /* Dark theatrical takeover (theme-independent) so the card, rays + particles all glow. The
     backdrop is near-opaque + heavily blurred so the earn page behind dissolves into a soft
     vignette (a lighter blur let the hero card bleed through as an obvious rectangle). */
  background:
    radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--grad-b) 40%, transparent), transparent 66%),
    rgba(7, 8, 11, 0.95);
  /* Light blur only — the 0.95-opaque backdrop already hides the page, and a large full-screen
     backdrop-filter re-composites every frame behind the animating card (a real FPS cost). */
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); opacity: 0; transition: opacity 220ms var(--ease);
  touch-action: none; user-select: none; -webkit-user-select: none;
}
.gacha-stage.is-open { opacity: 1; }
.gacha-stage.flash::before {
  content: ""; position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none;
  animation: gacha-flash 720ms var(--ease) forwards;
}
@keyframes gacha-flash { 0% { opacity: 0; } 10% { opacity: 0.6; } 100% { opacity: 0; } }
/* Reveal impact: the whole card jolts (screen-shake feel) and the prize icon punches in. */
.gacha-stage.shake .gacha-card-wrap { animation: gacha-shake 520ms var(--ease); }
@keyframes gacha-shake {
  0% { transform: translate(0, 0) rotate(0deg); }
  12% { transform: translate(-10px, 6px) rotate(-1.6deg); }
  26% { transform: translate(9px, -7px) rotate(1.4deg); }
  40% { transform: translate(-7px, 5px) rotate(-1deg); }
  56% { transform: translate(6px, -4px) rotate(0.8deg); }
  72% { transform: translate(-4px, 3px) rotate(-0.5deg); }
  88% { transform: translate(2px, -1px) rotate(0.2deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
.gacha-stage.is-revealed .gacha-reveal-icon { animation: gacha-pop 540ms var(--ease); }
@keyframes gacha-pop { 0% { transform: scale(1); } 32% { transform: scale(1.3); } 66% { transform: scale(0.94); } 100% { transform: scale(1); } }

.gacha-card-wrap { position: relative; z-index: 1; perspective: 1200px; cursor: grab; touch-action: none; }
.gacha-card-wrap:active { cursor: grabbing; }
/* The spinner rotates BOTH stacked cards as one plane; we crossfade mystery→prize at the
   edge-on midpoint (no preserve-3d / backface-visibility — those are fragile and a `filter`
   silently breaks them). Spin ends at 720° (= upright) so the prize text never mirrors. */
/* 1.5× bigger (was 196×264) so the prize reads like a real collectible card. */
.gacha-spinner { --glow: rgba(255, 255, 255, 0.9); position: relative; width: 300px; height: 416px; animation: gacha-float 3s ease-in-out infinite; }
.gacha-spinner.is-spinning { animation: gacha-spin 1s var(--ease) forwards; }
.gacha-stage.is-revealed .gacha-spinner { animation: none; transform: none; }
/* BorderGlow (reactbits.dev/components/border-glow) — a bright arc that travels around the
   card's border, autoplaying. Masked conic gradient = border ring only; drop-shadow = bloom.
   The glow tints to the prize value once revealed (stays white on a common pull). */
@property --gborder { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
.gacha-spinner::before {
  content: ""; position: absolute; inset: -3px; z-index: 5; pointer-events: none;
  border-radius: calc(var(--radius-lg) + 3px); padding: 3px;
  background: conic-gradient(from var(--gborder), transparent 52%, var(--glow) 76%, #fff 82%, var(--glow) 88%, transparent 96%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  filter: drop-shadow(0 0 6px var(--glow));
  animation: gborder-spin 3s linear infinite;
}
@keyframes gborder-spin { to { --gborder: 360deg; } }
.gacha-stage--rare.is-revealed .gacha-spinner { --glow: #7dd3fc; }
.gacha-stage--epic.is-revealed .gacha-spinner { --glow: #e879f9; }
.gacha-stage--legendary.is-revealed .gacha-spinner { --glow: #ffce5a; }
@keyframes gacha-float { 0%, 100% { transform: translateY(0) rotateY(0deg); } 50% { transform: translateY(-10px) rotateY(0deg); } }
@keyframes gacha-spin { 0% { transform: rotateY(0deg) scale(0.92); } 60% { transform: rotateY(540deg) scale(1.16); } 100% { transform: rotateY(720deg) scale(1); } }
.gacha-card {
  position: absolute; inset: 0; border-radius: var(--radius-lg); overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; text-align: center;
  box-shadow: 0 30px 56px -14px rgba(0, 0, 0, 0.65); transition: opacity 180ms linear;
}
/* Pre-reveal mystery card: the default agent-card look (the store buy-card's animated aurora
   gradient) with the translucent LetterGlitch matrix (a `.gtc-glitch` canvas) over it. The 3px
   border matches the glow ring + the revealed prize card's border. */
.gacha-card--back {
  opacity: 1; border: 3px solid rgba(255, 255, 255, 0.55);
  background: linear-gradient(125deg, var(--grad-a), var(--grad-b) 42%, var(--grad-c) 72%, var(--grad-a) 100%);
  background-size: 240% 240%;
  box-shadow: 0 30px 56px -14px rgba(0, 0, 0, 0.65);
  animation: buy-card-aurora 9s ease-in-out infinite;
}
.gacha-stage.show-front .gacha-card--back { opacity: 0; }
.gacha-stage.show-front .gacha-card--front { opacity: 1; }
/* Glyph alpha lives in the colours (≈30% white); the canvas itself stays fully opaque. */
.gtc-glitch { position: absolute; inset: 0; z-index: 0; opacity: 1; }
/* Superpower wordmark over a very soft scrim (dimmed — no hard black circle).
   The logo art is a DARK wordmark → brightness(0) invert(1) makes it white (matches .buy-card-logo). */
.gacha-back-emblem {
  position: relative; z-index: 1; display: flex; align-items: center; justify-content: center;
  width: 64%; aspect-ratio: 1 / 1; border-radius: 50%;
  background: radial-gradient(circle, rgba(0, 0, 0, 0.1) 30%, transparent 66%);
}
.gacha-back-logo { width: 82%; opacity: 0.92; filter: brightness(0) invert(1) drop-shadow(0 0 12px rgba(255, 255, 255, 0.3)); }
.gacha-shimmer {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 36%, rgba(255, 255, 255, 0.4) 50%, transparent 64%);
  transform: translateX(-120%); animation: gacha-sheen 2.4s var(--ease) infinite;
}
@keyframes gacha-sheen { 0% { transform: translateX(-120%); } 55%, 100% { transform: translateX(120%); } }

/* ----- Prize as a collectible (Pokémon-style) trading card ----- */
/* One per-rarity accent var drives the holographic border, gem, art glow, and icon tint. */
.gacha-card--front {
  --gtc-border: linear-gradient(140deg, #e2e8f0, #94a3b8 50%, #e2e8f0);
  --gtc-accent: #b8c2cf;
  opacity: 0; align-items: stretch; padding: 11px; border: 3px solid transparent;
  /* Dark rarity-tinted interior so the full-bleed pixels glow over it (screen blend). */
  background:
    radial-gradient(circle at 50% 42%, color-mix(in srgb, var(--gtc-accent) 18%, #0a0b0d), #0a0b0d 80%) padding-box,
    var(--gtc-border) border-box;
  box-shadow: 0 30px 56px -14px rgba(0, 0, 0, 0.65);
}
/* Radiating light shafts behind the card — a canvas burst (earnings.ui.ts createRays) whose
   rays vary in LENGTH + WIDTH so they fill the surrounding space, esp. on desktop. The burst
   is value-tinted + slow-spun in JS; this radial mask dissolves the far ends into the dark
   before the viewport edges (no hard rectangle / no edge-to-edge "hyperspace" wall). */
.gacha-rays {
  position: absolute; inset: 0; z-index: 0; opacity: 0; pointer-events: none;
  transition: opacity 700ms var(--ease);
  -webkit-mask: radial-gradient(circle at 50% 50%, #000 30%, transparent 72%);
  mask: radial-gradient(circle at 50% 50%, #000 30%, transparent 72%);
}
.gacha-rays.is-on { opacity: 1; }
.gtc-art-glow {
  position: absolute; inset: 0; z-index: 1; margin: auto; width: 62%; height: 46%; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--gtc-accent) 55%, transparent), transparent 70%); filter: blur(16px);
}
/* Light-pillar card background (reactbits.dev/backgrounds/light-pillar, reimplemented in CSS
   to avoid a Three.js dependency): a value-tinted vertical beam with a slow volumetric shimmer,
   revealed with the prize. Deliberately DIM (screen blend + low-opacity layers) so it never
   competes with the icon / prize text above it (z-index 2). */
.gtc-pillar {
  position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  border-radius: inherit; opacity: 0; transition: opacity 700ms var(--ease);
  mix-blend-mode: screen; --pillar: var(--gtc-accent);
}
.gacha-stage.is-revealed .gtc-pillar { opacity: 1; }
/* the beam: bright-top → value → dark-bottom, with a soft column falloff to the card edges */
.gtc-pillar::before {
  content: ""; position: absolute; left: 50%; top: -22%; bottom: -22%; width: 78%;
  transform: translateX(-50%);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--pillar) 55%, #fff) 0%,
    var(--pillar) 46%,
    color-mix(in srgb, var(--pillar) 50%, #000) 100%);
  -webkit-mask: radial-gradient(56% 94% at 50% 46%, #000 6%, rgba(0, 0, 0, 0.4) 40%, transparent 74%);
  mask: radial-gradient(56% 94% at 50% 46%, #000 6%, rgba(0, 0, 0, 0.4) 40%, transparent 74%);
  filter: blur(16px); opacity: 0.4;
}
/* slow volumetric shimmer drifting up the beam */
.gtc-pillar::after {
  content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 78%;
  transform: translateX(-50%);
  background: linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, 0.85) 50%, transparent 100%);
  background-repeat: no-repeat; background-size: 100% 42%; background-position: 50% 50%;
  -webkit-mask: linear-gradient(90deg, transparent, #000 50%, transparent);
  mask: linear-gradient(90deg, transparent, #000 50%, transparent);
  filter: blur(12px); opacity: 0.22;
  animation: gtc-pillar-flow 6s ease-in-out infinite;
}
@keyframes gtc-pillar-flow { 0% { background-position: 50% 128%; } 100% { background-position: 50% -28%; } }
/* Prismatic-burst card background (reactbits.dev/backgrounds/prismatic-burst via the shared
   WebGL2 port in prismaticBurst.ts) — agent-prize cards only: tier-tinted rays radiating out
   from behind the mascot, revealed with the prize. Replaces the pillar on those cards. The
   canvas clears to opaque black, so screen-blend it like the pillar: black is neutral under
   screen, leaving the card's rarity-tinted interior visible with the rays added on top. */
.gtc-burst {
  position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  border-radius: inherit; opacity: 0; transition: opacity 700ms var(--ease);
  mix-blend-mode: screen;
}
.gacha-stage.is-revealed .gtc-burst { opacity: 1; }
/* Scrim over the ray field: dims the top (rarity chip + name) and bottom (description)
   bands so the text stays legible, while the centre — behind the mascot — stays hot.
   Painted INSIDE the screen-blended host, so darkening here just fades the rays out. */
.gtc-burst::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.62), transparent 34%, transparent 60%, rgba(0, 0, 0, 0.68));
}
/* The minted agent's live mascot, where the icon medallion sits on other prizes. Sized so
   the resting ball (~48% of the short side) reads close to the 100px medallion while keeping
   the headroom the reference jump/celebrate arcs need. */
.gtc-mascot { display: block; width: 172px; height: 172px; }
/* Currency prize art — the branded animated SVGs (credit coin / power bolt / XP badge),
   free-floating like the agent card's mascot rather than boxed in the icon medallion. */
.gtc-prize-img { display: block; width: 116px; height: 116px; filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.45)); }
/* The upgrade card's crown/star pop (upgrade.svg) — its artwork sits smaller inside a
   500x500 viewBox than the currency icons, so it gets a larger box to match their weight. */
.gtc-prize-img--upgrade { width: 168px; height: 168px; }
.gtc-foil {
  position: absolute; inset: 0; z-index: 4; pointer-events: none; border-radius: var(--radius-lg);
  background: linear-gradient(115deg, transparent 42%, rgba(255, 255, 255, 0.14) 49%, transparent 56%);
  transform: translateX(-120%); animation: gacha-sheen 3s ease-in-out infinite;
}
/* Prize CONTENT — its own layer, hidden until the card lands (the shake), then it pops in.
   Three rows: NAME centered in the top half, ICON dead-centre, DESCRIPTION in the bottom
   half. The rarity chip is pinned to the top corners, out of the row flow. */
.gtc-content {
  position: relative; z-index: 2; flex: 1; width: 100%;
  display: grid; grid-template-rows: 1fr auto 1fr; align-items: center; justify-items: center;
  opacity: 0; transition: opacity 260ms var(--ease);
}
.gacha-stage.is-revealed .gtc-content { opacity: 1; }
.gtc-header { position: absolute; top: 0; left: 0; right: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between; padding: 1px 3px; }
.gacha-card--front .gacha-reveal-rarity { font-size: 11px; font-weight: 800; letter-spacing: 1.6px; text-transform: uppercase; color: var(--gtc-accent); }
.gtc-gem { display: inline-flex; color: var(--gtc-accent); filter: drop-shadow(0 0 6px var(--gtc-accent)); }
.gtc-gem .material-symbols-rounded { font-size: 19px; }
/* NAME — the card's headline, centred in the top half. Theme-independent light: the card is
   always dark, so the theme --text token would vanish against it. */
.gtc-name { align-self: center; text-align: center; padding: 0 8px; font-size: 25px; font-weight: 800; line-height: 1.15; letter-spacing: -0.3px; color: #f4f6fb; }
/* ICON — dead-centre of the card. */
.gtc-icon-wrap { display: flex; align-items: center; justify-content: center; }
.gacha-card--front .gacha-reveal-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100px; height: 100px; margin: 0; border-radius: 50%; color: #fff;
  background: color-mix(in srgb, var(--gtc-accent) 16%, rgba(0, 0, 0, 0.5));
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--gtc-accent) 60%, transparent), 0 0 30px -4px var(--gtc-accent);
}
.gacha-card--front .gacha-reveal-icon .material-symbols-rounded { font-size: 56px; filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5)); }
/* DESCRIPTION — one-sentence use case, centred in the bottom half. */
.gtc-desc { align-self: center; text-align: center; max-width: 90%; font-size: 12px; line-height: 1.45; color: rgba(255, 255, 255, 0.62); }
/* Card-progress line on a card reveal — "7/9 to ★4 · name" (or a "ready" call-out). Frosted
   pill in the card accent so the win means something on the spot. */
.gtc-progress {
  align-self: center; margin-top: 8px; padding: 4px 11px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: 0.1px;
  color: #fff; background: rgba(255, 255, 255, 0.14); border: 1px solid rgba(255, 255, 255, 0.22);
}
.gtc-progress.is-ready { background: color-mix(in srgb, var(--gtc-accent, #38bdf8) 34%, transparent); border-color: transparent; }
/* Rarity → accent var + outer glow (icon gold-fills on legendary). */
.gacha-stage--rare .gacha-card--front { --gtc-border: linear-gradient(140deg, #7dd3fc, #0ea5e9 50%, #7dd3fc); --gtc-accent: #38bdf8; box-shadow: 0 30px 56px -14px rgba(0, 0, 0, 0.65), 0 0 48px -8px #0ea5e9; }
.gacha-stage--epic .gacha-card--front { --gtc-border: linear-gradient(140deg, #e879f9, #a855f7 50%, #e879f9); --gtc-accent: #c084fc; box-shadow: 0 30px 56px -14px rgba(0, 0, 0, 0.65), 0 0 54px -6px #a855f7; }
.gacha-stage--legendary .gacha-card--front { --gtc-border: linear-gradient(140deg, #fde047, #f5a623 50%, #fde047); --gtc-accent: #ffce5a; box-shadow: 0 30px 56px -14px rgba(0, 0, 0, 0.65), 0 0 72px -6px #ffce5a; }
.gacha-stage--legendary .gacha-card--front .gacha-reveal-icon { background: linear-gradient(135deg, #ffce5a, #f5a623); color: #1a1400; }
/* Hints. */
.gacha-hint, .gacha-continue { position: relative; z-index: 1; }
.gacha-hint { display: inline-flex; flex-direction: column; align-items: center; gap: 1px; color: rgba(255, 255, 255, 0.82); font-size: 13px; font-weight: 600; }
.gacha-hint-arrow { font-size: 26px !important; color: #fff; animation: gacha-bob 1.2s var(--ease) infinite; }
@keyframes gacha-bob { 0%, 100% { transform: translateY(0); opacity: 0.65; } 50% { transform: translateY(-8px); opacity: 1; } }
.gacha-continue { font-size: 12px; color: rgba(255, 255, 255, 0.6); opacity: 0; transition: opacity 240ms var(--ease); }
.gacha-stage.is-revealed .gacha-continue { opacity: 1; }
.gacha-stage.is-revealing .gacha-hint, .gacha-stage.is-revealed .gacha-hint { display: none; }
/* Booster-pack progress dots — one per card in the hand; fills as each is flipped. */
.gacha-progress { position: relative; z-index: 1; display: inline-flex; gap: 7px; align-items: center; }
.gacha-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, 0.22); transition: background 220ms var(--ease), transform 220ms var(--ease); }
.gacha-dot.is-done { background: #fff; transform: scale(1.15); }
/* Confetti on reveal reuses the shared new-agent-deploy shower (fireConfetti / .confetti-canvas). */

/* ===== Star-up confirm — an agent card: name/star head, the mascot idling over a
   prismatic-burst backdrop, the three stats the upgrade moves (+% gains in green),
   and Cancel / Upgrade (x/x). The burst canvas fills the card behind everything. ===== */
.upg-overlay {
  /* Above the Yield sheet (z 1100) it's launched from — matches .confirm-overlay (1400). */
  position: fixed; inset: 0; z-index: 1500; display: flex; align-items: center; justify-content: center; padding: 20px;
  background: color-mix(in srgb, var(--bg) 70%, transparent); backdrop-filter: blur(5px);
  opacity: 0; transition: opacity 200ms var(--ease);
}
.upg-overlay.is-open { opacity: 1; }
.upg-card {
  position: relative; overflow: hidden;
  /* Same footprint + radius + throw as the gacha prize card (300×416) so the two
     card moments in the reinvest loop read as one family. */
  width: 300px; height: 416px; max-width: 100%;
  display: flex; flex-direction: column;
  border-radius: var(--radius-lg); padding: 16px;
  /* Near-black base so the burst's additive rays carry the card's color story. */
  background: #08080c;
  box-shadow: 0 30px 56px -14px rgba(0, 0, 0, 0.65), inset 0 0 0 1.5px rgba(255, 255, 255, 0.14);
  transform: translateY(12px) scale(0.96); opacity: 0; transition: transform 280ms var(--ease), opacity 200ms var(--ease);
}
.upg-overlay.is-open .upg-card { transform: none; opacity: 1; }
/* Success beat 1 — crescendo shake: barely a tremor at first, vibrant by the end
   (amplitude + tilt grow through the keyframes; ends mid-throw, hidden by the flash). */
.upg-card.is-shaking { animation: upg-shake-card 500ms linear; }
@keyframes upg-shake-card {
  0% { transform: translate(0, 0) rotate(0deg); }
  15% { transform: translate(-0.5px, 0.5px) rotate(-0.1deg); }
  30% { transform: translate(1px, -1px) rotate(0.15deg); }
  45% { transform: translate(-2px, 1.5px) rotate(-0.3deg); }
  60% { transform: translate(3px, -2.5px) rotate(0.5deg); }
  72% { transform: translate(-4.5px, 3.5px) rotate(-0.8deg); }
  84% { transform: translate(6px, -4.5px) rotate(1.1deg); }
  92% { transform: translate(-7px, 5px) rotate(-1.3deg); }
  100% { transform: translate(7px, -5px) rotate(1.4deg); }
}
/* Success beat 2 — the card bounces up and spins a full turn, landing upright
   (360° = upright, so the face never ends mirrored). Perspective comes from the
   overlay so the spin reads as 3D. */
.upg-overlay { perspective: 1200px; }
.upg-card.is-spinning { animation: upg-card-spin 750ms var(--ease); }
@keyframes upg-card-spin {
  0% { transform: rotateY(0deg) translateY(0) scale(1); }
  45% { transform: rotateY(200deg) translateY(-16px) scale(1.1); }
  100% { transform: rotateY(360deg) translateY(0) scale(1); }
}
/* Well dimmed — an ambient glow behind the mascot, not a light show; the mascot
   + stats stay the brightest things on the card. On upgrade success it flares
   to full brightness (.is-bright + a live intensity/speed bump) to celebrate. */
.upg-burst { position: absolute; inset: 0; pointer-events: none; opacity: 0.5; transition: opacity 600ms var(--ease); }
.upg-burst.is-bright { opacity: 1; }
.upg-card-head { position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px; color: #fff; }
.upg-card-name {
  font-family: var(--font-display); font-size: 18px; font-weight: 600;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.45);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.upg-star-slot { display: inline-flex; flex: none; }
/* Star glaze — when the badge steps up: a scale pop plus a shine sweeping across it. */
.upg-star-glaze { position: relative; overflow: hidden; border-radius: 999px; animation: upg-star-pop 620ms var(--ease); }
.upg-star-glaze::after {
  content: ""; position: absolute; inset: -40%;
  background: linear-gradient(115deg, transparent 32%, rgba(255, 255, 255, 0.95) 50%, transparent 68%);
  transform: translateX(-130%); animation: upg-star-shine 900ms var(--ease) 140ms forwards;
}
@keyframes upg-star-pop { 0% { transform: scale(1); } 35% { transform: scale(1.4); } 70% { transform: scale(0.94); } 100% { transform: scale(1); } }
@keyframes upg-star-shine { to { transform: translateX(130%); } }
/* The mascot floats over the burst's bright core; its canvas is transparent.
   80% of the stage (not full-bleed) so the card keeps air around it. */
.upg-stage { position: relative; z-index: 1; flex: 1; min-height: 0; }
.upg-mascot { position: absolute; inset: 0; margin: auto; display: block; width: 80%; height: 80%; cursor: pointer; }
/* Stat tiles — Gemini-style hierarchy: the display-face number leads, the green
   gain chip is the second read, the label whispers. Perspective is for the
   post-upgrade flip, where each tile turns edge-on and lands on its new value. */
.upg-stats { position: relative; z-index: 1; display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; perspective: 640px; }
.upg-stat.is-flipping { animation: upg-stat-flip 460ms var(--ease); }
@keyframes upg-stat-flip { 0% { transform: rotateX(0deg); } 50% { transform: rotateX(92deg); } 100% { transform: rotateX(0deg); } }
.upg-stat {
  display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 10px 4px 8px;
  border-radius: var(--radius-md); text-align: center; color: #fff;
  background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(3px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.09);
}
.upg-stat-value { font-family: var(--font-display); font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.upg-stat-delta {
  font-size: 11.5px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ok);
  background: color-mix(in srgb, var(--ok) 16%, transparent); border-radius: 999px; padding: 1.5px 8px;
}
.upg-stat-label { font-size: 10px; color: rgba(255, 255, 255, 0.6); }
/* Placeholder delta (previewStarUp) — reserves the chip's height so a stat with no % gain still
   lines its value/label up with the others. */
.upg-stat-delta--ghost { visibility: hidden; }
/* Preview-only bits (previewStarUp): a subtitle under the name + a card-progress bar above the
   actions. Reuse the ceremony's card chrome; these just fill the "not ready yet" state. */
.upg-preview-sub {
  position: relative; z-index: 1; text-align: center; margin: -2px 0 8px;
  font-size: 12.5px; font-weight: 700; letter-spacing: 0.2px;
  color: color-mix(in srgb, var(--grad-b) 60%, #fff);
}
.upg-prog { position: relative; z-index: 1; margin-top: 12px; }
.upg-prog-bar { height: 8px; border-radius: 999px; background: var(--bg-elev-3); overflow: hidden; }
.upg-prog-fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c)); transition: width 0.3s var(--ease); }
.upg-prog-label { margin-top: 7px; text-align: center; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-dim); }
/* Clear breathing room between the progress message and the action buttons (they were crowded). */
.upg-prog + .upg-actions { margin-top: 18px; }
.upg-actions { position: relative; z-index: 1; display: flex; gap: 8px; margin-top: 12px; }
/* The 300px card leaves ~130px per button — tighten so "Upgrade (x/x)" stays one line. */
.upg-actions .upg-btn { padding: 11px 6px; font-size: 13.5px; white-space: nowrap; }
.upg-actions .upg-btn--go { flex: 1.35; }
/* The screen joins the card's crescendo — same subtle→vibrant ramp, smaller travel. */
.upg-screen-shake { animation: upg-shake-screen 550ms linear both; }
@keyframes upg-shake-screen {
  0% { transform: translate(0, 0); }
  25% { transform: translate(0.5px, -0.5px); }
  45% { transform: translate(-1px, 1px); }
  62% { transform: translate(2px, -1.5px); }
  76% { transform: translate(-3px, 2px); }
  88% { transform: translate(4px, -3px); }
  100% { transform: translate(0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .upg-card.is-shaking, .upg-card.is-spinning, .upg-screen-shake,
  .upg-star-glaze, .upg-star-glaze::after, .upg-stat.is-flipping { animation: none; }
}
.upg-btn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 12px; border: none; border-radius: 999px; cursor: pointer; font: inherit; font-size: 14px; font-weight: 650; transition: transform var(--ease) 120ms, filter var(--ease) 120ms; }
.upg-btn:active { transform: scale(0.97); }
.upg-btn--ghost { background: var(--bg-elev-3); color: var(--text); }
.upg-btn--go { background: linear-gradient(120deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c)); color: #fff; }
.upg-btn--go:hover { filter: brightness(1.06); }
.upg-btn--go .material-symbols-rounded { font-size: 18px; }

@media (prefers-reduced-motion: reduce) {
  .gacha-spinner, .gacha-spinner.is-spinning { animation: none; }
  .gacha-spinner::before { animation: none; }
  .gacha-shimmer, .gacha-hint-arrow, .gtc-pillar::after { animation: none; }
  .gacha-stage.flash::before { animation: none; }
  .gacha-stage.shake .gacha-card-wrap { animation: none; }
  .gacha-stage.is-revealed .gacha-reveal-icon { animation: none; }
  .upg-card { transition: opacity 200ms var(--ease); transform: none; }
}
@media (max-width: 480px) { .earn-pockets { grid-template-columns: 1fr; } }

/* Hide the authed app shell until boot.ts confirms the user is signed in. The Tasks
   view is static HTML, so without this it paints for the ~0.5–1s the /api/me check is
   in flight before the sign-in screen comes up (a flash of authenticated content for
   logged-out visitors). index.html sets .is-booting before first paint; boot.ts clears
   it the moment auth passes. visibility (not display) avoids a reflow on reveal. */
.is-booting .layout { visibility: hidden; }

/* Full-page boot splash (index.html #app-splash): covers the base layout until
   the home is composed. Torn down via html.is-app-ready (boot.ts gates, main.ts
   first task paint, or the 10s inline failsafe). DELIBERATELY below every gate/
   overlay (.auth-overlay 1000, welcome 9999): a user-blocking gate must never be
   masked by the loader while its promise pends. */
#app-splash {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}
/* Two-act splash, "charge-up → burst" (owner note: make it exciting). The mascot
   is BUILT in CSS — no JS, no asset fetch, it must animate in the pre-bundle window.
   ACT 1 · CHARGE: the camera lives inside the face (bg = head color, scale(3.4)):
   big glossy eyes act — saccade darts, squash-stretch blinks, focus squints — while
   energy motes get pulled INTO the face, an aura breathes, and the camera sways.
   ACT 2 · BURST (html.is-app-ready, from main.ts): eyes flash white → shockwave
   rings → the camera slams back with overshoot → radial particle burst as ring/
   sparks ignite → the mascot does a 360° flip-jump with a squash landing → smile
   eyes + "Ready!" bubble slams in → overlay fades. Background eases to a DIM scrim
   at t=0 (not transparent): the home is visible-but-dimmed so the celebration reads
   against it, and stays interactive (pointer-events: none). The outro fade
   (splash-gone) lifts the dim together with the mascot.
   Gate paths (sign-in / agent picker / failsafe) use is-splash-skip: instant hide. */
#app-splash { background: #0b0b0f; overflow: hidden; }
html.is-splash-skip #app-splash { display: none; }
html.is-app-ready #app-splash {
  /* flat rgba (not a gradient) so the 0.4s background transition from the opaque
     act-1 color actually interpolates — background-image swaps are instant */
  background: rgba(11, 11, 15, 0.65);
  pointer-events: none;
  animation: splash-gone 0.25s ease 1.5s forwards;
  transition: background 0.4s ease;
}
.splash-scan {
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 40%, rgba(120, 80, 255, 0.09) 50%, transparent 60%);
  background-size: 300% 100%;
  animation: splash-scan 3.2s ease-in-out infinite;
  pointer-events: none;
}
html.is-app-ready .splash-scan { display: none; }
.splash-flash {
  /* act-2 opener: a full-screen white/violet pop, the "ignition" */
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.95), rgba(198, 0, 255, 0.3) 42%, transparent 72%);
  opacity: 0;
  pointer-events: none;
}
html.is-app-ready .splash-flash { animation: splash-flash 0.5s ease-out forwards; }
.splash-zoom { transform: scale(5); animation: splash-creep 9s ease-in-out infinite alternate; }
html.is-app-ready .splash-zoom { animation: splash-slam-out 0.55s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
.splash-sway { animation: splash-sway 6.5s ease-in-out infinite alternate; }
html.is-app-ready .splash-sway { animation: none; }
.splash-mascot {
  position: relative;
  width: 118px;
  height: 118px;
  animation: splash-bob 2.4s ease-in-out infinite;
}
html.is-app-ready .splash-mascot { animation: splash-flip-land 0.7s cubic-bezier(0.34, 1.3, 0.5, 1) 0.28s both; }
.splash-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(from 0deg, #0036ff, #c600ff, #0036ff);
  opacity: 0; /* act 1: inside the face — no ring */
  animation: splash-spin 2.2s linear infinite;
}
html.is-app-ready .splash-ring {
  animation: splash-spin 2.2s linear infinite, splash-bloom 0.3s ease 0.28s forwards;
}
.splash-ring::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: inherit;
  filter: blur(16px);
  opacity: 0.55;
  z-index: -1;
}
.splash-head {
  position: absolute;
  inset: 9px;
  border-radius: 50%;
  /* act 1: TRANSPARENT — the screen must hold nothing but the two eyes; any
     face-colored disc silhouettes against ambient light (owner: no face pane).
     The head fills in under the ignition flash, before the zoom-out reveals it. */
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
}
html.is-app-ready .splash-head { animation: splash-headfill 0.2s ease 0.08s forwards; }
.splash-gaze {
  /* saccade layer: fast darts move BOTH eyes; blinks live on the eyes themselves */
  display: flex;
  align-items: center;
  gap: 16px;
  animation: splash-gaze 4.6s ease-in-out infinite;
}
html.is-app-ready .splash-gaze { animation: none; }
.splash-eye {
  width: 13px;
  height: 34px;
  border-radius: 99px;
  background: linear-gradient(180deg, #ffffff 0%, #cfcfda 100%);
  box-shadow: 0 0 10px rgba(255, 255, 255, 0.28);
  position: relative;
  transform-origin: 50% 30%;
  animation: splash-eyes-act 4.6s ease-in-out infinite;
}
.splash-eye::after {
  /* gloss dot — the eyes read shiny/alive at close-up */
  content: "";
  position: absolute;
  top: 8%;
  left: 20%;
  width: 42%;
  height: 15%;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.95);
}
html.is-app-ready .splash-eye { animation: splash-smile 0.3s ease 0.88s forwards; }
.splash-halo {
  position: absolute;
  inset: -70px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(98, 54, 255, 0.3), transparent 62%);
  opacity: 0; /* act 1: OFF — a head-shaped glow silhouetted the circle (owner: no circle) */
  animation: splash-halo 2.4s ease-in-out infinite;
  pointer-events: none;
}
html.is-app-ready .splash-halo {
  animation: splash-halo 2.4s ease-in-out infinite, splash-bloom 0.3s ease 0.28s forwards;
}
/* ACT 1 · charge motes: energy pulled INTO the face from all directions */
.splash-charge {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  animation: splash-charge 1.35s ease-in infinite;
}
html.is-app-ready .splash-charge { display: none; }
.ch1 { --cx: -128px; --cy: -54px; background: #6aa6ff; }
.ch2 { --cx: 134px; --cy: -38px; background: #c600ff; animation-delay: 0.22s; }
.ch3 { --cx: 92px; --cy: 108px; background: #e4e4e4; animation-delay: 0.45s; width: 4px; height: 4px; }
.ch4 { --cx: -104px; --cy: 92px; background: #0036ff; animation-delay: 0.65s; }
.ch5 { --cx: -8px; --cy: -140px; background: #c08bf5; animation-delay: 0.9s; width: 4px; height: 4px; }
.ch6 { --cx: 16px; --cy: 136px; background: #6aa6ff; animation-delay: 1.1s; width: 4px; height: 4px; }
.ch7 { --cx: 142px; --cy: 42px; background: #e4e4e4; animation-delay: 1.32s; width: 3px; height: 3px; }
.ch8 { --cx: -140px; --cy: -8px; background: #c600ff; animation-delay: 1.5s; width: 3px; height: 3px; }
/* ACT 2 · orbit sparks (ignite with the ring) */
.splash-spark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}
.sp1 { --r: 74px; background: #6aa6ff; }
.sp2 { --r: 74px; background: #c08bf5; width: 4px; height: 4px; }
.sp3 { --r: 74px; background: #e4e4e4; width: 4px; height: 4px; }
.sp4 { --r: 92px; background: #c600ff; }
.sp5 { --r: 92px; background: #0091ff; width: 4px; height: 4px; }
.sp6 { --r: 92px; background: #e4e4e4; width: 3px; height: 3px; }
html.is-app-ready .sp1 { animation: splash-orbit 3.8s linear infinite, splash-bloom 0.3s ease 0.34s forwards; }
html.is-app-ready .sp2 { animation: splash-orbit 3.8s linear infinite, splash-bloom 0.3s ease 0.34s forwards; animation-delay: -1.27s, 0.34s; }
html.is-app-ready .sp3 { animation: splash-orbit 3.8s linear infinite, splash-bloom 0.3s ease 0.34s forwards; animation-delay: -2.53s, 0.34s; }
html.is-app-ready .sp4 { animation: splash-orbit-r 5.6s linear infinite, splash-bloom 0.3s ease 0.34s forwards; }
html.is-app-ready .sp5 { animation: splash-orbit-r 5.6s linear infinite, splash-bloom 0.3s ease 0.34s forwards; animation-delay: -2.8s, 0.34s; }
html.is-app-ready .sp6 { animation: splash-orbit-r 5.6s linear infinite, splash-bloom 0.3s ease 0.34s forwards; animation-delay: -4.2s, 0.34s; }
/* ACT 2 · radial particle burst on ignition */
.splash-burst {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}
html.is-app-ready .splash-burst { animation: splash-burst 0.7s cubic-bezier(0.17, 0.67, 0.35, 1) 0.3s forwards; }
.b1  { --a: 0deg;   --d: 118px; background: #c600ff; }
.b2  { --a: 30deg;  --d: 98px;  background: #e4e4e4; width: 5px; height: 5px; }
.b3  { --a: 60deg;  --d: 126px; background: #0036ff; }
.b4  { --a: 90deg;  --d: 96px;  background: #6aa6ff; width: 5px; height: 5px; }
.b5  { --a: 120deg; --d: 120px; background: #c08bf5; }
.b6  { --a: 150deg; --d: 100px; background: #e4e4e4; width: 4px; height: 4px; }
.b7  { --a: 180deg; --d: 124px; background: #c600ff; }
.b8  { --a: 210deg; --d: 94px;  background: #6aa6ff; width: 5px; height: 5px; }
.b9  { --a: 240deg; --d: 118px; background: #0036ff; }
.b10 { --a: 270deg; --d: 102px; background: #e4e4e4; width: 4px; height: 4px; }
.b11 { --a: 300deg; --d: 122px; background: #c08bf5; }
.b12 { --a: 330deg; --d: 98px;  background: #c600ff; width: 5px; height: 5px; }
.b13 { --a: 15deg;  --d: 140px; background: #e4e4e4; width: 4px; height: 4px; }
.b14 { --a: 105deg; --d: 136px; background: #c600ff; width: 4px; height: 4px; }
.b15 { --a: 195deg; --d: 142px; background: #6aa6ff; width: 4px; height: 4px; }
.b16 { --a: 285deg; --d: 138px; background: #c08bf5; width: 4px; height: 4px; }
/* ACT 2 · shockwave rings */
.splash-wave {
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.55);
  opacity: 0;
  pointer-events: none;
}
.splash-wave.w2 { border-color: rgba(198, 0, 255, 0.6); }
html.is-app-ready .splash-wave.w1 { animation: splash-wave 0.6s ease-out 0.06s forwards; }
html.is-app-ready .splash-wave.w2 { animation: splash-wave 0.65s ease-out 0.16s forwards; }
.splash-wave.w3 { border-width: 2px; border-color: rgba(106, 166, 255, 0.5); }
html.is-app-ready .splash-wave.w3 { animation: splash-wave 0.7s ease-out 0.26s forwards; }
.splash-wave.w4 { border-width: 2px; }
html.is-app-ready .splash-wave.w4 { animation: splash-wave 0.5s ease-out 0.82s forwards; } /* landing echo */
.splash-bubble {
  position: absolute;
  top: calc(50% + 96px);
  left: 50%;
  transform: translateX(-50%) scale(0.4) rotate(-5deg);
  opacity: 0;
  background: #1e1e26;
  border: 1px solid #2f2f3a;
  border-radius: 18px;
  padding: 9px 20px;
  font-size: 15px;
  font-weight: 600;
  color: #ececf1;
  white-space: nowrap;
}
.splash-bubble::after { content: " ✨"; }
html.is-app-ready .splash-bubble {
  animation: splash-bubble-slam 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.92s forwards;
}
@keyframes splash-spin {
  to { transform: rotate(360deg); }
}
@keyframes splash-bob {
  50% { transform: translateY(-7px); }
}
@keyframes splash-sway {
  0% { transform: rotate(-1.4deg) translate(-3px, 2px); }
  100% { transform: rotate(1.4deg) translate(3px, -2px); }
}
/* act-1 saccades + a power-surge beat: darts, then a rapid jitter with a flare */
@keyframes splash-gaze {
  0%, 12%, 30%, 56%, 68%, 82%, 100% { transform: translate(0, 0); filter: brightness(1); }
  15%, 24% { transform: translate(-8px, 0); filter: brightness(1); }
  33%, 42% { transform: translate(8px, -2px); filter: brightness(1); }
  60%, 64% { transform: translate(3px, 5px); filter: brightness(1); }
  70% { transform: translate(-2px, 1px); filter: brightness(1.7); }
  72% { transform: translate(2px, -1px); filter: brightness(2.1); }
  74% { transform: translate(-2px, 0); filter: brightness(1.7); }
  76% { transform: translate(1px, 1px); filter: brightness(1.3); }
  86%, 94% { transform: translate(-4px, -4px); filter: brightness(1); }
}
/* act-1 eye acting: snap blinks (squash), pop-open overshoot, focus squint, surprise */
@keyframes splash-eyes-act {
  0%, 5%, 9%, 34%, 50%, 54%, 58%, 84%, 100% { transform: scaleY(1) scaleX(1); }
  7% { transform: scaleY(0.08) scaleX(1.25); }
  11% { transform: scaleY(1.14) scaleX(0.94); }
  38%, 46% { transform: scaleY(0.42) scaleX(1.06); }
  52%, 56% { transform: scaleY(0.08) scaleX(1.25); }
  62% { transform: scaleY(1.22) scaleX(1.06); }
  66%, 82% { transform: scaleY(1) scaleX(1); }
  90% { transform: scaleY(0.08) scaleX(1.25); }
  93% { transform: scaleY(1.1) scaleX(0.96); }
}
@keyframes splash-smile {
  to { transform: translateY(-28%) scaleY(0.36) scaleX(1.12); }
}
@keyframes splash-halo {
  50% { transform: scale(1.14); }
}
@keyframes splash-bloom {
  to { opacity: 1; }
}
@keyframes splash-charge {
  0% { transform: translate(var(--cx), var(--cy)) scale(1.1); opacity: 0; }
  22% { opacity: 0.95; }
  85% { opacity: 0.5; }
  100% { transform: translate(0, 0) scale(0.15); opacity: 0; }
}
@keyframes splash-flash {
  0% { opacity: 0; }
  14% { opacity: 0.95; }
  100% { opacity: 0; }
}
@keyframes splash-wave {
  0% { transform: scale(0.25); opacity: 0.85; }
  100% { transform: scale(3); opacity: 0; }
}
@keyframes splash-slam-out {
  0% { transform: scale(5.2); }
  55% { transform: scale(0.85); }
  78% { transform: scale(1.07); }
  100% { transform: scale(1); }
}
@keyframes splash-flip-land {
  0% { transform: translateY(0) rotate(0) scale(1); }
  34% { transform: translateY(-40px) rotate(200deg) scale(1.02); }
  60% { transform: translateY(2px) rotate(360deg) scaleX(1.16) scaleY(0.8); }
  74% { transform: translateY(-12px) rotate(360deg) scale(1); }
  88% { transform: translateY(0) rotate(360deg) scaleX(1.05) scaleY(0.95); }
  100% { transform: translateY(0) rotate(360deg) scale(1); }
}
@keyframes splash-burst {
  0% { transform: rotate(var(--a)) translateY(-46px) scale(1); opacity: 1; }
  100% { transform: rotate(var(--a)) translateY(calc(-1 * var(--d))) scale(0.2); opacity: 0; }
}
@keyframes splash-bubble-slam {
  0% { transform: translateX(-50%) scale(0.4) rotate(-5deg); opacity: 0; }
  70% { transform: translateX(-50%) scale(1.12) rotate(1.5deg); opacity: 1; }
  100% { transform: translateX(-50%) scale(1) rotate(0); opacity: 1; }
}
@keyframes splash-headfill {
  to { background-color: #0b0b0f; }
}
@keyframes splash-creep {
  from { transform: scale(5); }
  to { transform: scale(5.45); }
}
@keyframes splash-gone {
  to { opacity: 0; visibility: hidden; }
}
@keyframes splash-scan {
  0%, 100% { background-position: 0% 0; }
  50% { background-position: 100% 0; }
}
@media (prefers-reduced-motion: reduce) {
  #app-splash * { animation: none !important; }
  /* No animated outro under reduced motion — `forwards` never fires, so hide directly. */
  html.is-app-ready #app-splash { display: none; }
}

/* ── Post-purchase welcome / "deploying your agent" loading screen ─────────── */
/* Post-purchase reload: hide the app shell until the loading overlay is up, so the
   empty tasks screen never flashes before it (body's var(--bg) shows behind, matching
   the overlay). main.ts clears .is-welcoming the moment showWelcome() runs. */
.is-welcoming .layout { visibility: hidden; }
.welcome-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  opacity: 1;
  transition: opacity 0.5s var(--ease);
}
.welcome-overlay.done {
  opacity: 0;
  pointer-events: none;
}
.welcome-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 24px;
  max-width: 360px;
  text-align: center;
}
.welcome-mark {
  font-size: 56px;
  color: var(--accent);
  font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 48;
  animation: welcome-pulse 1.6s var(--ease) infinite;
}
@keyframes welcome-pulse {
  0%, 100% { transform: scale(1); opacity: 0.8; }
  50% { transform: scale(1.12); opacity: 1; }
}
.welcome-title {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 600;
  color: var(--text);
}
.welcome-sub {
  font-size: 14px;
  color: var(--text-dim);
  min-height: 20px;
}
.welcome-bar {
  width: 240px;
  height: 6px;
  border-radius: 999px;
  background: var(--bg-elev-2);
  overflow: hidden;
}
.welcome-bar-fill {
  width: 0%;
  height: 100%;
  border-radius: 999px;
  background-color: var(--accent);
  background-image: linear-gradient(90deg, transparent, color-mix(in srgb, var(--bg) 45%, transparent), transparent);
  background-size: 200% 100%;
  transition: width 0.3s var(--ease);
  animation: welcome-shimmer 1.4s linear infinite;
}
@keyframes welcome-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.confetti-canvas {
  position: fixed;
  inset: 0;
  z-index: 10000;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .welcome-mark, .welcome-bar-fill { animation: none; }
}

/* ============================================================================
   Landing page (pre-auth marketing, superpower.io) — landing.ts
   Minimalist, Gemini Material-3. The hero is an abstract WebGL "organic sphere"
   (orb.ts) standing in for the agent; everything else is quiet and spacious.
   Prefixed .lp-*; reuses the design tokens, no landing-only palette.
   ========================================================================== */
html.lp-open, html.lp-open body { overflow: hidden; }

.lp-host {
  position: fixed;
  inset: 0;
  z-index: 3000;
  overflow-y: auto;
  overflow-x: hidden;
  scroll-padding-top: 64px;
  scroll-behavior: smooth;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  -webkit-font-smoothing: antialiased;
}
.lp-root { min-height: 100%; display: flex; flex-direction: column; }
.lp-main { position: relative; z-index: 1; display: flex; flex-direction: column; }

/* ---- scroll-driven background (fixed; cross-fades + scales with --bgP) ---- */
.lp-bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.lp-bg span { position: absolute; inset: -8%; display: block; will-change: opacity, transform; }
.lp-bg-a {
  opacity: calc(1 - var(--bgP, 0));
  transform: scale(calc(1 + var(--bgP, 0) * 0.22));
  background: radial-gradient(1000px 660px at 50% -10%, color-mix(in srgb, #9b5cff 20%, transparent), transparent 60%);
}
.lp-bg-b {
  opacity: var(--bgP, 0);
  transform: scale(calc(1.22 - var(--bgP, 0) * 0.22));
  background:
    radial-gradient(860px 680px at 12% 6%, color-mix(in srgb, #ff6a1e 24%, transparent), transparent 56%),
    radial-gradient(980px 760px at 90% 94%, color-mix(in srgb, #2f7bff 30%, transparent), transparent 58%);
}

/* ---- nav ---- */
.lp-nav {
  position: sticky;
  top: 0;
  z-index: 30;
  backdrop-filter: saturate(140%) blur(14px);
  background: color-mix(in srgb, var(--bg) 64%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.lp-nav-stuck { border-bottom-color: var(--border); background: color-mix(in srgb, var(--bg) 86%, transparent); }
.lp-nav-inner { max-width: 1100px; margin: 0 auto; padding: 13px 24px; display: flex; align-items: center; gap: 18px; }
.lp-brand { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; color: var(--text); font-family: var(--font-display); font-weight: 700; font-size: 19px; letter-spacing: -0.3px; }
.lp-mark { font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24; font-size: 24px; }
.lp-mark-grad { background: linear-gradient(135deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.lp-nav-links { display: flex; gap: 4px; }
.lp-nav-links a { color: var(--text-dim); text-decoration: none; font-size: 14px; font-weight: 500; padding: 8px 12px; border-radius: 999px; transition: color 0.15s var(--ease), background 0.15s var(--ease); }
.lp-nav-links a:hover { color: var(--text); background: var(--bg-elev-1); }
.lp-nav-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.lp-icon-btn { display: inline-flex; align-items: center; gap: 5px; height: 38px; padding: 0 12px; border: 1px solid var(--border); background: transparent; color: var(--text-dim); border-radius: 999px; cursor: pointer; font-family: var(--font-ui); font-size: 13px; font-weight: 600; transition: color 0.15s var(--ease), border-color 0.15s var(--ease), background 0.15s var(--ease); }
.lp-icon-btn:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg-elev-1); }
.lp-icon-btn .material-symbols-rounded { font-size: 19px; }

/* ---- buttons ---- */
.lp-btn { display: inline-flex; align-items: center; justify-content: center; gap: 7px; border: 1px solid transparent; border-radius: 999px; padding: 12px 22px; min-height: 44px; font-family: var(--font-ui); font-size: 14.5px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: transform 0.13s var(--ease), filter 0.15s var(--ease), background 0.15s var(--ease), border-color 0.15s var(--ease); }
.lp-btn:active { transform: scale(0.97); }
.lp-btn .material-symbols-rounded { font-size: 19px; }
.lp-btn-primary { background: var(--accent); color: var(--on-accent); }
.lp-btn-primary:hover { filter: brightness(1.08); }
.lp-btn-ghost { background: transparent; color: var(--text); border-color: var(--border-strong); }
.lp-btn-ghost:hover { background: var(--bg-elev-1); }
.lp-btn-lg { min-height: 52px; padding: 15px 28px; font-size: 15.5px; }
.lp-btn-xl { min-height: 58px; padding: 17px 38px; font-size: 17px; }
.lp-btn-sm { min-height: 36px; padding: 8px 16px; font-size: 13px; }
.lp-nav-cta { padding: 9px 18px; min-height: 38px; }
.lp-btn-spotlight {
  color: #fff; border: none;
  background: linear-gradient(120deg, var(--grad-a), var(--grad-b) 52%, var(--grad-c));
  background-size: 180% 180%;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--grad-b) 36%, transparent);
  animation: lp-shift 9s ease-in-out infinite;
}
.lp-btn-spotlight:hover { filter: brightness(1.05); transform: translateY(-1px); }
.lp-btn-spotlight:active { transform: scale(0.98); }

/* ---- shared primitives ---- */
.lp-eyebrow { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; letter-spacing: 0.2px; color: var(--text-dim); padding: 7px 14px; border: 1px solid var(--border); border-radius: 999px; background: color-mix(in srgb, var(--bg-elev-1) 70%, transparent); }
.lp-eyebrow .lp-mark { font-size: 17px; }
.lp-eyebrow-center { margin: 0 auto; }
.lp-h1 { margin: 26px 0 0; font-family: var(--font-display); font-weight: 700; font-size: clamp(38px, 6vw, 66px); line-height: 1.03; letter-spacing: -1.6px; color: var(--text); }
.lp-h2 { margin: 16px 0 0; font-family: var(--font-display); font-weight: 700; font-size: clamp(27px, 3.6vw, 42px); line-height: 1.08; letter-spacing: -0.8px; color: var(--text); }
.lp-h2-center { text-align: center; }
.lp-lede { margin: 18px 0 0; max-width: 50ch; font-size: clamp(16px, 1.5vw, 18.5px); line-height: 1.6; color: var(--text-dim); }
.lp-lede-center { margin-left: auto; margin-right: auto; text-align: center; }

/* ---- the organic-sphere orb ---- */
.lp-orb { position: relative; display: grid; place-items: center; }
/* Hero orb is a big, immersive backdrop the copy overlays (absolute-centered). */
/* The hero orb scales up + fades as you scroll (you "dive into" it) via --heroP. */
.lp-orb-hero { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(calc(1 + var(--heroP, 0) * 1.45)); opacity: calc(1 - var(--heroP, 0) * 1.15); width: min(820px, 96vw, 90vh); aspect-ratio: 1; z-index: 0; pointer-events: none; will-change: transform, opacity; }
.lp-orb-portal { width: min(620px, 82vw, 58vh); aspect-ratio: 1; margin: 0 auto 2px; }
.lp-orb-canvas { position: relative; z-index: 1; width: 100%; height: 100%; display: block; }
/* Faint ambient behind the canvas; the orb's own WebGL bloom carries the glow. */
.lp-orb-halo {
  position: absolute; inset: 2%; z-index: 0; border-radius: 50%;
  background: radial-gradient(closest-side,
    color-mix(in srgb, #2f74ff 30%, transparent),
    color-mix(in srgb, #ff5a24 12%, transparent) 56%,
    transparent 72%);
  filter: blur(46px);
  opacity: 0.5;
  animation: lp-breathe 7s ease-in-out infinite;
}
/* CSS fallback sphere — only when WebGL is unavailable (.is-fallback set by JS) */
.lp-orb-fallback { display: none; }
.lp-orb.is-fallback .lp-orb-canvas { display: none; }
.lp-orb.is-fallback .lp-orb-fallback {
  display: block; position: absolute; z-index: 1; inset: 12%; border-radius: 50%;
  background:
    radial-gradient(circle at 36% 28%, #cde6ff 0%, transparent 38%),
    conic-gradient(from 210deg, var(--grad-a), var(--grad-b) 40%, var(--grad-c) 66%, var(--grad-a));
  box-shadow: inset -16px -22px 40px rgba(0,0,0,0.4), 0 20px 60px color-mix(in srgb, var(--grad-b) 40%, transparent);
  filter: saturate(1.05);
  animation: lp-spin 18s linear infinite;
}

/* ---- hero: copy overlays the orb, sitting in its dark hollow center ---- */
.lp-hero {
  position: relative; width: 100%; margin: 0 auto;
  min-height: 100svh;
  padding: 84px 24px 64px;
  display: grid; place-items: center; text-align: center; overflow: hidden;
}
.lp-hero-copy { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; max-width: 540px; transform: translateY(calc(var(--heroP, 0) * -40px)) scale(calc(1 - var(--heroP, 0) * 0.08)); opacity: calc(1 - var(--heroP, 0) * 1.55); will-change: transform, opacity; }
/* Theme-aware scrim so the headline stays legible over the orb's bright ribbons. */
.lp-hero-copy::before {
  content: ""; position: absolute; z-index: -1; inset: -44% -16%;
  background: radial-gradient(ellipse 60% 54% at 50% 50%,
    color-mix(in srgb, var(--bg) 92%, transparent) 0%,
    color-mix(in srgb, var(--bg) 80%, transparent) 44%,
    transparent 76%);
}
.lp-hero-copy .lp-h1, .lp-hero-copy .lp-lede { text-shadow: 0 2px 26px color-mix(in srgb, var(--bg) 78%, transparent); }
.lp-cta-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 30px; }
.lp-scroll-hint {
  position: absolute; bottom: 22px; left: 50%; transform: translateX(-50%);
  display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid var(--border); background: color-mix(in srgb, var(--bg-elev-1) 70%, transparent);
  color: var(--text-faint); cursor: pointer; transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
  animation: lp-bob 2.4s ease-in-out infinite;
}
.lp-scroll-hint:hover { color: var(--text); border-color: var(--border-strong); }
.lp-scroll-hint .material-symbols-rounded { font-size: 24px; }

.lp-section-head { display: flex; flex-direction: column; align-items: center; text-align: center; max-width: 760px; margin: 0 auto; }

/* ---- what is Genie + how it delivers value (narrative + Works/Learns/Earns) ---- */
.lp-what { max-width: 1100px; width: 100%; margin: 0 auto; padding: clamp(72px, 11vw, 140px) 24px clamp(56px, 9vw, 110px); display: flex; flex-direction: column; align-items: center; scroll-margin-top: 72px; }
/* Scrubbed entrance: the section scales + fades UP into focus as you scroll in
   (fallback 1 = fully visible, so reduced-motion / no-JS shows it normally). */
.lp-what .lp-section-head, .lp-what .lp-values {
  transform: scale(calc(0.78 + var(--whatP, 1) * 0.22));
  opacity: calc(var(--whatP, 1) * 1.15 - 0.15);
  transform-origin: center center;
  will-change: transform, opacity;
}
.lp-values { list-style: none; margin: clamp(40px, 6vw, 64px) 0 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; width: 100%; }
.lp-value { position: relative; padding: 30px 26px 28px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: color-mix(in srgb, var(--bg-elev) 55%, transparent); transition: border-color 0.2s var(--ease), transform 0.2s var(--ease), background 0.2s var(--ease); }
.lp-value:hover { border-color: var(--border-strong); transform: translateY(-3px); background: var(--bg-elev); }
.lp-value-n { position: absolute; top: 22px; right: 24px; font-family: var(--font-display); font-weight: 700; font-size: 13px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.lp-value-icon { font-size: 34px; font-variation-settings: "FILL" 1, "wght" 500; background: linear-gradient(135deg, var(--grad-a), var(--grad-b) 55%, var(--grad-c)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.lp-value-kicker { margin: 16px 0 0; font-family: var(--font-display); font-weight: 700; font-size: 21px; letter-spacing: -0.3px; color: var(--text); }
.lp-value-body { margin: 9px 0 0; font-size: 14.5px; line-height: 1.55; color: var(--text-dim); }

/* ---- closing CTA ---- */
.lp-portal { position: relative; width: 100%; padding: clamp(64px, 10vw, 130px) 24px clamp(72px, 11vw, 150px); display: flex; flex-direction: column; align-items: center; text-align: center; scroll-margin-top: 64px; }
.lp-portal-inner { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; max-width: 640px; }
.lp-portal .lp-btn-xl { margin-top: 30px; }

/* ---- footer ---- */
.lp-footer { border-top: 1px solid var(--border); }
.lp-footer-grid { max-width: 1100px; margin: 0 auto; padding: clamp(40px, 5vw, 60px) 24px clamp(24px, 3vw, 32px); display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 28px; }
.lp-footer-tag { margin: 12px 0 0; color: var(--text-faint); font-size: 13.5px; max-width: 28ch; line-height: 1.5; }
.lp-footer-tag-strong { color: var(--text); font-weight: 600; margin-top: 6px; }
.lp-footer-col h4 { margin: 0 0 12px; font-size: 13px; font-weight: 600; color: var(--text); }
.lp-footer-col a { display: block; color: var(--text-faint); text-decoration: none; font-size: 13.5px; padding: 5px 0; transition: color 0.15s var(--ease); }
.lp-footer-col a:hover { color: var(--text); }
.lp-footer-base { max-width: 1100px; margin: 0 auto; padding: 16px 24px 28px; border-top: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--text-faint); font-size: 13px; }

/* ---- reveal on scroll (fade + scale-in) ---- */
.lp-host [data-reveal] { opacity: 0; transform: translateY(28px) scale(0.965); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); will-change: opacity, transform; }
.lp-host [data-reveal].in { opacity: 1; transform: none; }

/* ---- keyframes ---- */
@keyframes lp-shift { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
@keyframes lp-breathe { 0%, 100% { opacity: 0.6; transform: scale(0.97); } 50% { opacity: 0.95; transform: scale(1.03); } }
@keyframes lp-bob { 0%, 100% { transform: translateX(-50%) translateY(0); } 50% { transform: translateX(-50%) translateY(5px); } }
@keyframes lp-spin { to { transform: rotate(360deg); } }

/* ---- responsive ---- */
@media (max-width: 900px) {
  .lp-values { grid-template-columns: 1fr; max-width: 460px; margin-left: auto; margin-right: auto; gap: 16px; }
}
@media (max-width: 860px) { .lp-nav-links { display: none; } }
@media (max-width: 600px) {
  .lp-nav-inner { padding: 11px 16px; }
  .lp-hero, .lp-what, .lp-portal { padding-left: 16px; padding-right: 16px; }
  /* immersive full-bleed orb on phones (bleeds past the edges) */
  .lp-orb-hero { width: min(680px, 138vw); top: 46%; }
  .lp-h1 { font-size: clamp(34px, 11vw, 46px); }
  .lp-footer-grid { grid-template-columns: 1fr 1fr; padding-left: 16px; padding-right: 16px; }
  .lp-footer-brand { grid-column: 1 / -1; }
  .lp-cta-row .lp-btn { flex: 1 1 auto; }
  .lp-btn-xl { width: 100%; }
  .lp-lang-label { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .lp-host { scroll-behavior: auto; }
  .lp-host [data-reveal] { opacity: 1; transform: none; transition: none; }
  .lp-orb-halo, .lp-btn-spotlight, .lp-scroll-hint, .lp-orb.is-fallback .lp-orb-fallback { animation: none; }
  /* hold the scroll-driven scale/fade static (JS also stops setting --heroP/--bgP) */
  .lp-orb-hero { transform: translate(-50%, -50%) !important; opacity: 1 !important; }
  .lp-hero-copy { transform: none !important; opacity: 1 !important; }
  .lp-bg-a { opacity: 1 !important; transform: none !important; }
  .lp-bg-b { opacity: 0 !important; }
  .lp-what .lp-section-head, .lp-what .lp-values { transform: none !important; opacity: 1 !important; }
}

/* ---- login modal overlays the landing (kept mounted behind as a blurred backdrop) ---- */
html.lp-login-open .auth-overlay {
  z-index: 3500;
  background: color-mix(in srgb, var(--bg) 52%, transparent);
  -webkit-backdrop-filter: saturate(130%) blur(11px);
  backdrop-filter: saturate(130%) blur(11px);
  animation: lp-fade-in 0.28s var(--ease);
}
html.lp-login-open .auth-card { animation: lp-card-in 0.34s var(--ease); }
@keyframes lp-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes lp-card-in { from { opacity: 0; transform: translateY(14px) scale(0.96); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  html.lp-login-open .auth-overlay, html.lp-login-open .auth-card { animation: none; }
}

/* ---- mascot rage-tap explode: full-screen companion effects (mascotExplodeFx.ts) ----
   The mascot's own WebGL scene stays self-contained to its canvas; these three
   pieces are what make the home hero's explode read as hitting the WHOLE
   screen instead: a full-viewport flash, a shake on the entire page, and a
   shower of blocky "pixel" confetti. z-index sits above every other overlay
   (the highest prior in this file is the welcome/confetti overlay at 10000). */
.mascot-explode-flash {
  position: fixed;
  inset: 0;
  z-index: 10500;
  background: #fff;
  pointer-events: none;
  animation: mascot-explode-flash-pop 550ms ease-out forwards;
}
@keyframes mascot-explode-flash-pop {
  0% { opacity: 0; }
  10% { opacity: 1; }
  100% { opacity: 0; }
}

/* Shakes the whole page (not just the mascot's own head) — applied to <body>
   so every fixed-position element (composer, nav, banners…) rattles together
   as one rigid unit instead of the shake being scoped to a sub-tree. */
.mascot-explode-shake { animation: mascot-explode-shake 600ms cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }
@keyframes mascot-explode-shake {
  10%, 90% { transform: translate3d(-2px, 0, 0); }
  20%, 80% { transform: translate3d(5px, -3px, 0); }
  30%, 50%, 70% { transform: translate3d(-10px, 5px, 0); }
  40%, 60% { transform: translate3d(10px, -5px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .mascot-explode-shake { animation: none; }
}

.mascot-confetti-layer {
  position: fixed;
  inset: 0;
  z-index: 10400;
  overflow: hidden;
  pointer-events: none;
}
/* Plain solid squares, straight fall, no rotation — deliberately blocky/
   "pixel" rather than the fluttering-paper look of the welcome screen's
   rotating rect confetti (welcome.ts's fireConfetti). */
.mascot-confetti-pixel {
  position: absolute;
  top: -12px;
  animation-name: mascot-confetti-fall;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}
@keyframes mascot-confetti-fall {
  0% { transform: translate(0, 0); opacity: 1; }
  100% { transform: translate(var(--drift, 0px), 110vh); opacity: 0.85; }
}

/* ============================================================================
   Pre-auth CINEMATIC landing (landing.ts). Fully namespaced under `.lp-cine`
   with `lpc-` classes + `#lpc*` ids so nothing here collides with the app's
   global styles. Dark GSAP scroll-film; the mascot is the app's own
   createMascot(). Typography: Inter + JetBrains Mono (loaded in index.html).
   The host sits at z-index 900 — below the Privy auth overlay (z 1000).
   ============================================================================ */
.lp-cine{
  --lpc-bg:#0f0f10;--lpc-ink:#f3f3f4;--lpc-dim:#b9bcc0;--lpc-faint:#83878d;--lpc-line:rgba(255,255,255,.10);
  position:fixed;inset:0;z-index:900;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch;
  color:var(--lpc-ink);font-family:'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;
  background:
    radial-gradient(1100px 720px at 50% 30%, rgba(99,27,255,.16), transparent 60%),
    radial-gradient(820px 620px at 74% 64%, rgba(198,0,255,.10), transparent 60%),
    var(--lpc-bg);
}
.lp-cine *{box-sizing:border-box;margin:0;padding:0}
.lp-cine h1{font-size:clamp(2rem,5.6vw,4.6rem);line-height:1.03;letter-spacing:-.032em;font-weight:700;text-shadow:0 2px 50px rgba(0,0,0,.9)}
.lpc-g{background:linear-gradient(110deg,#4d7bff,#8a6cff 55%,#c600ff);-webkit-background-clip:text;background-clip:text;color:transparent}

.lpc-nav{position:fixed;top:0;left:0;right:0;z-index:40;display:flex;align-items:center;justify-content:space-between;padding:26px 40px}
.lpc-logo{height:34px;filter:brightness(0) invert(1) drop-shadow(0 1px 10px rgba(0,0,0,.5))}
.lpc-nav-r{display:flex;align-items:center;gap:16px}
.lpc-lang{font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.05em;color:var(--lpc-faint);border:1px solid var(--lpc-line);border-radius:100px;padding:7px 12px;cursor:pointer;user-select:none;transition:border-color .2s}
.lpc-lang:hover{border-color:var(--lpc-ink)}
.lpc-lang b{color:var(--lpc-ink);font-weight:700}
.lpc-socials{display:flex;align-items:center;gap:6px}
.lpc-social{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:100px;color:var(--lpc-dim);text-decoration:none;transition:color .2s,background .2s}
.lpc-social:hover{color:var(--lpc-ink);background:rgba(255,255,255,.07)}
.lpc-social svg{width:18px;height:18px;display:block}

.lpc-scroller{height:500vh;position:relative}
.lpc-stage{position:sticky;top:0;height:100vh;width:100%;overflow:hidden}
.lpc-bg{position:absolute;inset:-10%;z-index:0;opacity:0;background:radial-gradient(1100px 720px at 50% 62%, rgba(66,120,255,.15), transparent 60%)}
.lpc-world{position:absolute;inset:0;z-index:2;will-change:transform}

.lpc-net{position:absolute;inset:0;width:100%;height:100%;overflow:visible;z-index:1}
.lpc-net line{stroke:rgba(150,165,255,.30);stroke-width:1;vector-effect:non-scaling-stroke;stroke-dasharray:2 3;animation:lpc-march 2.4s linear infinite}
@keyframes lpc-march{to{stroke-dashoffset:-5}}

.lpc-agent{position:absolute;transform:translate(-50%,-50%);pointer-events:none;filter:saturate(.62) brightness(.92);transition:filter .7s ease}
.lpc-agent.is-ack{filter:saturate(.95) brightness(1.04)}

#lpcHero{position:absolute;left:50%;top:70%;width:150vmin;height:150vmin;pointer-events:none;z-index:4;filter:saturate(.9) brightness(.96);transition:filter .7s ease}
#lpcHero.is-verified{filter:saturate(1.12) brightness(1.12) drop-shadow(0 0 34px rgba(90,150,255,.55))}

.lpc-ring{position:absolute;left:50%;top:70%;width:15vmin;height:15vmin;transform:translate(-50%,-50%) rotate(-90deg);z-index:3;opacity:0;pointer-events:none}
.lpc-ring circle{fill:none;stroke:#78c8ff;stroke-width:2.4;stroke-linecap:round;filter:drop-shadow(0 0 6px rgba(120,200,255,.85))}

.lpc-beam{position:absolute;left:50%;bottom:32%;width:2px;height:0;transform:translateX(-50%);z-index:18;
  background:linear-gradient(to top,rgba(120,200,255,.12),#7fc4ff);box-shadow:0 0 14px rgba(120,200,255,.9);opacity:0}
.lpc-beam::before{content:"";position:absolute;top:-4px;left:50%;transform:translateX(-50%);width:9px;height:9px;border-radius:50%;background:#cfe6ff;box-shadow:0 0 14px rgba(150,210,255,1),0 0 26px rgba(90,160,255,.7)}
.lpc-hbeam{position:absolute;top:70%;height:2px;width:0;z-index:5;transform:translateY(-50%);opacity:0;box-shadow:0 0 14px rgba(120,200,255,.9)}
.lpc-hbeam.left{right:53%;background:linear-gradient(to left,rgba(120,200,255,.12),#7fc4ff)}
.lpc-hbeam.right{left:53%;background:linear-gradient(to right,rgba(120,200,255,.12),#7fc4ff)}
.lpc-hbeam::after{content:"";position:absolute;top:50%;transform:translateY(-50%);width:9px;height:9px;border-radius:50%;background:#cfe6ff;box-shadow:0 0 14px rgba(150,210,255,1),0 0 26px rgba(90,160,255,.7)}
.lpc-hbeam.left::after{left:-4px}
.lpc-hbeam.right::after{right:-4px}

.lpc-record{position:absolute;left:50%;top:37%;transform:translate(-50%,14px);z-index:22;opacity:0;pointer-events:none;
  width:min(430px,88vw);padding:18px 20px;border-radius:16px;font-family:'JetBrains Mono',monospace;
  background:rgba(18,20,26,.74);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border:1px solid rgba(255,255,255,.12);box-shadow:0 30px 80px rgba(0,0,0,.55), inset 0 0 0 1px rgba(90,150,255,.08)}
.lpc-record-hd{display:flex;align-items:center;gap:8px;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--lpc-faint);margin-bottom:15px}
.lpc-record-hd .lpc-chain{margin-left:auto;color:#6b7078;font-weight:700;letter-spacing:.14em;transition:color .4s}
.lpc-record-hd .lpc-chain.on{color:#5aa0ff}
.lpc-record ul{list-style:none;display:grid;gap:12px}
.lpc-record li{display:flex;align-items:center;gap:12px;font-size:13.5px;color:#6b7078;transition:color .4s ease}
.lpc-record li.done{color:#e7e9ee}
.lpc-record .lpc-chk{flex:none;width:18px;height:18px;border-radius:50%;border:1.5px solid #3a3f48;display:grid;place-items:center;transition:.35s ease}
.lpc-record li.done .lpc-chk{background:#22c55e;border-color:#22c55e;box-shadow:0 0 12px rgba(34,197,94,.55)}
.lpc-record .lpc-chk::after{content:"✓";font-size:11px;line-height:1;color:#04210f;opacity:0;transition:opacity .3s ease}
.lpc-record li.done .lpc-chk::after{opacity:1}

.lpc-beat{position:absolute;left:0;right:0;z-index:20;text-align:center;padding:0 8vw;pointer-events:none}
.lpc-heroCopy{top:30%;transform:translateY(-50%)}
.lpc-heroCopy .lpc-cta{pointer-events:auto}
.lpc-problemCopy,.lpc-solutionCopy{top:15%;opacity:0}
#lpcSolutionCopy{top:9%}
#lpcSolutionCopy h1{font-size:clamp(2.4rem,5.2vh,4.6rem);line-height:1.04}
.lpc-eyebrow{font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;letter-spacing:.28em;text-transform:uppercase;color:var(--lpc-dim);margin-bottom:26px}
.lpc-heroCopy h1{font-size:clamp(2.4rem,6.4vw,5.4rem);line-height:1.0}
.lpc-heroCopy h1 .lpc-g{background:linear-gradient(110deg,#0036ff,#631bff 52%,#c600ff);-webkit-background-clip:text;background-clip:text;color:transparent}
.lpc-sub{margin:22px auto 0;max-width:50ch;font-size:clamp(1rem,1.5vw,1.2rem);line-height:1.6;color:var(--lpc-dim);text-shadow:0 2px 30px rgba(0,0,0,.9)}
.lpc-cta{display:flex;gap:14px;justify-content:center;margin-top:40px}
.lpc-btn{font-family:'JetBrains Mono',monospace;font-size:.84rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;padding:14px 24px;border-radius:100px;text-decoration:none;border:0;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s}
.lpc-btn:hover{transform:translateY(-1px)}
.lpc-btn-p{background:#fff;color:#0f0f10}

.lpc-cue{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:40;font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.3em;color:var(--lpc-faint);text-transform:uppercase;animation:lpc-bob 1.8s ease-in-out infinite}
@keyframes lpc-bob{0%,100%{opacity:.35;transform:translate(-50%,0)}50%{opacity:1;transform:translate(-50%,5px)}}

.lpc-finale{position:relative;z-index:1;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;
  padding:12vh 8vw 11vh;background:radial-gradient(1000px 720px at 50% 42%, rgba(99,27,255,.16), transparent 62%),var(--lpc-bg)}
.lpc-genieWrap{position:relative;width:31vmin;height:31vmin;max-width:260px;max-height:260px;margin-bottom:-14px;will-change:transform}
.lpc-genieWrap::before{content:"";position:absolute;inset:8%;border-radius:50%;z-index:0;
  background:radial-gradient(closest-side, rgba(90,150,255,.34), transparent 72%);filter:blur(4px)}
.lpc-finale canvas{position:relative;z-index:1;width:100%;height:100%;filter:saturate(1.12) brightness(1.1) drop-shadow(0 0 34px rgba(90,150,255,.5))}
.lpc-finale .lpc-eyebrow{margin-bottom:20px}
.lpc-finale h1{max-width:15ch}
.lpc-finale .lpc-sub{max-width:52ch}
.lpc-finale .lpc-cta{margin-top:38px}
.lpc-finale .lpc-btn{padding:16px 30px;font-size:.9rem}

.lpc-bloom{position:absolute;left:50%;top:70%;width:200vmax;height:200vmax;transform:translate(-50%,-50%);border-radius:50%;z-index:36;opacity:0;pointer-events:none;
  background:radial-gradient(closest-side, rgba(244,246,255,.99), rgba(172,140,255,.95) 30%, rgba(99,27,255,.6) 55%, rgba(15,15,16,0) 72%)}
.lpc-stage .lpc-finale{position:absolute;inset:0;min-height:0;z-index:30;opacity:0;pointer-events:none}
.lpc-stage .lpc-finale.is-live{pointer-events:auto}

/* static fallback: no GSAP / prefers-reduced-motion — page reads at rest */
.lp-cine.is-static .lpc-scroller{height:auto}
.lp-cine.is-static .lpc-stage{height:auto;min-height:100vh;overflow:visible;padding:16vh 0}
.lp-cine.is-static .lpc-heroCopy{position:static;transform:none;opacity:1}
.lp-cine.is-static .lpc-problemCopy,.lp-cine.is-static .lpc-solutionCopy{display:none}
.lp-cine.is-static .lpc-cue{display:none}

@media (max-width:640px){
  .lpc-nav{padding:15px 16px}
  .lpc-logo{height:24px}
  .lpc-nav-r{gap:9px}
  .lpc-lang{padding:6px 9px;font-size:10.5px;white-space:nowrap}
  .lpc-socials{gap:2px} .lpc-social{width:30px;height:30px} .lpc-social svg{width:17px;height:17px}
  #lpcHero{width:124vmin;height:124vmin;top:76%}
  .lpc-ring{top:76%} .lpc-hbeam{top:76%} .lpc-bloom{top:76%} .lpc-beam{bottom:24%}
  .lpc-eyebrow{margin-bottom:15px;font-size:10.5px;letter-spacing:.2em}
  .lp-cine h1{font-size:clamp(2rem,8.6vw,2.7rem)}
  .lpc-heroCopy h1{font-size:clamp(2.3rem,10.5vw,3.1rem);line-height:1.03}
  .lpc-sub{font-size:1rem;max-width:30ch;margin-top:18px}
  .lpc-beat{padding:0 6vw}
  .lpc-heroCopy{top:38%}
  .lpc-problemCopy,.lpc-solutionCopy{top:23%}
  #lpcSolutionCopy{top:22%}
  #lpcSolutionCopy h1{font-size:clamp(2rem,8.6vw,2.6rem);line-height:1.05}
  .lpc-cta{flex-direction:column;align-items:center;gap:11px;margin-top:26px}
  .lpc-btn{width:min(300px,84vw);text-align:center;padding:15px 24px}
  .lpc-finale .lpc-btn{padding:15px 24px;font-size:.85rem}
  .lpc-record{width:90vw;padding:16px 18px;top:52%}
  .lpc-record li{font-size:12.5px}
  .lpc-finale{padding:12vh 7vw 12vh}
  .lpc-finale h1{font-size:clamp(2.4rem,12vw,3.4rem)}
  .lpc-finale .lpc-sub{font-size:1rem;max-width:32ch}
  .lpc-cue{bottom:15px}
}
@keyframes smx-shake {
  0%, 100% { transform: translate(0, 0); } 15% { transform: translate(-2px, 1px); }
  30% { transform: translate(3px, -1px); } 45% { transform: translate(-2px, 2px); }
  60% { transform: translate(2px, -2px); } 75% { transform: translate(-1px, 1px); } 90% { transform: translate(1px, 0); }
}
@keyframes smx-grain {
  0% { transform: translate(0, 0); } 25% { transform: translate(-4%, 3%); } 50% { transform: translate(3%, -2%); }
  75% { transform: translate(-2%, -3%); } 100% { transform: translate(2%, 2%); }
}
@keyframes smx-power {
  0% { opacity: 1; transform: scaleY(.006); } 16% { opacity: 1; transform: scaleY(.006); }
  38% { opacity: .95; transform: scaleY(1); } 100% { opacity: 0; transform: scaleY(1); }
}
.plugin-mount { width: 100%; }
/* Pull-to-refresh: the live Genie mascot (no text) descends from the top edge
   as you drag — a random face each pull, "thinking" while the board refetches
   underneath. The content stays put, so the refresh is barely noticeable. */
/* Overlays ABOVE the sticky tab row (owner 2026-08-05: "the pull anim should
   be on top of the tab section") — under the snackbar layer (10400+) only. */
.ptr { position: absolute; top: 0; left: 50%; transform: translate(-50%, -100%); opacity: 0; width: 72px; height: 72px; pointer-events: none; z-index: 10300; }
.ptr-canvas { width: 72px; height: 72px; display: block; }


.home-chip-badge { background: var(--sm-up); color: var(--sm-on-up); border-radius: 999px; font-size: 10.5px; font-weight: 700; padding: 1px 6px; margin-left: 2px; }

/* Same chip row, session flavor: rides inside .composer-wrap directly above the
   composer AND IN ITS COLUMN — the width and the centring come from the shared
   `.composer, .session-chips` rule, so this must not reset the horizontal
   margin back to 0 (which is what left the pill hard against the panel edge).
   The 8px bottom padding is the gap to the composer's top. */
.session-chips { margin: 0 auto; padding: 2px 2px 8px; }

/* ------------------------------------------------------------------ */
/* Add-to-Home-Screen install nudge (install.ts) — mascot-led bottom  */
/* sheet. Above modals (1400/1500), below the toast (20000) so the    */
/* "Genie is on your Home Screen" confirmation can land over it.      */
/* ------------------------------------------------------------------ */
.a2hs-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
  z-index: 1600;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.25s var(--ease);
}
.a2hs-overlay.is-open { opacity: 1; }
.a2hs-sheet {
  width: min(440px, 100%);
  background: var(--bg-elev);
  border-radius: var(--radius-hero) var(--radius-hero) 0 0;
  border-top: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  padding: 10px 24px calc(22px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  transform: translateY(100%);
  transition: transform 0.3s var(--ease);
}
.a2hs-overlay.is-open .a2hs-sheet { transform: none; }
.a2hs-handle { width: 36px; height: 4px; border-radius: 4px; background: var(--border); margin-bottom: 8px; }
/* The live mascot (createMascot canvas) — the sheet's hero; big enough that its
   tap-reactions invite touching without needing a hint. */
.a2hs-mascot { width: 168px; height: 168px; cursor: pointer; touch-action: manipulation; }
.a2hs-title {
  margin: 8px 0 2px;
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
  letter-spacing: -0.01em;
}
.a2hs-sub {
  margin: 0 0 12px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-faint);
  text-align: center;
  max-width: 32ch;
}
.a2hs-install {
  width: 100%;
  border: none;
  border-radius: 999px;
  padding: 13px 0;
  min-height: 46px;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: filter 0.15s var(--ease);
}
.a2hs-install:hover { filter: brightness(1.08); }
.a2hs-install:disabled { opacity: 0.6; cursor: default; }
.a2hs-later {
  border: none;
  background: none;
  color: var(--text-faint);
  font: inherit;
  font-size: 13px;
  padding: 10px 16px;
  cursor: pointer;
}
.a2hs-later:hover { color: var(--text-dim); }
/* iOS two-step instruction card, share-sheet flavored. */
.a2hs-steps {
  width: 100%;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-bottom: 4px;
}
.a2hs-step {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  font-size: 13.5px;
  color: var(--text);
}
.a2hs-step + .a2hs-step { border-top: 1px solid var(--border); }
.a2hs-step-n {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--bg-elev-3);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 700;
  display: grid;
  place-items: center;
}
.a2hs-step svg { flex: none; width: 20px; height: 20px; }

/* ------------------------------------------------------------------ */
/* Early-access trial funnel (docs/EARLY_ACCESS_TRIAL.md, trial.ui.ts) */
/* ------------------------------------------------------------------ */
.trial-choice-panel,
.trial-activate-panel {
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.trial-choice-head { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 6px; padding-top: 6px; }
/* Landing-style bottom hero: the mascot rises out of the panel's bottom edge, top
   half visible. Negative margins cancel the panel padding so the clip line IS the
   panel edge; overflow hides the lower half. */
.trial-mascot-hero {
  /* Fixed to the SCREEN bottom behind the panel — the landing-page stage: a big
     mascot rising from the viewport edge, top half visible. */
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  height: min(200px, 24dvh);
  overflow: hidden;
  display: flex;
  justify-content: center;
  pointer-events: none;
}
.trial-mascot-hero-canvas {
  width: min(400px, 90vw);
  height: min(400px, 90vw);
  flex: none;
  pointer-events: auto;
}
.trial-choice-title { font-size: 1.25rem; font-weight: 700; text-align: center; }
.trial-choice-sub { color: var(--text-dim); font-size: 0.85rem; text-align: center; max-width: 42ch; margin: 0 auto; }
.trial-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev-1);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* The hero card commits to the brand gradient (owner call 2026-07-16: this funnel
   surface overrides the gradient-is-Brain-only rule — the mockup won). */
.trial-card-instant {
  border-color: rgba(110, 90, 255, 0.55);
  background:
    linear-gradient(160deg, rgba(0, 54, 255, 0.14), rgba(198, 0, 255, 0.07)),
    var(--bg-elev-1);
}
.trial-card-tag { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-dim); }
.trial-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  border: 1px solid rgba(90, 130, 255, 0.4);
  background: rgba(0, 84, 255, 0.16);
  color: #9db4ff;
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
}
.trial-pill-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.trial-cta-primary {
  background: linear-gradient(90deg, #0036ff 0%, #7b2bff 60%, #c600ff 100%);
  color: #fff;
  border: none;
  font-weight: 700;
}
.trial-cta-primary:hover { filter: brightness(1.1); }
.trial-card-heading { font-weight: 700; }
.trial-card-price { display: flex; align-items: baseline; gap: 8px; font-variant-numeric: tabular-nums; }
.trial-price-now { font-size: 1.6rem; font-weight: 800; }
.trial-price-was { text-decoration: line-through; color: var(--text-dim); }
.trial-price-off { color: var(--ok); font-weight: 700; font-size: 0.85rem; }
.trial-card-note { color: var(--text-dim); font-size: 0.8rem; }
.trial-cta { width: 100%; }
.trial-decay-strip { display: flex; gap: 6px; font-variant-numeric: tabular-nums; }
.trial-decay-tile {
  flex: 1;
  border-radius: 10px;
  padding: 7px 4px;
  text-align: center;
  background: var(--bg-elev-2);
  font-size: 0.7rem;
  color: var(--text-dim);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.trial-decay-tile b { font-size: 0.85rem; color: var(--text); }
.trial-decay-tile.is-past { opacity: 0.45; }
.trial-decay-tile.is-now { outline: 1px solid var(--accent); background: var(--accent-dim); }
.trial-decay-full b { color: var(--danger-text); }
.trial-keeps { display: flex; flex-direction: column; gap: 6px; font-size: 0.85rem; }
.trial-fee-line { color: var(--text-dim); font-size: 0.75rem; text-align: center; }
.trial-grace-warn {
  border: 1px solid var(--danger);
  background: var(--danger-bg);
  color: var(--danger-text);
  border-radius: var(--radius);
  padding: 10px 12px;
  font-size: 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Launch banner: shown ONCE per app launch (owner call — the trial clock must not
   squat in the titlebar over normal use), then it slides itself away. The standing
   access point is the hamburger row (.nav-trial-row) below. */
.trial-banner {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: 50%;
  transform: translate(-50%, -180%);
  opacity: 0;
  transition: transform 0.34s var(--ease), opacity 0.34s var(--ease);
  z-index: 1050;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  max-width: calc(100vw - 28px);
  border: 1px solid var(--danger);
  border-radius: 999px;
  background: var(--bg-elev-2);
  color: var(--danger-text);
  padding: 7px 8px 7px 14px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  box-shadow: var(--shadow-lg);
}
.trial-banner.is-in { transform: translate(-50%, 0); opacity: 1; }
.trial-banner.is-grace { background: var(--danger-bg); }
.trial-banner-cta {
  background: rgba(128, 128, 160, 0.16);
  color: inherit;
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 0.7rem;
  font-weight: 700;
}
.trial-banner.is-grace .trial-banner-cta { background: var(--danger); color: #fff; }
.trial-badge-dot {
  width: 6px;
  height: 6px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--danger-text);
  animation: trial-pulse 1.6s ease-in-out infinite;
}
@keyframes trial-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
  .trial-badge-dot { animation: none; }
}
.trial-urgency { color: var(--danger-text); font-size: 0.8rem; font-weight: 600; font-variant-numeric: tabular-nums; }

/* Persistent trial row in the hamburger drawer (the standing access point) —
   pinned to the bottom, directly ABOVE the profile footer. The slot absorbs the
   free space (margin-top:auto); when it's present the footer drops its own auto so
   the two sit adjacent (else the space splits and a gap opens between them). */
.nav-trial-slot { margin-top: auto; }
.nav-trial-slot:not([hidden]) + .nav-drawer-foot { margin-top: 0; }
.nav-trial-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--danger);
  border-radius: 12px;
  background: var(--danger-bg);
  color: var(--text);
  cursor: pointer;
  text-align: left;
}
.nav-trial-main { flex: 1 1 auto; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.nav-trial-title { font-size: 0.86rem; font-weight: 700; color: var(--danger-text); }
.nav-trial-sub { font-size: 0.75rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-trial-chev { font-size: 18px; color: var(--text-faint); flex: 0 0 auto; }

/* Grace-state activation sheet (owner mockup §7): loss-framed — a red recycle
   countdown + a "deleted forever" list — with a danger "Save it" CTA. */
.trial-grace-head {
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--danger);
  border-radius: 999px;
  padding: 4px 12px;
  color: var(--danger-text);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.06em;
}
.trial-recycle-hero {
  border: 1px solid var(--danger);
  border-radius: 16px;
  background: var(--danger-bg);
  padding: 18px;
  text-align: center;
}
.trial-recycle-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--text-dim);
}
.trial-recycle-clock {
  margin-top: 6px;
  font-size: 2.6rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--danger-text);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.trial-loss {
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.trial-loss-head {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--text-faint);
}
.trial-loss-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 0.9rem;
  line-height: 1.35;
  color: var(--text);
}
.trial-loss-x { color: var(--danger-text); font-weight: 800; flex: 0 0 auto; }
.trial-cta.is-danger {
  background: linear-gradient(100deg, #e0483a, #c0362b);
  color: #fff;
}
:root[data-theme="light"] .trial-pill {
  color: #2447cc;
  background: rgba(0, 54, 255, 0.08);
  border-color: rgba(0, 54, 255, 0.3);
}
/* Start free trial: deliberately QUIET next to the gradient Buy CTA (the buy is the
   hero; the trial is the fallback). */
.trial-cta.ghost {
  background: var(--bg-elev-2);
  color: var(--text-dim);
  border: 1px solid var(--border);
  font-weight: 600;
}
.trial-cta.ghost:hover { color: var(--text); }
/* Mobile: the mascot is the landing stage — let it dominate the bottom edge. */
.trial-mascot-hero { width: 100vw; height: min(250px, 30dvh); }
.trial-mascot-hero-canvas { width: min(500px, 125vw); height: min(500px, 125vw); }
/* Desktop: the 560px modal read as tiny on a big screen — scale the whole stage. */
@media (min-width: 900px) {
  .settings-panel.trial-choice-panel { width: min(700px, 100%); padding: 32px 36px; gap: 18px; }
  .trial-choice-title { font-size: 1.7rem; }
  .trial-choice-sub { font-size: 0.95rem; max-width: 52ch; }
  .trial-price-now { font-size: 2.2rem; }
  .trial-keeps { font-size: 0.95rem; }
  .trial-card { padding: 20px; }
  .trial-cta { padding: 15px 16px; font-size: 1.05rem; }
  .trial-decay-tile { padding: 10px 6px; font-size: 0.8rem; }
  .trial-decay-tile b { font-size: 1.05rem; }
  .trial-pill { font-size: 0.78rem; }
  .trial-mascot-hero { height: 300px; }
  .trial-mascot-hero-canvas { width: 600px; height: 600px; }
}
/* Price ON the CTA: strike + discount ride the button. */
.trial-cta-primary { display: flex; gap: 8px; align-items: baseline; justify-content: center; }
.trial-cta-was { text-decoration: line-through; opacity: 0.65; font-weight: 600; font-size: 0.85em; }
.trial-cta-off { color: #7dffb0; font-weight: 700; font-size: 0.85em; }
/* Scarcity block: sold-at-this-price bar + next tranche price + total supply. */
.trial-supply { display: flex; flex-direction: column; gap: 6px; font-variant-numeric: tabular-nums; }
.trial-supply-row, .trial-supply-sub { display: flex; justify-content: space-between; align-items: baseline; }
.trial-supply-row { font-size: 0.8rem; color: var(--text-dim); }
.trial-supply-count { color: var(--text); font-weight: 700; }
.trial-supply-bar { height: 5px; border-radius: 3px; background: var(--bg-elev-3); overflow: hidden; }
.trial-supply-bar i { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg, #0036ff, #c600ff); }
.trial-supply-sub { font-size: 0.72rem; color: var(--text-dim); }
/* The choice screen's own overlay (deliberately NOT .settings-overlay: the buy modal's
   double-open guard checks that class, and the buy sheet stacks on top of this). */
.trial-choice-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
  display: grid;
  place-items: center;
  padding: 20px;
  z-index: 1090; /* just under .settings-overlay (1100) so the buy sheet covers it */
}
/* Roomier instant card: 4 short lines, generous rhythm — less brochure, more poster. */
.trial-card-instant { gap: 14px; padding: 18px; }
.trial-keeps-roomy { gap: 11px; line-height: 1.5; }
.trial-supply { margin-top: 2px; }
@media (min-width: 900px) {
  .trial-card-instant { gap: 16px; padding: 24px; }
  .trial-keeps-roomy { gap: 13px; }
}
/* Quiet emphasis (v2 — the animated ring/glow/shine was unreadable): a static soft
   bloom and a slightly stronger gradient edge. No motion, full text contrast. */
.trial-card-instant {
  border-color: rgba(123, 43, 255, 0.65);
  box-shadow: 0 0 36px rgba(123, 43, 255, 0.18), 0 0 90px rgba(0, 54, 255, 0.1);
}
/* Merged funnel bits inside the buy modal (EARLY_ACCESS_TRIAL.md §2). */
.buy-earn-props { display: flex; flex-direction: column; gap: 8px; }
.buy-earn-prop { font-size: 0.86rem; line-height: 1.45; }
/* Trial fallback (owner call v4) — a quiet secondary pill right under the buy CTA;
   opens the rules confirm sheet (owner call v3). */
.buy-trial-btn {
  width: 100%;
  padding: 13px 22px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font-size: 14.5px;
  font-weight: 700;
  cursor: pointer;
}
.buy-trial-btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); }
.buy-trial-btn:disabled { opacity: 0.6; cursor: default; }
/* Poker-deck fan: ghost cards behind the live mascot card + a ×N chip. */
.buy-card { z-index: 1; }
.buy-card-ghost {
  position: absolute;
  inset: 10px; /* = .buy-card-stage padding, so ghosts mirror the live card's box exactly */
  border-radius: var(--radius-lg);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.4);
}
.buy-card-ghost-1 { transform: rotate(-5deg) translate(-12px, 8px); }
.buy-card-ghost-2 { transform: rotate(4deg) translate(12px, 10px); }
.buy-card-ghost-3 { transform: rotate(-9deg) translate(-22px, 18px); }
.buy-deck-count {
  position: absolute;
  top: -8px;
  right: 2px;
  z-index: 2;
  background: linear-gradient(90deg, #0036ff, #c600ff);
  color: #fff;
  border-radius: 999px;
  padding: 4px 11px;
  font-size: 0.78rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
/* ---- Buy-modal drop stage (owner redesign v4) — single column everywhere. ---- */
.buy-card-stage {
  background:
    radial-gradient(60% 50% at 50% 42%, rgba(0, 54, 255, 0.14), transparent 70%),
    radial-gradient(50% 40% at 55% 60%, rgba(198, 0, 255, 0.1), transparent 70%);
  border-radius: 24px;
  padding: 10px;
}
/* Name row: name + the 🎲 reroll die (random names, rerollable — no name field). */
.buy-card-name-wrap { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.buy-card-reroll {
  background: rgba(255, 255, 255, 0.1);
  border: none;
  border-radius: 8px;
  font-size: 0.85rem;
  line-height: 1;
  padding: 4px 6px;
  cursor: pointer;
  transition: transform 0.15s ease;
}
.buy-card-reroll:hover { transform: rotate(-20deg) scale(1.15); }
.buy-card-reroll:active { transform: rotate(180deg) scale(0.9); }
/* Poke hint: fades after the first poke (the mascot reacts to taps). */
.buy-poke-hint {
  position: absolute;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  font-size: 0.72rem;
  color: var(--text-dim);
  background: rgba(0, 0, 0, 0.45);
  border-radius: 999px;
  padding: 4px 12px;
  pointer-events: none;
  white-space: nowrap;
  transition: opacity 0.5s ease;
}
.buy-poke-hint.is-gone { opacity: 0; }
.buy-card-stage { position: relative; }
/* ---- Money block (owner redesign v4): ONE white number, one payback sub-line,
   one rotating value-prop line. Rides over the sticky deck on mobile (z 3). ---- */
.buy-agent-card-col .buy-money { position: relative; z-index: 3; }
.buy-money { text-align: center; display: flex; flex-direction: column; gap: 5px; width: min(430px, 100%); }
.buy-hero-num {
  font-size: 43px; /* ~80% of the old 54px (owner call) */
  font-weight: 800;
  letter-spacing: -1.6px;
  line-height: 1.05;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* Promo glint (owner call): a slow metallic sheen sweeps the money. The gradient
   paints only glyphs with no color of their own ($ + digits) — "Earns" and "/day"
   keep their explicit dim ink over it. */
@supports (-webkit-background-clip: text) {
  .buy-hero-num {
    background: linear-gradient(110deg, #fff 35%, #cdb6ff 46%, #96a4ff 50%, #cdb6ff 54%, #fff 65%);
    background-size: 240% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: buy-hero-glint 3.6s ease-in-out infinite;
  }
}
@keyframes buy-hero-glint {
  0%, 45% { background-position: 115% 0; }
  90%, 100% { background-position: -15% 0; }
}
@media (prefers-reduced-motion: reduce) { .buy-hero-num { animation: none; } }
.buy-hero-unit { font-size: 0.42em; font-weight: 700; color: var(--text-dim); letter-spacing: 0; }
/* "Earns" eyebrow ABOVE the number + the "for training the network" line BELOW it —
   the two quiet ends of the headline sentence, thin/light (owner call). */
.buy-hero-eyebrow,
.buy-hero-why { color: var(--text-faint); font-size: 12px; font-weight: 500; }
.buy-hero-eyebrow { margin-bottom: -3px; }
.buy-hero-why { margin-top: -2px; }
.buy-hero-soon {
  align-self: center;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 10px;
}
/* Feature rotation = the Genie's chat bubbles over the card (replaced the money-block
   ticker, owner call). Pop in, hold, fade — sides alternate like a conversation. No
   backdrop-filter (compositor lags it behind momentum scroll in this sheet). */
.buy-card-bubble {
  position: absolute;
  bottom: 14%; /* under the mascot orb, above the poke hint — never over the face */
  right: -8px;
  z-index: 3;
  max-width: 78%;
  padding: 9px 13px;
  border-radius: 16px;
  border-top-right-radius: 4px;
  /* M3 secondary-container tone (owner call: too many competing gradients) — a soft
     lavender surface, not a saturated gradient. Reads as the Genie's speech bubble
     while leaving the CTA gradient as the only vivid call to action. */
  background: color-mix(in srgb, #7b5bff 16%, var(--bg-elev-1));
  border: 1px solid color-mix(in srgb, #7b5bff 30%, transparent);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.35;
  text-align: left;
  opacity: 0;
  pointer-events: none;
}
.buy-card-bubble.is-left {
  right: auto;
  left: -8px;
  border-radius: 16px;
  border-top-left-radius: 4px;
}
.buy-card-bubble.is-in { animation: buy-bubble-pop 3.2s ease forwards; }
@keyframes buy-bubble-pop {
  0% { opacity: 0; transform: translateY(8px) scale(0.9); }
  7%, 82% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-6px) scale(0.96); }
}
@media (prefers-reduced-motion: reduce) {
  .buy-card-bubble.is-in { animation: none; opacity: 1; }
}

/* ---- Price ladder: one quiet line + a thin bar (scarcity = "it only rises"). ---- */
.buy-ladder { width: 100%; }
.buy-ladder-line { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 12.5px; color: var(--text-faint); }
.buy-ladder-line b { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.buy-ladder-bar { height: 3px; border-radius: 4px; background: var(--bg-elev-2); overflow: hidden; margin-top: 6px; }
.buy-ladder-fill { height: 100%; border-radius: 4px; background: var(--text); transition: width 0.7s cubic-bezier(0.2, 0.7, 0.2, 1); }
.buy-ladder-sold { font-variant-numeric: tabular-nums; }
/* A staged buy briefly flashes the fill as it ticks up. */
.buy-ladder-fill.is-tick { animation: buy-ladder-tick 0.6s ease; }
@keyframes buy-ladder-tick {
  0%, 100% { filter: none; }
  40% { filter: brightness(1.5) saturate(1.3); }
}

/* ---- Purchase theatre toast (billing.ui.ts): a staged "others are buying / using"
   feed. Body-mounted + position:fixed so it drops from the TOP OF THE SCREEN over the
   modal titlebar, JS-aligned to the panel width. `.is-buy` is the hyped variant. ---- */
.buy-toast {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 12px); /* top of the SCREEN, below any notch */
  z-index: 1200; /* above .settings-overlay (1100) */
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: 14px;
  padding: 9px 12px;
  overflow: hidden;
  transform: translateY(-220%);
  opacity: 0;
  transition: transform 0.42s cubic-bezier(0.2, 0.85, 0.25, 1), opacity 0.42s;
}
.buy-toast.is-show { transform: none; opacity: 1; }
.buy-toast-body { flex: 1; min-width: 0; position: relative; z-index: 2; }
.buy-toast-name { font-size: 12.5px; font-weight: 700; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text); }
.buy-toast-sub { display: block; color: var(--text-faint); font-size: 10px; }
.buy-toast-plus {
  flex: 0 0 auto; position: relative; z-index: 2;
  font-size: 12px; font-weight: 800; color: #04120a;
  background: var(--good, #37d67a); border-radius: 999px; padding: 2px 8px;
  display: none;
}
/* Quiet usage toast — recedes into the app surface. */
.buy-toast.is-use { background: var(--bg-elev); border: 1px solid var(--border); box-shadow: var(--shadow-lg); }
/* Hyped buy toast — brand-tint glass, bouncy wiggle entrance + a color-cycling border. */
.buy-toast.is-buy {
  background: linear-gradient(135deg, rgba(43, 74, 255, 0.36), rgba(150, 28, 255, 0.34)), var(--bg-elev);
  border: 1.6px solid rgba(170, 120, 255, 0.85);
}
.buy-toast.is-buy .buy-toast-name { font-weight: 800; }
.buy-toast.is-buy .buy-toast-plus { display: block; }
.buy-toast.is-buy::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 60px; left: -80px; z-index: 3;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.4), transparent);
  animation: buy-toast-shine 1.05s ease 0.12s;
}
@keyframes buy-toast-shine { to { left: 120%; } }
.buy-toast.is-buy.is-show { animation: buy-toast-pop 0.74s cubic-bezier(0.2, 1.5, 0.3, 1), buy-toast-flash 1.15s ease-in-out 0.15s infinite; }
@keyframes buy-toast-pop {
  0% { transform: translateY(-220%) scale(0.9); }
  50% { transform: translateY(0) scale(1.06); }
  64% { transform: rotate(-2.4deg); }
  76% { transform: rotate(1.8deg); }
  88% { transform: rotate(-0.9deg); }
  100% { transform: none; }
}
@keyframes buy-toast-flash {
  /* The color-cycling BORDER carries the "flashy" feel; the ambient bloom is kept
     tight + dim so it no longer bleeds a violet wash onto the modal titlebar below. */
  0%, 100% { border-color: rgba(170, 120, 255, 0.9); box-shadow: 0 0 0 1px rgba(198, 0, 255, 0.18), 0 8px 20px rgba(70, 40, 160, 0.28); }
  50% { border-color: rgba(0, 225, 255, 0.95); box-shadow: 0 0 10px 1px rgba(0, 200, 255, 0.4), 0 8px 20px rgba(110, 40, 210, 0.3); }
}
/* Confetti sparks on a buy (left side, where the emoji leads). */
.buy-toast-spark { position: absolute; top: 13px; left: 16px; width: 5px; height: 5px; border-radius: 1px; pointer-events: none; opacity: 0; z-index: 3; }
.buy-toast.is-buy.is-show .buy-toast-spark { animation: buy-toast-spark 0.7s ease-out 0.05s; }
.buy-toast-spark.s1 { background: #ffd23f; transform: rotate(20deg); }
.buy-toast-spark.s2 { background: #37d67a; left: 34px; }
.buy-toast-spark.s3 { background: #c600ff; left: 54px; }
.buy-toast-spark.s4 { background: #00c2ff; left: 74px; }
@keyframes buy-toast-spark {
  0% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-18px) scale(0.4); }
}
@media (prefers-reduced-motion: reduce) {
  .buy-ladder-fill, .buy-toast { transition: opacity 0.2s; }
  .buy-toast.is-buy.is-show, .buy-toast.is-buy::after, .buy-toast.is-buy.is-show .buy-toast-spark, .buy-ladder-fill.is-tick { animation: none; }
  .buy-toast { transform: none; }
}

/* Quantity row: naked chips + stepper, centered, no label (v4). */
.buy-qty-center { justify-content: center; }

/* ---- Fine print row: payouts fold, coupon fold, trial link on ONE quiet line;
   an opened fold wraps onto its own full-width line. ---- */
.buy-fineline {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 4px 18px;
  font-size: 12.5px;
  color: var(--text-faint);
}
/* Accordion toggles: the links keep ONE line forever; the open pane renders in
   .buy-finedetail below the row (owner call — <details> folds stacked the links
   and crunched the opened form mid-row). */
.buy-fine-toggle {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-faint);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.buy-fine-toggle:hover,
.buy-fine-toggle.is-open { color: var(--text-dim); }
.buy-finedetail { width: 100%; margin-top: -6px; }
.buy-finedetail .buy-earn-rows { max-width: 340px; margin: 8px auto 0; text-align: left; }
.buy-finedetail .buy-coupon-row { max-width: 340px; margin: 8px auto 0; }
.buy-earn-percap { text-align: center; font-size: 12px; color: var(--text-faint); }
/* The pane content came from old panel homes — strip any inherited divider. */
.buy-finedetail .buy-earn-pane { margin-top: 0; border-top: none; padding-top: 0; }

/* ---- Trial rules confirm sheet (owner call v3): the gate before the trial starts —
   numbered rules, one red-marked warning, one confirm tap. ---- */
.trial-rules-cover {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(5, 5, 8, 0.72);
  display: grid;
  place-items: center;
  padding: 20px;
}
.trial-rules {
  width: min(420px, 100%);
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 24px 24px 16px;
  box-shadow: 0 40px 90px rgba(0, 0, 0, 0.7);
}
.trial-rules h3 { margin: 0 0 4px; font-size: 18px; }
.trial-rules-sub { color: var(--text-dim); font-size: 13.5px; margin-bottom: 16px; }
.trial-rule { display: flex; gap: 11px; align-items: flex-start; font-size: 14px; line-height: 1.5; margin-bottom: 12px; color: var(--text-dim); }
.trial-rule b { color: var(--text); }
.trial-rule-n {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--border);
  color: var(--text-faint);
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 800;
  margin-top: 2px;
}
.trial-rule.is-warn { border-left: 2px solid #ff6a6a; padding-left: 12px; margin-left: 3px; }
.trial-rule.is-warn b { color: #ff8484; }
.trial-rules-start {
  width: 100%;
  border: none;
  border-radius: 999px;
  padding: 14px;
  background: var(--text);
  color: var(--bg);
  font-size: 15.5px;
  font-weight: 800;
  cursor: pointer;
  margin-top: 8px;
}
.trial-rules-start:disabled { opacity: 0.6; }
.trial-rules-back {
  width: 100%;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
  padding: 10px 8px 4px;
  cursor: pointer;
}
.trial-rules-back:hover { color: var(--text); }
/* Hero parallax (owner call): on the single-column sheet the card deck holds just
   under the titlebar while the descriptive content scrolls faster and slides up
   OVER it — sticky is scoped to the card column, so once the value card has ridden
   over the deck the whole column departs together. CSS-only: sticky runs on the
   compositor, so it stays in sync with iOS momentum scroll. */
@media (max-width: 679.98px) {
  .buy-agent-card-col .buy-card-stage {
    position: sticky;
    top: 76px; /* tucked under the sticky titlebar (~73px) */
    z-index: 0;
  }
}
/* Dark veil over the deck as content rides over it (--deck-dim set per scroll frame
   in billing.ui.ts). Oversized inset so the fan's spilling ghost corners dim too;
   z-index 5 tops everything inside the stage's own stacking context (card z1,
   poke hint / ×N chip z2). Invisible at 0 — desktop never sets it. */
.buy-card-stage::after {
  content: "";
  position: absolute;
  inset: -20px;
  border-radius: 32px;
  background: #0a0a0e;
  opacity: var(--deck-dim, 0);
  pointer-events: none;
  z-index: 5;
}
/* (Squarer-card experiment reverted — owner call: keep the 5/7 poker aspect,
   just smaller overall.) */

/* ---- "You're invited" — the login card's invite step (EARLY_ACCESS_TRIAL §2 step 1).
   Rides the normal .auth-card overlay on the landing; .auth-body supplies the gaps. ---- */
.ig-code-box {
  border: 1px solid rgba(155, 122, 255, 0.45);
  border-radius: 16px;
  padding: 11px 14px 8px;
  background: rgba(120, 80, 255, 0.07);
  text-align: center;
}
.ig-code-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--text-faint);
}
.ig-code {
  width: 100%;
  background: none;
  border: none;
  outline: none;
  color: var(--text);
  text-align: center;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 21px;
  font-weight: 700;
  letter-spacing: 5px;
  text-transform: uppercase;
  padding: 6px 0 2px;
}
.ig-code::placeholder { color: var(--text-faint); letter-spacing: 3px; }
.ig-chip {
  align-self: center;
  width: fit-content;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-dim);
  background: rgba(128, 128, 160, 0.14);
  border-radius: 999px;
  padding: 5px 12px;
}
.auth-btn.ig-btn { font-weight: 700; }
.ig-alt { text-align: center; font-size: 13px; color: var(--text-faint); margin-top: 2px; }
.ig-alt-link { color: var(--text-dim); text-decoration: underline; text-underline-offset: 3px; }
.ig-alt-link:hover { color: var(--text); }
/* SLIDE TO CONFIRM. A button underneath, so the keyboard and a screen reader
   get the same action the gesture does. */
.slide { position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 52px; padding: 0; border: none; border-radius: 999px; background: var(--bg-elev-2); color: var(--text); font: inherit; cursor: pointer; overflow: hidden; }
/* A PILL, not a bar (owner 2026-08-01: "should not have a square progress,
   needs to be rounded"). Inset to the knob's own 4px so the fill reads as the
   trail the knob is dragging behind it, with a leading edge the same shape as
   the knob's — a square edge under a round knob is the thing that looks unfinished. */
.slide-fill { position: absolute; left: 4px; top: 4px; bottom: 4px; width: 44px; border-radius: 999px; background: color-mix(in srgb, var(--sm-up) 26%, transparent); pointer-events: none; }
.slide-t { position: relative; font-size: 13.5px; font-weight: 680; pointer-events: none; padding: 0 56px; text-align: center; }
.slide-knob { position: absolute; left: 4px; top: 4px; width: 44px; height: 44px; border-radius: 50%; background: var(--sm-up); color: var(--sm-on-up); display: grid; place-items: center; touch-action: none; cursor: grab; }
.slide-knob:active { cursor: grabbing; }
.slide-knob svg { width: 22px; height: 22px; pointer-events: none; }
.slide.snap .slide-knob, .slide.snap .slide-fill { transition: transform .18s ease, width .18s ease; }
.slide.done { background: color-mix(in srgb, var(--sm-up) 22%, transparent); }
.slide:disabled { cursor: default; }
@media (prefers-reduced-motion: reduce) { .slide.snap .slide-knob, .slide.snap .slide-fill { transition: none; } }
/* -- THE SECURE CREDENTIAL SHEET (request_secret) ------------------------
   Core, not plugin: the form answers a request for ANY secret -- its kind is
   okx_keys | generic and it POSTs to the core /api/secrets/drop route. It
   wore sm-gate and sm-gsheet names, which is what the leak audit was seeing;
   the names are generic now and the surface stayed where it belongs. Rendered
   by src/web/secretDrop.ts AND by the plugin's own exchange gate, which is the
   other reason these live in the shared sheet. */

.gate-i { appearance: none; background: none; border: none; cursor: pointer; font: inherit; font-size: 11.5px; color: var(--accent, #5b7cfa); padding: 0; }
.gate-help { background: var(--bg, #131314); border-radius: 12px; padding: 10px 12px; margin-bottom: 10px; }
.gate-step { display: flex; gap: 10px; align-items: flex-start; font-size: 12.5px; line-height: 1.45; padding: 4px 0; }
.gate-n { flex: 0 0 20px; height: 20px; border-radius: 50%; background: color-mix(in srgb, var(--accent, #5b7cfa) 22%, transparent); color: var(--accent, #5b7cfa); font-size: 11px; font-weight: 700; font-style: normal; display: flex; align-items: center; justify-content: center; }
.gate-note { font-size: 11px; color: var(--text-faint); line-height: 1.5; margin-top: 8px; }
.gate-note.bad { color: var(--sm-down); }
.gsheet-grip { width: 36px; height: 4px; border-radius: 2px; background: var(--border); margin: 4px auto 10px; }
.gsheet-x { position: absolute; top: 10px; right: 12px; appearance: none; background: none; border: none; cursor: pointer; color: var(--text-faint); font-size: 15px; line-height: 1; padding: 6px; }


/* Go-live gate (§10.4 A0) — M3 form, mockup parity. */
.gate { background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 14px 16px; margin-top: 10px; }
.gate-h { font-size: 14px; font-weight: 700; margin-bottom: 10px; }
.gate-f { margin-bottom: 10px; }
.gate-l { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-dim); margin-bottom: 5px; }
.gate-in { width: 100%; box-sizing: border-box; background: var(--bg, #131314); border: 1px solid var(--border); border-radius: 12px; color: var(--text); font: inherit; font-size: 13px; padding: 10px 12px; }
/* Secret-drop form (docs/SECRET_DROP.md). Reuses the .gate field styles; these four
   are what the credential sheet adds: the agent's stated reason, an error line, and a
   two-button row where declining is as reachable as submitting. */
.gate-sub { font-size: 12.5px; line-height: 1.5; color: var(--text-dim); margin: -4px 0 12px; }
.gate-err { font-size: 12px; line-height: 1.45; color: var(--sm-down); margin: 2px 0 10px; }
.gate-row { display: flex; gap: 10px; align-items: center; margin-top: 4px; }
.gate-go { flex: 1; appearance: none; border: none; cursor: pointer; font: inherit; font-size: 13px; font-weight: 650; border-radius: 12px; padding: 11px 14px; background: var(--accent, #5b7cfa); color: #fff; }
.gate-go[disabled] { opacity: 0.6; cursor: default; }
.gate-skip { appearance: none; background: none; border: 1px solid var(--border); cursor: pointer; font: inherit; font-size: 13px; color: var(--text-dim); border-radius: 12px; padding: 11px 14px; }
.gate-skip[disabled] { opacity: 0.6; cursor: default; }

/* Exchange-connect bottom sheet (owner 2026-07-27): slide-up modal, scrim,
   escape/backdrop/✕ to dismiss. */
.gsheet-scrim { position: fixed; inset: 0; z-index: 90; display: flex; align-items: flex-end; justify-content: center; background: rgba(0,0,0,0); transition: background 0.22s ease; }
.gsheet-scrim.in { background: rgba(0,0,0,0.55); }
.gsheet { position: relative; width: min(520px, 100%); max-height: 88vh; overflow-y: auto; background: var(--bg-elev, #1e1f20); color: var(--text); border: 1px solid var(--border); border-bottom: none; border-radius: 26px 26px 0 0; padding: 8px 16px calc(20px + env(safe-area-inset-bottom, 0px)); transform: translateY(100%); transition: transform 0.24s cubic-bezier(0.2, 0, 0, 1); box-shadow: 0 -18px 48px rgba(0,0,0,0.45); }
.gsheet-scrim.in .gsheet { transform: translateY(0); }
.gsheet .gate { background: none; border: none; padding: 0 0 4px; margin: 0; }
/* ── A plugin screen that supplies its own top row ──────────────────────────
   Core does not know what the screen puts up there; it only stands its own bar
   down so there is one row, not two, and lets the screen start at the very top
   of the pane. Set by setVirtualScreenOpen from the view's own declaration, so
   every screen that declares nothing is untouched. */
/* MOBILE ONLY. On desktop the pane sits beside the task list and the bar is
   how you get back to it, so it stays; the phone is where two stacked rows cost
   a band of the screen. 899px is core's own breakpoint — the same one that
   decides whether the back chevron shows — so the two never disagree. The flag
   itself stays width-blind: the screen declares that it draws a top row, and
   core decides where that is worth acting on. */
@media (max-width: 899px) {
  .session-bar.is-supplied { display: none; }
  .session-panel.bar-supplied .transcript { padding-top: 0; }
}

/* ══ THE M3 FILTER CHIP, AND THE REST OF THE SHARED SCREEN CHROME ═════════
   The Trade screen's tab strip (Home / Trending / Leaderboard / Feed) is
   `.sm-tab` in Smart Money's own stylesheet. The Never-miss-out screen has to be
   the same component (owner, 2026-08-27: "nav bar font size and style should be
   material 3, and similar to smart money's Trade screen") — and it must also
   work with Smart Money NOT INSTALLED, because core boots with any plugin
   absent and a screen borrowing another plugin's class loses its type the moment
   that plugin is switched off.

   So the chip lives here. EVERY VALUE IS `.sm-tab`'S, COPIED NOT INVENTED, so
   Smart Money is pixel-identical before and after: 13px/560 label, 9px 15px
   padding on line-height 1 (a 31px pill), `--bg-elev-1` ground, and the selected
   fill `--sm-chip-on` with the on-surface colour at 620.

   `.on` is Smart Money's selected class and `.is-on` is nmo's; both are here so
   neither screen has to rename anything to share the rule.

   ADOPTING IT: `plugins/smartmoney/web/quant.ts` `mkTab()` emits `"sm-tab"` —
   adding `m3-chip` there and deleting `.sm-tab`'s size declarations makes the
   sharing real rather than parallel. That is two lines in a file this pane does
   not own; until then the suites compare the two rule sets and fail on drift. */
.m3-chiprow { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; }
.m3-chiprow::-webkit-scrollbar { display: none; }
.m3-chip {
  flex: 0 0 auto; white-space: nowrap; font: inherit; font-size: 13px; font-weight: 560;
  color: var(--text-dim); background: var(--bg-elev-1); border: none; border-radius: 999px;
  padding: 9px 15px; line-height: 1; cursor: pointer;
  transition: background .14s, color .14s, transform .1s;
}
.m3-chip:hover { background: var(--bg-elev-2); }
.m3-chip:active { transform: scale(0.96); }
.m3-chip.on, .m3-chip.is-on { background: var(--sm-chip-on); color: var(--text); font-weight: 620; }
.m3-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The range row is smaller and reads in the up colour when chosen (from
   Smart Money's `.qn-range-b`). */
.m3-rangechip {
  appearance: none; border: none; background: none; cursor: pointer; font: inherit;
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em; color: var(--text-faint);
  border-radius: 999px; padding: 4px 10px;
}
.m3-rangechip.on, .m3-rangechip.is-on { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 14%, transparent); }
/* A section label: small, uppercase, letter-spaced, quiet. */
.m3-sectionlabel {
  font-size: 11px; font-weight: 640; color: var(--text-faint);
  letter-spacing: 0.07em; text-transform: uppercase; padding: 0 0 12px;
}
/* Nothing to show yet: a sentence, left-aligned under whatever it is about. */
.m3-empty { font-size: 13px; color: var(--text-faint); padding: 10px 0; }

/* ══ A SCREEN THAT SUPPLIES ITS OWN TOP ROW ═══════════════════════════════
   Its row stands where the session top bar stands, so it is that bar's box:
   the same padding, the same gap, the same 40px round icon buttons, and the
   title set in `.session-title`'s scale rather than a display number. The
   trailing controls end at the row's content edge, exactly where the kebab
   they replace ends — no margin after the last one. */
.m3-toprow { display: flex; align-items: center; gap: 8px; padding: 12px 16px; }
.m3-toprow-title { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m3-toprow-actions { margin-left: auto; margin-right: 0; display: flex; align-items: center; gap: 4px; flex: none; }
.m3-toprow-actions > :last-child { margin-right: 0; }
/*
 * ═══ THE GLYPH ON THE EDGE, NOT THE BOX ═══
 *
 * A 40px round button around a 20px glyph carries 10px of tap target on each
 * side. Sitting the BOX on the 16px content inset therefore leaves the glyph
 * ~26px from the screen's edge, which is what the owner was looking at: the row
 * reads as if it stops short. Pulling the group right by exactly that inner
 * padding puts the glyph where the eye expects the row to end, and leaves the
 * tap target the full 40px it was.
 *
 * The offset is DERIVED — half the difference between core's own button size
 * and the glyph the row sets — so neither number is typed twice and changing
 * either one moves the pull with it.
 *
 * THE SESSION BAR'S KEBAB DOES NOT DO THIS: it is the same 40px box around a
 * 20px glyph and sits on the plain inset. So a screen wearing `.pull-glyph`
 * ends ~10px further right than that bar does. On a phone the two never appear
 * together — a screen that supplies its own top row replaces it — so this is
 * opt-in rather than a change to core's bar, which nobody has asked to move.
 */
.m3-toprow-actions.pull-glyph {
  margin-right: calc((var(--icon-btn-size, 40px) - var(--m3-toprow-glyph, 20px)) / -2);
}
/* A text button on the same row as 40px icon buttons: the chip's label, a
   32px pill, so it never stands taller than what it sits beside. */
.m3-textbtn {
  flex: none; font: inherit; font-size: 13px; font-weight: 560; line-height: 1;
  height: 32px; padding: 0 14px; border: none; border-radius: 999px;
  color: var(--text); background: var(--bg-elev-1); cursor: pointer; white-space: nowrap;
}
.m3-textbtn:hover { background: var(--bg-elev-2); }
.m3-textbtn:disabled { opacity: 0.5; cursor: default; }
.m3-textbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Copyright (c) 2026 Roy Liu. All rights reserved.
   SPDX-License-Identifier: LicenseRef-Proprietary */
/* ═══ SMART MONEY — THE PLUGIN'S OWN STYLESHEET ═══════════════════════════
   Extracted from src/web/styles.css on 2026-08-17 (split scrub). Contributors
   to the public core repository must never see trading code, and 1,600-odd
   trading selectors sitting in the core stylesheet were the largest remaining
   leak on the web side.

   HOW IT IS SERVED: scripts/build-web.mjs APPENDS this file to the core
   stylesheet when the plugin tree is present, and emits the core stylesheet
   alone when it is not. A build seam, not a runtime fetch — the browser still
   loads exactly one /styles.css, so there is no second request, no flash of
   unstyled trading UI, and nothing to get out of order.

   WHAT IS IN HERE: every .sm-* and .qn-* block, the @keyframes they animate,
   and the .st-* Stacks-editor blocks — unprefixed, but rendered only by
   plugins/smartmoney/web/quant.ts, which makes them plugin DOM by the same
   test. Whole blocks moved in their original relative order; nothing was split.

   WHAT STAYED, and why, is in the round report: the --sm-up/--sm-down/
   --sm-on-up/--sm-chip-on theme TOKENS remain in core :root because a core
   surface (.home-chip-badge) reads them, and a custom property is a
   declaration rather than a selector. The .smx-* boot-scanner styles stay
   because smartmoneyScanner.ts was a core web file then. Round 2 moved it here,
   and these styles followed it.

   ORDER: these rules now land after ALL core rules instead of interleaved.
   Two blocks were order-sensitive — the ones whose SUBJECT is a core element
   (.transcript:has(> .plugin-mount) and body.qn-composer-off's three) — and both
   only match when plugin DOM or plugin state exists, so arriving later in the
   cascade strengthens them and their absence removes them cleanly. */

/* A client surface (Quant, the board, Predict) leads with its OWN tab bar, so the
   24px of chat-message breathing room under the title bar only pushed it away
   from the nav (owner 2026-07-29). A conversation still gets the full inset. */
.transcript:has(> .plugin-mount) { padding-top: 8px; }
body.qn-composer-off .composer-wrap,
body.qn-composer-off .usage-hud,
body.qn-composer-off .context-hint {
  max-height: 0;
  overflow: hidden;
  transform: translateY(18px);
  opacity: 0;
  pointer-events: none;
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  margin-bottom: 0;
}

/* 聪明钱 palette matched to the 预测市场 screen (owner call 2026-07-17): the
   board sits on the app's neutral ground and paints its cards/borders as a thin
   wash IN the text color (color-mix), exactly like predict's --s1/--s2. That
   auto-themes — a light wash on the dark ground, a dark wash on the light
   ground — so no per-theme hex overrides are needed (the old committed-dark
   block is gone). The boot scanner keeps its own instrument surface (smx). */
.sm-root {
  --bg-elev-1: color-mix(in srgb, var(--text) 5%, transparent);
  --bg-elev-2: color-mix(in srgb, var(--text) 9%, transparent);
  --bg-elev-3: color-mix(in srgb, var(--text) 14%, transparent);
  --border: color-mix(in srgb, var(--text) 9%, transparent);
  --border-strong: color-mix(in srgb, var(--text) 16%, transparent);
  /* Active-chip wash, matching predict's --chip-on (text 16%). */
  --sm-chip-on: color-mix(in srgb, var(--text) 16%, transparent);
  /* Card surface — the tonal wash in DARK, pure WHITE in light (owner call:
     light-theme cards should be white, not grey). Separate from the chip fill
     so nav chips stay tonal pills while cards go white. */
  --sm-card: var(--bg-elev-1);
  /* Same type foundation as predict (M3/Robinhood): SF-Pro-first system stack,
     antialiased, tabular numerals everywhere — NOT the app's Google Sans. */
  font-family: -apple-system, "SF Pro Text", "Segoe UI", Roboto, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
  max-width: 960px; margin: 0 auto; padding: 6px 4px 28px; display: flex; flex-direction: column; container-type: inline-size;
  /* TRANSPARENT root (like predict's .pd-root): the board rides the container's
     elevated surface instead of sinking a darker --bg (#131314) well inside it.
     Measured: predict sits on #1e1f20; the old `background: var(--bg)` made the
     board a darker panel-in-a-panel. Cards come from the text-color washes. */
  background: transparent; color: var(--text);
}
/* Light theme: cards are WHITE, not the grey text-wash (owner call). Chips keep
   their tonal fill (they stay --bg-elev-1). */
:root[data-theme="light"] .sm-root { --sm-card: #ffffff; }
/* Flat mockup sections (owner: match the approved mockup, not chat-card boxes). */
.sm-flat { margin-top: 4px; }
.sm-body { flex: 1 1 auto; }
.sm-sec { font-size: 13px; font-weight: 650; color: var(--text-dim); padding: 18px 0 4px; }
.sm-sec-flex { display: flex; justify-content: space-between; align-items: baseline; }
.sm-cap { font-size: 12px; color: var(--text-faint); padding: 2px 0 8px; }
.sm-cap-inline { font-size: 12px; font-weight: 400; color: var(--text-faint); }
.sm-empty { color: var(--text-faint); font-size: 14px; padding: 28px 0; text-align: center; }

.sm-list { display: flex; flex-direction: column; }
.sm-row { display: flex; align-items: center; gap: 12px; padding: 19px 0; border-bottom: 1px solid var(--border); cursor: pointer; }
.sm-row:last-child { border-bottom: none; }
.sm-avatar { width: 34px; height: 34px; border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; color: #eceef4; }
.sm-row-main { flex: 1; min-width: 0; }
.sm-row-name { font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sm-row-sub { display: flex; align-items: center; gap: 12px; font-size: 12px; color: var(--text-faint); margin-top: 3px; font-variant-numeric: tabular-nums; }
.sm-sub-bit { display: inline-flex; align-items: center; gap: 4px; }
.sm-sub-ico { font-size: 15px; line-height: 1; }
/* PnL owns a right-aligned fixed slot so the numbers line up down the list. */
.sm-row-pnl { flex: none; min-width: 62px; text-align: right; font-size: 17px; font-weight: 700; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
/* TONED BY THE NUMBER'S OWN SIGN (owner 2026-08-17). This rule hard-coded the up
   token, so every row's dollar figure was green — including a losing one, which
   is the same class of defect as the balance-growth "+$177k". Same two tokens as
   the trades and strategies screens; no third convention. The caption inside sets
   its own colour, so it is unaffected by this. */
.sm-row-pnl.up { color: var(--sm-up); }
.sm-row-pnl.down { color: var(--sm-down); }
.sm-row-pnl-cap { font-size: 10px; font-weight: 500; letter-spacing: 0; color: var(--text-faint); margin-top: 1px; white-space: nowrap; }
/* The caption is the signed 30-day percent now, and the percent IS the list's
   order (owner 2026-08-17) — so it is toned like the persona hero's delta rather
   than left faint. `--text-faint` above stays the base: it is what an
   unmeasurable row's "Last 30 days" still wears, which is the one row where a
   percent would have to be invented. Same two tokens the trades and strategies
   screens use for up/down; no third convention. */
.sm-row-pnl-cap.up { color: var(--sm-up); font-weight: 620; }
.sm-row-pnl-cap.down { color: var(--sm-down); font-weight: 620; }
/* Fixed-width pill so Follow/Following don't shift the columns. */
.sm-row .sm-follow-pill { min-width: 84px; text-align: center; margin-top: 0; padding: 7px 10px; }
/* The sparkline is decoration — the first thing to go when width runs out. */
@media (max-width: 390px) { .sm-row .sm-mini { display: none; } }

.sm-alert { border: 1px solid var(--border); border-radius: 14px; padding: 11px 13px; margin-bottom: 8px; }
/* Instagram anatomy for move cards: header (who/what · when), chart media,
   one fact line, actions. */
.sm-ig-head { display: flex; align-items: center; gap: 10px; }
/* 34px was an avatar for a 15px title; against a 13px one it dwarfed the line it
   leads. 26px is the app's small leading-disc size and the arrow drops with it. */
.sm-ig-ava { flex: none; width: 26px; height: 26px; border-radius: 999px; display: grid; place-items: center; font-size: 12px; font-weight: 800; }
.sm-ig-ava.long { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 14%, transparent); }
.sm-ig-ava.short { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 13%, transparent); }
.sm-ig-id { min-width: 0; }
/* THE CARD TITLE, at the app's own card-title size (owner 2026-08-17). It was
   15px/650 with negative tracking — heavier and larger than any other card title
   in the app, which is what made this card read as imported. 13px/700 is exactly
   `.sm-hist-coin`, the Recent-trades row's ticker, i.e. the treatment a
   title-inside-a-list already has here. The action word keeps its tone and the
   ticker keeps its <b>; the emphasis survives, the shouting does not. */
.sm-ig-act { font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Muted like every other sub-line, and at the app's caption size (`.sm-cap`) so
   the card's two quiet lines are one size rather than two near-misses. */
.sm-ig-sub { font-size: 12px; color: var(--text-faint); margin-top: 1px; font-variant-numeric: tabular-nums; }
/* The chart is the hero: full-bleed to the card edges (card pads 11px 13px). */
.sm-ig-chart { display: block; width: calc(100% + 26px); height: auto; margin: 10px -13px 6px; }
.sm-ig-acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
.sm-alert.is-new { border-color: color-mix(in srgb, var(--sm-up) 35%, transparent); }
.sm-alert-line { font-size: 14px; }
.sm-alert-line b { font-weight: 650; }
.sm-alert-meta { font-size: 11.5px; color: var(--text-faint); margin: 3px 0 8px; }
.sm-mini-btn { font-size: 12.5px; font-weight: 600; color: var(--on-accent); background: var(--accent); border: none; border-radius: 999px; padding: 6px 13px; cursor: pointer; }

.sm-back-row { display: flex; align-items: center; gap: 6px; background: none; border: none; color: var(--text-dim); font-size: 13px; padding: 8px 0; cursor: pointer; }
.sm-hero { padding: 6px 0 2px; display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.sm-hero-main { min-width: 0; }
/* Follow pill at the dashboard's top right (persona). */
.sm-follow-pill { flex: none; margin-top: 2px; font-size: 12.5px; font-weight: 650; color: var(--sm-up); background: none; border: 1px solid color-mix(in srgb, var(--sm-up) 42%, transparent); border-radius: 999px; padding: 6px 14px; cursor: pointer; white-space: nowrap; }
.sm-follow-pill.done { color: var(--text-faint); border-color: var(--border); }
.sm-hero-name { font-size: 13px; color: var(--text-dim); }
.sm-hero-pnl { font-size: 38px; font-weight: 600; letter-spacing: -0.03em; color: var(--sm-up); line-height: 1.12; font-variant-numeric: tabular-nums; }
.sm-hero-cap { font-size: 12.5px; color: var(--text-faint); margin-top: 3px; }
.sm-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(72px, 1fr)); gap: 14px 10px; padding: 14px 0; border-bottom: 1px solid var(--border); }
.sm-cell-k { font-size: 11px; color: var(--text-faint); }
.sm-cell-v { font-size: 14px; font-weight: 650; margin-top: 2px; }
.sm-cell-v.up { color: var(--sm-up); }
.sm-cell-v.down { color: var(--sm-down); }
.sm-replay { display: block; width: 100%; height: auto; margin-top: 6px; }
.sm-trades { display: flex; flex-direction: column; margin-top: 4px; }
/* (legacy .sm-trade block removed — superseded by the coin-dot history rows
   below; its inset box-shadow was the stray green bar clipping the coin pfp) */
.sm-trade-v { font-variant-numeric: tabular-nums; }
.sm-cta { display: block; width: 100%; margin-top: 14px; background: var(--sm-up); color: var(--sm-on-up); font-size: 14.5px; font-weight: 640; border: none; border-radius: 999px; padding: 12px; cursor: pointer; }
.sm-cta.done { background: var(--bg-elev-2); color: var(--text-dim); }
.sm-cta.ghost { background: none; border: 1px solid var(--border); color: var(--sm-up); font-weight: 600; margin-top: 10px; }

/* PR6: 打法 playbook + 如果你跟 calculator (persona cards) */
.sm-kv { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--border); font-size: 13.5px; }
.sm-kv:last-of-type { border-bottom: none; }
.sm-kv-k { color: var(--text-faint); flex: none; }
.sm-kv-v { font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }
.sm-kv-v.up { color: var(--sm-up); }
.sm-kv-v.down { color: var(--sm-down); }
.sm-risk { margin-top: 10px; border: 1px solid color-mix(in srgb, var(--sm-down) 25%, transparent); background: color-mix(in srgb, var(--sm-down) 7%, transparent); border-radius: 12px; padding: 9px 12px; font-size: 12.5px; color: var(--text-dim); line-height: 1.55; }
.sm-chips { display: flex; gap: 8px; padding: 8px 0 2px; }
.sm-chip { font-size: 12.5px; border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px; background: none; color: var(--text-dim); cursor: pointer; font-variant-numeric: tabular-nums; }
.sm-chip.sel { color: var(--text); border-color: var(--border-strong); background: var(--bg-elev-1); font-weight: 650; }
.sm-calc-big { font-size: 21px; font-weight: 600; letter-spacing: -0.01em; padding-top: 8px; }


/* PR7: mockup parity — tabs, hero sparkline, row minis, footnote, cell subs */
/* Top spacing matches predict exactly (pd-root pad-top 6 + pd-chips margin-top
   2 = 32px clearance from the session header): enough to clear the header's drop
   shadow, no pull-up. */
/* Pill-chip navigation, matching predict's .pd-chips/.pd-chip exactly (M3 tonal
   chips, not the old green-underline tabs) — 13px/560, wash bg, active = the
   neutral chip-on wash + 620, no green accent (predict parity). */
.sm-tabs { display: flex; gap: 8px; margin: 2px 2px 0; padding-bottom: 2px; overflow-x: auto; scrollbar-width: none; }
.sm-tabs::-webkit-scrollbar { display: none; }
/* ── THE STRIP SAYS WHEN IT HAS SOMEWHERE TO GO ──
   At 390px the tabs need 446px of a 318px scrollport, so the last one sat entirely past the right
   edge with nothing to suggest the row moved — the Playbook tab was undiscoverable on a phone
   (dede, live, 2026-08-22). A soft fade at whichever edge has more behind it is the ordinary
   affordance for this and costs the five existing tabs nothing.

   `mask-image` rather than an overlay: the row already has a sticky bell sibling and a badge that
   deliberately overhangs, and a positioned gradient on top would have to dodge both. A mask fades
   the strip's own pixels and cannot cover anything else. The classes are set by `quant.ts` only
   when the content really overflows, so a strip that cannot scroll is never faded. */
.sm-tabs.more-right { mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%); }
.sm-tabs.more-left { mask-image: linear-gradient(to right, transparent 0, #000 34px); }
.sm-tabs.more-left.more-right {
  mask-image: linear-gradient(to right, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
}
/* THE CHIP ITSELF IS CORE'S `.m3-chip` (src/web/styles.css) — every button here
   carries both classes. It moved so the Never-miss-out screen could be the same
   component without depending on this plugin: core boots with either plugin
   absent, and a screen borrowing another plugin's class loses its type the
   moment that plugin is switched off. Nothing about this screen changed: the
   values in `.m3-chip` are the ones that were on this line. */
.sm-tab-new { font-size: 8.5px; font-weight: 680; letter-spacing: 0.06em; color: var(--sm-on-up); background: var(--sm-up); border-radius: 4px; padding: 1.5px 5px; margin-left: 6px; vertical-align: 1px; }
.sm-flow-eyebrow { font-size: 12px; color: var(--text-dim); margin-bottom: 2px; }
.sm-flow-sub { font-size: 12.5px; color: var(--text-dim); margin-top: 3px; font-variant-numeric: tabular-nums; }
.sm-flow-sub.up { color: var(--sm-up); font-weight: 650; }
.sm-flow-sub.down { color: var(--sm-down); font-weight: 650; }
.sm-spark-wrap { padding: 10px 0 2px; }
.sm-spark { display: block; width: 100%; height: 92px; }
.sm-mini { flex: none; width: 56px; height: 24px; }
.sm-cell-sub { font-size: 11px; color: var(--text-faint); margin-top: 1px; }
/* Methodology footer under the board list: a concise 'what qualifies' line +
   this sweep's numbers. Its own hairline separates it from the last row. */
.sm-foot { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.sm-foot .sm-cap:not(:first-child) { padding-top: 2px; }
.sm-hero-pnl { font-size: 40px; }

/* PR8: alert-card context lines + compact CTAs + chase note */
.sm-alert-ctx { font-size: 13px; color: var(--text-dim); padding: 2px 0; }

/* F3: price×OI quadrant chip (docs/SMART_MONEY_RECEIPTS.md §3) — colored text
   line, not a pill; green/red only for fresh conviction, neutral otherwise. */
.sm-ctx { display: inline-block; font-size: 11.5px; font-weight: 600; padding: 3px 0 0; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.sm-ctx.up { color: var(--sm-up); }
.sm-ctx.down { color: var(--sm-down); }

/* Persona mockup parity: identity header, hero delta, range row, history rows */
.sm-pid { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.sm-pid-name { font-size: 15px; font-weight: 640; letter-spacing: -0.01em; }
.sm-pid-name .sm-pid-alink { color: inherit; text-decoration: none; }
.sm-pid-sub .sm-pid-alink { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.sm-verify { font-size: 16px; color: var(--sm-up); margin-left: 5px; vertical-align: -3px; }
.sm-pid-sub { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; font-variant-numeric: tabular-nums; }
.sm-hero-delta { font-size: 13px; font-weight: 650; margin-top: 3px; font-variant-numeric: tabular-nums; }
.sm-hero-delta.up { color: var(--sm-up); }
.sm-hero-delta.down { color: var(--sm-down); }
/* Realized / unrealized split under the hero (owner call 2026-07-17): breaks
   the headline into booked vs open-position paper. */
.sm-hero-split { font-size: 12px; margin-top: 5px; display: flex; flex-wrap: wrap; gap: 4px 12px; font-variant-numeric: tabular-nums; }
.sm-split { font-weight: 650; }
.sm-split.up { color: var(--sm-up); }
.sm-split.down { color: var(--sm-down); }
.sm-split.mut { color: var(--text-dim); font-weight: 500; }
.sm-split-k { color: var(--text-faint); font-weight: 500; }
.sm-trade { display: flex; align-items: center; gap: 10px; width: 100%; background: none; border: none; border-bottom: 1px solid var(--border); color: inherit; text-align: left; font: inherit; padding: 12px 2px; cursor: pointer; }
.sm-trade.sel { border-bottom-color: transparent; background: color-mix(in srgb, var(--sm-up) 6%, transparent); border-radius: 10px; }
.sm-trade-main { flex: 1; min-width: 0; }
.sm-trade-t { display: block; font-size: 15px; font-weight: 680; letter-spacing: -0.01em; }
.sm-trade-sub { display: block; font-size: 11.5px; color: var(--text-faint); margin-top: 1px; font-variant-numeric: tabular-nums; }
.sm-trade-right { flex: none; display: flex; align-items: center; gap: 8px; }
.sm-trade-v { font-size: 15.5px; font-weight: 680; letter-spacing: -0.01em; }
.sm-trade-v.up { color: var(--sm-up); }
.sm-trade-v.down { color: var(--sm-down); }
.sm-trade-v.mut { color: var(--text-dim); font-weight: 650; }
.sm-tag { font-style: normal; font-size: 9px; font-weight: 680; letter-spacing: 0.05em; border-radius: 4px; padding: 2.5px 6px; }
.sm-tag.win { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 14%, transparent); }
.sm-tag.loss { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 13%, transparent); }
.sm-tag.open { color: var(--text-faint); border: 1px dashed var(--border-strong); }
/* Human-gate receipt on the persona identity line (VENUES PRD §3). */
.sm-tag.human { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 12%, transparent); }
.sm-trade-host:has(.sm-trade-panel) { border-bottom: 1px solid var(--border); padding: 2px 0 12px; }
.sm-trade-panel .sm-replay { margin-top: 4px; }
/* Inside the Details card the replay is a folded row's body, so it takes the
   row's own left inset (past the timeline rail) and a little breathing room
   under it — not the full-bleed treatment a standalone card would want. */
.sm-hist-host .sm-trade-panel { padding: 2px 0 10px 18px; }
.sm-hist-host .sm-replay { margin-top: 2px; }
.sm-avs { display: inline-flex; margin-right: 6px; vertical-align: -1px; }
.sm-avs i { width: 11px; height: 11px; border-radius: 50%; border: 1.5px solid var(--bg); margin-left: -4px; display: inline-block; }
.sm-avs i:first-child { margin-left: 0; }

/* Persona §1: benchmark chart legend (his ROI vs board average) */
.sm-bench-legend { display: flex; gap: 16px; font-size: 11px; color: var(--text-dim); padding: 6px 0 0; font-variant-numeric: tabular-nums; }
.sm-bench-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 5px; vertical-align: -1px; }
.sm-bench-dot.him { background: var(--sm-up); }
.sm-bench-dot.avg { background: var(--text-faint); }

/* F2: follow-record badge on Winners rows (docs/SMART_MONEY_RECEIPTS.md §2) */
.sm-fr { font-size: 11px; font-weight: 640; margin-top: 1px; font-variant-numeric: tabular-nums; }
.sm-fr.up { color: var(--sm-up); }
.sm-fr.down { color: var(--sm-down); }
.sm-fr.thin { color: var(--text-faint); font-weight: 500; }

/* F1: 资金流向 Flow tab (docs/SMART_MONEY_RECEIPTS.md §1) */
.sm-flow-hero { padding: 14px 0 4px; }
.sm-flow-net { font-size: 32px; font-weight: 600; letter-spacing: -0.03em; line-height: 1.1; }
.sm-flow-net.up { color: var(--sm-up); }
.sm-flow-net.down { color: var(--sm-down); }
.sm-flow-bar { display: flex; align-items: center; gap: 14px; border-bottom: 1px solid var(--border); margin-top: 8px; }
.sm-flow-win { border: none; background: none; color: var(--text-dim); font-size: 12.5px; font-weight: 700; padding: 6px 0 9px; position: relative; cursor: pointer; }
.sm-flow-win.on { color: var(--sm-up); }
.sm-flow-win.on::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--sm-up); border-radius: 2px; }
.sm-flow-hint { margin-left: auto; font-size: 10.5px; color: var(--text-faint); padding-bottom: 6px; }
.sm-flow-row { border-bottom: 1px solid var(--border); }
.sm-flow-row:last-child { border-bottom: none; }
/* No-consensus rows stay full-brightness (owner call 2026-07-17: dimming made
   the common single-wallet case read as broken) — the caption + sunk order
   carry the distinction. */
.sm-flow-head { display: grid; grid-template-columns: 32px 1fr auto; gap: 12px; align-items: center; width: 100%; border: none; background: none; color: inherit; text-align: left; padding: 13px 2px; cursor: pointer; font: inherit; }
.sm-flow-coin { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; font-size: 10.5px; font-weight: 700; color: var(--text); }
.sm-flow-sym { font-weight: 640; font-size: 14.5px; }
.sm-flow-who { font-size: 12px; color: var(--text-dim); margin-top: 1px; }
.sm-flow-right { text-align: right; }
.sm-flow-amt { font-weight: 640; font-size: 14.5px; }
.sm-flow-amt.up { color: var(--sm-up); }
.sm-flow-amt.down { color: var(--sm-down); }
.sm-flow-right .sm-ctx { display: block; }
/* ★ display:flex DEFEATS [hidden] (its display:none loses to a later rule), so the
   collapsed state must be restated. This bit the board once already via
   .sm-secline; here it rendered every row expanded. */
.sm-flow-receipts { margin: 0 8px 9px; padding: 12px 14px; background: var(--bg-elev-1); border-radius: 12px; display: flex; flex-direction: column; gap: 10px; }
.sm-flow-receipts[hidden] { display: none; }
/* Kept for news headline dates (asset read → Fundamentals). */
.sm-flow-r-ago { flex: none; font-size: 11px; color: var(--text-faint); white-space: nowrap; }

/* ── Wallet receipt as an M3 two-line list item (owner 2026-07-27) ─────────
   Primary line: who + when. Supporting line: tonal verb chip + price + amount.
   Names lose the link underline — weight and color carry the affordance. */
.sm-rc { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.sm-rc + .sm-rc { border-top: 1px solid var(--border); padding-top: 10px; }
.sm-rc-top { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.sm-rc-top .sm-name, .sm-rc-top .sm-strong { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; font-size: 13px; font-weight: 600; border-bottom: none; }
.sm-rc-ago { flex: none; font-size: 11px; color: var(--text-faint); white-space: nowrap; }
.sm-rc-sub { display: flex; align-items: center; gap: 8px; min-width: 0; }
.sm-rc-verb { flex: none; font-size: 11px; font-weight: 650; letter-spacing: 0.02em; white-space: nowrap; border-radius: 999px; padding: 3px 10px; }
.sm-rc-verb.up { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 12%, transparent); }
.sm-rc-verb.down { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 12%, transparent); }
.sm-rc-px { flex: none; font-size: 11.5px; color: var(--text-faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
.sm-rc-amt { flex: none; margin-left: auto; font-size: 13px; font-weight: 640; white-space: nowrap; font-variant-numeric: tabular-nums; }
.sm-rc-amt.up { color: var(--sm-up); }
.sm-rc-amt.down { color: var(--sm-down); }
.sm-rc-sub .sm-copy-pill { margin-left: 8px; }
/* Inside an asset-read category body there is no gapped container — the item
   carries its own list rhythm so receipts align with the kv rows around them. */
.sm-acat-b .sm-rc { padding: 7px 0; }
.sm-acat-b .sm-rc + .sm-rc { padding-top: 10px; }
/* Entry price is context, not a decision input (the ticket restates it) — it is
   the first thing to go when the row runs out of width. */
@container (max-width: 460px) { .sm-rc-px { display: none; } }

/* ── Flow tab v2 (2026-07-25, restyled 2026-07-25 to M3 — owner pick B) ───────
   The comparison across windows IS the signal (buying 1h / selling 24h = you are
   the exit), but three same-size numbers in three columns gave the eye nowhere to
   land and scaled their bars against three different maxima, so nothing could be
   compared across them. Now: the LONG window is one dominant figure at tile scale
   (matching .sm-tile-v), the short windows live in a sparkline, and rows sit in a
   tonal surface-container.

   House idiom (see PR12 above): Gemini/M3 surfaces — tonal card, 16dp radius,
   state layers, error-container — with ROBINHOOD numerals: the figures keep their
   heavy weight and tight tracking rather than M3's lighter 400/500 defaults,
   because density is the point on a tab built for scanning many coins. */
.sm-flow-venue { display: inline-flex; gap: 2px; background: var(--sm-card); border: 1px solid var(--border); border-radius: 10px; padding: 2px; margin: 12px 0 2px; }
.sm-flow-vbtn { border: none; background: none; color: var(--text-dim); font: inherit; font-size: 13px; padding: 8px 15px; border-radius: 8px; cursor: pointer; min-height: 36px; }
.sm-flow-vbtn.on { background: color-mix(in srgb, var(--sm-up) 14%, transparent); color: var(--sm-up); font-weight: 650; }
/* headline-small, M3 weight — the surrounding chrome is where M3 shows, not the data */
.sm-flow-q { font-size: 20px; font-weight: 550; letter-spacing: -0.01em; margin: 14px 0 3px; text-wrap: balance; }
.sm-flow-qsub { font-size: 12px; color: var(--text-dim); margin-bottom: 12px; }
/* surface-container */
.sm-flow-card { background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 4px; overflow: hidden; }
.sm-flow-row + .sm-flow-row { border-top: 1px solid var(--border); }
.sm-flow-head3 { display: grid; grid-template-columns: minmax(0, 1fr) 72px minmax(80px, auto); gap: 10px; align-items: center; width: 100%; border: none; background: none; color: inherit; text-align: left; padding: 12px 10px; cursor: pointer; font: inherit; border-radius: 12px; min-height: 64px; transition: background 0.12s ease; }
/* M3 state layers — the rows are buttons and previously gave no press feedback */
@media (hover: hover) { .sm-flow-head3:hover { background: var(--bg-elev-1); } }
.sm-flow-head3:active { background: var(--bg-elev-2); }
.sm-flow-tok { display: flex; align-items: center; gap: 11px; min-width: 0; }
.sm-flow-coin { width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; font-size: 11.5px; font-weight: 700; color: #000; flex: none; }
.sm-flow-stack { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sm-flow-sym { font-weight: 600; font-size: 16px; letter-spacing: 0.005em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* label-small */
.sm-flow-sub2 { font-size: 10.5px; color: var(--text-faint); letter-spacing: 0.04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Direction of travel across the windows; colour follows the dominant figure. */
.sm-flow-spark { display: block; }
.sm-flow-lead { text-align: right; min-width: 0; }
.sm-flow-leadv { font-size: 19px; font-weight: 680; letter-spacing: -0.02em; line-height: 1.1; white-space: nowrap; }
.sm-flow-leadv.up { color: var(--sm-up); }
.sm-flow-leadv.down { color: var(--sm-down); }
.sm-flow-leadv.zero { color: var(--text-faint); }
.sm-flow-leadk { font-size: 9.5px; color: var(--text-faint); letter-spacing: 0.08em; text-transform: uppercase; margin-top: 3px; white-space: nowrap; }
.sm-flow-row.dim .sm-flow-leadv { color: var(--text-dim); }
/* error-container: this is the most consequential sentence on the screen, and as
   bare ink it read like a rendering accident. Only rendered where the windows
   actually disagree — a warning on every row is a warning nobody reads. */
.sm-flow-warn { display: flex; gap: 9px; align-items: flex-start; margin: 0 8px 9px; padding: 11px 13px; border-radius: 12px; background: color-mix(in srgb, var(--warn) 13%, transparent); color: var(--warn); font-size: 11.5px; line-height: 1.45; }
.sm-flow-warn svg { flex: none; margin-top: 1px; }
/* filled tonal button at a real touch target (was a 24px outline pill) */
.sm-copy-pill { flex: none; margin-left: 6px; min-height: 36px; padding: 0 12px; font-size: 12px; font-weight: 650; color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 18%, transparent); border: none; border-radius: 999px; cursor: pointer; }
.sm-copy-pill:active { background: color-mix(in srgb, var(--sm-up) 28%, transparent); }
/* Loading rows shaped like real ones (same sm-skel pulse as the Overview hero).
   A cold digest is a live ~20s Hyperliquid walk, and "Nobody moved" during it
   would state as fact something we haven't finished checking. */
.sm-flow-skel { background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 4px; }
.sm-flow-skel-row { display: grid; grid-template-columns: 38px minmax(0, 1fr) 72px 80px; gap: 10px; align-items: center; padding: 12px 10px; min-height: 64px; }
.sm-flow-skel-row + .sm-flow-skel-row { border-top: 1px solid var(--border); }
.sm-flow-skel-dot { width: 38px; height: 38px; border-radius: 50%; }
.sm-flow-skel-name { height: 14px; width: 58%; }
.sm-flow-skel-cell { height: 14px; }

/* ── 跟这单 copy ticket ──────────────────────────────────────────────────────
   A bottom sheet on phones, a centred card once there's room. The user places
   the order themselves; this only prefills it. */
/* SELF-CONTAINED TOKENS. The sheet mounts on <body>, not inside .sm-root, because
   .sm-root sets `container-type: inline-size` — containment makes it the containing
   block for position:fixed descendants, so a scrim nested there would be trapped
   inside the card instead of covering the screen. Mounting outside costs us every
   token .sm-root declares (--border, --sm-card, --bg-elev-*), so they are restated
   here from the root-level primitives (--text/--bg/--sm-up ARE on :root). */
.sm-sheet-scrim {
  --border: color-mix(in srgb, var(--text) 9%, transparent);
  --sm-card: color-mix(in srgb, var(--text) 5%, transparent);
  position: fixed; inset: 0; z-index: 90; background: rgba(0, 0, 0, 0.55);
  display: flex; align-items: flex-end; justify-content: center;
  font-variant-numeric: tabular-nums;
  /* Whatever the on-screen keyboard is covering, measured from visualViewport
     and set by the sheet that opened. The bottom-aligned sheet lands above it
     instead of behind it. 0 when there is no keypad. */
  padding-bottom: var(--kb, 0px);
  /* And the scrim ENDS above the session's composer, rather than dimming it:
     an overlay over a control is the same as removing the control. 0 on any
     screen that has no composer parked at the bottom. */
  bottom: var(--dock, 0px);
}
/* The composer's own 32px fade (.composer-wrap::before) can never reach a
   sheet — the scrim paints in a higher layer — so a docked sheet ended in a
   hard cut at the composer's edge (owner report 2026-08-01). Repaint the same
   gradient on the scrim itself. Height rides the dock: 0 when no composer is
   parked below, so an undocked sheet keeps its clean bottom. */
.sm-sheet-scrim::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: min(32px, var(--dock, 0px));
  background: linear-gradient(to top, var(--bg-elev), transparent);
  pointer-events: none;
}
/* Opaque on purpose: --sm-card and friends are translucent washes, and a sheet
   you can read the page through is how the ticket ended up double-printed over
   its own receipts row in testing. */
/**
 * `position: relative` IS LOAD-BEARING (R128, coco item 1). The amount step over this sheet is
 * `position: absolute; inset: 0`; with no positioned ancestor it anchored to the LAYOUT viewport,
 * so when the iOS keyboard shrank the visible box the sheet moved with it and the step did not —
 * coco measured the confirm slider 162 px below the visible bottom. The sheet is the containing
 * block now, so the step is exactly the sheet's box, keyboard or no keyboard.
 */
.sm-sheet { position: relative; width: 100%; max-width: 460px; max-height: min(92vh, 100%); overflow-y: auto; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 18px 18px 0 0; padding-bottom: 18px; animation: sm-sheet-in 0.24s cubic-bezier(0.3, 0.9, 0.3, 1); }
/* Always full height, top edge at the top of the usable screen — the scrim
   already ends above the composer (--dock), so "100%" can never cover it.

   ═══ AND THE BASE MAX-HEIGHT HAS TO GO WITH IT (R118, coco item 5) ═══
   `.sm-sheet` caps every sheet at `min(92vh, 100%)`, and `height: 100%` does not lift a maximum —
   so "tall" was 92 LEGACY viewport units, on a unit that ignores the browser chrome iOS moves. The
   tall sheet is sized from the VISUAL viewport instead: `100dvh` follows the chrome, `--vvh` is the
   measured `visualViewport.height` that the sheet's own opener keeps up to date (dockScrim listens
   to visualViewport resize AND scroll), and `100%` of the scrim is the floor if neither exists. */
.sm-sheet.tall {
  height: var(--vvh, 100dvh);
  max-height: var(--vvh, 100dvh);
}
/* ═══ ONE SCREEN, NOTHING TO SCROLL (owner, 2026-08-26 — R125 amendment) ═══
   *"the slide up modale should just host all content in one screen without needing user to
   scroll"*, and on desktop the same sheet showed a 450-px void under the two buttons.

   The sheet becomes the column: the top bar takes its own height, the body takes the rest, and
   inside the body every row is its natural height EXCEPT the chart, which grows into whatever is
   left. Nothing is a fixed share of the viewport any more (that was `viewportShare: 0.5`, which is
   what made the chart too big on the phone and left the void on the desktop).

   THE BODY IS STILL A SCROLL BOX, and it scrolls only when the rows genuinely do not fit — when
   the viewport is shorter than the rows' own heights plus the chart's 160-px floor. Measured in
   Chrome on this build, stepping the window down four pixels at a time: it starts to scroll at
   628 px of window height on a 390-wide phone, and at 592 px on a 1280-wide window (the chip row
   wraps to two lines on the phone, which is the difference). Anything taller than that, and
   `scrollHeight === clientHeight`: there is nothing to scroll. */
.sm-sheet.tall { display: flex; flex-direction: column; overflow: hidden; }
/**
 * AND THE TWO PIXELS ITS OWN BORDER WAS EATING (R128, coco item 2). The sheet is `box-sizing:
 * border-box` at `height: var(--vvh)`, so its 1-px top and bottom borders come out of the CONTENT
 * box: on a 640-px window the rows had 578 px to fit into rather than 580, and coco measured a
 * body that scrolled by exactly 2. A sheet that fills the window has no edge to draw up there
 * anyway — the top edge is the top of the screen and the bottom edge is the bottom of it. The
 * side edges keep theirs, which is what the desktop side sheet is drawn with.
 */
.sm-sheet.tall { border-top: none; border-bottom: none; }
.sm-sheet.tall > .sm-sheet-head { flex: none; }
.sm-sheet.tall > .qn-ts { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* (Which row inside the body grows is the trade sheet's own business, and is written with the
   rest of its rules further down: `.qn-ts > *` / `.qn-ts > .qn-ts-chart`.) */
/* No band under the buttons either: the sheet's own bottom padding is the CTA row's safe-area
   inset now, and nothing else. */
.sm-sheet.tall { padding-bottom: 0; }
/* THE SHEET COVERS THE COMPOSER when it asks to (R125). `--dock` is already 0 for it, so the
   scrim reaches the bottom edge and its composer-fade has no height; this says so in the
   stylesheet as well, because a rule that only exists as a variable is a rule nobody can find. */
.sm-sheet-scrim.sm-sheet-over { bottom: 0; }
.sm-sheet-scrim.sm-sheet-over::after { content: none; }

/* ═══ AND ON A DESKTOP IT IS AN M3 SIDE SHEET (owner, 2026-08-26) ═══
   *"make sure on desktop the layout is arranged properly. follow material 3 design principles"* —
   he had a phone-shaped column in the middle of a 1280-px window.

   WIDTH: 480 px. Material 3 puts a standard side sheet at 256–400 dp and allows wider where the
   content needs it; this one holds a chart, a position block and two full-width buttons, and the
   phone layout it shares is drawn at 390–460. At 400 the two buttons and the price row start to
   crowd; at 480 they sit exactly as they do on the phone and 800 px of the page is still visible
   beside the sheet, which is the point of a side sheet rather than a dialog.

   WHERE IT STARTS: 840 px, Material 3's expanded window class. Between 600 and 839 (the medium
   class) the sheet stays the centred one it already is — a 480-px panel there would leave under
   160 px of page beside it, which reads as a broken dialog rather than a sheet anchored to
   something. That is the "sensible middle": medium keeps today's behaviour. */
@media (min-width: 840px) {
  .sm-sheet-scrim:has(> .sm-sheet.tall) {
    align-items: stretch;
    justify-content: flex-end;
    bottom: 0;
    padding-bottom: 0;
  }
  .sm-sheet-scrim:has(> .sm-sheet.tall)::after { content: none; }
  .sm-sheet.tall {
    width: 480px;
    max-width: 480px;
    /* The outer edge only — the sheet is attached to the right edge of the window. */
    border-radius: 28px 0 0 28px;
    border-right: none;
    animation: sm-side-in 0.24s cubic-bezier(0.3, 0.9, 0.3, 1);
  }
}
@keyframes sm-side-in { from { transform: translateX(18px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .sm-sheet.tall { animation: none; } }
@keyframes sm-sheet-in { from { transform: translateY(14px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .sm-sheet { animation: none; } }
@media (min-height: 720px) and (min-width: 520px) {
  .sm-sheet-scrim { align-items: center; }
  .sm-sheet { border-radius: 18px; }
}
/* Desktop: the sheet floats mid-screen, so it can never sit on the composer —
   the mobile bottom clearance (--dock) only carved a bright full-width strip
   out of the dimmed page, cutting task rows in half under the sheet (owner
   screenshot 2026-08-03). Dim everything; a modal may cover the composer. */
@media (min-height: 720px) and (min-width: 900px) {
  .sm-sheet-scrim { bottom: 0; }
  .sm-sheet-scrim::after { content: none; }
  /* The add-strategy picker gets desktop room — 460px is a phone width, and
     the strategy cards inside were clipped mid-label. Recent activity too
     (owner screenshot 2026-08-06): its cards are paragraphs, and at phone
     width every one wrapped into a dense block. */
  .sm-sheet:has(.st-picker),
  .sm-sheet:has(.qn-ck-in-sheet) { max-width: 720px; }
}
/* Taller since 2026-08-04 (owner: "nav bar could be taller") — M3 top-bar
   proportions: more breathing room, larger title. */
.sm-sheet-head { display: flex; align-items: center; gap: 8px; padding: 20px 20px 13px; position: sticky; top: 0; background: var(--bg); z-index: 1; }
.sm-sheet-head b { font-size: 18px; }
.sm-sheet-x { margin-left: auto; border: none; background: none; color: var(--text-dim); font-size: 22px; line-height: 1; padding: 0 4px; cursor: pointer; }
.sm-ticket-src { margin: 0 16px; background: var(--sm-card); border: 1px solid var(--border); border-radius: 12px; padding: 11px 12px; }
.sm-ticket-who { display: flex; align-items: center; gap: 8px; font-size: 13px; flex-wrap: wrap; }
.sm-hexav { width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center; flex: none; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 8.5px; color: var(--sm-on-up); }
.sm-ticket-name { font-weight: 620; }
.sm-ticket-rec { margin-left: auto; font-size: 11.5px; color: var(--text-dim); }
.sm-ticket-act { margin-top: 7px; font-size: 14.5px; }
.sm-ticket-act b.up { color: var(--sm-up); }
.sm-ticket-act b.down { color: var(--sm-down); }
.sm-ticket-play { margin-top: 6px; font-size: 11.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.sm-ticket-sec { padding: 15px 16px 0; }
.sm-ticket-lbl { display: block; font-size: 11px; color: var(--text-faint); letter-spacing: 0.05em; margin-bottom: 8px; }
.sm-chips { display: flex; gap: 8px; flex-wrap: wrap; }
.sm-chip { border: 1px solid var(--border); background: none; color: var(--text-dim); border-radius: 9px; padding: 7px 14px; font: inherit; font-size: 13.5px; cursor: pointer; }
.sm-chip.on { border-color: var(--sm-up); color: var(--text); font-weight: 640; background: color-mix(in srgb, var(--sm-up) 8%, transparent); }
.sm-ticket-lev { display: flex; align-items: center; gap: 12px; margin-top: 11px; font-size: 13px; color: var(--text-dim); }
.sm-stepper { display: flex; align-items: center; border: 1px solid var(--border); border-radius: 9px; overflow: hidden; }
.sm-stepper button { width: 34px; height: 30px; border: none; background: none; color: var(--text); font-size: 16px; cursor: pointer; }
.sm-stepper span { min-width: 44px; text-align: center; font-weight: 640; color: var(--text); }
.sm-ticket-hint { font-size: 11px; color: var(--text-faint); }
.sm-ticket-kv { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; color: var(--text-dim); padding: 5px 0; }
.sm-ticket-kv b { color: var(--text); font-weight: 620; }
.sm-ticket-sl { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
.sm-ticket-slbox { border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; }
.sm-ticket-slt { font-size: 11px; color: var(--text-faint); }
.sm-ticket-slpx { font-size: 15.5px; font-weight: 640; margin-top: 3px; }
.sm-ticket-sleff { font-size: 11.5px; margin-top: 3px; }
.sm-ticket-sleff.down { color: var(--sm-down); }
.sm-ticket-sleff.up { color: var(--sm-up); }
.sm-ticket-risk { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin: 14px 16px 0; padding: 10px 12px; border-radius: 12px; background: var(--sm-card); font-size: 13px; }
.sm-ticket-risk b.down { color: var(--sm-down); font-weight: 700; }
.sm-ticket-cta { margin: 14px 16px 0; }
.sm-ticket-go { width: 100%; background: var(--sm-up); color: var(--sm-on-up); border: none; border-radius: 12px; padding: 14px; font: inherit; font-size: 15.5px; font-weight: 700; cursor: pointer; }
.sm-ticket-trust { margin: 10px 18px 0; font-size: 11px; color: var(--text-faint); line-height: 1.65; text-align: center; }
.sm-ticket-ask { display: block; margin: 12px auto 0; border: none; background: none; color: var(--text-dim); font: inherit; font-size: 12.5px; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
/* Alert-card CTAs: compact side-by-side pills (mockup parity), not full-width slabs. */
/* The app's standard compact pill. The old `margin: 10px 8px 0 0` fought the
   flex `gap` of the only row that uses these, so the two buttons sat unevenly
   spaced and pushed off their own baseline. */
.sm-cta.compact { display: inline-block; width: auto; padding: 8px 18px; font-size: 12.5px; margin: 0; }
/* M3's SECONDARY action is a filled tonal pill, not an outlined one. `ghost`
   (hairline border, accent text) beside a filled primary read as two button
   systems on one card. */
.sm-cta.tonal { background: var(--bg-elev-2); color: var(--text); font-weight: 620; border: none; }
.sm-cta.tonal:hover { background: var(--sm-chip-on); }
.sm-chase { font-size: 12.5px; color: var(--text-dim); padding-top: 8px; }
.sm-chase.warn { color: var(--warn); }

/* PR9: board polish — mockup-exact follow text-chip + hex avatars */
.sm-avatar.hex { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; letter-spacing: 0.5px; }

/* PR12: full-width gen-UI widget — a hero band (plain-language verdict + glance
   tiles) over a two-column grid: the 24h SIGNAL LIST (owner: a list, not prose)
   on the left, the leaderboard on the right. Gemini surfaces (tiles, rounding,
   tonal) with Robinhood density (hairlines, tabular nums, green/red only).
   Container queries collapse it to one column on a narrow pane. */
.sm-hero-band { display: flex; flex-direction: column; gap: 14px; padding: 10px 0 4px; }
.sm-hero-head { min-width: 0; }
.sm-tldr-eyebrow { font-size: 11px; font-weight: 600; color: var(--text-faint); letter-spacing: 0.07em; text-transform: uppercase; }
.sm-tldr-verdict { font-size: 31px; font-weight: 600; letter-spacing: -0.03em; margin-top: 6px; line-height: 1.12; color: var(--text); }
.sm-tldr-verdict.up { color: var(--sm-up); }
.sm-tldr-verdict.down { color: var(--sm-down); }
/* Glance tiles — four Gemini cards, Robinhood numerals (2×2 on a narrow pane). */
.sm-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 22px; }
.sm-tile { min-width: 0; background: none; border: none; border-radius: 0; padding: 2px 0; }
.sm-tile-k { font-size: 10px; font-weight: 640; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); }
.sm-tile-v { font-size: 19px; font-weight: 680; margin-top: 4px; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sm-tile-v.up { color: var(--sm-up); }
.sm-tile-v.down { color: var(--sm-down); }
.sm-tile-v.mut { color: var(--text-faint); font-weight: 650; }
/* Vertical sections: ① TLDR hero ② movers breakdown ③ board. */
.sm-main { min-width: 0; margin-top: 16px; }
.sm-main.gap { margin-top: 14px; }
/* Card blocks: each tab is a stack of white/elevated cards (owner call
   2026-07-18) — the 24h digest and the winners list are separate cards on
   Overview; your traders + recent moves on Following. */
.sm-block { background: color-mix(in srgb, var(--text) 5%, transparent); border-radius: 20px; padding: 16px; }
.sm-block + .sm-block { margin-top: 12px; }
:root[data-theme="light"] .sm-block { background: #fff; border: 1px solid var(--border); }
.sm-block-head { font-size: 11px; font-weight: 640; color: var(--text-faint); letter-spacing: 0.07em; text-transform: uppercase; padding: 0 0 12px; }
.sm-block > .sm-hero-band:first-child { padding-top: 0; }
.sm-block .sm-foot { margin-top: 10px; }
.sm-block .sm-alert:last-child { margin-bottom: 0; }
/* display:flex/grid on the class beats the UA [hidden] rule — restate it. */
.sm-secline[hidden], .sm-movers[hidden] { display: none; }
/* Digest loading: pulse placeholders shaped like the verdict/tiles/signals
   (same idiom as .home-earn-skel). */
.sm-skel { background: var(--bg-elev-2); border-radius: 8px; animation: sm-skel-pulse 1.3s ease-in-out infinite; }
.sm-skel.verdict { height: 26px; width: min(320px, 72%); margin-top: 4px; }
.sm-skel.tile { min-width: 0; min-height: 56px; border-radius: var(--radius-md); }
.sm-skel.sig { height: 14px; }
@keyframes sm-skel-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.9; } }
@media (prefers-reduced-motion: reduce) { .sm-skel { animation: none; } }
.sm-secline { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 16px 0 6px; }
.sm-secline.gap { padding-top: 22px; }
.sm-secline-t { font-size: 13px; font-weight: 640; color: var(--text-dim); }
.sm-secline-h { font-size: 11.5px; color: var(--text-faint); }
/* §2 mover cards — who → what he did → where he stands (Gemini tonal card). */
.sm-movers { display: grid; grid-template-columns: 1fr; gap: 10px; }
.sm-mover { background: none; border: 1px solid var(--border); border-radius: 16px; padding: 13px 15px; min-width: 0; }
.sm-mover.tap { cursor: pointer; }
.sm-mover.tap:hover { border-color: var(--border-strong); }
.sm-mover-head { display: flex; align-items: center; gap: 10px; padding-bottom: 2px; }
.sm-avatar.sm { width: 30px; height: 30px; font-size: 11px; }
.sm-mover-main { flex: 1; min-width: 0; }
.sm-mover-name { font-size: 14.5px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sm-mover-sub { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; }
.sm-mover-right { flex: none; text-align: right; }
.sm-mover-pnl { font-size: 14.5px; font-weight: 640; color: var(--sm-up); font-variant-numeric: tabular-nums; }
.sm-mover-mv { padding-top: 10px; }
.sm-mover-mv + .sm-mover-mv { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 12px; }
.sm-mover-move { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 13.5px; }
.sm-mover-move-k { min-width: 0; }
.sm-mover-move-k .sm-verb { font-weight: 650; }
.sm-mover-move-k .sm-coin { color: var(--text); font-weight: 700; }
.sm-mover-move-v { flex: none; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.sm-mover-amt { flex: none; font-size: 14px; font-weight: 640; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.sm-mover-amt.up { color: var(--sm-up); }
.sm-mover-amt.down { color: var(--sm-down); }
.sm-mover-meta { display: flex; justify-content: space-between; gap: 10px; font-size: 11.5px; color: var(--text-faint); margin-top: 5px; font-variant-numeric: tabular-nums; }
/* Persona: dashboard | replay, then playbook | calculator; one CTA at the bottom. */
.sm-persona { display: flex; flex-direction: column; }
.sm-sig { display: flex; flex-direction: column; }
.sm-sig-row { display: flex; align-items: center; gap: 11px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.sm-sig-row:last-child { border-bottom: none; }
.sm-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--text-faint); }
.sm-dot.up { background: var(--sm-up); }
.sm-dot.down { background: var(--sm-down); }
.sm-sig-txt { flex: 1; min-width: 0; font-size: 14px; color: var(--text-dim); line-height: 1.4; }
.sm-sig-txt .sm-coin { color: var(--text); font-weight: 700; }
.sm-sig-txt .sm-strong { color: var(--text); font-weight: 650; font-variant-numeric: tabular-nums; }
.sm-sig-txt .sm-verb { font-weight: 650; }
.sm-sig-amt { flex: none; font-size: 14.5px; font-weight: 750; font-variant-numeric: tabular-nums; color: var(--text); letter-spacing: -0.01em; }
.sm-sig-amt.up { color: var(--sm-up); }
.sm-sig-amt.down { color: var(--sm-down); }
.sm-sig-amt.mut { color: var(--text-faint); font-weight: 600; }
.sm-sig-quiet { font-size: 14px; color: var(--text-faint); padding: 14px 0; }
.sm-name { font: inherit; font-weight: 700; color: var(--text); background: none; border: none; padding: 0; cursor: pointer; border-bottom: 1px solid var(--border-strong); }
.sm-name:hover { border-bottom-color: var(--text); }
.sm-verb.up { color: var(--sm-up); }
.sm-verb.down { color: var(--sm-down); }
.sm-rank { width: 20px; flex: none; text-align: center; font-size: 12.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* Wide pane: headline + tiles share a row; mover cards tile side-by-side. */
@container (min-width: 680px) {
  .sm-hero-band { flex-direction: row; align-items: center; justify-content: space-between; gap: 28px; }
  .sm-hero-head { flex: 1; }
  .sm-tldr-verdict { font-size: 30px; }
  .sm-tiles { flex: 0 1 300px; gap: 14px 26px; }
  .sm-tile { padding: 2px 0; }
  .sm-tile-v { font-size: 18px; }
  .sm-movers { grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); }
  /* Persona goes two-up; flat sections gain card chrome so columns read apart.
     Default stretch (no align-items:start) keeps each row's two cards the SAME
     height (owner call: blocks of the same size). */
  /* THE TWO-COLUMN SPLIT IS GONE for this screen (owner 2026-08-17): the
     persona card ran out of content and ended halfway up a much taller trades
     card, leaving a ragged void beside a list. One card, summary then trades. */
  .sm-persona > .sm-p-one { background: var(--sm-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 18px 16px; margin-top: 10px; min-width: 0; }
}

.sm-alert-head { display: flex; align-items: center; gap: 8px; font-size: 16.5px; font-weight: 640; letter-spacing: -0.01em; margin-bottom: 2px; }
.sm-side { flex: none; font-size: 11px; font-weight: 680; border-radius: 6px; padding: 1.5px 6px; }
.sm-side.long { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 15%, transparent); }
.sm-side.short { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 15%, transparent); }
/* The footer fact line in the muted caption style. Every number on it keeps its
   label — "Position … → … · Avg price …" — this is tone, not an information cut. */
.sm-alert-pos { font-size: 12px; color: var(--text-faint); margin-top: 2px; font-variant-numeric: tabular-nums; }

/* ---- smart money: asset-first tile grid + detail (docs/AUTO_QUANT.md §8) ---- */
.sm-flowbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.sm-flowbar .sm-flow-venue { margin: 0; }
.sm-flowlist-wrap[hidden] { display: none; }
.sm-gridhost { min-height: 120px; }
.sm-gridhost[hidden] { display: none; }
/* Fill the pane: the map is the page here, so it takes the height it can get
   (owner 2026-07-26 — the 4:3 box left half the screen empty on desktop). */
.sm-grid { position: relative; width: 100%; aspect-ratio: 4 / 3; max-height: none; min-height: 340px; }
@media (min-width: 720px) { .sm-grid { aspect-ratio: 3 / 2; min-height: 520px; } }
@media (min-height: 900px) { .sm-grid { min-height: 620px; aspect-ratio: auto; height: min(72vh, 780px); } }
.sm-gt {
  position: absolute; border: 1px solid rgba(255,255,255,0.07); border-radius: 10px; padding: 7px 9px;
  color: var(--text); font: inherit; cursor: pointer; text-align: left;
  display: flex; flex-direction: column; justify-content: space-between; min-width: 0; overflow: hidden;
}
.sm-gt:focus-visible { outline: 2px solid var(--accent, #5b7cfa); outline-offset: 1px; z-index: 2; }
.sm-gt:hover { border-color: rgba(255,255,255,0.22); }
/* Trap keeps its own channel — a ring, not a hue: colour is stance only. */
.sm-gt.trap { border-color: color-mix(in srgb, var(--warn) 70%, transparent); }
.sm-gt-more {
  display: block; width: 100%; margin-top: 6px; padding: 9px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--border); background: var(--sm-card); color: var(--text-dim);
  font: inherit; font-size: 11.5px; font-weight: 650;
}
.sm-gt-top { display: flex; align-items: center; gap: 5px; width: 100%; min-width: 0; }
.sm-gt-sym { font-weight: 750; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #fff; }
.sm-gt.small .sm-gt-sym { font-size: 10.5px; }
.sm-gt.mid .sm-gt-sym { font-size: 12.5px; }
.sm-gt.big .sm-gt-sym { font-size: 18px; }
.sm-gt-badge { margin-left: auto; flex: none; font-size: 9px; font-weight: 700; background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.82); border-radius: 999px; padding: 2px 6px; white-space: nowrap; }
.sm-gt-spark { width: 100%; height: 20px; opacity: 0.95; flex: 1 1 auto; min-height: 0; }
.sm-gt.big .sm-gt-spark { height: 46px; }
.sm-gt-sub { font-size: 9.5px; font-weight: 600; color: rgba(255,255,255,0.62); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sm-gt.big .sm-gt-sub { font-size: 11.5px; }
.sm-gt-net { font-weight: 750; }
.sm-gridcap { font-size: 10.5px; color: var(--text-faint); margin: 8px 2px 0; }

.sm-spotflow-h { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin-bottom: 8px; }
.sm-spotflow { display: flex; flex-direction: column; gap: 7px; }
.sm-sfrow { display: flex; align-items: center; gap: 9px; }
.sm-sfk { font-size: 11px; font-weight: 700; color: var(--text-dim); width: 30px; flex: none; }
.sm-sftrack { flex: 1; height: 14px; background: color-mix(in srgb, var(--text) 6%, transparent); border-radius: 7px; overflow: hidden; }
.sm-sffill { height: 100%; border-radius: 7px; }
.sm-sffill.up { background: var(--sm-up); }
.sm-sffill.down { background: var(--sm-down); }
.sm-sfv { font-size: 11.5px; font-weight: 700; width: 66px; text-align: right; flex: none; }
.sm-sfv.up { color: var(--sm-up); }
.sm-sfv.down { color: var(--sm-down); }

/* Material 3: 8dp spacing scale, 16dp corners on containers, tonal surfaces
   with no hard shadows. Layout is two real columns (see sm-asset-cols) rather
   than grid-placed singletons, which left holes when a row didn't fill. */
/* `min-width: 0` so a wide child can shrink instead of widening the column: a
   flex item's default `min-width: auto` refuses to go below its content's
   minimum, which is the mechanism by which one long row pans a whole page. The
   sibling `.sm-asset-main`/`.sm-asset-side` have carried this for the same
   reason; the outer column was missing it. */
.sm-asset { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.sm-body { container-type: inline-size; }
.sm-asset-cols { display: flex; flex-direction: column; gap: 16px; }
.sm-asset-main, .sm-asset-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* Desktop layout (owner 2026-07-27): NO side column — the chart runs FULL
   WIDTH under the verdict, and the six-pillar read follows beneath it. The
   old ≥900px two-column split (chart left, read right) is retired. */
.sm-asset-skel { height: 72px; border-radius: 14px; }
.sm-chart-skel { height: clamp(220px, 30dvh, 280px); border-radius: 14px; }
.sm-asset-head { display: flex; flex-direction: column; align-items: center; gap: 3px; text-align: center; padding: 8px 0 2px; }
.sm-asset-sym { font-size: 34px; font-weight: 800; letter-spacing: -0.02em; }
.sm-asset-pill { font-size: 11px; font-weight: 800; letter-spacing: 0.04em; padding: 3px 9px; border-radius: 999px; }
.sm-asset-pill.long { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 15%, transparent); }
.sm-asset-pill.short { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 15%, transparent); }
.sm-asset-pill.spot { color: var(--text-dim); background: color-mix(in srgb, var(--text) 8%, transparent); }
.sm-asset-px { margin-left: 0; text-align: center; }
.sm-asset-pxv { font-size: 24px; font-weight: 750; font-variant-numeric: tabular-nums; }
/* While a finger is on the chart this number is a PAST price, not the live one.
   The change line beside it says the time, and this dims it a shade so the two
   states are never confused at a glance — the live price is the bright one. */
.sm-asset-pxv.scrub { color: var(--text-dim); }
.sm-asset-pxc { font-size: 13px; font-weight: 700; }
.sm-asset-pxc.up { color: var(--sm-up); }
.sm-asset-pxc.down { color: var(--sm-down); }

/* A CHAT LINE (owner 2026-08-02): the mascot beside the message, the tint on
   the BUBBLE — the old shape tinted a whole box with the face floating inside
   it. The clipped top-left corner points at the sender, M3 chat style. And
   clear air below (margin), so Genie's take never crowds the strategy cards —
   the take is an opinion, the cards are the controls (owner: "genie's
   suggestion should not block user from accessing these strategies"). */
.sm-verd { display: flex; flex-direction: row; align-items: flex-start; gap: 10px; background: none; border: none; border-radius: 0; padding: 4px 2px 2px; margin-bottom: 16px; }
.sm-verd-bub { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; background: var(--bg-elev-1); border-radius: 4px 16px 16px 16px; padding: 11px 14px; }
/* Same type scale as the picker's receipt bubble (the owner's reference):
   13px, quiet — a chat message, not a headline over a paragraph. */
.sm-verd-title { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; }
/* The verdict as a pill, beside the name — the answer a reader wants, in the
   place they look first (owner 2026-08-09). It sat as the last clause of a
   four-line paragraph before this. */
.sm-verd-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sm-verd-head .sm-verd-title { flex: 0 1 auto; }
.sm-verd-pill { flex: none; font-size: 10px; font-weight: 800; letter-spacing: 0.06em; line-height: 1; padding: 4px 8px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap; }
.sm-verd-pill.up { color: var(--up); background: color-mix(in srgb, var(--up) 14%, transparent); border-color: color-mix(in srgb, var(--up) 34%, transparent); }
.sm-verd-pill.down { color: var(--down); background: color-mix(in srgb, var(--down) 14%, transparent); border-color: color-mix(in srgb, var(--down) 34%, transparent); }
/* A lean is the same colour at half the confidence — no fill, just the outline,
   so "LEANS BULLISH" cannot be mistaken at a glance for "BULLISH". */
.sm-verd-pill.thin { background: none; border-style: dashed; }
.sm-verd-pill.flat { color: var(--text-dim); background: var(--bg-elev-2); border-color: var(--border); }
.sm-verd-pill.warn { color: var(--warn, #E8A33D); background: color-mix(in srgb, var(--warn, #E8A33D) 14%, transparent); border-color: color-mix(in srgb, var(--warn, #E8A33D) 38%, transparent); }
.sm-verd.warn .sm-verd-bub { background: var(--bg-elev-1); }
.sm-verd.wait .sm-verd-bub { background: var(--bg-elev-1); }
.sm-verd-dot { width: 26px; height: 26px; border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 12px; font-weight: 800; background: linear-gradient(135deg, #0036FF, #C600FF); margin-top: 1px; }
/* The animated mascot as the bubble's avatar — the canvas draws its own face,
   so no disc behind it. Slightly larger than the lettered dot it replaces:
   a face needs a few more pixels to read as one. */
.sm-verd-m { width: 38px; height: 38px; flex: none; margin-top: -2px; }
.sm-verd-t { color: var(--text-dim); font-size: 13px; line-height: 1.5; max-width: 62ch; }

.sm-asset-chart { background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 12px; }
/* Hero redesign (owner 2026-07-27): full-bleed chart, no card chrome. */
.sm-chart-hero .sm-asset-chart { background: none; border: none; border-radius: 0; padding: 0; }
.sm-chart-hero { margin: 0; }
.sm-chart-hero .sm-tfrow { display: flex; justify-content: space-evenly; gap: 0; margin: 8px 0 0; }
.sm-chart-hero .sm-tfbtn { background: none; border: none; }
.sm-chart-hero .sm-tfbtn.on { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); border-radius: 8px; }
.sm-asset-svg { display: block; width: 100%; height: auto; }
/* Chart height is owned by drawAssetChart's viewBox. A CSS max-height on this
   aspect-locked SVG shrinks both axes and letterboxes the phone chart. */
/* Trade-window view (owner 2026-08-07): the svg draws wider than the screen
   and this scroller pans it; scrollbars hidden, momentum scrolling on. */
.sm-chart-scroll { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.sm-chart-scroll::-webkit-scrollbar { display: none; }
.sm-tfbtn.trade.on { background: color-mix(in srgb, var(--sm-up) 20%, transparent); color: var(--sm-up); }
/* Chart typography is owned by the DESIGN SYSTEM, not by per-element SVG
   attributes: CSS beats presentation attributes, so this one rule pins every
   axis price, level tag and indicator label to M3 labelSmall in the app font
   (owner 2026-07-27: "not following Material 3 and inconsistent with the UI"). */
.sm-asset-svg text, .sm-osc-svg text {
  font-family: inherit;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.02em;
}
/* Axis prices are secondary information — one step quieter than the labels. */
.sm-asset-svg text.ax { fill: var(--text-faint); }
/* Level tags run one step smaller than the rest of the chart type (owner
   2026-08-11: "font can be smaller it's fine"). They are the one label with a
   hard width budget — they have to fit the gutter lane beside the plot without
   clipping a digit — and at labelSmall the longest of them ("SL $104,231.5")
   did not. The tighter tracking buys the last few pixels. */
.sm-asset-svg g.sm-lvltag text { font-size: 8.5px; font-weight: 700; letter-spacing: 0; }
.sm-tfrow { display: flex; gap: 6px; margin-bottom: 12px; }
.sm-tfbtn { min-width: 40px; height: 28px; border-radius: 8px; border: 1px solid var(--border); background: none; color: var(--text-dim); font: inherit; font-size: 10.5px; font-weight: 500; letter-spacing: 0.02em; cursor: pointer; padding: 0 10px; transition: background 0.16s, color 0.16s, border-color 0.16s; }
.sm-tfbtn.on { background: color-mix(in srgb, var(--sm-up) 14%, transparent); border-color: transparent; color: var(--sm-up); }
/* LIVE: the one control on this row that moves, because the chart behind it does.
   The dot sits inline with the word and inherits the button's colour, so it goes
   green with the tab when selected and stays muted when it is not — the pulse
   says "this refreshes", the colour says "and it is what you are looking at". */
.sm-tfbtn .sm-tflive-dot { display: inline-block; width: 5px; height: 5px; margin-right: 5px; border-radius: 50%; background: currentColor; vertical-align: middle; animation: sm-tflive 1.8s ease-in-out infinite; }
@keyframes sm-tflive { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
/* A blinking dot next to live money is exactly what reduced-motion is for. The dot
   stays — it is the label's meaning, not decoration — it simply stops blinking. */
@media (prefers-reduced-motion: reduce) { .sm-tfbtn .sm-tflive-dot { animation: none; } }
/* THE SHORT-HISTORY NOTE, directly under the row it explains (owner 2026-08-19).
   Quiet: it is a fact about the chart, not a warning about it — a young listing
   is an ordinary thing and a loud caption would read as a fault. It sits in the
   row's own bottom margin rather than adding height, so a chart with nothing to
   say about itself is exactly the size it was. Hidden when empty, and the
   attribute really hides it (a `display` rule would otherwise beat `[hidden]`,
   the lesson `.qn-pane` already carries). */
.sm-tfnote { margin: -8px 0 12px; font-size: 11.5px; line-height: 1.45; color: var(--text-faint); }
.sm-tfnote[hidden] { display: none; }
/* THE CURRENT-PRICE MARK, BREATHING — LIVE only.
   A ring that grows out of the dot and fades, on the compositor, with no JS clock
   beside the 15-second refetch. `transform-box: fill-box` makes the scale happen
   around the circle's OWN centre, so one rule works wherever the mark lands on the
   chart — no per-frame geometry, nothing to recompute when the price moves.
   `pointer-events: none` keeps it out of the scrub hit-test: the ring is the widest
   thing on the plot and would otherwise swallow touches near the live price. */
/* A BIGGER BEAT (owner 2026-08-15: the LIVE pulse should be more prominent).
   It reaches 5.6x instead of 3.4x, starts brighter, and holds opacity further
   into the travel — the old curve was already invisible by the time the ring was
   large, so growing it alone would have changed nothing anyone could see. Slower
   too: a bigger ring at the same speed reads as agitation rather than a
   heartbeat. Still pure CSS — a JS loop here would be a second clock beside the
   15-second refetch, running on every asset screen for as long as it is open. */
/* THE TRADE SHEET'S CHART GETS THE SAME BEAT (R100). It is the same renderer and the same ring;
   the rule was scoped to the asset screen's own SVG, so on the sheet the circle was drawn and sat
   still. Three seconds there, matching the sheet's own three-second price reading — the ring is a
   heartbeat for a number that really does change on that clock. */
.sm-asset-svg .sm-live-halo { transform-box: fill-box; transform-origin: 50% 50%; pointer-events: none; animation: sm-live-halo 2.4s ease-out infinite; }
.qn-ts-svg .sm-live-halo { transform-box: fill-box; transform-origin: 50% 50%; pointer-events: none; animation: sm-live-halo 3s ease-out infinite; }
@keyframes sm-live-halo {
  0% { transform: scale(1); opacity: 0.6; }
  55% { opacity: 0.28; }
  85% { opacity: 0.06; }
  100% { transform: scale(5.6); opacity: 0; }
}
/* Reduced motion drops the RING and keeps the dot beneath it. The mark is
   information; the pulse is emphasis, and only one of those is safe to remove. */
@media (prefers-reduced-motion: reduce) {
  .sm-asset-svg .sm-live-halo,
  .qn-ts-svg .sm-live-halo { display: none; animation: none; }
}

.sm-aflags { display: flex; gap: 6px; flex-wrap: wrap; }
.sm-aflag { font-size: 11.5px; font-weight: 650; color: var(--text-dim); background: var(--sm-card); border: 1px solid var(--border); border-radius: 999px; padding: 5px 11px; }
.sm-aflag.dim { opacity: 0.7; border-style: dashed; }

.sm-aread-h { display: flex; align-items: baseline; font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); margin: 2px 2px -4px; }
.sm-aread-badge { margin-left: auto; font-size: 11px; font-weight: 700; letter-spacing: 0; text-transform: none; color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 12%, transparent); border-radius: 999px; padding: 3px 9px; }
.sm-acats { border: 1px solid var(--border); border-radius: 16px; overflow: hidden; background: var(--sm-card); }
.sm-acat { border-bottom: 1px solid var(--border); }
.sm-acat:last-child { border-bottom: none; }
.sm-acat-h { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 10px 12px; background: none; border: none; color: inherit; font: inherit; cursor: pointer; min-height: 46px; }
.sm-acat-h:focus-visible { outline: 2px solid var(--accent, #5b7cfa); outline-offset: -2px; }
.sm-acat-h:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.sm-acat-h { transition: background 0.12s; }
@media (prefers-reduced-motion: reduce) { .sm-acat-h { transition: none; } }
.sm-amark { width: 21px; height: 21px; border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 800; }
.sm-amark.strong { background: var(--sm-up); color: #fff; }
.sm-amark.ok { background: color-mix(in srgb, var(--sm-up) 16%, transparent); color: var(--sm-up); }
.sm-amark.neutral { background: color-mix(in srgb, #a8842c 20%, transparent); color: #a8842c; }
.sm-amark.bad { background: color-mix(in srgb, var(--sm-down) 16%, transparent); color: var(--sm-down); }
.sm-amark.na { background: color-mix(in srgb, var(--text) 6%, transparent); color: var(--text-faint); }
.sm-acat-t { min-width: 0; flex: 1; }
.sm-acat-n { display: block; font-size: 13px; font-weight: 700; line-height: 1.25; }
.sm-acat-s { display: block; font-size: 11.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.35; margin-top: 1px; }
.sm-acat-c { margin-left: auto; color: var(--text-faint); flex: none; transition: transform 0.15s; }
.sm-acat-h.open .sm-acat-c { transform: rotate(90deg); }
.sm-acat-b { padding: 0 12px 11px 43px; }
.sm-akv { display: flex; font-size: 12px; color: var(--text-dim); padding: 3px 0; gap: 10px; }
.sm-akv b { color: var(--text); font-variant-numeric: tabular-nums; margin-left: auto; text-align: right; font-weight: 700; }
.sm-akv b.up { color: var(--sm-up); }
.sm-akv b.down { color: var(--sm-down); }
@media (prefers-reduced-motion: reduce) { .sm-acat-c { transition: none; } }

/* asset detail: TA legend + news rows */
.sm-talegend { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }
.sm-taregime { font-size: 10px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 13%, transparent); border-radius: 999px; padding: 3px 9px; }
.sm-aflag.good { color: var(--sm-up); border-color: color-mix(in srgb, var(--sm-up) 35%, transparent); }
.sm-aflag.bad { color: var(--sm-down); border-color: color-mix(in srgb, var(--sm-down) 45%, transparent); background: color-mix(in srgb, var(--sm-down) 10%, var(--sm-card)); }
.sm-newsrow { display: flex; align-items: baseline; gap: 7px; padding: 4px 0; font-size: 12px; }
.sm-newstag { flex: none; font-size: 9px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; border-radius: 4px; padding: 2px 5px; background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text-dim); }
.sm-newstag.exploit, .sm-newstag.regulatory, .sm-newstag.delisting, .sm-newstag.unlock, .sm-newstag.team, .sm-newstag.depeg { background: color-mix(in srgb, var(--sm-down) 18%, transparent); color: var(--sm-down); }
.sm-newstag.etf, .sm-newstag.listing, .sm-newstag.upgrade, .sm-newstag.partnership, .sm-newstag.supply { background: color-mix(in srgb, var(--sm-up) 15%, transparent); color: var(--sm-up); }
.sm-newslink { color: var(--text); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sm-newslink:hover { text-decoration: underline; }

/* the quiet day — a designed state, not an error (docs/AUTO_QUANT.md §8) */
.sm-quiet { background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 22px 18px; text-align: center; }
.sm-quiet-h { font-size: 15.5px; font-weight: 700; margin-bottom: 6px; }
.sm-quiet-b { font-size: 13px; color: var(--text-dim); max-width: 42ch; margin: 0 auto; line-height: 1.5; }
.sm-quiet-r { font-size: 11.5px; font-weight: 650; color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 12%, transparent); border-radius: 999px; padding: 5px 12px; display: inline-block; margin-top: 12px; }
.sm-osc-svg { display: block; width: 100%; height: 46px; margin-top: 4px; border-top: 1px solid var(--border); padding-top: 4px; }
.sm-taswatch { display: inline-block; width: 12px; height: 0; border-top: 2px solid var(--sw, var(--text-dim)); margin-right: 5px; vertical-align: middle; border-radius: 2px; }
.sm-taswatch.dash { border-top-style: dashed; }
.sm-newstag.none { background: color-mix(in srgb, var(--text) 7%, transparent); color: var(--text-faint); }

.sm-nochart { text-align: center; padding: 22px 16px; }
.sm-nochart-t { font-size: 14px; font-weight: 700; margin-bottom: 5px; }
.sm-nochart-s { font-size: 12.5px; color: var(--text-dim); max-width: 40ch; margin: 0 auto; line-height: 1.5; }
.sm-newsbox { background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 12px 16px; }
.sm-newsbox-h { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin-bottom: 7px; }
/* newsbox follows the stack — no column placement (owner 2026-07-27) */

/* ---- Strategies tab (AUTO_QUANT §10.4 A2b/A3): tabs, play carousel, ticket ---- */
/* THE ASSET TAB ROW SCROLLS; THE PAGE DOES NOT (owner 2026-08-16, mobile
   screenshot: the whole asset screen panned sideways, clipping the left edge of
   this row and the summary card).

   MEASURED CAUSE, not a guess. This row went from three tabs to six when
   Holders/Feed/About landed. At 13.5px/600 with `padding: 9px 15px` each, the
   six labels come to roughly 490px of intrinsic width; a 360px phone leaves
   about 328px of content box. A flex row with no wrapping and no scrolling
   cannot be narrower than its min-content, so ~160px of it pushed the whole
   document wider than the viewport and the PAGE panned instead of the row.

   The chart was the other suspect and is innocent: `.sm-asset-svg` is
   `width: 100%` with a scaling viewBox, so it fits at any width — the new price
   axis draws inside that box and adds nothing to it.

   This is the same idiom `.sm-tabs` already uses (overflow-x + hidden
   scrollbar + non-shrinking children), so the row scrolls within itself and the
   page cannot. Nothing here clips an overhanging badge: `.sm-atab-n` sits inside
   its tab's padding box, unlike the nav bell that needed `.qn-navrow`. */
.sm-atabs {
  display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin: 0;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.sm-atabs::-webkit-scrollbar { display: none; }
.sm-atab { appearance: none; background: none; border: none; cursor: pointer; padding: 9px 15px; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--text-dim); border-bottom: 2.5px solid transparent; flex: 0 0 auto; white-space: nowrap; }
/* A SCROLL CONTAINER CLIPS AN OUTSET FOCUS RING. There was no rule here before,
   so keyboard focus drew the browser default just OUTSIDE the tab — which this
   row now clips. Drawn inset instead, so making the row scrollable does not
   quietly cost the focus indicator. */
.sm-atab:focus-visible { outline: 2px solid var(--accent, #4c8dff); outline-offset: -2px; }
.sm-atab.on { color: var(--accent, #4c8dff); border-color: var(--accent, #4c8dff); }
/* Live-count chip on the Positions tab. */
.sm-atab-n { display: inline-block; margin-left: 6px; min-width: 16px; padding: 0 4px; border-radius: 999px; background: var(--sm-up); color: var(--sm-on-up, #fff); font-size: 10.5px; font-weight: 700; line-height: 16px; text-align: center; }
/* POSITIONS TAB (owner 2026-08-06): this asset's plays; a tapped row lights
   its mark on the chart. */
.sm-pos { display: flex; flex-direction: column; gap: 8px; padding: 2px 0 12px; }
/* ── THE POSITION CARD, RESTRUCTURED FOR A PHONE (owner ruling 2026-08-17:
   "reorganize so that the ui works properly in mobile, using material 3
   principles") ─────────────────────────────────────────────────────────────
   It was one centred flex line — sparkle · main · P&L · ✕ — which left the
   content column 166px of a 356px card, measured. Every failure the owner
   listed came out of that: labels wrapped mid-word, the facts broke mid-clause,
   the K2-08 sentence ran four lines, and the P&L painted over the Hand-back
   button. The card is a COLUMN now; the head line does the horizontal work and
   everything else gets the full width. Gaps come from this file's own scale
   (the positions-card family uses 6/8/10/14). */
.sm-pos-row { display: flex; flex-direction: column; align-items: stretch; gap: 8px; width: 100%; text-align: left; background: var(--sm-card); border: 1px solid transparent; border-radius: 14px; padding: 12px 14px; font: inherit; color: var(--text); cursor: pointer; }
/* LINE ONE, and the only horizontal line: the sparkle, who this is, what it is
   worth, and the way out. `min-width: 0` on the identity is what lets the money
   keep its width instead of the two overlapping. */
.sm-pos-hd { display: flex; align-items: center; gap: 10px; min-width: 0; }
.sm-pos-hd > .sm-pos-t { flex: 1 1 auto; min-width: 0; }
.sm-pos-hd > .sm-pos-r { margin-left: auto; }
.sm-pos-row.on { border-color: color-mix(in srgb, var(--text) 30%, transparent); }
.sm-pos-dir { flex: none; font-size: 13px; }
.sm-pos-dir.long { color: var(--sm-up); }
.sm-pos-dir.short { color: var(--sm-down); }
.sm-pos-dir.grid { color: var(--text-dim); }
/* Long/Short word in the title — same colours as the home row's (owner
   2026-08-06: "long / short color in sync"). */
.sm-pos-dirw { font-size: 12.5px; font-weight: 650; }
.sm-pos-dirw.long { color: var(--sm-up); }
.sm-pos-dirw.short { color: var(--sm-down); }
.sm-pos-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.sm-pos-t { font-size: 13.5px; font-weight: 650; display: flex; align-items: center; gap: 7px; }
.sm-pos-sub { font-size: 12px; line-height: 1.45; color: var(--text-dim); margin-top: 0; }
.sm-pos-chip { font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; border-radius: 999px; padding: 1.5px 7px; }
.sm-pos-chip.ok { background: color-mix(in srgb, var(--sm-up) 18%, transparent); color: var(--sm-up); }
.sm-pos-chip.warn { background: color-mix(in srgb, #e6a23c 20%, transparent); color: #e6a23c; }
.sm-pos-chip.dim { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text-dim); }
/* ── THE PLAYBOOK (G5c): the user's own record ──
   Same overline section idiom as POSITIONS and the review widget above it, so
   it reads as another part of the same screen rather than a feature bolted on.
   Quiet by construction: a proven entry gets a tonal chip and slightly stronger
   text, a forming one gets neither, and nothing here uses colour to praise or
   scold — the number says what happened. */
.qn-playbook { margin-top: 14px; }
.qn-pb-body { display: flex; flex-direction: column; gap: 8px; }
.qn-pb-row { display: flex; flex-direction: column; gap: 2px; }
.qn-pb-head { display: flex; align-items: center; gap: 8px; }
.qn-pb-t { font-size: 13px; color: var(--text-dim); }
.qn-pb-row.proven .qn-pb-t { font-weight: 600; color: var(--text); }
.qn-pb-badge {
  font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  border-radius: 999px; padding: 1.5px 7px; margin-left: auto;
  background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text-dim);
}
.qn-pb-badge.proven { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.qn-pb-sub { font-size: 11px; line-height: 1.45; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.qn-pb-note { font-size: 11px; line-height: 1.5; color: var(--text-dim); }
.qn-pb-note.strong { margin-top: 10px; color: var(--text); font-weight: 600; }
.qn-pb-starters { display: flex; flex-direction: column; gap: 6px; }
/* The borrowed cards are visibly on loan: a dashed edge, no fill, and the
   heading above them says whose they are. */
.qn-pb-card {
  border: 1px dashed color-mix(in srgb, var(--text) 18%, transparent);
  border-radius: 12px; padding: 8px 10px; display: flex; flex-direction: column; gap: 2px;
}
.qn-pb-claim { font-size: 12px; color: var(--text); }
.qn-pb-test { font-size: 11px; line-height: 1.45; color: var(--text-dim); }
/* ── THE COCKPIT (G5b): the doors, who owns each, and the one boundary ──
   Material 3 idiom, and deliberately quiet: this block sits inside a card that
   already carries the position's vitals, so it reads as two more lines of the
   same card rather than a panel bolted on. Every colour is a theme token, so
   both themes are one rule; nothing here is a coloured strip (the owner killed
   those on 2026-08-13). */
/* The 6px trailing inset is the collision fix (owner screenshot 2026-08-17):
   `.sm-pos-r` — the +$20.94 / +7.6% column — is the very next thing on the row,
   and the door row's trailing control was landing flush against it. 6px is this
   block's own gap, not a new number. */
.sm-doors { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; padding-right: 6px; }
.sm-door { display: flex; align-items: center; gap: 8px; font-size: 12px; min-height: 44px; }
/* THE LABEL NEVER WRAPS (owner ruling 2026-08-17). "Stop loss" was breaking to
   two lines in a 26px box — it was competing for width with the value, the
   owner chip and two buttons inside a 160px column. The card is stacked now, so
   the width exists; `nowrap` is what guarantees it stays spent on one line, in
   either language, however long a future door name is. */
.sm-door-k { flex: none; white-space: nowrap; color: var(--text-dim); }
.sm-door-v { font-weight: 650; font-variant-numeric: tabular-nums; }
/* The owner badge: a tonal chip, and the USER's is the one that reads as
   claimed — the whole product ruling is that this door is theirs. */
.sm-door-own {
  font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  border-radius: 999px; padding: 1.5px 7px;
  background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text-dim);
}
.sm-door-own.mine { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }
/* THE ACTIONS ARE MARKS, NOT WORDS (owner 2026-08-17: "use icon for function,
   rather use text"). Tonal ICON buttons in one group that owns the trailing
   edge — previously each button pushed itself there with its own
   `margin-left: auto`, and the last one ended up flush against the P&L column.
   The group's 10px leading gap is the row's own gap, so the marks can never
   touch the price and the YOURS chip to their left either.
   The words are not gone, they moved: each button carries the same t() string
   as its tooltip and its accessible name. */
/* THE INTERNAL GAP IS 16px, NOT 4px, AND THAT IS A TOUCH FACT (measured
   2026-08-17). Each mark grows a 6px transparent skirt to reach 44px; at a 4px
   gap the two skirts OVERLAPPED, and `elementFromPoint` walking out from the
   first button's centre stopped at 35px wide because the point had already
   entered the second button's skirt. 16px leaves the two hit areas 4px apart
   and both a full 44. The group's own guarantees are untouched: it still owns
   the trailing edge (`margin-left: auto`) with the row's 10px lead. */
.sm-door-acts { margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 16px; padding-left: 10px; }
/* PAINTED 32, TAPPED 44 (owner ruling 2026-08-17, and the 44px floor from the
   idea cards). Two 44px discs side by side on a door row read as a toolbar and
   crowd the number they belong to, so the button keeps a 32px face and grows
   its TOUCH area with a transparent `::after` — `inset: -6px` on 32px is 44px
   exactly. Hit-testing lands on the pseudo-element's host, so the target is
   real; the browser pass measures it with elementFromPoint rather than trusting
   the arithmetic. The group's 4px gap plus the two 6px skirts still leaves 16px
   between the two hit areas, so neither steals the other's taps. */
.sm-door-act {
  position: relative;
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; font-size: 17px; line-height: 1;
  color: var(--accent); border-radius: 999px; cursor: pointer;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.sm-door-act::after { content: ""; position: absolute; inset: -6px; border-radius: 999px; }
.sm-door-act.back { color: var(--text-dim); background: color-mix(in srgb, var(--text) 8%, transparent); }
.sm-door-act:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.sm-door-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The boundary and the receipt are the same weight: a quiet number and what it
   is a number of, never a banner. */
/* THE LAST-MILE RECEIPT — the only thing wearing this class since the owner
   removed the K2-08 sentence from the card on 2026-08-17 (the bound is still
   explained where the rule bites: in the "Move the stop loss" prompt, and in
   the server's own refusal). It is prose, so it keeps prose treatment: a
   measure near the 45-75 characters reading wants rather than the full card
   width, and `text-wrap: pretty` to keep a single word off the last line. */
.sm-door-note {
  font-size: 11px; line-height: 1.5; color: var(--text-dim); margin-top: 2px;
  max-width: 48ch; text-wrap: pretty;
}
.sm-door-note.receipt { color: var(--text); }
.sm-pos-pnl { flex: none; font-size: 13px; font-weight: 650; font-variant-numeric: tabular-nums; }
.sm-pos-pnl.up { color: var(--sm-up); }
.sm-pos-pnl.down { color: var(--sm-down); }
.sm-pos-r { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.sm-pos-pct { font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.sm-pos-pct.up { color: var(--sm-up); }
.sm-pos-pct.down { color: var(--sm-down); }
/* Per-row actions on a LIVE position (owner 2026-08-07): close it, or send
   Genie to re-judge its exits. They sit after the P&L so the number the owner
   scans for keeps its place, and the whole group is flex:none so a long
   position line can never squeeze the controls out of reach. Both are spans
   with role=button — the row itself is a <button>, and a nested one is invalid
   HTML — so they need their own focus ring to stay keyboard-reachable. */
.sm-pos-acts { flex: none; display: flex; align-items: center; gap: 2px; margin-left: 2px; }
/* The 44px floor applies here too — it was a 34px target on the head line,
   beside the money, which is the last control that should be easy to miss and
   the last one that should be easy to hit by accident. Same technique as the
   door marks: the paint stays small, the target is real. */
.sm-pos-act { position: relative; flex: none; font-size: 11px; font-weight: 650; color: var(--text-dim); border-radius: 999px; padding: 5px 9px; cursor: pointer; white-space: nowrap; }
.sm-pos-act::after { content: ""; position: absolute; inset: -6px; border-radius: 999px; }
.sm-pos-act:hover { color: var(--text); background: color-mix(in srgb, var(--text) 12%, transparent); }
.sm-pos-act:focus-visible { outline: 2px solid var(--text); outline-offset: 1px; color: var(--text); }
/* The ✕ is a real square target rather than a text pill — 34px of touchable
   area around the glyph. Spelling the action out was tried and reverted (owner
   2026-08-08): the words cost more row than the position's own stop price. */
.sm-pos-act.x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; font-size: 13px;
}
/* A holding row MOVES MONEY when tapped; a waiting one only releases a
   reservation. Only the first gets the warning colour — it is the difference
   between the two, carried at no width. */
.sm-pos-act.x.hold { color: var(--sm-down); }
/* THE SPARKLE LEADS THE CARD (owner 2026-08-08): first thing on the row, a
   full 40px circle — inline in the text it was both hard to hit and easy to
   miss. It keeps Genie's colour so it never reads as the destructive ✕, which
   sits at the opposite end of the card. flex:none so a long position line can
   never shrink the target. */
.sm-pos-lead {
  /* 40px painted, 44px tapped — the same floor as the door marks and the ✕.
     Measured at 38 on the live card before this (the border-box rounds down at
     this density), which is under both the 44pt iOS and 48dp M3 minimums for
     the control the owner asked to be "big enough to click on". */
  position: relative;
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 999px; cursor: pointer;
  font-size: 21px; line-height: 1;
  color: var(--grad-b);
  background: color-mix(in srgb, var(--grad-b) 14%, transparent);
}
.sm-pos-lead::after { content: ""; position: absolute; inset: -4px; border-radius: 999px; }
.sm-pos-lead:hover { background: color-mix(in srgb, var(--grad-b) 26%, transparent); }
/* WHAT THE ONE-ASSET TAP DID, on the row it was about. Full width so it wraps
   under the row's own line rather than squeezing it, and dim because it is an
   acknowledgement — the verdict chip that follows is the answer. */
.sm-pos-note {
  flex: 1 0 100%;
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--text-dim);
}
.sm-pos-lead:focus-visible { outline: 2px solid var(--grad-b); outline-offset: 2px; }
@media (max-width: 430px) {
  .sm-pos-lead { width: 38px; height: 38px; font-size: 20px; }
}
.sm-pos-act.x:hover { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 16%, transparent); }
.sm-pos-act.x:focus-visible { outline-color: var(--sm-down); color: var(--sm-down); }
.sm-pos-empty { color: var(--text-dim); font-size: 13px; padding: 14px 2px; }
/* Share button + PnL-card modal (owner 2026-08-06). The scrim mounts on
   <body>, outside .sm-root's token scope — restate what it needs (same
   reason as .sm-sheet-scrim). */
.sm-asset-head { position: relative; }
.sm-share-btn { position: absolute; top: 4px; right: 2px; width: 38px; height: 38px; display: grid; place-items: center; background: var(--sm-card); border: 1px solid var(--border); border-radius: 999px; color: var(--text-dim); cursor: pointer; }
.sm-share-btn:hover { color: var(--text); }
.sm-share-scrim {
  --border: color-mix(in srgb, var(--text) 9%, transparent);
  position: fixed; inset: 0; z-index: 95; background: rgba(0, 0, 0, 0.6);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.sm-share-box { display: flex; flex-direction: column; gap: 14px; width: min(420px, 92vw); }
.sm-share-box canvas { width: 100%; height: auto; display: block; border-radius: 18px; border: 1px solid var(--border); }
.sm-share-row { display: flex; gap: 10px; }
.sm-share-act { flex: 1; padding: 13px 0; border-radius: 999px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; font-size: 14px; font-weight: 650; cursor: pointer; }
.sm-share-act.primary { background: var(--sm-up, #2ecc71); color: var(--sm-on-up, #08130a); border-color: transparent; }
.sm-strat-car { display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory; padding: 2px 2px 8px; scrollbar-width: none; }
.sm-strat-car::-webkit-scrollbar { display: none; }
.sm-strat-card { flex: 0 0 min(320px, 86%); scroll-snap-align: start; background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 16px; }
/* The standing build-your-own slot at the carousel's end (owner 2026-08-04):
   a dashed add-card — tonal plus, centered pitch, one CTA. The whole card is
   the button; the CTA is paint. */
.sm-strat-new { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; text-align: center; cursor: pointer; background: transparent; border: 1.5px dashed color-mix(in srgb, var(--text) 24%, transparent); font: inherit; color: var(--text); }
.sm-strat-new:hover { background: var(--bg-elev-1); }
.sm-strat-new-plus { width: 42px; height: 42px; border-radius: 50%; background: var(--bg-elev-2); display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 500; color: var(--text-dim); }
.sm-strat-new-t { font-size: 15px; }
.sm-strat-new .sm-strat-blurb { text-align: center; }
.sm-strat-new-cta { pointer-events: none; width: auto; padding: 10px 18px; }
/* Deep-linked from a Home tile: a brief ring marks the card the tap promised. */
.sm-strat-card.sm-strat-focus { border-color: color-mix(in srgb, var(--sm-up) 55%, var(--border)); box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--sm-up) 40%, transparent); transition: border-color 0.5s, box-shadow 0.5s; }
.sm-strat-card.hi { border-color: color-mix(in srgb, var(--accent, #4c8dff) 45%, var(--border)); }
/* The owner's own rule in the asset list — the same violet its board tiles wear. */
.sm-strat-card.mine { border-color: color-mix(in srgb, #a06bff 40%, var(--border)); background: linear-gradient(170deg, color-mix(in srgb, #a06bff 8%, transparent), transparent 55%), var(--sm-card); }
.sm-strat-minechip { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: color-mix(in srgb, #b58cff 75%, var(--text)); background: color-mix(in srgb, #a06bff 16%, transparent); border-radius: 999px; padding: 2px 8px; margin-left: auto; white-space: nowrap; }
.sm-strat-card.mine .sm-strat-rej { margin-left: 0; }
/* M3 titleMedium for the name; the direction chip carries the only colour. */
.sm-strat-head { display: flex; align-items: center; gap: 8px; font-size: 15px; letter-spacing: 0.01em; flex-wrap: wrap; }
/* Leverage sits with the title, not in the fine print: the headline return is
   a LEVERAGED number and must never be read without its multiplier. Amber —
   it qualifies the number rather than approving it (owner 2026-07-28). */
.sm-strat-lev { font-size: 10px; font-weight: 700; letter-spacing: 0.04em; color: var(--warn, #e2a03f); background: color-mix(in srgb, var(--warn, #e2a03f) 14%, transparent); border-radius: 999px; padding: 2px 8px; white-space: nowrap; }
.sm-strat-blurb { font-size: 12.5px; color: var(--text-dim); margin: 5px 0 0; line-height: 1.45; }
/* Grouped disclosure (owner 2026-07-27: "more organized, easier to read").
   A labelSmall header + its rows, separated by whitespace and one hairline —
   no dashed boxes, no nested cards. */
.sm-strat-grp { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.sm-strat-glbl { font-size: 10.5px; font-weight: 500; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-faint); margin-bottom: 6px; }
.sm-strat-grp .sm-akv { font-size: 11.5px; padding: 4px 0; align-items: baseline; }
.sm-strat-grp .sm-akv b { font-weight: 600; text-align: right; max-width: 64%; line-height: 1.4; }
.sm-outpair { display: flex; justify-content: center; align-items: baseline; gap: 10px; margin: 10px 0 2px; font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; }
.sm-outpair .down { color: var(--sm-down); }
.sm-outpair .up { color: var(--sm-up); }
.sm-outpair .sep { color: var(--text-faint); font-weight: 400; }
.sm-ticket { border-top: 1px solid var(--border); margin-top: 12px; padding-top: 10px; }
/* A level that came from the card, not from the user. Same rhythm as the input
   it replaced, none of the affordance — these are facts (the record was earned
   with them), so they must not look typeable. */
/* Composing: the strategy's own exits, set on the card where its tested levels
   and its record already are. */
/* M3 spacing: the exits editor is a form, not a footnote — 12px between its
   rows, 20px of air from the record above it (owner 2026-08-01). */
.sm-exits { display: flex; flex-direction: column; gap: 12px; margin-top: 20px; }
.sm-exbad { font-size: 11.5px; line-height: 1.45; color: var(--sm-down); }
/* Steppers, and what the numbers add up to. Every tap is a known increment and
   the lines under it say what the combination MEANS — a free number box asks
   the creator to invent a value and tells them nothing about it. */
.sm-exrow { display: flex; align-items: center; gap: 12px; min-height: 40px; }
.sm-exlab { flex: 1 1 auto; font-size: 12.5px; color: var(--text-dim); }
.sm-exctl { display: flex; align-items: center; gap: 2px; }
/* The value is a real INPUT now (owner 2026-08-02: type it or step it), styled
   as a Material 3 filled field only while it has focus — at rest it reads as
   the same quiet number it always was, same type ramp as every figure here. */
.sm-exval { min-width: 52px; width: 52px; text-align: center; font-size: 15px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; font-family: inherit; color: var(--text); background: none; border: none; border-radius: 10px; padding: 6px 2px; }
.sm-exval:focus { outline: none; background: var(--bg-elev-2); }
/* A PRICE NEEDS ROOM A PERCENT DOES NOT. 52px fits "1.3"; it truncates
   "598.845". These rows carry levels now (owner 2026-08-08: "SL / TP ... should
   be actual price level"), so the field grows and the label yields. */
.sm-exval.wide { min-width: 96px; width: 96px; font-size: 14px; }
.sm-exunit { font-size: 12.5px; color: var(--text-dim); margin-right: 2px; min-width: 14px; }
.sm-exfb { display: flex; flex-direction: column; gap: 3px; padding-top: 4px; font-size: 11.5px; line-height: 1.45; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.sm-exfb .warn { color: var(--warn, #d2a13a); }
.sm-exfb .bad { color: var(--sm-down); }
/* The tested-baseline row, as an M3 tonal surface (owner 2026-08-02: "this
   part needs a better layout"): supporting text left, a TEXT button right —
   a disabled pill next to a two-line caption read as two orphans. */
.sm-exbase { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 8px; padding: 10px 14px; background: var(--bg-elev-1); border-radius: 12px; }
.sm-exreset { appearance: none; border: none; background: none; padding: 8px 10px; margin: -8px -10px -8px 0; border-radius: 10px; font: inherit; font-size: 12.5px; font-weight: 650; color: var(--sm-up); cursor: pointer; white-space: nowrap; }
.sm-exreset:hover:not(:disabled) { background: color-mix(in srgb, var(--sm-up) 12%, transparent); }
.sm-exreset:disabled { color: var(--text-faint); cursor: default; }
/* ── The strategy COPY screen (owner 2026-08-02): copying one strategy works
   like copying a Stack — a full screen over everything, amount first. ── */
/* A VIEW inside the session pane, not an overlay: the pane owns it, the
   composer keeps its own layer below (no z-index games to lose), and the
   sticky CTA anchors to the pane's visible bottom the same way the Stack
   copy screen's does — because it uses the same qn-bcta mechanism. Height
   fills the pane's viewport so the CTA has a bottom to stick to. */
.sm-copyscr { display: flex; flex-direction: column; min-height: calc(100dvh - 230px); }
.sm-copyscr-h { display: flex; align-items: center; gap: 10px; padding: 6px 2px 8px; }
.sm-copyscr-t { font-size: 16px; font-weight: 700; letter-spacing: -0.01em; }
.sm-copyscr-b { flex: 1 1 auto; display: flex; flex-direction: column; padding: 8px 2px 0; }
/* Same vertical rhythm as the Stack copy screen: the amount group floats
   centered in the free space, the CTA anchors at the bottom. */
.sm-copyscr .qn-cf-amt { flex: 1 1 auto; justify-content: center; }
/* Wider, roomier (owner 2026-08-02: "make it wider, more space") — the card
   spans the pane up to a readable cap instead of shrink-wrapping its rows,
   and the rows breathe. The centered amount column would otherwise hug it
   to content width. */
/* Full pane width, like the Stack review it mirrors (owner 2026-08-03) — a
   centered 420px column read as an inset card floating in the pane. */
/* THE REVIEW READS AT THE SAME WIDTH AS THE SCREEN BEFORE IT (owner 2026-08-08:
   "this is a bit too wide should be similar width as prev page"). The amount
   screen's cards are capped at 420px and centred; the review stretched to the
   whole pane, so the same order looked like two different products and the k/v
   rows had a hand-span of dead space between label and value on a desktop
   pane. Same cap, same centring. */
.sm-copyrev { display: flex; flex-direction: column; gap: 10px; margin: 12px auto 0; width: 100%; max-width: 420px; }
/* AND AT THE SAME HEIGHT AS THE SCREEN BEFORE IT (owner 2026-08-08: "the center
   piece should be in a similar position to the previous screen"). The amount
   column grows and centres itself in the pane; the review did neither, so it
   pinned to the top with a pane's worth of empty space under it and the whole
   order jumped upward between one step and the next. Same growth, same centring
   — one order, one position. */
.sm-copyscr .sm-copyrev { flex: 1 1 auto; justify-content: center; }
.sm-copyrev .sm-arm { margin-top: 14px; }
.sm-tfact { margin-left: auto; font-size: 12.5px; color: var(--text-dim); padding: 5px 0; }
.sm-tin { width: 110px; margin-left: auto; background: color-mix(in srgb, var(--sm-card) 60%, #000 8%); border: 1px solid var(--border); border-radius: 10px; color: var(--text); font: inherit; font-size: 12.5px; padding: 5px 9px; text-align: right; }
/* Leverage: a swipe bar + a typeable number, so the whole 1..max range is
   visible in one row and nothing truncates on a narrow card. */
.sm-levblk { padding: 2px 0 4px; }
.sm-levval { margin-left: auto; display: inline-flex; align-items: baseline; gap: 1px; }
.sm-levin { width: 62px; padding: 4px 6px; text-align: right; }
.sm-levx { font-size: 12px; font-weight: 600; color: var(--text-dim); }
.sm-levbar { -webkit-appearance: none; appearance: none; display: block; width: 100%; height: 20px; margin: 6px 0 0; background: none; cursor: pointer; }
.sm-levbar::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--text) 14%, transparent); }
.sm-levbar::-moz-range-track { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--text) 14%, transparent); }
.sm-levbar::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 16px; height: 16px; margin-top: -6px; border-radius: 50%; background: var(--accent, #4c8dff); border: none; }
.sm-levbar::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--accent, #4c8dff); border: none; }
.sm-levbar:focus-visible { outline: 2px solid var(--accent, #4c8dff); outline-offset: 3px; border-radius: 4px; }
.sm-levends { display: flex; justify-content: space-between; font-size: 10.5px; color: var(--text-faint); margin-top: 2px; }
/* The card's record belongs to the tested rung — say so when you're on it. */
.sm-levblk.tested .sm-levends span:first-child { color: var(--accent, #4c8dff); }
/* What the chosen rung actually costs — never a blocker, always visible. */
.sm-levnote { font-size: 11px; line-height: 1.45; color: var(--text-faint); display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
.sm-levnote.warn { color: var(--warn); }
.sm-levnote.bad { color: var(--sm-down); }
.sm-arm { display: block; width: 100%; margin-top: 20px; appearance: none; cursor: pointer; font: inherit; font-size: 13.5px; font-weight: 700; padding: 11px; border-radius: 999px; border: none; background: var(--accent, #4c8dff); color: #071120; }
.sm-arm:disabled { opacity: 0.7; cursor: default; }
.sm-arm.quiet { background: color-mix(in srgb, var(--text-dim) 14%, transparent); color: var(--text-dim); }
/* Four-layer flow (§10.4c): each surface leads with its question. */
.sm-layer-q { font-size: 11px; letter-spacing: 0.07em; text-transform: uppercase; font-weight: 700; color: var(--text-faint); margin: 2px 2px 8px; }
.sm-layer-q-sub { text-transform: none; letter-spacing: 0; font-weight: 600; }
/* ---- Quant screen (four-layer home, AUTO_QUANT §10.4c) ---- */
/* max-width WITHOUT an auto inline margin pins the column to the left edge, so
   on a wide pane (and worse with the task list collapsed) every row sat left of
   a big empty gutter while the composer below it stayed centred — the mismatch
   is what read as broken (owner 2026-07-29). width:100% keeps it flush on
   phones, where max-width never binds. */
/* Auto on = the reveal's green settled over the WHOLE screen: strongest at
   the nav bar, gone by the composer. Sits above the opaque session bar
   (z2), below sheets/toasts; never catches taps. */
.qn-autocast {
  position: absolute; inset: 0; pointer-events: none; z-index: 3;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--sm-up) 13%, transparent) 0%,
    color-mix(in srgb, var(--sm-up) 6%, transparent) 34%,
    transparent 70%
  );
}
.qn-wrap { display: flex; flex-direction: column; gap: 14px; padding: 0 2px 24px; width: 100%; max-width: 980px; margin-inline: auto; min-height: calc(100vh - 220px); container-type: inline-size; container-name: qnwrap; }
/* Autopilot-on reveal: a brand-tinted wash in a circle growing OUT OF the
   Auto pill until it covers the WHOLE screen — nav bar to composer — then
   fading (owner 2026-08-05, reference: react-native-nitro-theme-transition's
   circular theme reveal; replaced the top→bottom scan band). Geometry (center,
   radius, clip) is animated from quant.ts per tap — the pill moves with
   layout, a keyframe can't know where it is. Fixed overlay on <body>, under
   the snackbar layer (10400+), above all content chrome. */
/* Feed on desktop = Instagram's answer: one centered column at phone
   width. Full-pane cards blew the charts up ~3x (the SVG scales with
   width) and made every label shout. */
.sm-feed { width: 100%; max-width: 540px; margin-inline: auto; }

.qn-scanfx { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 10300; }
/* The wash: brightest at the pill, thinning outward — same subtle palette
   the scan band used ("more transparent and subtle", owner 2026-08-04). */
.qn-scanfx-wash {
  position: absolute; inset: 0;
  background: radial-gradient(
    circle at var(--cx, 50%) var(--cy, 20%),
    rgba(0, 217, 7, 0.18) 0%,
    rgba(0, 217, 7, 0.10) 45%,
    rgba(0, 143, 4, 0.07) 100%
  );
  filter: blur(2px);
}
/* The crisp wavefront riding the circle's edge. */
.qn-scanfx-ring {
  position: absolute; border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.3);
  box-shadow: 0 0 24px rgba(0, 217, 7, 0.3), inset 0 0 18px rgba(0, 217, 7, 0.15);
  mix-blend-mode: screen;
}
@media (prefers-reduced-motion: reduce) { .qn-scanfx { display: none; } }

/* ── Home layout: one column on a phone, two on a wide pane ─────────────────
   Owner 2026-07-29: "on desktop, under the dashboard hero, show the list of
   strategies; on the right of the dashboard, show open positions, and recent
   activities in 2 tabs" — then, having used it: "on mobile keep them in 2 tabs
   as in desktop".

   So the TABS are unconditional and only the COLUMNS are conditional. (The first
   cut showed both panels stacked on a phone, on the theory that a screen which
   scrolls needn't hide anything; the owner tried it and preferred one thing at a
   time everywhere. Their call — and it keeps one behaviour to reason about.)

   A CONTAINER query, not a media query: this pane's width depends on the task
   list (and the desktop collapse toggle), so viewport width is the wrong
   question — at 1700px the pane can be 1090 or 1640. DOM order is the phone
   order; the grid re-places the same three blocks for the wide case. */
.qn-cols { display: flex; flex-direction: column; gap: 14px; }
/* WHAT YOUR MONEY IS DOING — four tonal cards between the curve and the Stacks.
   auto-fit so they land 4-up with room and 2×2 on a narrow phone rather than
   squeezing four numbers into one line. */
.qn-snap { display: grid; grid-template-columns: repeat(auto-fit, minmax(74px, 1fr)); gap: 8px; }
.qn-snapc { background: var(--bg-elev-1); border-radius: 16px; padding: 11px 10px; text-align: center; min-width: 0; }
.qn-snapv { font-size: 15.5px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.qn-snapv.up { color: var(--sm-up); } .qn-snapv.down { color: var(--sm-down); } .qn-snapv.none { color: var(--text-faint); }
.qn-snapl { font-size: 10.5px; color: var(--text-faint); margin-top: 2px; }
/* Supporting facts under the hero P&L: at-work + unrealized. */
.qn-hero-work { display: flex; align-items: baseline; gap: 6px; font-size: 12px; color: var(--text-faint); margin-top: 3px; font-variant-numeric: tabular-nums; }
.qn-hero-work .up { color: var(--sm-up); }
.qn-hero-work .down { color: var(--sm-down); }
/* Manual / Autopilot — M3 segmented-button reading of what Robinhood
   would ship: hairline-outlined track, tonal fill on the selected side
   with a leading check (the M3 signature), the green fill earned only by
   Autopilot. Emphasized easing, no glow, no shouting. */
.qn-modeseg { position: relative; display: grid; grid-template-columns: 1fr 1fr; background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 999px; padding: 4px; margin-top: 10px; }
/* THE PILL TRAVELS, AND IT LANDS A LITTLE HARD (owner 2026-08-17: "need the pill
   move animation (left / right) when it happens, needs to be theatrical").
   `transform`, so it runs on the compositor and never touches layout; 0.4s so the
   trip is actually seen; and an overshoot curve so it arrives, passes the seat by
   a hair and settles back instead of gliding to a stop.
   THE OVERSHOOT IS SIZED TO THE RAIL, not picked for feel: cubic-bezier(0.2,1.2,
   0.3,1) peaks at 1.0125 of the travel, and the travel is (half the rail − 4px),
   so on the 318px column that is 1.9px past the seat — inside the 4px of padding
   the thumb leaves on the far side at rest. An easeOutBack (…1.56…) peaks at
   1.098 = 15px and would push the pill visibly outside the rail's own edge. */
.qn-modeseg-thumb { position: absolute; top: 4px; bottom: 4px; left: 4px; width: calc(50% - 4px); border-radius: 999px; background: var(--bg-elev-2); transition: transform 0.4s cubic-bezier(0.2, 1.2, 0.3, 1), background 0.4s cubic-bezier(0.2, 0, 0, 1); }
.qn-modeseg.auto .qn-modeseg-thumb { transform: translateX(100%); background: var(--sm-up); }
/* THE SQUASH, for exactly the flight — added with the seat change and removed on
   `animationend` (quant.ts paintSnapshot), so a pill sitting still is never
   mid-animation. It is on the `scale` property, NOT inside `transform`: the two
   compose (translate/scale apply before `transform`), so the slide above keeps
   its own rule and the end state is untouched — scale is back to 1 at 100%.
   Sized to stay inside the rail too: +3.5% of a ~150px thumb is 2.6px on the
   leading edge, under the 4px of padding; the 12% vertical squash is 2px on a
   34px pill. Small numbers, but they are what a rubber band looks like at this
   size — and NOT verified on screen (no server this round), so they are
   deliberately on the conservative side of "reads well". */
.qn-modeseg-thumb.flit { animation: qn-modeseg-flit 0.4s cubic-bezier(0.2, 1.2, 0.3, 1); }
@keyframes qn-modeseg-flit {
  0% { scale: 1 1; }
  40% { scale: 1.035 0.88; }
  72% { scale: 0.985 1.04; }
  100% { scale: 1 1; }
}
.qn-modeseg-b { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; gap: 7px; background: none; border: none; font: inherit; font-size: 13.5px; font-weight: 600; letter-spacing: 0.01em; color: var(--text-dim); padding: 10px 0; cursor: pointer; border-radius: 999px; transition: color 0.2s; }
.qn-modeseg-b.on { color: var(--text); font-weight: 650; }
.qn-modeseg.auto .qn-modeseg-b.on { color: var(--sm-on-up); }
/* Motion off means OFF: no slide and no squash. quant.ts also skips the two-step
   entirely in that case, so the switch is built in its final seat and there is
   not even one frame of the old one. */
@media (prefers-reduced-motion: reduce) {
  .qn-modeseg-thumb { transition: none; }
  .qn-modeseg-thumb.flit { animation: none; }
}
/* Under the zeros of an empty dashboard — says what would fill them. */
/* THE CHECKUP SCREEN: the findings timeline. Rows are cards like the positions
   sheet's; needs-you rows carry the down-accent edge; muted rows sit dim. */
.qn-ck { padding: 8px 16px 28px; display: flex; flex-direction: column; gap: 12px; }
/* Inside the Recent-activity sheet nothing else pads — the sheet body is a
   bare flex column — so the cards sat flush against the modal border (owner
   screenshot 2026-08-06). M3 insets: 16px on phones, 24px in the desktop
   modal. */
.qn-ck-in-sheet { padding: 4px 16px 16px; }
@media (min-height: 720px) and (min-width: 900px) {
  .qn-ck-in-sheet { padding: 4px 24px 20px; }
}
.qn-ck-head { padding: 4px 2px 0; }
/* Stamp + "look now" CTA on ONE line; category chips + search under them
   (owner 2026-08-06 redesign of the Recent-activity header). */
.qn-ck-filter { display: flex; flex-direction: column; gap: 8px; padding: 2px 2px 0; }
.qn-ck-search { width: 100%; box-sizing: border-box; background: var(--sm-card); border: 1px solid var(--border); border-radius: 999px; padding: 9px 14px; font: inherit; font-size: 13px; color: var(--text); }
.qn-ck-search::placeholder { color: var(--text-faint); }
.qn-ck-search:focus { outline: none; border-color: color-mix(in srgb, var(--text) 25%, transparent); }
.qn-ck-cats { display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; }
.qn-ck-cats::-webkit-scrollbar { display: none; }
.qn-ck-cat { flex: none; border: 1px solid var(--border); background: none; color: var(--text-dim); border-radius: 999px; padding: 5px 12px; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
.qn-ck-cat.on { background: var(--text); color: var(--bg); border-color: transparent; }
.qn-ck-title { font-size: 18px; font-weight: 700; letter-spacing: -0.01em; }
.qn-ck-stamp { font-size: 12px; color: var(--text-faint); margin-top: 3px; }
.qn-ck-ask {
  align-self: flex-start; background: var(--sm-card); color: var(--text);
  border: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  border-radius: 999px; padding: 9px 16px; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.qn-ck-ask:disabled { color: var(--text-faint); cursor: default; }
/* The look-in-progress spinner inside the ask button. */
.qn-ck-askspin {
  display: inline-block; width: 12px; height: 12px; margin-right: 7px; vertical-align: -1px;
  border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
.qn-ck-list { display: flex; flex-direction: column; gap: 10px; }
.qn-ck-row { background: var(--sm-card); border-radius: 18px; padding: 12px 14px 14px; }
/* NO COLOURED STRIP down the side of a bubble (owner 2026-08-13: "no need for
   big red strip at the left of the bubble"). The row's own red DOT is the
   urgency affordance the design system already has, in both themes; a 3px bar
   said the same thing louder and turned every needs-you note into an alarm. */
.qn-ck-row.is-muted { opacity: 0.55; display: flex; align-items: center; gap: 10px; }
.qn-ck-row.is-muted .qn-ck-text { flex: 1 1 auto; }
.qn-ck-row-h { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.qn-ck-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); }
.qn-ck-dot.need { background: var(--sm-down); }
.qn-ck-dot.agent { background: var(--sm-up); }
.qn-ck-dot.eng { background: #5b8def; }
/* The decision bubble — the one attention signal, riding the Recent-activity
   tab since the positions circle retired (2026-08-04). */
.qn-posbtn { position: relative; }
.qn-tab-need {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; margin-left: 6px; padding: 0 4px;
  border-radius: 999px; background: var(--sm-down); color: #fff;
  font-size: 10px; font-weight: 700; vertical-align: 1px; box-sizing: border-box;
}
.qn-ck-when { font-size: 11px; color: var(--text-faint); flex: 1 1 auto; }
.qn-ck-x { background: none; border: none; color: var(--text-faint); font-size: 16px; line-height: 1; cursor: pointer; padding: 2px 4px; }
.qn-ck-text { font-size: 13.5px; line-height: 1.5; }
/* Outcomes wear the money's color, same code as the bell: green won, red lost. */
.qn-ck-text.up { color: var(--sm-up); }
.qn-ck-text.down { color: var(--sm-down); }
.qn-ck-act {
  margin-top: 10px; background: var(--bg-elev-2); color: var(--text); border: none;
  border-radius: 999px; padding: 7px 14px; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.qn-ck-mh { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin-top: 6px; }
.qn-ck-empty { color: var(--text-faint); font-size: 13.5px; line-height: 1.5; padding: 8px 2px; }
.qn-live-review { font-size: 11.5px; color: var(--text-faint); padding: 2px 2px 0 18px; }

/* THE NEWS DECK: a horizontal row of dismissible cards. As a vertical list it
   pushed Your portfolios off the first screen; a deck costs one row's height
   whatever the news volume is. Snap so a swipe lands on a card, not between two. */
.qn-ndeck { display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; padding: 2px 0 4px; scrollbar-width: none; }
.qn-ndeck::-webkit-scrollbar { display: none; }
.qn-ncard { flex: 0 0 auto; width: min(78%, 300px); scroll-snap-align: start; display: flex; flex-direction: column; gap: 8px; background: var(--bg-elev-1); border-radius: 18px; padding: 14px; min-width: 0; }
/* ══════════ HOME'S TAPE — MOVES BY THE WALLETS YOU FOLLOW ══════════════════
   Replaces the eight top-trader cards (each a 30-day total) with a strip of
   individual moves — owner's mockup, 2026-08-23.

   IT IS A REAL SCROLLER, AND THAT IS WHY THERE IS NO CSS ANIMATION HERE.
   The owner wants to drag, flick and trackpad his way to earlier and later
   cards rather than wait for the loop. A `transform` animation and a scrollbar
   cannot share an element — a transformed track ignores `scrollLeft`, and a
   scroller ignores a transform that moved its content out from under it. So the
   auto-advance drives `scrollLeft` from quant.ts and everything native (touch
   momentum, trackpad, keyboard) comes for free.

   WHAT THAT TRADED AWAY, said plainly: the loop used to be zero JavaScript,
   composited off the main thread. It is now one `requestAnimationFrame` writing
   one number per frame — no layout, no paint — cancelled outright whenever the
   strip is parked, hidden, off screen or under a finger. Idle is still nothing.

   `scroll-behavior` is deliberately NOT smooth: a smooth-scrolling box fights
   every frame of an animation that moves it. */
.qn-tape {
  position: relative; overflow-x: auto; overflow-y: hidden; padding: 2px 0 4px;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain; scroll-behavior: auto; cursor: grab;
}
.qn-tape::-webkit-scrollbar { display: none; }
/* A mouse drag is ours (the browser only drag-scrolls for touch), so the cursor
   has to say so — including over the cards, which are buttons and would
   otherwise keep their pointer while the strip is being dragged under them. */
.qn-tape.dragging, .qn-tape.dragging * { cursor: grabbing; }
.qn-tape-track { display: flex; gap: 8px; width: max-content; }
.qn-tape-half { display: flex; gap: 8px; }
/* `touch-action` is left alone on purpose: pinning it to pan-x would stop a
   finger that lands on the strip from scrolling the PAGE, which on a phone
   makes the tape a trap in the middle of Home. */

/* SAY THE CONSEQUENCE, NOT THE STATE — and give it room to say it. These sit on
   their own row under the heading rather than beside it: "Full for today"
   shipped on the title row, was short enough to fit, and the owner had to ask
   what it meant. A consequence needs a sentence and a sentence needs width.
   Each appears only while it is TRUE and vanishes on its own. */
.qn-tape-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 0 2px 8px; }
.qn-tape-chip {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11px; font-weight: 650; letter-spacing: 0.01em; border-radius: 999px; padding: 3px 9px;
}
/* `.qn-tape-chip.full` is GONE. It painted "No new moves until tomorrow", which
   was true while `smart_alerts` was the only place a move was written. Saturday's
   `smart_moves` lane records every move whatever the daily budget does and this
   strip reads that table, so the chip's condition was deleted from the payload
   and the rule had nothing left that could ever match it. */
.qn-tape-chip.dark { color: var(--text-faint); box-shadow: inset 0 0 0 1px var(--border); }

/* ══════════ THE CARD IS TWO LINES, AND IT SIZES ITSELF ══════════════════════
   Owner 2026-08-23: *"make each card 2 lines / keep only what is vital."*

   IT IS NO LONGER 226px. A fixed width has to be either the worst case (wasting
   space on nearly every card) or narrower than the worst case (truncating it),
   and "no clipped text" cannot be promised by a number that was chosen before
   the content was known. `max-content` promises it structurally: the card is
   exactly as wide as its widest line and there is nothing left to clip.

   MEASURED ON THE LIVE ROWS, not assumed. Line 2 runs 174–260px across the
   twenty real moves — one of them passes 240px — and line 1 tops out near 254px
   on a long verb carrying a full figure. So cards land roughly 200–290px and a
   390px phone still shows one whole card plus a clear peek of the next. The
   `max-width` is a backstop nothing in the live data reaches, not a target. */
.qn-tapecard {
  /* 348, not 320 (R136). The owner's list added the leverage and the clock time to the card, and
     measured on his live strip at 390 px the widest row wants 348 — one leveraged open with a
     price, a wallet, a size and the Live chip. The old cap clipped exactly that row; every other
     card is narrower than the cap and unchanged, because the width is `max-content` and the cap
     only ever binds the widest. The strip scrolls, so a card that is 348 wide on a 390 screen
     still shows whole. */
  flex: 0 0 auto; width: max-content; min-width: 190px; max-width: 348px;
  display: flex; flex-direction: column; gap: 5px;
  background: var(--bg-elev-1); border: none; border-radius: 18px; padding: 11px 13px;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
/* LINE 1 — asset, verb, figure. The figure is pushed to the trailing edge by
   `margin-left: auto` rather than `space-between`, so on an add or a flip (which
   carry no figure, and that is 7 of the 20 live rows) the verb does not drift
   into the middle of an empty line. */
.qn-tape-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* R137 — 9px, not 8: two letters at 8px on a 20px disc were the smallest text on the screen. */
.sm-aico.qn-tape-ico { width: 20px; height: 20px; --sm-aico-init-size: 9px; flex: none; }
.qn-tape-coin { font-size: 13.5px; font-weight: 700; letter-spacing: -0.01em; color: var(--text); flex: none; }
/* The verb is the Feed's verb and wears the Feed's tone (`.sm-verb.up/.down`);
   this only gives it the size. It no longer needs truncation — a self-sizing
   card cannot clip it — but `nowrap` stays, because a wrapped verb would make
   the card three lines again by another route. */
.qn-tape-verb { font-size: 12px; font-weight: 650; white-space: nowrap; flex: none; }
.qn-tape-top .qn-tape-fig { margin-left: auto; }
/* LINE 2 — the price, then who / how big / how long ago. One row, two tones:
   the PRICE is a step brighter because "at what price" is a fact the owner named
   by itself, and the rest is the frame around it. `nowrap` on both, and the
   card's own width is what guarantees neither is cut. */
.qn-tape-foot { display: flex; align-items: baseline; gap: 5px; min-width: 0; white-space: nowrap; }
.qn-tape-px { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text-dim); white-space: nowrap; flex: none; }
.qn-tape-px.none { color: var(--text-faint); font-style: italic; }
/* R136 — how the wallet behind this row is watched. The live one takes the app's own up tone at
   chip weight; a polled one is quiet text, because "checked every minute" is a caveat, not news. */
.qn-tape-watch { font-size: 10.5px; line-height: 1.3; color: var(--text-faint); white-space: nowrap; }
.qn-tape-watch.live { color: var(--sm-up); font-weight: 650; }
/* R136 — when the move was made, at the trailing edge of line one (the owner's own fallback when
   line two could not hold it at 390px). Quiet, because the figure beside it is the loud number. */
.qn-tape-when { font-size: 10.5px; line-height: 1.3; color: var(--text-faint); white-space: nowrap; flex: none; }
/* R136 — the side word inside the verb: green long, red short, on every kind, while the action
   keeps the text colour. */
.qn-tape-side.up { color: var(--sm-up); }
.qn-tape-side.down { color: var(--sm-down); }
/* …and the relative age, after the clock time it explains. Muted, so "when" reads first. */
.qn-tape-age { font-size: 10.5px; line-height: 1.3; color: var(--text-faint); white-space: nowrap; }
.qn-tape-meta { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; white-space: nowrap; flex: none; }
/* ── A LIVE FIGURE AND A SETTLED ONE MUST NOT LOOK ALIKE ───────────────────
   Four separate signals, because a reader glancing at a moving strip catches
   one of them at most and they must not all be the same one:
     SHAPE   live is an OUTLINE, settled is FILLED — legible with no colour at all;
     MOTION  live carries a breathing dot, settled carries nothing that moves;
     WORDS   "so far" against "final", printed on the card, not in a tooltip;
     SPEECH  the accessible name says unrealised or banked in a sentence.
   Colour is the fifth and the least load-bearing: green/red says which way, and
   says nothing about whether the number is finished. */
.qn-tape-fig { flex: none; display: inline-flex; align-items: center; gap: 4px; border-radius: 999px; padding: 2px 8px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.qn-tape-pct { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; }
/* `.qn-tape-state` — the "so far" / "final" word beside the number — went with the words on
   2026-08-26 ("no need to show text like 'so far' 'final'"). The chip's own two looks (outline
   while it moves, filled once it is banked) still say the same thing without a syllable. */
.qn-tape-fig.done { color: var(--bg); }
.qn-tape-fig.done.up { background: var(--sm-up); }
.qn-tape-fig.done.down { background: var(--sm-down); }
.qn-tape-fig.live { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; }
.qn-tape-fig.live.up { color: var(--sm-up); }
.qn-tape-fig.live.down { color: var(--sm-down); }
.qn-tape-pulse { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: none; animation: qn-breathe 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .qn-tape-pulse { animation: none; } }
/* NO NUMBER IS A STATE, NOT A GAP. A price we could not fetch and a close whose
   exit nobody measured both land here: a dash, faint, with the reason in the
   tooltip and the accessible name. Never the last number it had, never a zero. */
.qn-tape-fig.none { color: var(--text-faint); font-size: 13px; font-weight: 600; padding: 2px 4px; }
.qn-tape-foot { font-size: 11px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The only words left under the strip, and both are empty states: nothing has
   been recorded, or nobody is followed yet. The second is tappable because it is
   the only route off an empty strip anywhere on Home. */
.qn-tape-note { font-size: 11.5px; line-height: 1.45; color: var(--text-faint); padding: 6px 2px 0; }
.qn-tape-note.tap { color: var(--text-dim); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.qn-toptraders-loading { color: var(--text-faint); font-size: 13px; padding: 8px 2px; }
.qn-nhead { display: flex; align-items: center; gap: 7px; }
.qn-nsym { font-size: 11px; font-weight: 700; letter-spacing: 0.04em; color: var(--text-dim); }
/* A coin you actually hold wears the dot — money at risk right now reads
   differently from one a Stack is merely watching. */
.qn-nsym.held::before { content: ""; display: inline-block; width: 5px; height: 5px; border-radius: 50%; background: var(--sm-up); margin-right: 6px; vertical-align: middle; }
.qn-ntag { font-size: 10px; font-weight: 620; border-radius: 999px; padding: 2px 7px; text-transform: lowercase; white-space: nowrap; }
.qn-ntag.flag { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 14%, transparent); }
.qn-ntag.catalyst { color: var(--text-dim); background: var(--bg-elev-2); }
.qn-nago { font-size: 10.5px; color: var(--text-faint); margin-left: auto; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Three lines, then stop: a deck of equal cards beats four ragged ones. */
.qn-ntitle { font-size: 13.5px; line-height: 1.38; color: var(--text); flex: 1 1 auto; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.qn-nfoot { display: flex; align-items: center; justify-content: space-between; margin-top: 2px; }
.qn-nx { appearance: none; border: none; background: none; color: var(--text-faint); font-size: 19px; line-height: 1; padding: 4px 6px; margin-left: -6px; border-radius: 50%; cursor: pointer; }
.qn-nx:hover { color: var(--text); background: var(--bg-elev-2); }
.qn-ngo { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 50%; background: var(--bg-elev-2); color: var(--text); text-decoration: none; }
.qn-ngo:hover { background: var(--bg-elev-3); }
.qn-ngo svg { width: 17px; height: 17px; }
.qn-side { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
/* On a phone the positions live in a MODAL (owner 2026-07-30) — the home is the
   performance story, and a table under the chart made them share the first
   screen. The same element is a sticky side column once there's room for one,
   so nothing is hidden on a pane wide enough to just show it. */
/* Inline Home section on narrow screens (owner 2026-08-04) — the wide
   container query below re-places it as the sticky right column. */
.qn-cols > .qn-side { display: flex; }
.qn-side-panels { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.qn-side-panel { display: flex; flex-direction: column; min-width: 0; }
/* With tabs at every width a blank panel reads as a failure, so the empty states
   are always on. */
.qn-side-empty { display: block; font-size: 12.5px; color: var(--text-faint); padding: 10px 2px; }
/* ~40dp pills since 2026-08-04 (owner: "tab pill could be taller"). */
.qn-side-tab.on { background: var(--sm-chip-on); color: var(--text); font-weight: 620; }
/* Either/or — display, not [hidden]: a flex/grid display rule beats [hidden] and
   the panel would stay visible anyway (the lesson .qn-pane already carries). */
.qn-side[data-active="positions"] > .qn-side-panels > .qn-side-panel[data-tab="activity"],
.qn-side[data-active="activity"] > .qn-side-panels > .qn-side-panel[data-tab="positions"] { display: none; }
@container qnwrap (min-width: 860px) {
  /* FIVE rows, and the last one is the flexible one, for a reason worth keeping
     (owner 2026-08-02, staging screenshot): the positions rail spans the whole
     reading column, so when the rail is TALLER than the four sections beside it
     — twelve open positions against a fresh account's short left column — the
     grid hands that surplus to the tracks the rail spans. With four auto rows
     that meant 140px of dead air between the chart and the cards, another 140
     between the cards and the news, and Your portfolios shoved off the bottom
     of the screen. A flexible fifth row is where the surplus goes now: `fr`
     tracks absorb a spanning item's excess, so the content rows stay at their
     content height and the sections stay stacked as written. */
  .qn-cols { display: grid; grid-template-columns: minmax(0, 1fr) 318px; column-gap: 18px; row-gap: 14px; align-items: start; grid-template-rows: auto auto auto auto auto auto 1fr; }
  /* THE STRIP SITS UNDER THE MANUAL/AUTOPILOT SWITCH (owner 2026-08-24, with a
     screenshot). It took row 1 for one day — "the loop sits on top of Total
     balance", 2026-08-23 — and these three rows are the wide half of moving it
     back down. The narrow half is DOM order in quant.ts; neither half does the
     other, because below this container query `.qn-cols` is a flex column where
     DOM order is visual order, and inside it every row is claimed BY NAME so
     DOM order decides nothing at all. Change one without the other and the
     strip sits in two different places depending on how wide the pane is. */
  .qn-cols > .qn-hero { grid-column: 1; grid-row: 1; }
  .qn-cols > .qn-sec-snap { grid-column: 1; grid-row: 2; }
  .qn-cols > .qn-sec-toptraders { grid-column: 1; grid-row: 3; }
  .qn-cols > .qn-sec-news { grid-column: 1; grid-row: 4; }
  /**
   * ═══ YOUR STRATEGIES HAS ITS OWN ROW (R135, coco item 3) ═══
   *
   * `.qn-sec-port` and `.qn-sec-watchlist` both claimed row 5, so on a wide screen the rules
   * section and the watch list were laid into the same cell and painted over each other — coco
   * measured both starting at y=521 at 1280×657, with one rule's row reaching 152 px down over a
   * watch list that ends at 633. It was invisible while the section was empty AND unreachable
   * (R133 put it on the column); the moment it had a row it had somebody else's seat.
   *
   * Six content rows now, in DOM order, and the flexible row moves to seven so the sticky
   * positions rail still has somewhere to put its surplus.
   */
  .qn-cols > .qn-sec-port { grid-column: 1; grid-row: 6; }
  /* THE WATCH LIST TAKES THIS ROW (owner 2026-08-19). It held the idea-review
     panel, which is deleted; before that it held "Your portfolios", which moved
     into the Advanced sheet. Column 1, row 4 — under Top traders, matching the
     DOM order, and explicit rather than auto-placed so the row cannot move when
     something above it is added or taken away.
     Below 860px this rule does not apply and the section sits in flex order,
     directly after Top traders, exactly where a phone already had it. */
  .qn-cols > .qn-sec-watchlist { grid-column: 1; grid-row: 5; }
  /* Spans the reading column and sticks: Your portfolios scrolls for a long
     time, and positions are what you keep glancing at while it does.
     It is CAPPED to the viewport and scrolls inside itself, because a sticky
     column taller than the screen pins its top at 4px and leaves its bottom
     rows permanently unreachable — on staging that was positions 10 through 12,
     visible in no scroll position at all. The 220px is the same allowance
     .qn-wrap already makes for the chrome above and the composer below (the
     composer measures 209px on a desktop pane), so a stuck rail ends just
     above it rather than under it. */
  .qn-cols > .qn-side { display: flex; grid-column: 2; grid-row: 1 / span 7; position: sticky; top: 4px; max-height: calc(100dvh - 220px); overflow-y: auto; overscroll-behavior: contain; }
}
/* The unread badge overhangs the bell button on purpose, but .sm-tabs is a
   scroll container (overflow-x: auto), and a scroll container CLIPS anything
   outside its content box — the badge lost its top-right corner (owner
   2026-07-29). overflow-y can't be `visible` while overflow-x scrolls, so the
   fix is clearance INSIDE the scrollport: padding is part of the padding box and
   renders, so the overhang has room without escaping. */
.qn-tabs { margin: 0 2px; align-items: center; padding: 5px 6px 2px 0; }
/* THE BELL IS THE LAST THING ON THE NAV BAR, AND IT STAYS THERE (owner
   2026-08-16: "anchored to the right").
   `margin-left: auto` puts it at the end of the row. That alone is not enough:
   .sm-tabs scrolls horizontally, so on a narrow screen the end of the CONTENT
   is off to the right and the bell would scroll away with it. `position:
   sticky; right: 0` pins it to the right edge of the SCROLLPORT instead, so it
   holds its corner at every width while the tabs slide underneath it.
   The backdrop is what stops a tab showing through as one passes beneath, and
   the padding-right already on .qn-tabs is the 2026-07-29 clearance that keeps
   the overhanging badge from being clipped by the scroll container. */
/* The nav line: tabs that scroll, and a bell that does not. The bell cannot
   live inside .sm-tabs — that is a scroll container, so `position: sticky;
   right: 0` resolves against its CONTENT box and the bell hung off the right
   edge of a 390px screen until the row was scrolled to its end (measured).
   Here it is a sibling of the scroller, so it holds the corner at every width.
   The float/hide transform that `.qn-tabs` toggles on scroll stays on the tabs
   themselves; the row is a plain flex line and inherits none of it. */
.qn-navrow { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* A selected wallet mark gets a halo — visible after a TAP, not on hover: the
   phone is the first screen and it has no hover. Kept when the strategy-preview
   block around it was deleted (2026-08-17): the mark is drawn by the LIVE asset
   chart in smartmoney.ts, it merely sat inside that block's line range. */
.sm-wmark { transition: opacity 0.15s ease; }
/* ═══ THE ONE-LETTER MARK (R142) ═══
   A single letter IS the mark — the owner's "L" for a long and "S" for a short — so it has to be
   read rather than glanced at. Drawn in the direction's colour on the page background it measures
   4.26:1 (green) and 4.39:1 (red) in the light theme, under the 4.5:1 floor. Filled the other way
   round it clears it: the disc takes the direction's colour and the letter the background's, which
   is 9.66:1 and 5.99:1 in the dark theme. The light theme's tokens are already the darker pair and
   still measure 4.26 / 4.39 against white, so there the disc goes a tenth towards the text colour
   — 4.88:1 and 5.07:1, measured. Four initials keep the hollow puck they have always had. */
.sm-wmark-disc.up { fill: var(--sm-up); }
.sm-wmark-disc.down { fill: var(--sm-down); }
:root[data-theme="light"] .sm-wmark-disc.up { fill: color-mix(in srgb, var(--sm-up) 90%, var(--text)); }
:root[data-theme="light"] .sm-wmark-disc.down { fill: color-mix(in srgb, var(--sm-down) 90%, var(--text)); }
.sm-wmark-ink { fill: var(--bg); }
.sm-wmark.on { filter: drop-shadow(0 0 5px var(--grad-b)); }

/* ══ R-THIST · the wallet detail's past trades ════════════════════════════
   The right card used to render EMPTY when a wallet had no `topEpisodes` — a
   real, styled, blank panel beside a summary claiming six trades. Same dot rail
   as the strategy timeline and the asset feed: one pattern for "a list of things
   that happened", not a third. */
.sm-hist { display: flex; flex-direction: column; }
/* The window/coverage sentence, directly under the title it qualifies. */
.sm-hist-window { padding: 0 2px 6px; }
.sm-hist-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 4px; }
/* THE RAIL: one dot per trade, toned by OUTCOME rather than direction — on a
   timeline the dot IS the trade, so it carries the fact worth seeing down the
   whole column. Direction stays a chip on the row. Sits a little lower than the
   old dot because the date headline now leads the row. */
.sm-hist-dot { flex: none; width: 8px; height: 8px; border-radius: 999px; margin-top: 5px; position: relative; }
.sm-hist-dot.win { background: var(--sm-up); }
.sm-hist-dot.loss { background: var(--sm-down); }
.sm-hist-dot.none { background: var(--text-faint); }
.sm-hist-row:not(:last-child) .sm-hist-dot::after {
  content: ""; position: absolute; left: 50%; top: 11px; width: 1.5px; height: 30px;
  transform: translateX(-50%); background: var(--border);
}
.sm-hist-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.sm-hist-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; min-width: 0; }
.sm-hist-coin { font-size: 13px; font-weight: 700; color: var(--text); }
.sm-hist-side { font-size: 9.5px; font-weight: 650; padding: 1px 7px; border-radius: 999px; }
.sm-hist-side.long { background: color-mix(in srgb, var(--sm-up) 16%, transparent); color: var(--sm-up); }
.sm-hist-side.short { background: color-mix(in srgb, var(--sm-down) 16%, transparent); color: var(--sm-down); }
.sm-hist-px { font-size: 11.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.sm-hist-sub { font-size: 10.5px; color: var(--text-faint); }
/* The date headline that opens each row — small, coloured, and the thing the
   eye lands on when scanning the rail. */
/* NEUTRAL, deliberately (owner 2026-08-17). It was painted with the up token,
   so every row's date read as a win — including the losing ones. The outcome
   already has a carrier: the rail dot beside it. A date is a fact about when,
   not a verdict about how it went. */
.sm-hist-date { font-size: 10.5px; font-weight: 700; letter-spacing: 0.02em; color: var(--text-dim); }
.sm-hist-ico { width: 16px; height: 16px; flex: none; }
/* P&L and the chevron travel together on the right, so the number never drifts
   away from the affordance that says the row opens. */
.sm-hist-right { flex: none; display: flex; align-items: center; gap: 4px; padding-top: 12px; }
.sm-hist-chev { font-size: 18px; color: var(--text-faint); transition: transform .16s ease; }
@media (prefers-reduced-motion: reduce) { .sm-hist-chev { transition: none; } }
.sm-hist-row[aria-expanded="true"] .sm-hist-chev { transform: rotate(180deg); }
.sm-hist-row:disabled .sm-hist-chev { visibility: hidden; }
/* The reveal row. Not a link and not a primary action — a quiet way down the
   list, sized like the rows it opens. */
.sm-hist-more {
  appearance: none; border: none; background: none; cursor: pointer; font: inherit;
  width: 100%; text-align: left; padding: 10px 4px 4px 22px;
  font-size: 12.5px; font-weight: 620; color: var(--sm-up);
}
.sm-hist-more:hover { text-decoration: underline; }
.sm-hist-more:focus-visible { outline: 2px solid var(--accent, #4c8dff); outline-offset: -2px; border-radius: 8px; }
.sm-hist-pnl { flex: none; font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; padding-top: 1px; }
.sm-hist-pnl.up { color: var(--sm-up); }
.sm-hist-pnl.down { color: var(--sm-down); }
.sm-hist-pnl.none { color: var(--text-faint); }

/* ══ R-PULSE · the Pulse list + the asset screen's Holders/Feed/About ═════
   Built to the owner's reference screenshots (transcribed by the PM) in the
   app's dark tokens and the M3 language of R9: tonal surfaces, sentence-case,
   no tracked caps, theme tokens only. */
.qn-pulse-chips { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 2px 12px; }
.qn-pulse-chip {
  appearance: none; border: none; cursor: pointer; font: inherit;
  padding: 7px 14px; border-radius: 999px; font-size: 12.5px; font-weight: 620;
  background: var(--bg-elev-2); color: var(--text-dim);
}
.qn-pulse-chip.on { background: var(--text); color: var(--bg); }
.qn-pulse-note { font-size: 11px; color: var(--text-faint); padding: 0 2px 8px; }
.qn-pulse-note.stale { color: var(--warn); }
.qn-pulse-list { display: flex; flex-direction: column; }
.qn-pulse-row {
  appearance: none; border: none; cursor: pointer; font: inherit; text-align: left; width: 100%;
  display: flex; align-items: center; gap: 12px; padding: 11px 10px;
  border-radius: 16px; background: none; color: var(--text);
}
.qn-pulse-row:nth-child(odd) { background: var(--bg-elev-2); }
/* The icon: a ticker disc with the venue badge overlaid at its corner, per the
   reference. The app has no asset-art pipeline, so the disc IS the icon. */
.qn-pulse-ico { flex: none; position: relative; width: 38px; height: 38px; }
.qn-pulse-icot {
  width: 100%; height: 100%; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; letter-spacing: -0.01em;
  background: var(--bg-elev-3); color: var(--text-dim);
}
.qn-pulse-badge {
  position: absolute; right: -2px; bottom: -2px;
  width: 15px; height: 15px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  font-size: 8px; font-weight: 700;
  border: 2px solid var(--bg); background: var(--bg-elev-3); color: var(--text-faint);
}
.qn-pulse-badge.okx { background: var(--grad-b); color: var(--bg); }
.qn-pulse-badge.hyperliquid { background: var(--sm-up); color: var(--bg); }
.qn-pulse-mid { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.qn-pulse-name { display: flex; align-items: center; gap: 6px; min-width: 0; }
.qn-pulse-tick { font-size: 14px; font-weight: 700; color: var(--text); }
.qn-pulse-lev {
  flex: none; padding: 1px 6px; border-radius: 999px;
  font-size: 9.5px; font-weight: 650; background: var(--bg-elev-3); color: var(--text-faint);
}
.qn-pulse-vol { font-size: 11.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.qn-pulse-r { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: none; }
.qn-pulse-px { font-size: 14px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.qn-pulse-ch { font-size: 11.5px; font-weight: 620; font-variant-numeric: tabular-nums; }
.qn-pulse-ch.up { color: var(--sm-up); }
.qn-pulse-ch.down { color: var(--sm-down); }
.qn-pulse-ch.none { color: var(--text-faint); }

/* ── Holders ─────────────────────────────────────────────────────────────── */
.sm-hold-oi { font-size: 12.5px; font-weight: 650; color: var(--text-dim); padding: 10px 2px 4px; }
.sm-hold-head { display: flex; align-items: center; gap: 3px; justify-content: flex-end; padding: 6px 10px 4px; }
.sm-hold-headl { font-size: 10.5px; color: var(--text-faint); }
.sm-hold-tip {
  appearance: none; border: none; background: none; cursor: pointer; padding: 0;
  width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; color: var(--text-faint);
}
.sm-hold-list { display: flex; flex-direction: column; }
.sm-hold-row { display: flex; align-items: center; gap: 10px; padding: 10px; border-radius: 16px; min-width: 0; }
.sm-hold-row:nth-child(odd) { background: var(--bg-elev-2); }
.sm-hold-l { display: flex; align-items: center; gap: 9px; min-width: 0; flex: 1 1 auto; }
.sm-hold-pfp {
  flex: none; width: 30px; height: 30px; border-radius: 999px; object-fit: cover;
  background: var(--bg-elev-3);
}
.sm-hold-pfp.is-init {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: var(--text-dim);
}
.sm-hold-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sm-hold-name { font-size: 13.5px; font-weight: 650; color: var(--text); }
.sm-hold-chip { font-size: 9.5px; font-weight: 650; padding: 1px 7px; border-radius: 999px; }
.sm-hold-chip.long { background: color-mix(in srgb, var(--sm-up) 16%, transparent); color: var(--sm-up); }
.sm-hold-chip.short { background: color-mix(in srgb, var(--sm-down) 16%, transparent); color: var(--sm-down); }
.sm-hold-sub { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.sm-hold-r { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: none; }
.sm-hold-size { font-size: 13.5px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.sm-hold-pnl { font-size: 11.5px; font-weight: 650; font-variant-numeric: tabular-nums; }
.sm-hold-pnl.up { color: var(--sm-up); }
.sm-hold-pnl.down { color: var(--sm-down); }
.sm-hold-pnl.none { color: var(--text-faint); }
@container qnwrap (max-width: 520px) {
  /* The name and its chip stack above the entry line rather than squeezing the
     six-figure size column off the row. */
  .sm-hold-who { flex: 1 1 auto; }
  .sm-hold-name { font-size: 13px; }
}

/* ── Feed: the dot rail from R9, reused rather than re-invented ──────────── */
.sm-feed { display: flex; flex-direction: column; }
.sm-feed-row { display: flex; gap: 10px; padding: 9px 8px; }
.sm-feed-dot { flex: none; width: 9px; height: 9px; border-radius: 999px; margin-top: 6px; position: relative; }
.sm-feed-dot.long { background: var(--sm-up); }
.sm-feed-dot.short { background: var(--sm-down); }
.sm-feed-row:not(:last-child) .sm-feed-dot::after {
  content: ""; position: absolute; left: 50%; top: 12px; width: 1.5px; height: 34px;
  transform: translateX(-50%); background: var(--border);
}
.sm-feed-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.sm-feed-when { font-size: 11px; font-weight: 700; color: var(--grad-b); }
.sm-feed-line { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.sm-feed-line .sm-hold-pfp { width: 22px; height: 22px; font-size: 10px; }
.sm-feed-name { font-size: 12.5px; font-weight: 650; color: var(--text); }
.sm-feed-what { font-size: 12.5px; color: var(--text-dim); }
.sm-feed-sub { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* ── About ───────────────────────────────────────────────────────────────── */
.sm-about-intro { font-size: 13px; line-height: 1.55; color: var(--text-dim); padding: 12px 2px; }
.sm-about-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 4px 2px 10px; }
.sm-about-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sm-about-l { font-size: 10.5px; color: var(--text-faint); }
.sm-about-v { font-size: 14px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
@container qnwrap (min-width: 860px) {
  .sm-about-grid { grid-template-columns: repeat(4, 1fr); }
}

/* ══ R10 · the census — everyone we verified, under the top-ten board ═════
   Same M3 language as R9: tonal surface, sentence-case header, no tracked caps,
   theme tokens only. A roster is a LIST, so the rows are quiet and the state is
   the only thing that carries colour. */
.sm-census { margin-top: 18px; }
.sm-census-h { font-size: 16px; font-weight: 700; color: var(--text); padding: 4px 2px 10px; }
.sm-census-body { display: flex; flex-direction: column; }
.sm-census-row {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px;
  border-radius: 14px; min-width: 0;
}
.sm-census-row:nth-child(odd) { background: var(--bg-elev-2); }
.sm-census-who { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 auto; }
.sm-census-av {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 999px;
  font-size: 9px; font-weight: 700;
  background: var(--bg-elev-3); color: var(--text-dim);
}
.sm-census-addr { font-size: 12px; color: var(--text); font-variant-numeric: tabular-nums; }
.sm-census-venue {
  flex: none; padding: 1px 7px; border-radius: 999px;
  font-size: 9.5px; font-weight: 650; background: var(--bg-elev-3); color: var(--text-faint);
}
.sm-census-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; min-width: 0; }
.sm-census-score { font-size: 12px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
/* The state is the one thing on the row that carries colour: on the board is
   the good outcome, qualified is neutral, held out is a fact and not a failure
   — so it is dim rather than red. */
.sm-census-state { font-size: 10.5px; font-weight: 620; padding: 2px 8px; border-radius: 999px; }
.sm-census-state.boarded { background: color-mix(in srgb, var(--sm-up) 16%, transparent); color: var(--sm-up); }
.sm-census-state.qualified { background: var(--bg-elev-3); color: var(--text-dim); }
.sm-census-state.held { background: none; color: var(--text-faint); font-weight: 550; padding-left: 0; }
.sm-census-ago { font-size: 10.5px; color: var(--text-faint); }
.sm-census-more {
  appearance: none; border: none; cursor: pointer; font: inherit; width: 100%;
  margin-top: 10px; padding: 11px 14px; border-radius: 999px;
  font-size: 13px; font-weight: 650; background: var(--bg-elev-2); color: var(--text);
}
.sm-census-more[hidden] { display: none; }
@container qnwrap (max-width: 520px) {
  /* On a phone the roster stacks: the address line, then state and age under
     it, so a long held-out phrase never squeezes the address to nothing. */
  .sm-census-row { flex-wrap: wrap; }
  .sm-census-right { width: 100%; justify-content: flex-start; padding-left: 32px; }
}


.qn-navrow > .qn-tabs { flex: 1 1 auto; min-width: 0; }
.qn-navbell { flex: none; margin-right: 2px; }
/* Quant's tab bar rides the scroll on its own (owner 2026-08-04, the
   composer stays put): sticky at the scrollport top; once the page is
   scrolled it FLOATS — solid backdrop, shadow, rounded base. Swiping up
   (scrolling down through content) slides it out via .hide; swiping back
   down slides it in. At the very top it sits in flow, never hidden. */
.qn-tabs {
  position: sticky;
  top: 0;
  z-index: 6;
  transition: transform 0.24s ease, box-shadow 0.24s ease, background 0.24s ease;
}
.qn-tabs.float {
  background: var(--bg-elev);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  border-radius: 0 0 14px 14px;
  padding-bottom: 6px;
}
.qn-tabs.hide { transform: translateY(-130%); }
/* ── The bell: liveness + updates in one tap (owner 2026-07-27) ─────────── */
.qn-bellwrap { margin-left: auto; position: relative; flex: none; }
.qn-bell { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 999px; border: none; background: var(--bg-elev-1); color: var(--text-dim); cursor: pointer; transition: background .14s; }
.qn-bell:hover { background: var(--bg-elev-2); }
.qn-bellic { width: 18px; height: 18px; }
.qn-bell-badge { position: absolute; top: -3px; right: -4px; background: var(--sm-up); color: var(--sm-on-up); border-radius: 999px; font-size: 9.5px; font-weight: 700; padding: 1.5px 5px; pointer-events: none; }
.qn-bellbub { position: absolute; right: 2px; top: 53px; z-index: 40; width: min(360px, calc(100vw - 28px)); background: var(--bg-elev); border: 1px solid var(--border); border-radius: 16px; padding: 12px 14px; box-shadow: 0 14px 40px rgba(0,0,0,0.35); }
.qn-bellbub-h { font-size: 12px; font-weight: 620; color: var(--text-dim); padding-bottom: 8px; border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums; }
.qn-bellbub-h.warn { color: var(--warn); }
.qn-bellbub-empty { font-size: 12px; color: var(--text-faint); padding: 12px 0 4px; }
.qn-bellbub-sum { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; padding: 4px 0 2px; border-bottom: 1px solid color-mix(in srgb, var(--text) 7%, transparent); }
.qn-bellbub-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.qn-bellbub-row:last-child { border-bottom: none; padding-bottom: 2px; }
.qn-bellbub-t { font-size: 12.5px; font-weight: 560; min-width: 0; }
.qn-bellbub-t.up { color: var(--sm-up); }
.qn-bellbub-t.down { color: var(--sm-down); }
.qn-bellbub-ago { flex: none; font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
/* Strategies tab — one M3 card per coin, its surviving strategies stacked
   inside. The .qn-pick card family it was scaled against is gone (Picked by AI,
   then the Top-strategies shelf that outlived it); the surface, radius and
   typographic scale here are the surviving definition. The divider between rows
   is the only new
   structure (it separates strategies WITHIN a coin, never between coins). */
/* Sort control — secondary to the tab bar, same M3 pill grammar, CENTRED
   (owner 2026-07-28). Reorders the coins AND the tiles inside each card. */
/* Breathing room above (owner 2026-08-02: "leave some margin before the
   tabs"), and the SAME pill language as the screen's main tabs — outlined
   ghosts beside filled tabs read as a second, unrelated control. */
/* The "Your rules" pill selected — the same violet its tiles wear. */
/* The mascot's chat line — shared by the picker receipt and anywhere else a
   sentence is Genie speaking. Clipped top-left corner points at the sender. */

/* Strategy tiles — three things to CHOOSE BETWEEN, not three rows to read. The
   strip runs to the card's edge so a partly-visible tile reads as "more this
   way" instead of a clipping bug, and snaps so it lands cleanly. */
/* 222px is load-bearing: the four-fact stat line is ~170px and must never wrap
   (owner 2026-07-28 picked the wide tile over the wrapped one). */
.qn-stile.t-gain { background: linear-gradient(170deg, color-mix(in srgb, var(--sm-up) 10%, transparent), transparent 60%), var(--bg-elev-2); border-color: color-mix(in srgb, var(--sm-up) 26%, transparent); }
.qn-stile.t-cons { background: linear-gradient(170deg, color-mix(in srgb, var(--info, #7896ff) 12%, transparent), transparent 60%), var(--bg-elev-2); border-color: color-mix(in srgb, var(--info, #7896ff) 28%, transparent); }
.qn-stile.t-act { background: linear-gradient(170deg, color-mix(in srgb, var(--warn) 10%, transparent), transparent 60%), var(--bg-elev-2); border-color: color-mix(in srgb, var(--warn) 26%, transparent); }
/* The owner's own rule on the board — violet, so "mine" never reads as one of
   the sweep's own axes. */
.qn-stile-eb.gain { color: color-mix(in srgb, var(--sm-up) 62%, var(--text)); }
.qn-stile-eb.cons { color: color-mix(in srgb, var(--info, #7896ff) 68%, var(--text)); }
.qn-stile-eb.act { color: color-mix(in srgb, var(--warn) 72%, var(--text)); }
.qn-stile-eb.mine { color: color-mix(in srgb, #b58cff 70%, var(--text)); }
.qn-stile-dir { font-weight: 800; }
.qn-stile-dir.long { color: var(--sm-up); }
.qn-stile-dir.short { color: var(--sm-down); }
/* The grid glyph is NEUTRAL — red/green claims a direction the ladder
   doesn't have (owner 2026-08-03). */
.qn-stile-dir.grid { color: var(--text-faint); }
.qn-stile-r.up { color: var(--sm-up); }
.qn-stile-r.down { color: var(--sm-down); }
/* The line must never wrap. Measured at 430px: the widest real line needs
   ~161px of the tile's 196px, and the widest line that could ever exist
   ("90 days | 128 trades | 100% wins | 10×") needs ~178px — so it fits with
   room to spare at 10px. Headroom is bought in separator padding, never by
   widening the tile: at 222px only two tiles are visible already, so every
   extra pixel of width costs the peek. */
.qn-sdot.on { background: color-mix(in srgb, var(--text) 62%, transparent); }

/* ── Stacks: Featured + Top performers (docs/QUANT_BASKETS.md §3-5) ─────────
   Material 3 minimal throughout, as the owner asked for on this screen: tonal
   surfaces instead of outlines, no component borders anywhere, pill controls,
   and hairline dividers only where a list genuinely needs them. */
.qn-sec { display: flex; flex-direction: column; }
/* ── THE PHONE'S "POSITIONS | IDEAS" SWITCHER IS GONE (2026-08-19) ────────────
   It switched between the positions rail and the idea-review panel. The panel
   was deleted on the owner's ruling ("delete this section"), so the second tab
   led to an empty screen — a control with one destination is not a control.
   `.qn-mtabs`, `.qn-mtab-badge` and the two `[data-mtab]` rules went with it. */

/* ── THE SCAN'S VERDICT CHIP — all that is left of the idea rows ─────────────
   The idea-review panel that owned `.qn-rv-item`, `.qn-rv-row`, `.qn-rv-chev`,
   `.qn-rv-why`, `.qn-rv-verdict` and `.qn-review-title` was deleted on
   2026-08-19; those rules went with it. The chip and its glyph stayed, because
   the WATCH CARD wears them — one asset, one mark for what the scan concluded.
   It has TWO tones now, not three: a card still being read shows a pulse and no
   chip at all, so there is nothing left for `working` to style. */
.qn-rv-chip {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 650; line-height: 1;
  /* 5px all round, not 4px 8px: the pill holds one glyph now, and the side
     padding a word needed left it lopsided. A live row also carries the pulse
     dots, which the 4px gap spaces. */
  padding: 5px; border-radius: 999px; white-space: nowrap;
}
/* ═══ THREE WEIGHTS, AND ONLY ONE OF THEM IS LOUD (owner 2026-08-19) ═════════
   Nine assets in a column wore nine error-red crosses, which made the routine
   answer — *"I looked and there is no trade here right now"* — read as nine
   faults, and buried the one green check on the day there was one. M3 keeps the
   error container for errors; routine lives at the low-emphasis end.

   `good` is the only saturated mark on this screen, deliberately. `.bad` is
   gone: nothing wears the error container here any more, because nothing on
   this row is an error. */
.qn-rv-chip.good { background: color-mix(in srgb, var(--sm-up) 16%, transparent); color: var(--sm-up); }
/* NO TRADE HERE — the normal answer. M3's low-emphasis outline: present and
   legible, and quiet enough that a column of them reads as a list rather than
   an alarm. */
.qn-rv-chip.quiet { background: none; border: 1px solid var(--border); color: var(--text-faint); }
/* NO ANSWER AT ALL — a different event from a rejection, so a different mark
   AND a different surface. Muted fill rather than an outline, so the three are
   told apart at a glance and not only by their glyphs. */
.qn-rv-chip.held { background: var(--bg-elev-2); color: var(--text-dim); }
/* A GLYPH, NOT A WORD (owner 2026-08-17: "use icon, not text"). The words are
   the chip's tooltip and its accessible name; nothing of them is visible. */
.qn-rv-vico { font-size: 16px; line-height: 1; }
.qn-sechead { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 4px 2px 8px; }
.qn-sech-right { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* The activity bell: icon button with the needs-you bubble riding it. */
.qn-bellbtn { position: relative; flex: none; display: grid; place-items: center; width: 30px; height: 30px; border-radius: 999px; background: var(--bg-elev-1); border: 1px solid var(--border); color: var(--text-dim); cursor: pointer; padding: 0; }
.qn-bellbtn .material-symbols-rounded { font-size: 17px; }
.qn-bellbtn .qn-tab-need { position: absolute; top: -4px; right: -4px; }
/* An overline sits closer to what it labels than a title does — the same 2px
   the hero's eyebrow uses above the balance. */
.qn-sechead.eb { padding: 2px 2px 10px; }
.qn-sech-t { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }

/* Featured card — identity ONLY (owner 2026-07-31). No number, no curve: a
   showcase that leads with a percent is an ad for whatever is green today. */
.qn-bcard.on { background: linear-gradient(170deg, color-mix(in srgb, var(--sm-up) 12%, transparent), transparent 62%), var(--bg-elev-2); }
/* The backdrop: a wash of the Stack's own palette plus an oversized glyph
   watermark, both faded well under the text they sit behind. */
.qn-bpfp.sm { width: 34px; height: 34px; font-size: 17px; }
.qn-bpfp.big { width: 48px; height: 48px; font-size: 24px; }

/* Top performers — a LIST, taller tonal rows, and the only place a Stack's
   number appears on the home. Losers stay in it, at the bottom, in red. */
.qn-tprow.on { background: linear-gradient(170deg, color-mix(in srgb, var(--sm-up) 9%, transparent), transparent 62%), var(--bg-elev-1); }
.qn-bspark-l.up { stroke: var(--sm-up); } .qn-bspark-l.down { stroke: var(--sm-down); }
.qn-tpret.up { color: var(--sm-up); } .qn-tpret.down { color: var(--sm-down); }

/* The positions button — a tonal circle at the chart's top-right, and now the
   hero's only control. The search that shared this row moved to the Search tab
   (owner 2026-08-01), taking its expand/collapse choreography with it: the
   wrapper no longer has to span the hero to animate a width, but it costs
   nothing to leave it spanning and it keeps the button's offset in one place. */
/* A right-anchored COLUMN since 2026-08-04: positions circle on top, the
   Auto pill under it. */
.qn-heroacts { position: absolute; top: 0; right: 2px; z-index: 3; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; pointer-events: none; }
.qn-heroacts > * { pointer-events: auto; }
/* Its own name: .qn-autobtn was already the legacy auto-pilot bar's class
   further down, whose bordered 13px rule overrode this pill's geometry —
   the twins looked different for exactly that reason (owner asked twice). */
.qn-heroauto { letter-spacing: 0.01em; }
.qn-heroauto.on { background: var(--sm-up); color: var(--sm-on-up); }
.qn-heroauto.on:hover { background: var(--sm-up); }
/* The positions pill and the Auto pill are TWINS (owner 2026-08-04: "make
   these 2 icons same size and style") — one explicit geometry, centered
   content, identical width; the lit state may only change color. */
.qn-posbtn { display: flex; align-items: center; justify-content: center; gap: 5px; height: 32px; min-width: 68px; padding: 0 12px; border: none; border-radius: 999px; background: var(--bg-elev-1); color: var(--text-dim); font-size: 12px; font-weight: 680; cursor: pointer; transition: background .14s; }
.qn-posbtn:hover { background: var(--bg-elev-2); }
.qn-posbtn svg { width: 16px; height: 16px; }
.qn-posbtn-n { font-size: 12px; font-weight: 680; font-variant-numeric: tabular-nums; }
/* Wide: the positions column is already on screen, so its button is redundant.
   Declared AFTER the base rule on purpose: same specificity, source order wins. */
/* Wide panes show positions inline in the side column, so the circle
   folds away — but the Auto pill is the ONLY autopilot control and stays
   (owner 2026-08-04: "where's auto button in desktop"). */
@container qnwrap (min-width: 860px) { .qn-posbtn:not(.qn-heroauto) { display: none; } }

/* ── The detail's three tabs (owner 2026-08-01) ──────────────────────────── */
/* Same pill row as the positions panel and Your portfolios — a third tab style
   on the same screen would be three ways to say "pick one". */
/* Overview stats wrap to two rows at four items rather than shrinking to fit. */
.qn-bstat-v.up { color: var(--sm-up); } .qn-bstat-v.down { color: var(--sm-down); }
.qn-bstat-v.none { color: var(--text-faint); }
/* BREAKDOWN — one hue, ramped by weight. Distinct colours would imply the picks
   belong to categories; they don't, they differ only in size. */
/* The %/$ switch and the two cuts, sized like controls rather than headings. */
.qn-bd-unit { display: flex; gap: 4px; background: var(--bg-elev-1); border-radius: 999px; padding: 3px; }
.qn-bd-ub { appearance: none; border: none; background: none; color: var(--text-faint); font: inherit; font-size: 12px; font-weight: 680; min-width: 28px; padding: 4px 8px; border-radius: 999px; cursor: pointer; }
.qn-bd-ub.on { background: var(--sm-chip-on); color: var(--text); }

/* ── The copy flow: amount → review → slide (owner 2026-08-01) ───────────── */
/* Pushed screens inside the same sheet, so a back arrow rather than a second
   scrim: stacking modals over modals is how people lose the thread of what
   they were doing. */
.qn-cf-head { display: flex; align-items: center; gap: 8px; padding: 6px 0 14px; }
.qn-cf-back { appearance: none; border: none; background: none; color: var(--text-dim); width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; cursor: pointer; flex: none; }
.qn-cf-back:hover { background: var(--bg-elev-2); color: var(--text); }
.qn-cf-back svg { width: 22px; height: 22px; }
.qn-cf-title { font-size: 15px; font-weight: 680; letter-spacing: -0.01em; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qn-cf-amt { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 8px 0 4px; }
/* FULL SCREEN once you're past the detail (owner 2026-08-01: "the screen
   should be full height / screen"). The amount stops being a sheet laid over a
   list and becomes the page you are on — figure in the middle of it, bar at the
   bottom. A half-height sheet asking for a number leaves the thing you were
   reading visible above it, which invites you to keep reading instead of
   answering. The back arrow is the way out, so nothing is lost by filling. */
/* The copy steps fill the screen they are on, and the figure sits in the
   MIDDLE of it (owner 2026-08-01: "the number input should be around center
   vertically"). The body has to grow for that to mean anything — without it the
   whole screen collects at the top and the CTA's `margin-top: auto` has no free
   space to push against, which is only visible on a short step like this one. */
.qn-screen .qn-cf-amt { flex: 1 1 auto; justify-content: center; }
.qn-cf-sub { font-size: 13px; color: var(--text-dim); }
.qn-cf-sub2 { font-size: 11.5px; color: var(--text-faint); }
/* The figure IS the screen. An input, not a display + keypad: the OS keyboard
   is better than any numpad we would draw, and on desktop there is a real one. */
.qn-cf-big { position: relative; display: flex; align-items: baseline; justify-content: center; gap: 2px; padding: 26px 0 8px; max-width: 100%; }
.qn-cf-cur { font-size: 34px; font-weight: 700; letter-spacing: -0.03em; color: var(--text-dim); }
/* Width is set from the SIZER, per keystroke: a fixed box left-aligns "$0" a
   third of the way across the screen, because the row centres the box and the
   text sits at one end of it (owner 2026-08-01: "numbers to enter should be
   centered"). Measuring beats arithmetic here — `ch` is the advance of "0",
   which the letter-spacing and the "." both break. */
.qn-cf-in { appearance: none; background: none; border: none; outline: none; font: inherit; font-size: 46px; font-weight: 700; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; color: var(--text); width: 1ch; max-width: 100%; text-align: left; padding: 0; }
.qn-cf-sizer { position: absolute; left: 0; top: 0; visibility: hidden; pointer-events: none; white-space: pre; font-size: 46px; font-weight: 700; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; }
.qn-cf-in::placeholder { color: var(--text-faint); }
/* What the Stack will really deploy, under the number that decides it. */
.qn-cf-err { font-size: 11.5px; color: var(--sm-down); min-height: 15px; text-align: center; }
/* The shortcuts are a separate decision from the number, and sat close
   enough to read as its underline. */
.qn-cf-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; padding-top: 28px; }
.qn-cf-chip { appearance: none; border: none; border-radius: 999px; background: var(--bg-elev-2); color: var(--text); font: inherit; font-size: 12.5px; font-weight: 620; padding: 9px 16px; cursor: pointer; transition: background .14s; }
.qn-cf-chip:hover { background: var(--bg-elev-3); }
.qn-cf-note.left { text-align: left; padding: 4px 0 0; }
.qn-cf-note.bad { color: var(--sm-down); }
.qn-cf-note { font-size: 10.5px; line-height: 1.5; color: var(--text-faint); text-align: center; padding: 2px 6px 0; }
/* The review card: label/value rows, one prose paragraph, no invented holdings. */
.qn-cf-card { display: flex; flex-direction: column; gap: 9px; background: var(--bg-elev-1); border-radius: 16px; padding: 14px; }
/* The exits control sits under the figure it belongs beside. A FIXED width in
   both states — the card used to shrink-wrap around the two pills and then
   jump to full width when "Set my own" added its rows (owner 2026-08-02:
   "keep the width of the trade setting area the same"). AFTER .qn-cf-card:
   same specificity, so order decides the padding/gap. */
.qn-cf-exits { margin: 18px auto 0; width: 100%; max-width: 420px; text-align: left; padding: 16px 18px; gap: 12px; }
.qn-cf-exits .sm-exrow { min-height: 44px; }
/* THE STEPPERS LINE UP DOWN THE CARD (owner 2026-08-08: "the leverage should
   have the same plus or minus alignment with the rest"). Leverage holds "3" and
   the price rows hold "0.349066", so a value box sized to its content put the
   − and + of the first row inches from the ones below it. One width for every
   value in this card; the label takes the slack. */
.qn-cf-exits .sm-exval, .qn-cf-exits .sm-exval.wide { min-width: 104px; width: 104px; font-size: 14px; }
.qn-cf-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.qn-cf-k { font-size: 12.5px; color: var(--text-dim); }
/* The ✨ that re-read a card's levels from the strategy board lived here (owner 2026-08-09). The
   board is retired (R127) and there is nothing to re-read, so the button and its breathing glow
   went with it. */
/* ROOM FOR EACH NUMBER (owner 2026-08-09: "this looks too packed, need more
   space for each param"). Seven price rows at the shared 9px gap read as one
   block of digits. Scoped to the modifier so the Stack copy review that shares
   this markup is untouched. */
.qn-cf-card.roomy { gap: 0; padding: 6px 14px; }
.qn-cf-card.roomy > .qn-cf-row { padding: 9px 0; border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
.qn-cf-card.roomy > .qn-cf-row:last-child { border-bottom: none; }
.qn-cf-card.roomy .qn-cf-k { font-size: 13px; }
.qn-cf-card.roomy .qn-cf-v { font-size: 14.5px; }
/* The setup picture under the review rows. Same chart the strategy card draws. */
.sm-copyrev-nt { margin-top: 10px; background: var(--bg-elev-1); border-radius: 16px; padding: 10px 12px 6px; }
.sm-copyrev-nt .sm-nt-svg { width: 100%; height: auto; display: block; }
/* A level the limits moved. Not an error — the trade is still placeable — so it
   reads as a correction rather than a failure. */
.qn-cf-note.warn { color: var(--sm-down); }
/* WHILE THE NUMBERS ARE BEING REPLACED, they read as not-yet-current. Without
   this the card looks live and settled during the wait, so the values appear to
   change for no reason when they land. The rows stay legible on purpose — this
   is a refresh, not a load, and hiding them would be a bigger lie than dimming
   them. Steppers go inert because pressing one mid-flight edits a number that is
   about to be overwritten. */
.qn-cf-exits.pulling .sm-exval, .qn-cf-exits.pulling .sm-exlab { opacity: 0.45; transition: opacity 0.18s ease; }
.qn-cf-exits.pulling .qn-wstep { opacity: 0.3; pointer-events: none; }
.qn-cf-exits .sm-exval { transition: opacity 0.18s ease; }
.qn-cf-v { font-size: 13px; font-weight: 660; font-variant-numeric: tabular-nums; text-align: right; }
.qn-cf-v.money { color: var(--sm-up); }
.qn-cf-v.risk { color: var(--sm-down); }

/* ── The Stack detail sheet (§5) ─────────────────────────────────────────── */
/* One height for three tabs: the tallest is measured on open and held here, so
   switching tabs never resizes the modal under your thumb. Still a flex column
   — its children used to be the sheet's own. */
/* M3 assist chips. They rendered as three bare lines of body text: the class
   had no rule at all after the "waiting" block was deleted (owner 2026-08-01:
   "the text here doesn't fit the rest of material 3 design principles"). */
/* THE BIGGEST THING ON THE SCREEN (owner 2026-08-01: "show performance chart
   (big real estate)"). It can be, now that there is a real series behind it —
   the durable track record — instead of an empty array. */
/* The headline, ABOVE the chart: the number is the answer, the chart is the
   working. Left-aligned under the name like the reference, not centred. */
/* The one-line description, between the name and the number. */
.qn-bsh-ret.up { color: var(--sm-up); } .qn-bsh-ret.down { color: var(--sm-down); }
/* The caveat under a replay is not a footnote — it is the difference between
   the chart above it being evidence and being a drawing. */
/* Four stats since the Overview gained an all-time return (owner 2026-08-01):
   auto-fit wraps to 2×2 on a phone rather than squeezing four into one row. */
/* Two-line M3 list items, no dividers — whitespace does the separation. */
/* A DAY AT A TIME: a rail down the left, a dot per day, the sentence, then the
   trades. The rail is what makes it read as a history rather than a table. */
.qn-day-dot.up { background: var(--sm-up); } .qn-day-dot.down { background: var(--sm-down); }
.qn-day-d.up { color: var(--sm-up); } .qn-day-d.down { color: var(--sm-down); }
.qn-day-r.up { color: var(--sm-up); } .qn-day-r.down { color: var(--sm-down); }
.qn-day-dot.flat { background: var(--text-faint); }
/* The coins, overlapped like a stack of chips — the reference's shape, and it
   keeps a six-coin day to one line. */
.qn-day-coins > .qn-cdisc { margin-right: -8px; box-shadow: 0 0 0 2px var(--bg-elev); }
.qn-day-coins > .qn-cdisc:last-of-type { margin-right: 0; }
.qn-btrades { display: flex; flex-direction: column; gap: 2px; }
.qn-btrade { display: flex; align-items: center; gap: 11px; padding: 7px 0; }
.qn-cdisc { flex: none; width: 28px; height: 28px; border-radius: 50%; background: var(--bg-elev-2); color: var(--text-dim); display: grid; place-items: center; font-size: 9.5px; font-weight: 700; letter-spacing: 0.02em; }
.qn-btrade-z { font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.qn-btrade-z.up { color: var(--sm-up); } .qn-btrade-z.down { color: var(--sm-down); }
.qn-balloc { display: flex; flex-direction: column; gap: 9px; }
.qn-ballocr { display: flex; align-items: center; gap: 10px; }
.qn-balloc-s { flex: none; width: 52px; font-size: 12px; font-weight: 620; }
.qn-balloc-bar { flex: 1 1 auto; height: 7px; border-radius: 999px; background: var(--bg-elev-2); overflow: hidden; }
.qn-balloc-fill { height: 100%; border-radius: 999px; background: color-mix(in srgb, var(--sm-up) 66%, transparent); }
.qn-balloc-bar.cash .qn-balloc-fill { background: color-mix(in srgb, var(--text) 26%, transparent); }
.qn-balloc-p { flex: none; width: 38px; text-align: right; font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text-dim); }
/* THE STACK AS A SCREEN (owner 2026-08-01: "should be a screen can go back
   with, not a modal"). It takes the Quant pane's place rather than covering it,
   so the composer stays where it is and the session header carries the name and
   the arrow back. min-height comes from JS — the scroller's height, so the
   anchored bar has something to sit against on a short Stack. */
/* The transcript already insets its content; the sheet's own 16px would double
   it. Bottom padding goes too — the bar carries its own. */
/* The composer's fade would otherwise land on the anchored button and read as a
   cut-off control. Nothing needs dissolving under an opaque bar. */
body.plugin-screen-open .composer-wrap::before { opacity: 0; }
/* The transcript's 44px bottom padding exists to clear that fade. Under an
   anchored bar it becomes a strip of content scrolling past BELOW the button —
   sticky can't leave its containing block, so the bar stops at the content box.
   No fade, no padding. */
body.plugin-screen-open .transcript { padding-bottom: 0; }

/* THE ACTION BAR IS ANCHORED (owner 2026-08-01, two reference shots: one
   full-width "Copy" before you copy, a pair after). The decision this screen
   asks for must not depend on how far you happened to scroll — a 208px chart,
   three tabs and a trade list push a button clean off a phone, and a sheet
   whose only action is somewhere below the fold reads as something to browse
   rather than something to act on. Sticky, not fixed: the SHEET is the
   scroller, and a fixed bar would escape it to hang over the page behind. */
/* `margin-top: auto` is what holds it down on a screen whose content doesn't
   fill it — sticky only ever pulls an element UP into view, so on the review,
   which is shorter than the display, bottom:0 alone left the bar floating in
   the middle of the page. It collapses to 0 the moment the content does fill,
   which is why the bar carries its own top padding. */
.qn-bcta { position: sticky; bottom: 0; z-index: 2; display: flex; flex-direction: column; gap: 8px; margin-top: auto; padding: 10px 0 max(10px, env(safe-area-inset-bottom)); background: var(--bg-elev); }
/* Content has to stop VISIBLY at the bar. Under a hard edge the last row reads
   as the end of the list; the fade says there is more of it up there. */
.qn-bcta::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 16px; background: linear-gradient(to top, var(--bg-elev), transparent); pointer-events: none; }
/* The sheet's own bottom padding would sit UNDER the stuck bar and then appear
   as an 18px jump the moment you hit the true bottom and it unsticks. The bar
   carries the bottom inset itself, safe-area included. */
.sm-sheet.qn-anch { padding-bottom: 0; }
.sm-sheet.qn-anch > .qn-bsheet { padding-bottom: 0; }
/* The follower's allocation: a stepper, so 35% is sayable. */
.qn-allocval { min-width: 62px; text-align: center; font-size: 19px; font-weight: 720; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
/* What "the Stack's exits" actually ARE — read off its picks, never described
   in the abstract. */
.qn-exlist { display: flex; flex-direction: column; gap: 3px; padding: 2px 0 2px; }
.qn-exline { font-size: 11.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.qn-exline.dim { color: var(--text-faint); }
.qn-exrow { display: flex; align-items: center; gap: 10px; }
.qn-exlab { flex: 1 1 auto; font-size: 12.5px; color: var(--text-dim); }
.qn-cs-ein { width: 88px; appearance: none; border: none; border-radius: 10px; background: var(--bg-elev-2); color: var(--text); font: inherit; font-size: 12.5px; padding: 7px 10px; text-align: right; font-variant-numeric: tabular-nums; }
.qn-cs-ein:focus-visible { outline: 2px solid color-mix(in srgb, var(--sm-up) 55%, transparent); outline-offset: 1px; }
/* Two ways forward, sharing the width: leaving is not a footnote to staying. */
/* THE CTA IS AS WIDE AS THE SECTIONS ABOVE IT (owner 2026-08-08: "review button
   should be as wide as the sections above"). The exits card is capped at 420px
   and centred; the button spanned the pane, so on a desktop pane it read as a
   banner belonging to the window rather than the control belonging to those
   cards. Scoped to the copy sheet so the Stack screens keep their own layout. */
.qn-cf-amt ~ .qn-bcta, .sm-copyrev ~ .qn-bcta, .qn-cf-sheet .qn-bcta { width: 100%; max-width: 420px; margin-left: auto; margin-right: auto; }
/* `.ghost` is declared further down with the same specificity and would win on
   order, so it is named here too rather than left to a coin toss. */
.qn-bcta-row > .qn-bgo, .qn-bcta-row > .qn-bgo.ghost { flex: 1 1 0; min-width: 0; padding: 13px 14px; font-size: 14px; font-weight: 680; }
/* The hard exit is an OPTION, not the sheet's call to action — and it belongs
   beside the positions it closes, not in the bar. Right-aligned and quiet. */
.qn-close-all { align-self: flex-end; margin-top: 10px; }
/* The creator's edit: a header icon opposite the Stack's face, quiet enough
   that a reader who can't use it doesn't read it as the screen's action. */
/* How far back the Overview's numbers go — the record's own span, next to the
   heading that reports it. */
.qn-bfol-on { font-size: 12.5px; font-weight: 660; color: var(--sm-up); }
.qn-brules { display: flex; gap: 6px; }
.qn-brules-l { font-size: 11.5px; color: var(--text-faint); padding-top: 6px; }
.qn-bgo { appearance: none; border: none; border-radius: 999px; padding: 13px 18px; font: inherit; font-size: 14px; font-weight: 680; background: var(--sm-up); color: var(--sm-on-up); cursor: pointer; }
/* A CTA that can't fire has to look like it. The amount screen opens at $0 with
   Review dead, and a full-strength green pill that does nothing on tap is worse
   than no button. */
.qn-bgo:disabled { opacity: 0.42; cursor: default; }
.qn-bgo.ghost { flex: none; background: var(--bg-elev-2); color: var(--text); padding: 10px 15px; font-size: 12.5px; font-weight: 620; }
/* The hand-off dial (owner 2026-08-03): Autopilot toggles — dim until lit,
   green when Genie has the whole lifecycle; Advanced opens the manual hub. */
.qn-mode-row { margin-top: 12px; }
.qn-mode-auto { background: var(--bg-elev-2); color: var(--text); }
.qn-mode-auto.on { background: var(--sm-up); color: var(--sm-on-up); }
.qn-mode-adv.on { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--text) 32%, transparent); }
/* The Advanced sheet body, on the M3 8dp grid: 16px side padding, 16px
   between groups, and no duplicate headline — the sheet title already names
   the place, so the section's own "Your portfolios" header hides here. The
   one action row keeps the 48dp touch minimum. */
.qn-mode-body { display: flex; flex-direction: column; gap: 16px; padding: 0 16px 16px; }
.qn-mode-body .qn-sechead { display: none; }
.qn-mode-body .qn-pbtn { min-height: 48px; }
/* Bottom padding clears the bar's fade: at full scroll the bar unsticks and
   the gradient would otherwise wash the last line of the one paragraph on the
   screen that has to be read. */

/* ── The Stack screen (§8c) ─────────────────────────────────────────────────
   A real screen, so it gets a screen's rhythm: an identity row you type into,
   a card per strategy carrying its own record and its weight, and one way to
   add more. Material 3 minimal — tonal surfaces, no borders, big tabular
   numbers doing the talking. */

/* One composed pick: which strategy, and how much of the Stack. Nothing else —
   the price, the win rate and the trade breakdown live on the strategy's own
   screen, one tap away through this card. */
/* The weight — the one decision that belongs to the Stack rather than the
   strategy, so it gets its own line under a hairline. */
.st-cta-row .qn-bgo { flex: 1 1 auto; }

/* ── The composer (§8c) ─────────────────────────────────────────────────────
   "+" beside the bell. It takes the auto margin so the two controls stay
   grouped at the right instead of the bell alone flying off. */
.qn-newstack + .qn-bellwrap { margin-left: 6px; }
.qn-csheet { display: flex; flex-direction: column; padding: 2px 16px 4px; }
.qn-cs-id { display: flex; align-items: center; gap: 11px; padding: 4px 0 14px; }
.qn-cs-name { flex: 1 1 auto; min-width: 0; appearance: none; border: none; background: var(--bg-elev-1); color: var(--text); border-radius: 14px; padding: 12px 14px; font: inherit; font-size: 15px; font-weight: 620; }
.qn-cs-name::placeholder { color: var(--text-faint); font-weight: 500; }
.qn-cs-name:focus-visible { outline: 2px solid color-mix(in srgb, var(--sm-up) 55%, transparent); outline-offset: 1px; }
/* The picker IS the board — its own strips scroll, so it needs no chrome. */
.qn-cs-picker { display: flex; flex-direction: column; gap: 10px; padding-bottom: 6px; }
.qn-cs-picker .qn-sgroup-h:disabled { cursor: default; opacity: 1; }
/* A picked tile: tinted surface + a filled check. Selection has to survive the
   tile's own "best at" tint, so it wins on both background and outline. */
.qn-stile.picked { background: linear-gradient(170deg, color-mix(in srgb, var(--sm-up) 22%, transparent), transparent 70%), var(--bg-elev-2); border-color: color-mix(in srgb, var(--sm-up) 60%, transparent); }
/* The pick control. The tile itself opens the strategy, so this has to look
   like its own target: a 26px disc with real contrast in both states, and a
   36px hit area around it (the tile behind it is the other tap). */
.qn-stile-check.on { background: var(--sm-up); color: var(--sm-on-up); font-size: 13px; }
/* The eyebrow runs under the control, so it stops short of it — and stays on
   ONE line: a two-line "BIGGEST GAIN · MOST CONSISTENT" pushes that tile's
   numbers down and the row of tiles stops lining up. */
.qn-cs-picker .qn-stile-eb { padding-right: 24px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.qn-bgo.danger { color: var(--sm-down); background: color-mix(in srgb, var(--sm-down) 12%, transparent); }
/* The weights list: what each pick is worth, and what's left in cash. */
.qn-cs-note { font-size: 11.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; padding: 8px 0 2px; line-height: 1.45; }
.qn-cs-weights { display: flex; flex-direction: column; gap: 8px; }
/* A card, not a list row: it's a tap target that opens the strategy (where the
   exits live), so it has to look like one. Same tonal treatment as a Top
   performers row. */
.qn-cs-wrow { display: flex; align-items: center; gap: 11px; padding: 13px; background: var(--bg-elev-1); border-radius: 16px; cursor: pointer; transition: background .14s; }
.qn-cs-wrow:hover { background: var(--bg-elev-2); }
.qn-cs-wrow:focus-visible { outline: 2px solid color-mix(in srgb, var(--sm-up) 55%, transparent); outline-offset: 2px; }
/* Steppers, not a slider: a weight is a number people mean exactly, and 5%
   steps on a 44px target beat chasing a thumb on a phone. */
.qn-wstep { appearance: none; border: none; width: 32px; height: 32px; border-radius: 50%; background: var(--bg-elev-2); color: var(--text-dim); font: inherit; font-size: 16px; font-weight: 700; line-height: 1; cursor: pointer; transition: background .14s, color .14s; }
.qn-wstep:hover { background: var(--bg-elev-3, #333537); color: var(--text); }
.qn-cs-wrow:hover .qn-wstep { background: var(--bg-elev-3, #333537); }
.qn-wstep:active { transform: scale(0.92); }
.qn-wstep:disabled { opacity: 0.32; cursor: default; }
.qn-wstep:disabled:hover { background: var(--bg-elev-2); color: var(--text-dim); }
.qn-wval { min-width: 46px; text-align: center; font-size: 13px; font-weight: 660; font-variant-numeric: tabular-nums; }

/* Pull to refresh — height is driven from JS, so it collapses to nothing at
   rest and never occupies a row of its own. */
.qn-pull { height: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; gap: 8px; transition: height 0.18s ease; }
.qn-pull-m { width: 34px; height: 34px; flex: 0 0 auto; }
.qn-pull-t { font-size: 10.5px; letter-spacing: 0.03em; color: var(--text-faint); }
.qn-pull.armed .qn-pull-t { color: var(--text-dim); }
/* Pane keeps the wrap's own rhythm so a pane's children space exactly as they
   did when they were direct children of .qn-wrap. Panes are hidden via inline
   display:none (inline beats this rule; clearing it restores flex). */
.qn-pane { display: flex; flex-direction: column; gap: 14px; }
/* Home's children are the (collapsed) pull strip and the layout grid, and a flex
   `gap` applies around a zero-height child just the same — so the pane was paying
   14px for a strip that isn't there. The grid brings its own gaps, so drop this
   one and the dashboard starts right under the tabs (owner 2026-07-29). */
.qn-pane-strategies { gap: 0; }
/* LOAD-BEARING: .sm-root pairs `margin: 0 auto` with `container-type:
   inline-size`. In a flex COLUMN parent, auto cross-axis margins turn off
   stretching, and inline-size containment computes the intrinsic width as if
   the board had no children — the whole pane collapsed to a ~56px sliver
   (owner bug report 2026-07-27: "leaderboard is acting up"). An explicit
   width restores the block-parent geometry the board was designed against. */
.qn-pane > .sm-root { width: 100%; }
/* Ticker search — the way into the ~230 perps the treemap can't show.
   M3 filled search field: 28px radius, tonal surface, leading icon. */
.qn-search { position: relative; margin: 2px 2px 0; }
.qn-searchbox { display: flex; align-items: center; gap: 10px; background: color-mix(in srgb, var(--text) 6%, transparent); border: 1px solid transparent; border-radius: 28px; padding: 0 16px; height: 48px; }
.qn-searchbox:focus-within { border-color: var(--accent, #4c8dff); background: color-mix(in srgb, var(--text) 4%, transparent); }
.qn-searchic { width: 20px; height: 20px; flex: 0 0 auto; color: var(--text-faint); }
.qn-searchin { flex: 1 1 auto; min-width: 0; appearance: none; background: none; border: none; outline: none; font: inherit; font-size: 14px; color: var(--text); }
.qn-searchin::placeholder { color: var(--text-faint); }
.qn-searchin::-webkit-search-cancel-button { -webkit-appearance: none; }
/* Results float over the map rather than pushing it down — the map must not
   jump every keystroke. */
.qn-results:not(:empty) { position: absolute; z-index: 30; left: 0; right: 0; top: 52px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 16px; padding: 6px; box-shadow: 0 12px 32px rgb(0 0 0 / 0.32); max-height: 320px; overflow-y: auto; }

/* The Search tab's own bar: full width, at the top of the tab named after it
   (owner 2026-08-01). It replaced the hero's expanding circle, so the overrides
   that made a 34px pill grow into the balance row are gone with it. */
.qn-sec-find { padding-bottom: 2px; }
.qn-sec-find .qn-search { margin: 0; }
.qn-result { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; appearance: none; background: none; border: none; cursor: pointer; font: inherit; text-align: left; padding: 9px 10px; border-radius: 10px; color: var(--text); }
.qn-result:hover, .qn-result:focus-visible { background: color-mix(in srgb, var(--text) 7%, transparent); outline: none; }
.qn-result-l { display: flex; flex-direction: column; min-width: 0; }
.qn-result-sym { font-size: 14px; font-weight: 600; }
.qn-result-vol { font-size: 10.5px; color: var(--text-faint); margin-top: 1px; }
.qn-result-r { display: flex; flex-direction: column; align-items: flex-end; flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.qn-result-px { font-size: 13px; font-weight: 600; }
.qn-result-ch { font-size: 11px; margin-top: 1px; }
.qn-result-ch.up { color: var(--sm-up); }
.qn-result-ch.down { color: var(--sm-down); }
.qn-noresult { font-size: 12px; color: var(--text-dim); padding: 12px 10px; }
/* Hosts the Top-assets LIST now (2026-08-04) — no reserved tall box. */
.qn-map { position: relative; width: 100%; }
.qn-ta-symline { display: flex; align-items: center; gap: 7px; }
.qn-ta-ic { width: 18px; height: 18px; border-radius: 50%; flex: none; }
.qn-tile { position: absolute; appearance: none; border: 1px solid rgba(255,255,255,0.05); cursor: pointer; border-radius: 10px; padding: 3px; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 0; font: inherit; text-align: center; overflow: hidden; transition: filter 0.12s; }
.qn-tile-head { display: flex; align-items: center; gap: 5px; max-width: 100%; }
.qn-tile-ic { width: calc(var(--sym) * 0.85); height: calc(var(--sym) * 0.85); border-radius: 50%; flex: none; }
.qn-tile-px { font-size: var(--stat, 10px); font-weight: 600; opacity: 0.95; line-height: 1.3; font-variant-numeric: tabular-nums; white-space: nowrap; }
.qn-tile-ch { font-size: var(--stat, 10px); font-weight: 700; line-height: 1.3; font-variant-numeric: tabular-nums; white-space: nowrap; }
.qn-tile-mc { font-size: calc(var(--stat, 10px) * 0.86); opacity: 0.62; font-variant-numeric: tabular-nums; white-space: nowrap; }
.qn-tile:hover { filter: brightness(1.18); z-index: 2; }

.qn-tile.up { background: color-mix(in srgb, var(--sm-up) 14%, #10141a); color: var(--sm-up); }
.qn-tile.down { background: color-mix(in srgb, var(--sm-down) 14%, #10141a); color: var(--sm-down); }


.qn-cap { width: 100%; font-size: 11px; color: var(--text-faint); margin-top: 2px; }

/* ---- Paper hero board (owner 2026-07-28: paper first, mockup A). One board:
   P&L hero → curve → Running → Today → New today. M3-tonal, hero leads. ---- */
/* `relative` so the positions button can sit at the chart's top-right without
   taking a row of its own (owner 2026-07-30). */
.qn-hero { position: relative; display: flex; flex-direction: column; gap: 4px; width: 100%; }
.qn-board { display: flex; flex-direction: column; gap: 10px; width: 100%; }
.qn-hero-ebrow { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
/* THE HOME'S ONE HEADING STYLE — an M3 overline, not a title (owner
   2026-08-01: "total balance / your portfolios should be of the same style…
   the home page should be very material 3 style minimal design"). Two selectors
   rather than two rules: the hero and the portfolios block were 11px/700
   uppercase-faint and 15px/700 sentence-case respectively, which read as two
   different screens stacked. It is also the treatment the owner's own reference
   used ("YOUR PORTFOLIOS", "YOUR WATCHLIST"), and the one .qn-run-grp and
   .qn-pgt already speak inside the cards. */
.qn-hero-eb, .qn-sech-eb { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); }
.qn-chip-paper { font-size: 9.5px; font-weight: 800; letter-spacing: 0.06em; color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); border-radius: 6px; padding: 2px 7px; }
.qn-hero-bigrow { display: flex; align-items: baseline; gap: 8px; }
.qn-hero-big { font-size: 42px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.12; font-variant-numeric: tabular-nums; color: var(--text); }
.qn-hero-big.up { color: var(--sm-up); } .qn-hero-big.down { color: var(--sm-down); }
/* Free-to-trade paper, beside the equity total it's part of. Dim and secondary:
   it's the number you check before arming, not the headline. */
.qn-hero-free { font-size: 13px; font-weight: 650; color: var(--text-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The loop's terms, shown only when they aren't the defaults — an icon can say ON
   but not "$100 a play until Thursday 02:14". Taps through to the sheet. */
.qn-hero-auto { display: block; appearance: none; border: none; background: none; padding: 2px 0 0; margin: 0; text-align: left; font: inherit; font-size: 11.5px; font-weight: 560; color: var(--text-faint); cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.qn-hero-auto:hover { color: var(--text-dim); }
.qn-hero-sub { font-size: 13px; font-weight: 600; color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* THE BOOK BREAKER'S LINE IS GONE from the balance panel (owner ruling 2026-08-24). `.qn-breaker`,
   `-copy` and `-clear` went with it. It was appended to the hero on its own, with no separator and
   no reserved height, so the panel simply has one element fewer — nothing above or below it moved. */
/* Now a button (tap swaps money ⇄ percent) — strip the UA chrome so it still
   reads as the line of text it was, and keep a hit area a thumb can find. */
.qn-hero-pnl { display: block; appearance: none; border: none; background: none; padding: 2px 0; margin-top: 0; text-align: left; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--text-dim); font-variant-numeric: tabular-nums; cursor: pointer; }
.qn-hero-pnl:active { opacity: 0.7; }
.qn-hero-pnl.up { color: var(--sm-up); } .qn-hero-pnl.down { color: var(--sm-down); }
/* The curve is the point of the dashboard, so it takes roughly half the band
   between the tabs and the composer instead of a 92px sliver (owner 2026-07-29).
   svh, not vh: a mobile URL bar that hides and shows must not resize the chart
   mid-scroll. The clamp keeps it readable on a short laptop window and stops it
   swallowing a tall desktop pane. Stretching is safe — the SVG is drawn with
   preserveAspectRatio: none and the line uses non-scaling-stroke below, so a
   taller box shows more detail rather than a fatter line. */
/* THE TRADE LOCK POSTER (owner 2026-08-06: "make the sales in your face"):
   full-pane, giant type, the \$10,000 as the centrepiece, the chart drawing
   itself across the top, brand glows behind. */
.qn-lock-host { min-height: 74vh; display: flex; align-items: stretch; justify-content: center; padding: 10px; }
.qn-lockcard.sales {
  position: relative; overflow: hidden; width: 100%; max-width: 560px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  gap: 12px; padding: 34px 24px 30px; border-radius: 24px;
  background: var(--sm-card); border: 1px solid var(--border); box-shadow: 0 16px 50px rgba(0, 0, 0, 0.4);
}
.qn-lockcard.sales::before, .qn-lockcard.sales::after {
  content: ""; position: absolute; width: 340px; height: 340px; border-radius: 50%; pointer-events: none;
}
/* Tonal glows, ONE hue family (M3, owner 2026-08-06): the app's primary
   green at two low alphas — no second saturated hue on the poster. */
.qn-lockcard.sales::before { top: -160px; right: -120px; background: radial-gradient(circle, color-mix(in srgb, var(--sm-up) 16%, transparent), transparent 65%); }
.qn-lockcard.sales::after { bottom: -170px; left: -130px; background: radial-gradient(circle, color-mix(in srgb, var(--sm-up) 10%, transparent), transparent 65%); }
.qn-lockcard.sales > * { position: relative; }
.qn-lock-anim { width: 100%; max-width: 400px; height: auto; display: block; margin-bottom: 4px; color: var(--text); }
/* One 4.2s master loop: candles pop → lines draw + comet rides → chips
   float → peak flare → everything breathes out and the loop restarts. */
.qn-anim-candle { transform-box: fill-box; transform-origin: 50% 100%; animation: qn-candle 4.2s ease-in-out infinite; }
@keyframes qn-candle { 0% { transform: scaleY(0); opacity: 0; } 8% { transform: scaleY(1); opacity: 1; } 88% { transform: scaleY(1); opacity: 1; } 100% { transform: scaleY(1); opacity: 0; } }
.qn-anim-ghost { stroke-dasharray: 420; stroke-dashoffset: 420; animation: qn-ghost 4.2s ease-in-out infinite; }
@keyframes qn-ghost { 0%, 6% { stroke-dashoffset: 420; } 46% { stroke-dashoffset: 0; } 88% { stroke-dashoffset: 0; opacity: 0.14; } 100% { stroke-dashoffset: 0; opacity: 0; } }
.qn-lock-line-path { stroke-dasharray: 460; stroke-dashoffset: 460; animation: qn-lock-draw 4.2s ease-in-out infinite; filter: drop-shadow(0 0 6px color-mix(in srgb, var(--sm-up) 55%, transparent)); }
@keyframes qn-lock-draw { 0%, 12% { stroke-dashoffset: 460; opacity: 1; } 58% { stroke-dashoffset: 0; opacity: 1; } 88% { stroke-dashoffset: 0; opacity: 1; } 100% { stroke-dashoffset: 0; opacity: 0; } }
.qn-anim-area { opacity: 0; animation: qn-area 4.2s ease-in-out infinite; }
@keyframes qn-area { 0%, 20% { opacity: 0; } 58% { opacity: 1; } 88% { opacity: 1; } 100% { opacity: 0; } }
.qn-anim-comet { opacity: 0; animation: qn-comet 4.2s linear infinite; filter: drop-shadow(0 0 8px #fff); }
@keyframes qn-comet { 0%, 12% { opacity: 0; } 14%, 56% { opacity: 1; } 60%, 100% { opacity: 0; } }
.qn-lock-animdot { opacity: 0; animation: qn-lock-dot 4.2s ease-in-out infinite; filter: drop-shadow(0 0 10px var(--sm-up)); }
@keyframes qn-lock-dot { 0%, 55% { opacity: 0; } 60% { opacity: 1; } 66% { opacity: 0.5; } 72%, 88% { opacity: 1; } 100% { opacity: 0; } }
.qn-anim-chip { opacity: 0; transform-box: fill-box; animation: qn-chip 4.2s ease-out infinite; }
.qn-anim-chip2 { animation-delay: 0.5s; }
@keyframes qn-chip { 0%, 42% { opacity: 0; transform: translateY(10px); } 52% { opacity: 1; transform: translateY(0); } 82% { opacity: 1; transform: translateY(-4px); } 92%, 100% { opacity: 0; transform: translateY(-10px); } } }
.qn-lockcard.sales .qn-lock-t { font-size: clamp(24px, 6.4vw, 32px); font-weight: 800; letter-spacing: -0.02em; line-height: 1.2; }
.qn-lock-big {
  font-size: clamp(52px, 15vw, 76px); font-weight: 800; letter-spacing: -0.03em; line-height: 1;
  /* M3: the display figure stays in the primary's tonal range — a subtle
     green→mint ramp, not a three-hue neon (owner 2026-08-06). */
  background: linear-gradient(100deg, var(--sm-up), #9fe8bd); -webkit-background-clip: text; background-clip: text; color: transparent;
  font-variant-numeric: tabular-nums;
}
.qn-lockcard.sales .qn-lock-s { font-size: clamp(14px, 4vw, 17px); font-weight: 700; color: var(--text); }
.qn-lock-line { font-size: 13px; color: var(--text-dim); margin-top: 4px; }
.qn-lock-alt { font-size: 11.5px; color: var(--text-faint); }
.qn-lockcard.sales .qn-lock-cta {
  margin-top: 8px; width: min(300px, 86%); padding: 15px 0; border: none; border-radius: 999px;
  font: inherit; font-size: 16px; font-weight: 800; background: var(--sm-up); color: var(--sm-on-up, #08130a); cursor: pointer;
  animation: qn-cta-breathe 2.4s ease-in-out infinite;
}
@keyframes qn-cta-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }
.qn-lock-cta { border: none; border-radius: 999px; padding: 12px 30px; font: inherit; font-size: 14px; font-weight: 700; background: var(--sm-up); color: var(--sm-on-up, #08130a); cursor: pointer; }
@media (prefers-reduced-motion: reduce) {
  .qn-lock-line-path, .qn-anim-ghost { animation: none; stroke-dashoffset: 0; }
  .qn-lock-animdot, .qn-anim-area { animation: none; opacity: 1; }
  .qn-anim-candle { animation: none; }
  .qn-anim-comet, .qn-anim-chip { display: none; }
  .qn-lockcard.sales .qn-lock-cta { animation: none; }
}
}
.qn-lock-t { font-size: 15px; font-weight: 700; }
.qn-lock-s { font-size: 13px; line-height: 1.5; color: var(--text-dim); }
.qn-lock-cta { margin-top: 4px; border: none; border-radius: 999px; padding: 10px 22px; font: inherit; font-size: 13.5px; font-weight: 700; background: var(--sm-up); color: var(--sm-on-up, #08130a); cursor: pointer; }

.qn-hero-chart { width: 100%; height: clamp(120px, 24vh, 260px); height: clamp(120px, 24svh, 260px); margin-top: 8px; display: block; }
/* FINGER-ON-THE-CHART scrub (owner 2026-08-06). The wrapper takes over the
   chart's top margin so the HTML overlay's coordinates line up with the svg
   exactly (a child margin would collapse out of the positioned box). pan-y
   keeps the page scrollable while a horizontal drag scrubs. */
.qn-scrub { position: relative; margin-top: 8px; touch-action: pan-y; cursor: crosshair; }
.qn-scrub > .qn-hero-chart { margin-top: 0; }
/* The line is a GRADIENT on a real 2px box, not a dashed border on a 0-width
   one: iOS Safari dropped the border version entirely (owner screenshot
   2026-08-06 — pill and dot painted, line didn't). Backgrounds paint
   everywhere the dot already does. */
.qn-scrub-line {
  position: absolute; top: 0; bottom: 0; width: 2px; transform: translateX(-1px);
  background: repeating-linear-gradient(to bottom, color-mix(in srgb, var(--text) 60%, transparent) 0 5px, transparent 5px 9px);
  pointer-events: none;
}
.qn-scrub-dot { position: absolute; width: 9px; height: 9px; border-radius: 50%; transform: translate(-50%, -50%); background: var(--text); border: 2px solid var(--bg); pointer-events: none; }
.qn-scrub-dot.up { background: var(--sm-up); }
.qn-scrub-dot.down { background: var(--sm-down); }
.qn-scrub-tag { position: absolute; top: 4px; font-size: 11px; white-space: nowrap; color: var(--text-dim); background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; pointer-events: none; }
.qn-hero-line { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; }
.qn-hero-line.up { stroke: var(--sm-up); } .qn-hero-line.down { stroke: var(--sm-down); }
.qn-hero-gtop.up { stop-color: var(--sm-up); stop-opacity: 0.24; }
.qn-hero-gtop.down { stop-color: var(--sm-down); stop-opacity: 0.24; }
.qn-hero-gbot.up { stop-color: var(--sm-up); stop-opacity: 0; }
.qn-hero-gbot.down { stop-color: var(--sm-down); stop-opacity: 0; }
.qn-range { display: flex; gap: 4px; margin-top: 2px; justify-content: center; }
.qn-range-b { appearance: none; border: none; background: none; cursor: pointer; font: inherit; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; color: var(--text-faint); border-radius: 999px; padding: 4px 10px; }
.qn-range-b.on { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 14%, transparent); }
/* Claimable paper — the weekly gacha and the bounty board (VIBE_TRADING §5a),
   the one place a little shine is earned.

   NAMED `qn-claim`, NOT `qn-pull`: it used to share the pull-to-refresh strip's
   class, and since this block comes later it was silently dressing that strip in
   a brand border, a gradient and 12+22px of box — a ~36px phantom band above the
   dashboard that read as "too much gap" (owner 2026-07-29). Two unrelated
   components, two names. */
.qn-claim { display: flex; align-items: center; gap: 10px; margin-top: 12px; padding: 11px 12px; border-radius: 14px; cursor: pointer; border: 1px solid color-mix(in srgb, var(--sm-up) 38%, transparent); background: linear-gradient(100deg, color-mix(in srgb, var(--sm-up) 15%, transparent), transparent); }
.qn-claim:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qn-claim.busy { opacity: 0.6; pointer-events: none; }
.qn-claim-l { min-width: 0; flex: 1; }
.qn-claim-name { font-size: 13.5px; font-weight: 650; }
.qn-claim-sub { font-size: 11.5px; color: var(--text-dim); margin-top: 2px; }
.qn-claim-cta { flex: none; font-size: 12px; font-weight: 700; padding: 6px 12px; border-radius: 999px; background: var(--sm-up); color: var(--sm-on-up); }

/* Auto-pilot — an icon in the dashboard's top-right corner (owner 2026-07-29),
   where a full-width card used to spend ~60px of a phone screen saying what a
   colour says. The toggle carries state in its fill; the small gear beside it
   opens the settings the card's tap used to. */
/* A LABELLED button, not a glyph: under the one-loop rule this is also how you
   close a running loop, and "close this loop" isn't something an icon can say. */
.qn-autobar { display: flex; align-items: stretch; gap: 6px; }
.qn-autobtn { flex: 1 1 auto; min-width: 0; appearance: none; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 11px 14px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-elev-1); color: var(--text-dim); font: inherit; font-size: 13px; font-weight: 620; cursor: pointer; transition: background .14s, color .14s, border-color .14s; }
.qn-autobtn svg { width: 19px; height: 19px; flex: none; }
.qn-autobtn-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qn-autobtn:hover { background: var(--bg-elev-2); color: var(--text); }
.qn-autobtn.running { background: color-mix(in srgb, var(--sm-up) 14%, transparent); border-color: color-mix(in srgb, var(--sm-up) 40%, transparent); color: var(--sm-up); }
.qn-autobtn.idle { background: color-mix(in srgb, var(--warn, #b8860b) 14%, transparent); border-color: color-mix(in srgb, var(--warn, #b8860b) 38%, transparent); color: var(--warn, #b8860b); }
.qn-autobtn.busy { opacity: 0.55; pointer-events: none; }
.qn-autobtn:focus-visible, .qn-autoi-set:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qn-autoi-set { appearance: none; display: grid; place-items: center; flex: none; width: 42px; border-radius: 999px; border: 1px solid var(--border); background: none; color: var(--text-faint); cursor: pointer; transition: background .14s, color .14s; }
.qn-autoi-set svg { width: 18px; height: 18px; }
.qn-autoi-set:hover { background: var(--bg-elev-1); color: var(--text-dim); }

.qn-auto-sheet { padding: 0 16px; }
.qn-auto-lede { font-size: 12.5px; line-height: 1.5; color: var(--text-dim); }
.qn-auto-h { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); margin-top: 18px; }
/* "What's set now" — the concierge card's own four rows, read back from storage
   so the loop's terms outlive the chat bubble that proposed them. */
.qn-auto-now { display: flex; flex-direction: column; margin-top: 8px; border-radius: 12px; background: var(--surface); overflow: hidden; }
.qn-auto-nrow { display: flex; align-items: baseline; gap: 12px; padding: 9px 12px; font-size: 12.5px; }
.qn-auto-nrow + .qn-auto-nrow { border-top: 1px solid color-mix(in srgb, var(--text) 7%, transparent); }
.qn-auto-nlabel { flex: none; color: var(--text-faint); min-width: 84px; }
.qn-auto-nval { flex: 1; min-width: 0; display: flex; align-items: baseline; justify-content: space-between; gap: 8px; color: var(--text); font-weight: 560; text-align: right; }
.qn-auto-nval > span { flex: 1; }
.qn-auto-nclear { flex: none; appearance: none; border: none; background: none; padding: 0; font: inherit; font-size: 11.5px; color: var(--text-faint); text-decoration: underline; cursor: pointer; }
.qn-auto-nclear:hover { color: var(--text-dim); }
/* Your rules — each with the verdict it was admitted on and the loop switch. */
.qn-auto-rulerow { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 12px; background: var(--surface); margin-top: 6px; }
.qn-auto-rule-l { flex: 1; min-width: 0; }
.qn-auto-rule-n { font-size: 13px; font-weight: 620; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qn-auto-rule-v { font-size: 11.5px; margin-top: 2px; color: var(--text-dim); }
.qn-auto-rule-v.ok { color: var(--sm-up); }
.qn-auto-rule-v.bad { color: var(--text-faint); }
.qn-auto-rule-sw { flex: none; appearance: none; font: inherit; font-size: 12px; font-weight: 700; padding: 7px 14px; border-radius: 999px; border: 1px solid var(--border); background: none; color: var(--text-faint); cursor: pointer; transition: background .14s, color .14s, border-color .14s; }
.qn-auto-rule-sw.on { background: color-mix(in srgb, var(--sm-up) 15%, transparent); border-color: color-mix(in srgb, var(--sm-up) 42%, transparent); color: var(--sm-up); }
/* Asked for, not enforced — visibly parked, never dressed as a setting. */
.qn-auto-unenf { margin-top: 8px; border-radius: 12px; border: 1px dashed color-mix(in srgb, var(--warn, #b8860b) 45%, transparent); padding: 8px 12px; }
.qn-auto-unenf-note { font-size: 11.5px; color: var(--warn, #b8860b); padding-bottom: 4px; }
.qn-auto-urow { display: flex; align-items: baseline; gap: 8px; padding: 5px 0; }
.qn-auto-utext { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text-dim); font-style: italic; }
.qn-auto-udrop { flex: none; appearance: none; border: none; background: none; font: inherit; font-size: 12px; color: var(--text-faint); cursor: pointer; padding: 2px 4px; }
.qn-auto-udrop:hover { color: var(--text-dim); }
.qn-auto-opts { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.qn-auto-opt { text-align: left; font: inherit; font-size: 13px; padding: 11px 12px; border-radius: 12px; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: pointer; }
.qn-auto-opt.on { border-color: var(--accent); background: var(--accent-dim); font-weight: 650; }
.qn-auto-fixed { display: flex; gap: 6px; margin-top: 8px; }
.qn-auto-amt { flex: 1; min-width: 0; font: inherit; font-size: 13px; padding: 10px 12px; border-radius: 12px; border: 1px solid var(--border); background: var(--surface); color: var(--text); }
.qn-auto-set { flex: none; font: inherit; font-size: 12.5px; padding: 10px 14px; border-radius: 12px; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: pointer; }
.qn-auto-rest { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.qn-auto-rrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 10px 12px; border-radius: 12px; background: var(--surface); }
.qn-auto-rname { font-size: 12.5px; font-weight: 620; }
.qn-auto-rwhy { flex: 1; min-width: 120px; font-size: 11.5px; color: var(--text-dim); }
.qn-auto-again { flex: none; font: inherit; font-size: 11.5px; padding: 6px 10px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: var(--text); cursor: pointer; }
/* The sheet lists no positions. A "Running now" block lived here briefly and the
   owner cut it (2026-07-30): the open plays are already the Quant home's Running
   section, and this sheet is for the SETUP. Same reason "Lately" went. */
.qn-auto-acts { display: flex; flex-direction: column; gap: 8px; margin-top: 20px; }
.qn-auto-toggle { font: inherit; font-size: 14px; font-weight: 650; padding: 13px; border-radius: 999px; border: none; background: var(--accent); color: var(--on-accent); cursor: pointer; }
.qn-auto-toggle.off { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
.qn-auto-closeall { font: inherit; font-size: 12.5px; padding: 11px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: var(--text-dim); cursor: pointer; }
.qn-hero-sec { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); margin-top: 14px; }
/* Which way the open book leans. Sits between the RUNNING label and the rows,
   dim and lower-case — an exposure fact, not an alarm. */
/* "In the market · 3" / "Waiting for a price · 2". NOT .qn-hero-sec: the tabbed
   layout hides that one (the tab names the panel), and these have to survive
   there — telling the two groups apart is the point. */
/* Title left, liveness stamp right (owner 2026-08-04) — one header row. */
/* The waiting room's empty state: a statement, not a heading — nothing to open,
   so it must not wear a toggle's affordance. Sentence case and un-tracked, for
   the same reason. */
.qn-run-grp-quiet { text-transform: none; letter-spacing: 0; font-weight: 500; cursor: default; }
.qn-run-grp { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin: 20px 0 8px; font-variant-numeric: tabular-nums; }
/* The composed pick, cut to the decision this screen actually asks for: which
   strategy, and how much of the Stack. Price, win rate and the trade breakdown
   moved to the strategy's own screen (one tap through the card).
   (owner 2026-07-31: "information overflow here — only info vital for user to
   make decision on that moment") */
/* Deliberately NOT green. A coloured backtest reads as money made; grey reads as
   the spec it is. */
.st-note.out { color: var(--warn, #f3c969); }
.st-drop { flex: none; align-self: flex-start; width: 28px; height: 28px; display: grid; place-items: center; font-size: 13px; line-height: 1; color: var(--text-faint); background: none; border: none; border-radius: 50%; cursor: pointer; transition: background .14s, color .14s; }
.st-drop:hover { background: var(--bg-elev-2); color: var(--text); }
/* inline-block, not align-self: `.st-card` is a block, so a flex property here
   silently does nothing and the pill stretches the full width. */

/* Following a Stack and holding none of it. Tonal cards, no borders — this is a
   status block, not a list of things to tap, so nothing here reads as a control.
   (owner 2026-07-31: "why there's no open position pending or orders made") */
/* No record is not a result. Neutral, unbold, never green — a Stack that has
   never traded must not look like one that traded to a flat finish. */
.qn-tpret.none { color: var(--text-faint); font-weight: 500; font-size: 11px; }
/* A backtest number wears the word and the muted ink — never the earned green/red. */
.qn-tpret.tested { color: var(--text-dim); font-weight: 500; font-size: 11px; }
/* The composer's "Your rules" — the user's own passing rules, pickable like tiles. */
.st-myrules { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 16px; }
.st-myrules-cap { margin-bottom: 8px; }
.st-myrule { display: flex; align-items: center; justify-content: space-between; gap: 10px; background: var(--sm-card); border-radius: 14px; padding: 10px 12px; }
/* In a rule row the add/check disc is a flex item, not a corner overlay — the
   tile rule's absolute positioning anchored it to the SCRIM (no positioned
   ancestor here), so it floated at the screen's top-right corner. */
.st-myrule .qn-stile-check { position: relative; top: auto; right: auto; flex: 0 0 auto; }
/* The Custom tab's rules list — same rows, plus a verdict chip. */
.qn-myrules { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
/* R135 — the rest of a long list, opened in place. A quiet full-width line, not a button that
   competes with the rows above it. */
.qn-myrules-all {
  appearance: none; border: none; background: none; cursor: pointer; font: inherit;
  font-size: 12.5px; font-weight: 600; color: var(--text-dim); text-align: left; padding: 4px 2px;
}
.qn-myrules-all:hover { color: var(--text); }
/* …and once it is open, an inner scroll ONLY when the list is long enough to push the composer off
   the screen. This number is the positions rail's allowance for the chrome above and the composer
   below, and it is the value a browser uses for the instant BEFORE the measured height lands. It
   is not the right height for this list: the rail starts at the top of its column and this list
   starts lower, so on a 657-px-tall pane this made the box 437 px inside a reading area 391.4 px
   tall and the last rows sat 63.6 px under the typing box (R138). The height that ships is
   measured from the reading area in `paintRulesList`. */
.qn-myrules.all { max-height: calc(100dvh - 220px); overflow-y: auto; overscroll-behavior: contain; }
.qn-rule-chip { font-size: 10px; font-weight: 650; letter-spacing: 0.02em; color: var(--text-faint); background: var(--bg-elev-2); border-radius: 999px; padding: 2px 8px; margin-left: 8px; white-space: nowrap; }
.qn-rule-chip.pass { color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 12%, transparent); }
.qn-bsh-ret.none { color: var(--text-faint); font-weight: 560; font-size: 15px; letter-spacing: 0; }
/* Where the number came from. Quiet, but present — a percentage nobody can
   source is the kind this branch has spent all day deleting. */

/* ── YOUR PORTFOLIOS (owner 2026-07-31) ──────────────────────────────────────
   The home's second block: the Stacks you copy, and the one you made. Cards, not
   rows — each carries four numbers, a status line and two controls, which is
   more than a row can hold without becoming a table nobody reads on a phone.
   The pieces (.qn-w*) are the ones the positions sheet used to render, kept
   under their old names because the markup didn't change, only its address. */
.qn-plist { display: flex; flex-direction: column; gap: 10px; }
/* THE DESCRIPTION. One line, always: it says what the Stack trades, and the
   status feed it replaced ("Running 3 of 4 · copying 11m · Next: SKHY · Sell the
   breakdown, in 7 days") wrapped to two and moved every couple of minutes. */
/* No return yet is a STATE, not a fault — same tonal chip language as the rest
   of the screen, and the row keeps its shape either way. */
.qn-wna { flex: none; font-size: 10px; font-weight: 660; letter-spacing: .04em; color: var(--text-faint); background: var(--bg-elev-2); border-radius: 999px; padding: 3px 8px; }
/* The name is the tap target; the row around it is not, so the stepper beside
   it can never be hit by aiming at the card. */

/* YOU vs THE STACK, side by side with a hairline between — the comparison IS the
   card, so the two halves get equal width and the divider does the separating
   that a border around each would over-state. `minmax(0, 1fr)` twice, because a
   long figure in one half must not steal width from the other. */
.qn-pgrp { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.qn-pgrp + .qn-pgrp { border-left: 1px solid var(--border, color-mix(in srgb, var(--text) 9%, transparent)); padding-left: 12px; }
.qn-pgt { font-size: 10px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-faint); }
.qn-pcells { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }
.qn-pstat { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.qn-pv { font-size: 15px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qn-pv.up { color: var(--sm-up); } .qn-pv.down { color: var(--sm-down); }
.qn-pl { font-size: 10px; color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The card's action row — just Edit, on a Stack you made. Allocation and the
   hard exit live in the Stack sheet (owner 2026-07-31: "no show allocator, dont
   show close all here"): a portfolio card is for reading. */
/* The neutral sibling of .qn-wstop — Edit, Find one, Create one. Same pill, no
   colour: none of them destroy anything. */
/* The way out of an empty tab, at the width of the card that says it's empty. */
.qn-pbtn { align-self: stretch; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 13.5px; font-weight: 650; color: var(--text); background: var(--bg-elev-2); border: none; border-radius: 999px; padding: 13px 16px; cursor: pointer; transition: background .14s; }
.qn-pbtn svg { width: 19px; height: 19px; flex: none; }
.qn-pbtn:hover { background: var(--bg-elev-3); }
/* Empty is a state with an action, never a shrug. */
.qn-pempty { display: flex; flex-direction: column; align-items: stretch; gap: 12px; text-align: center; background: var(--bg-elev-1); border-radius: 18px; padding: 16px 14px; }
.qn-pempty-t { font-size: 12.5px; color: var(--text-dim); }
/* Liveness under the rows: a breathing dot and the last mark. Answers "is this
   number stale or is the market flat" without opening the bell. */
.qn-run-live { display: flex; align-items: center; gap: 6px; padding: 0; font-weight: 500; letter-spacing: 0; text-transform: none; }
.qn-live-t { font-size: 10.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.qn-run-live.late .qn-live-t { color: var(--warn, #f3c969); }
.qn-live-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--sm-up); flex: none; animation: qn-breathe 2.4s ease-in-out infinite; }
.qn-live-dot.late { background: var(--warn, #f3c969); animation-duration: 1.1s; }
@keyframes qn-breathe { 0%, 100% { opacity: 0.35; transform: scale(0.82); } 50% { opacity: 1; transform: scale(1); } }
/* The loop itself breathes while it's hunting — the icon says ON, this says
   RUNNING. Kept slow and low-contrast: it's ambient, not an alert. */
.qn-autobtn.running svg { animation: qn-breathe 2.8s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .qn-live-dot, .qn-autobtn.running svg { animation: none; }
  .qn-live-dot { opacity: 1; }
}
/* Separate cards rather than a hairline-divided table: at six positions the
   ruled list read as a spreadsheet, and M3 puts air between items instead. */
.qn-run { display: flex; flex-direction: column; gap: 10px; width: 100%; }
.qn-run-row { appearance: none; border: none; background: var(--sm-card); border-radius: 18px; cursor: pointer; font: inherit; color: var(--text); display: flex; align-items: center; gap: 12px; text-align: left; padding: 14px 16px; }
/* Waiting-to-enter rows: the amber of their WAITING chip, as a whisper. */
.qn-run-row.pend { background: color-mix(in srgb, var(--warn) 6%, var(--sm-card)); }
/* `.qn-review` IS THE PLAYBOOK'S SURFACE NOW. The idea-review panel this block
   was written for was deleted on 2026-08-19 ("delete this section"), and with it
   went the head row, the supporting-text line and the three breathing dots
   and their keyframes. What is left is the section padding, which the Playbook
   (`qn-review qn-playbook`) still wears, and the two controls below — the CTA
   and the mm:ss clock — which the POSITIONS header uses and which the watch
   list's scan button now borrows so the two triggers cannot drift apart. */
.qn-review { display: block; padding: 14px 2px 4px; }
/* Compact tonal action on the overline itself. It shares the title's 11px
   utility scale and stays inside that line in both English and Chinese. */
.qn-review-cta {
  appearance: none; border: none; cursor: pointer; font: inherit; font-weight: 700; font-size: 11px; line-height: 1;
  margin: 0; padding: 3px 7px; border-radius: 999px; min-height: 0;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  transition: background 0.15s var(--ease), opacity 0.15s var(--ease);
}
/* THE POSITIONS HEADER'S ALWAYS-PRESENT LINE. Quiet — it is a receipt, not an
   announcement — and it sits directly under the header it belongs to. It never
   collapses to nothing, so it has no [hidden] rule: an empty line here would be
   the silence this was built to end. */
.qn-side-check {
  margin: -2px 0 6px; font-size: 11.5px; line-height: 1.45; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* The transient half of the Positions header: what just happened when the owner
   tapped, beside the standing fact of when it was last checked. Hidden when it
   has nothing to say — unlike the line above it, which is never allowed to be. */
.qn-side-check-note { margin: -4px 0 6px; font-size: 11.5px; line-height: 1.45; color: var(--text-dim); }
.qn-side-check-note[hidden] { display: none; }
/* The positions button rides the header's right-hand controls beside the bell. */
.qn-sech-right .qn-side-cta { margin-left: 4px; }
/* ── THE POSITIONS HEADER'S ROW (owner ruling 2026-08-17) ──────────────────
   Second verse of the ideas row: the position-REVIEW status left, the trigger
   right, one line, centred. The price watcher's countdown left this header —
   its receipt is the "Last checked" line below, which never needed a clock.
   `.qn-sech-right` is shared (other headers put pills here), so the row rules
   are scoped to this one container; the status itself REUSES `.qn-review-clock`
   from the ideas row rather than declaring a second look. */
.qn-side-head-right { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; justify-content: flex-end; }
/* Shrinkable, never the thing that pushes the button off the row: a long
   sentence wraps inside this column instead. Right-aligned because it hugs the
   trigger, with the section title holding the other end. */
.qn-side-head-right .qn-review-clock { flex: 0 1 auto; min-width: 0; text-align: right; }
.qn-side-head-right .qn-review-clock[hidden] { display: none; }
/* THE ICON BUTTON (owner 2026-08-15: "the review button needs to be icon button
   now text"). Square, not a text pill: the words moved to aria-label/title and
   the count beside it already says what this section is. 28px is the smallest
   square that still clears the 24px touch guidance at this density. */
.qn-review-cta-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; border-radius: 999px; flex: none;
}
.qn-review-cta-icon .material-symbols-rounded { font-size: 18px; line-height: 1; }
/* THE mm:ss CLOCK. Quiet and tabular so the digits do not jitter every second —
   a number that shifts left and right once a second reads as instability, which
   is the opposite of what a "yes, it is working" clock is for. */
.qn-review-clock {
  /* The row's flexible item since 2026-08-17 — it is the only text left, so it
     takes the free space and the trigger takes the end. `min-width: 0` is what
     lets a long sentence wrap INSIDE this column instead of widening the row and
     pushing the button off it. `line-height: 1` was right for a one-word clock
     and sets two wrapped lines solid, so it relaxes to 1.35. */
  flex: 1 1 auto; min-width: 0;
  font-size: 11px; font-weight: 600; line-height: 1.35;
  color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.qn-review-clock[hidden] { display: none; }
.qn-review-cta:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }
.qn-review-cta:active { background: color-mix(in srgb, var(--accent) 30%, transparent); }
.qn-review-cta:disabled { opacity: 0.5; cursor: default; }
.qn-review-cta[hidden] { display: none; }
.qn-review-cta:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Provenance on a row a review wrote: quiet, uppercase-free, never louder than
   the position it labels. */

.qn-run-row:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.qn-run-l { flex: 1; min-width: 0; }
.qn-run-n { display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: 14px; }
/* Genie-picked provenance: the AUTO chip in quiet gray (owner 2026-08-07:
   "subtle and not distracting"), same shape as the GRID/WAITING chips. */
.qn-run-wait.auto { color: var(--text-faint); background: color-mix(in srgb, var(--text-faint) 12%, transparent); }
.qn-run-wait { font-size: 9px; font-weight: 800; letter-spacing: 0.06em; color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); border-radius: 5px; padding: 2px 6px; }
/* A placed, unfilled order is WAITING ON THE MARKET, not doing work. Keep the
   server-era qn-run-wait class, but replace the loud word with one still clock.
   Its amber tile preserves the existing pending-order meaning and makes it the
   visual opposite of the bare moving pass sparkle below. */
.qn-order-pending,
.qn-order-confirmed {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; padding: 0; font-size: 15px; line-height: 1;
  letter-spacing: 0; cursor: default; animation: none;
}
/* CONFIRMED, NOT BOUGHT (owner 2026-08-23) — the condition printed and the fill
   is still being tried. It shares the pending badge's geometry so the row cannot
   shift when the state changes under the owner's eyes, and differs where a
   glance actually lands: a bolt instead of a still clock, and the accent tone
   instead of amber.
   IT IS NOT GREEN. `--sm-up` is what a filled position wears; a green badge on
   an order that bought nothing would tell him he owns something he does not,
   which is a worse lie than the silence being fixed here. */
.qn-order-confirmed { color: var(--accent); }
/* Long/Short beside the ticker (owner spec rev 2 2026-08-06: title =
   symbol + side on every position card; money/leverage on the line below). */
.qn-run-dirw { font-size: 12px; font-weight: 650; }
.qn-run-dirw.long { color: var(--sm-up); }
.qn-run-dirw.short { color: var(--sm-down); }
/* Which Stack a mirrored position came from — quiet, but never absent: a
   position the user didn't tap has to say who opened it. */
.qn-run-from { font-size: 9.5px; font-weight: 620; color: var(--text-faint); background: var(--bg-elev-2); border-radius: 5px; padding: 2px 6px; max-width: 108px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Where the trade ends — the pair a copier most needs and could not see. */
.qn-run-exit { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; margin-top: 4px; }
/* Wraps to two lines, never truncates (owner 2026-08-05: the ellipsis was
   swallowing the pace — the one number that turns "3.6% away" into "about
   90 minutes"). Vital info doesn't get cut for tidiness. */
/* THE POSITIONS MARQUEE — one quiet line, crossfading. Deliberately quieter
   than the rows it sits above: this is context, not an alert, and anything
   that moves next to live money reads as an alarm if it is loud. */
.qn-mq { padding: 2px 2px 8px; }
.qn-mq-line { font-size: 11.5px; color: var(--text-faint); line-height: 1.45; font-variant-numeric: tabular-nums; opacity: 1; transition: opacity 300ms ease; }
.qn-mq-line.is-out { opacity: 0; }
/* The per-row "why" — the engine's own reading of this position, one line
   under the detail. A shade dimmer again so the row's own numbers lead. */
.qn-run-why { font-size: 11px; color: var(--text-faint); margin-top: 3px; line-height: 1.4; font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .qn-mq-line { transition: none; } }
.qn-run-sub { font-size: 11.5px; color: var(--text-dim); margin-top: 5px; white-space: normal; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-height: 1.45; font-variant-numeric: tabular-nums; }
/* The trigger-distance line under a watch row — quieter than the row's own sub. */
.qn-run-gap { font-size: 11px; color: var(--text-faint); margin-top: 2px; }
/* Why a touched door still waits — the watcher's recorded refusal sentence. */
/* A PENDING ROW CARRIES FIVE FACTS AND THE REAL ENTRY CONDITION.
   Owner 2026-08-16 asked for entry, leverage, TP, SL and how far away. A
   confirmation now also has to name the eligible closed candle and its honest
   give-up window. The old three-line cap cut that sentence at "if it…" and hid
   TP/SL at 390px, making the screen omit the condition it exists to explain.
   Only pending rows uncap; the ordinary position summary keeps its two lines. */
.qn-run-row.pend .qn-run-sub { display: block; overflow: visible; -webkit-line-clamp: unset; }
.qn-run-r { text-align: right; flex-shrink: 0; }
/* A REVIEW THAT CHANGED NOTHING IS A TICK, NOT A SENTENCE (owner 2026-08-16).
   Muted on purpose: green and red on this row mean money, and a check mark that
   borrowed the profit colour would read as a gain rather than as "looked at it,
   nothing needed doing". The time and the wording live on its tooltip. */
.qn-pass-tick {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; font-size: 16px; line-height: 1;
  color: var(--text-dim); cursor: default;
}
/* THE AGENT IS WORKING ON THIS POSITION. A sparkle, not a circular loader: the
   card and its money are already loaded. The tiny brighten/tilt is activity in
   the pass only; terminal checks and pending orders stay still. */
@keyframes qn-pass-working {
  0%, 100% { opacity: 0.58; transform: scale(0.86) rotate(-7deg); }
  45% { opacity: 1; transform: scale(1.08) rotate(6deg); }
  70% { opacity: 0.78; transform: scale(0.96) rotate(1deg); }
}
.qn-pass-active {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; font-size: 17px; line-height: 1;
  color: var(--accent); cursor: default; transform-origin: center;
  animation: qn-pass-working 1.35s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .qn-pass-active { animation: none; opacity: 1; transform: none; }
  .qn-order-pending { animation: none; }
}

.qn-run-big { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.qn-run-big.up { color: var(--sm-up); } .qn-run-big.down { color: var(--sm-down); }
.qn-run-big.dim { color: var(--text-faint); font-weight: 600; }
.qn-run-pct { font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.qn-run-pct.up { color: color-mix(in srgb, var(--sm-up) 70%, var(--text-dim)); }
.qn-run-pct.down { color: color-mix(in srgb, var(--sm-down) 70%, var(--text-dim)); }
.qn-run-pct.dim { color: var(--text-faint); font-weight: 500; }
.qn-run-x { flex-shrink: 0; color: var(--text-faint); font-size: 12px; padding: 4px 2px 4px 6px; cursor: pointer; border-radius: 6px; }
/* Only a filled position books money on tap — see .sm-pos-act.x.hold. The colour
   is the whole difference between the two, and costs no room; the words did
   (owner 2026-08-08, seeing the pill push a stop price into an ellipsis). */
.qn-run-x.hold { color: var(--sm-down); }
.qn-run-x:hover { color: var(--sm-down); }
/* It's keyboard-reachable now (role=button + tabindex), so it needs a focus ring —
   the row it sits in is itself a button, and an invisible focus stop inside another
   control is worse than no focus stop at all. */
.qn-run-x:focus-visible { outline: 2px solid var(--sm-down); outline-offset: 1px; color: var(--sm-down); }
.qn-feed { display: flex; flex-direction: column; width: 100%; background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 4px 0; }
.qn-feed-row { display: flex; gap: 10px; align-items: flex-start; padding: 8px 14px; }
.qn-feed-row + .qn-feed-row { border-top: 1px solid color-mix(in srgb, var(--text) 6%, transparent); }
.qn-dot { flex: 0 0 7px; height: 7px; border-radius: 50%; margin-top: 5px; background: var(--text-faint); }
.qn-dot.ok { background: var(--sm-up); } .qn-dot.bad { background: var(--sm-down); }
.qn-dot.warn { background: var(--warn); } .qn-dot.dim { background: var(--text-faint); }
.qn-feed-b { min-width: 0; }
.qn-feed-t { font-size: 12.5px; line-height: 1.4; }
.qn-feed-t.up { color: var(--sm-up); } .qn-feed-t.down { color: var(--sm-down); }
.qn-feed-ago { font-size: 10.5px; color: var(--text-faint); margin-top: 1px; }
.qn-band { background: var(--sm-card); border: 1px solid var(--border); border-radius: 16px; padding: 14px 16px; }
.qn-band-t { font-weight: 700; font-size: 13px; }
.qn-band-s { font-size: 12px; color: var(--text-dim); margin-top: 3px; line-height: 1.45; }
.qn-plays { display: flex; flex-direction: column; gap: 8px; }
.qn-play { background: var(--sm-card); border: 1px solid var(--border); border-radius: 14px; padding: 10px 14px; }
.qn-play-hrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.qn-play-h { font-weight: 700; font-size: 13px; }
.qn-play-big { margin-left: auto; font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; }
.qn-play-big.up { color: var(--sm-up); } .qn-play-big.down { color: var(--sm-down); }
.qn-play-big.dim { color: var(--text-dim); font-size: 14px; font-weight: 600; }
.qn-play-x { appearance: none; cursor: pointer; font: inherit; font-size: 11.5px; font-weight: 600; color: var(--text-dim); background: color-mix(in srgb, var(--text-dim) 12%, transparent); border: none; border-radius: 999px; padding: 4px 12px; }
.qn-play-x:hover { color: var(--sm-down); }
.qn-play-x:disabled { opacity: 0.6; cursor: default; }
.qn-play-d { font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; margin-top: 2px; }
.qn-play-st { font-size: 11px; color: var(--text-faint); margin-top: 2px; }

/* TA legend controls (owner 2026-07-27): chips are toggles; RANGE pill retired. */
.sm-tatoggle.off { opacity: 0.38; }
.sm-tamode { font-weight: 700; color: var(--sm-up); background: color-mix(in srgb, var(--sm-up) 14%, transparent); border-radius: 999px; }

/* Chart mode: M3 segmented control + foldable overlay chips (owner 2026-07-27). */
.sm-taseg { display: inline-flex; padding: 2px; border: 1px solid var(--border); border-radius: 999px; background: color-mix(in srgb, var(--sm-card) 70%, transparent); }
.sm-tafold { display: flex; flex-wrap: wrap; gap: 6px; overflow: hidden; max-height: 90px; opacity: 1; transition: max-height 0.28s cubic-bezier(0.2, 0, 0, 1), opacity 0.2s; }
.sm-tafold.closed { max-height: 0; opacity: 0; }
@media (prefers-reduced-motion: reduce) { .sm-tafold { transition: none; } }

/* TA chips: M3 outlined filter chips (owner 2026-07-27). */
/* Quant tiles: fewer + larger, so the small ones stay readable. */
.qn-tile-s { font-size: var(--sym, 15px); font-weight: 700; letter-spacing: -0.01em; white-space: nowrap; line-height: 1.15; }


/* Chart mode control — icons, hero top-right (owner 2026-07-27). */
.sm-chart-hero { position: relative; }
.sm-chart-mode { position: absolute; top: -4px; right: 0; z-index: 2; }
.sm-chart-mode .sm-taseg-b { padding: 5px 10px; }
.sm-chart-mode .material-symbols-rounded { font-size: 17px; line-height: 1; display: block; }
/* Summary as an M3 tonal card, consistent with the rest of the UI. */
/* The outer accent BOX this rule used to draw is gone (owner 2026-08-02: "we
   don't need the rectangle that wraps around the bubble area") — it was a
   leftover from the pre-chat-line design, silently winning the cascade over
   the chat-line rule above (later rule, equal specificity — the .qn-side-tabs
   lesson again). The bubble carries its own surface; the cols gap alone
   separates it from GENIE'S READ. */
.sm-asset-cols > .sm-verd { flex: 1 0 100%; margin-bottom: 0; }
/* The warn-state container tint that used to live here is gone (owner
   2026-08-02: "shouldn't have the brown rectangle around the speech bubble")
   — the second stale sibling of the rule removed at the same address earlier
   today. The bubble is the only surface; the words carry the warning. */
.sm-verd-title { font-size: 15px; font-weight: 700; }
/* One type scale for every pill in the TA row. */

/* Strategy headline stats — the "why use this" row (owner 2026-07-27). */
/* ONE tonal container, three columns, hairline dividers — three separate
   rounded boxes read as three cards competing for attention. */
.sm-strat-hi { display: flex; margin: 12px 0 0; background: color-mix(in srgb, var(--text) 5%, transparent); border-radius: 12px; overflow: hidden; }
.sm-strat-stat { flex: 1; padding: 10px 6px; text-align: center; }
.sm-strat-stat + .sm-strat-stat { border-left: 1px solid color-mix(in srgb, var(--text) 8%, transparent); }
.sm-strat-statv { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.15; letter-spacing: -0.01em; }
.sm-strat-statv.up { color: var(--sm-up); } .sm-strat-statv.down { color: var(--sm-down); }
.sm-strat-statk { font-size: 10.5px; font-weight: 500; color: var(--text-faint); margin-top: 3px; }
/* The next trade, drawn (owner 2026-08-05): recent tape + the would-be entry,
   target and stop — the thesis as one picture, labels in the right gutter. */
.sm-nt-svg { width: 100%; height: auto; display: block; margin-top: 4px; }
/* ── the trade replay, in the app's own chart idiom (2026-08-17) ──────────────
   It used to borrow `.sm-nt-*` below — the Next-trade CARD's classes, sized for a
   standalone card — and inside a folded row that read as a chart from another
   app: a bright oversized entry line, a giant `in 65,129.8` in the gutter, a
   large triangle. Its own namespace now, so restyling it could not disturb the
   Next-trade card, which nobody asked about.

   THE LEVEL IDIOM IS THE ASSET CHART'S: a quiet dashed line, and the price on a
   small rounded tag at the right edge. The geometry is duplicated rather than
   shared — `drawAssetChart` emits its tags as an HTML string inside a much larger
   innerHTML assembly, and reaching across that seam is real surgery on a hot
   path. Same radius, same 8.5px/700 text, same 92% fill. If the tag changes
   shape it must change in both places; `drawReplay`'s own comment says so too.

   Every colour is a theme token, so both themes follow. The marks do not
   animate; the loading slot below reuses the app-wide reduced-motion-safe
   skeleton. */
/* The feed chart's unresolved slot reuses `.sm-skel`, the app-wide loading
   idiom. Its footprint is the replay's fixed 92px SCREEN height at the exact
   same width and margins as `.sm-ig-chart`, so swapping in the populated SVG
   cannot move the rest of the card. The replay viewBox now follows rendered
   width; an aspect ratio here would grow the loader on a wide card while the
   real chart stays 92px tall. `.sm-skel` already pulses opacity only and its
   global reduced-motion rule makes this a static placeholder. */
.sm-rep-loading {
  display: block; width: calc(100% + 26px); height: 92px;
  margin: 10px -13px 6px;
}
.sm-rep-price { fill: none; stroke: var(--text); stroke-opacity: 0.7; stroke-width: 1.4; stroke-linejoin: round; }
.sm-rep-dot { fill: var(--text); }
/* The exit is an OPEN RING — the signal that reads with no colour at all. A
   closed trade ends hollow on its exit level; a running one ends in the filled
   dot above, on the last price. They used to be the same dot. */
.sm-rep-ring { fill: none; stroke-width: 1.6; }
.sm-rep-ring.exit-up { stroke: var(--sm-up); }
.sm-rep-ring.exit-down { stroke: var(--sm-down); }
.sm-rep-ring.exit-flat { stroke: var(--text-faint); }
/* ── OPEN AND CLOSE MUST NOT LOOK THE SAME (owner 2026-08-17) ─────────────────
   Three signals differ, not one, so the pair survives a colour-blind reader, a
   screenshot and a grayscale print:

     entry → DOTTED line, NEUTRAL tag, SOLID triangle
     exit  → DASHED line, TONED tag, OPEN RING

   This is the ASSET CHART'S rule, read from its own level table rather than
   invented here: it passes the entry row `var(--text)` (the neutral text tone)
   and every other level — tp, sl, and the `exit` mark — a toned colour with
   `dashed`. Quieter than the old 0.8/3-3 either way: a level is context for the
   tape, not the subject. */
.sm-rep-line { stroke-width: 1; opacity: 0.5; }
.sm-rep-line.dotted { stroke-dasharray: 1 4; }
.sm-rep-line.dashed { stroke-dasharray: 5 4; }
/* The entry is NEUTRAL — it is a fact about where, not a verdict about how it
   went. Slightly more opaque because a dotted line at 0.5 reads as absent. */
.sm-rep-line.entry { stroke: var(--text); opacity: 0.55; }
.sm-rep-line.exit-up { stroke: var(--sm-up); }
.sm-rep-line.exit-down { stroke: var(--sm-down); }
.sm-rep-line.exit-flat { stroke: var(--text-faint); }
/* The tag: filled rounded rect, the number centred inside it.
   `--sm-on-up` is the app's existing "readable ON a saturated tone" colour —
   #04140b in light, #ffffff in dark — and that decision is the same one a red
   fill needs, so both tones use it rather than inventing a second token. */
.sm-rep-tag rect { opacity: 0.92; }
.sm-rep-tag text { font-size: 8.5px; font-weight: 700; fill: var(--sm-on-up); }
/* The NEUTRAL entry tag: `--text` on `--bg`, which is exactly what the asset
   chart does for its own entry row (`col === "var(--text)" ? "var(--bg)" : …`). */
.sm-rep-tag.entry rect { fill: var(--text); }
.sm-rep-tag.entry text { fill: var(--bg); }
.sm-rep-tag.exit-up rect { fill: var(--sm-up); }
.sm-rep-tag.exit-down rect { fill: var(--sm-down); }
.sm-rep-tag.exit-flat rect { fill: var(--text-faint); }
.sm-rep-tri.long { fill: var(--sm-up); }
.sm-rep-tri.short { fill: var(--sm-down); }
/* The caption under the chart: one muted sub-line, tabular so the two prices
   line up, and allowed to wrap on a narrow card rather than clipping. */
.sm-rep-cap {
  font-size: 11px; line-height: 1.45; color: var(--text-faint);
  font-variant-numeric: tabular-nums; padding: 2px 2px 4px; min-width: 0;
}
.sm-nt-price { fill: none; stroke: var(--text); stroke-opacity: 0.8; stroke-width: 1.6; stroke-linejoin: round; }
.sm-nt-dot { fill: var(--text); }
.sm-nt-line { stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.8; }
/* The entry wears the trade's direction (owner 2026-08-05): green long, red
   short — same colors as the triangle mark riding the line. */
.sm-nt-line.in.long { stroke: var(--sm-up); }
.sm-nt-line.in.short { stroke: var(--sm-down); }
.sm-nt-line.tp { stroke: var(--sm-up); }
.sm-nt-line.sl { stroke: var(--sm-down); }
.sm-nt-lbl { font-size: 9.5px; font-weight: 600; }
.sm-nt-lbl.in.long { fill: var(--sm-up); }
.sm-nt-lbl.in.short { fill: var(--sm-down); }
.sm-nt-lbl.tp { fill: var(--sm-up); }
.sm-nt-lbl.sl { fill: var(--sm-down); }
.sm-nt-tri.long { fill: var(--sm-up); }
.sm-nt-tri.short { fill: var(--sm-down); }
/* Mode control sits ABOVE the chart, right-aligned — never over the candles. */
.sm-chart-mode { position: static; display: flex; justify-content: flex-end; margin: 0 0 6px; }

/* ---- TA controls: Material 3 filter chips (single source of truth) ----
   M3 spec: 32dp target, 8dp corner (chips are NOT pills), 1dp outline,
   labelSmall type, tonal secondary-container fill when selected. */
.sm-tatool {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; box-sizing: border-box; padding: 0 10px;
  border: 1px solid var(--border); border-radius: 8px;
  background: none; color: var(--text-dim);
  font: inherit; font-size: 10.5px; font-weight: 500; letter-spacing: 0.02em; line-height: 1;
  appearance: none; cursor: pointer;
  transition: background 0.16s, color 0.16s, border-color 0.16s, opacity 0.16s;
}
.sm-tatoggle:not(.off) { background: color-mix(in srgb, var(--text) 9%, transparent); color: var(--text); border-color: transparent; }
.sm-tatoggle.off { opacity: 0.55; }
.sm-tatool:hover { border-color: color-mix(in srgb, var(--text) 26%, var(--border)); }
.sm-taswatch { width: 10px; height: 2px; border-radius: 1px; background: var(--sw, var(--text-dim)); border: 0; margin: 0; flex: none; }
/* Segmented chart-mode control — same family, icon-sized. */
.sm-taseg { display: inline-flex; padding: 2px; gap: 2px; border: 1px solid var(--border); border-radius: 10px; background: none; }
.sm-taseg-b {
  appearance: none; cursor: pointer; font: inherit;
  display: flex; align-items: center; justify-content: center;
  height: 24px; padding: 0 9px; border: none; border-radius: 8px;
  color: var(--text-faint); background: none;
  transition: background 0.16s, color 0.16s;
}
.sm-taseg-b.on { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
.sm-tafold { gap: 6px; }

/* A candidate that failed the bar is EVIDENCE, not an offer. */
.sm-strat-card.rejected { opacity: 0.72; border-style: dashed; }
.sm-strat-rej { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint); border: 1px solid var(--border); border-radius: 6px; padding: 2px 7px; margin-left: auto; }
.sm-strat-rejwhy { font-size: 11.5px; color: var(--warn); line-height: 1.45; margin: 6px 0 2px; }
/* The watch-section header doubles as its collapse toggle — same label type
   as every other group heading (font: inherit was resetting the button to
   body size, which read as a foreign typeface). */
button.qn-run-grp-toggle {
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left; width: 100%; display: block;
  font-family: inherit; font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-faint); font-variant-numeric: tabular-nums;
}

/* ── the asset's own icon, with the initial disc as its fallback (2026-08-16) ──
 *
 * THE SIZE LIVES ON THE WRAPPER, NOT THE IMAGE, and that is the whole point: the
 * square is reserved from the first frame, so an icon that arrives late — or
 * never, and swaps to the disc — cannot reflow a list somebody is already
 * scrolling. Sizing the <img> instead would leave a 0×0 box until the bytes
 * landed and shift every row beneath it.
 */
.sm-aico {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 999px; overflow: hidden;
  background: var(--bg-elev-3);
  /* R136 — the letters sit under the picture, so a logo that draws nothing leaves them showing
     instead of an empty circle (owner's screenshot, 2026-08-26). */
  position: relative;
}
.sm-aico-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* The existing disc idiom, unchanged — this is the fallback, not a new look. */
.sm-aico-init {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; height: 100%;
  /* Sized by a knob rather than by a descendant override: a smaller disc sets
     the variable on itself, so no selector anywhere has to repeat this class
     name to shrink its letters. */
  font-size: var(--sm-aico-init-size, 11px); font-weight: 700; letter-spacing: 0.02em;
  /* R137 — `--text`, not `--text-dim`: measured against the disc's own ground these read 9.6:1 in
     the dark theme and 12.8:1 in the light one, where the dimmer token was 7.2:1 both ways. Both
     cleared 4.5:1, but the letters are 8-9px on the smallest disc and the brighter token is what
     makes them read at that size. */
  color: var(--text);
}
/* THE GROUND A THIRD-PARTY LOGO IS DRAWN ON (R137). White, because the icon route serves black
   glyphs (ETH, XRP, SOL, ZEC) beside coloured discs with white insides (BTC, ARB) and only white
   is right for both. Applied only while a picture is present — a disc showing letters keeps the
   app's own surface, in whichever theme the reader is in. */
.sm-aico.has-img { background: #fff; }
/* …and on that white ground the letters have to be dark, because they are what shows through a
   logo that draws nothing at all (R137). #1f1f1f on #fff is 16.1:1; the app's own `--text` would
   be 1.2:1 in the dark theme, which is the invisible disc the owner photographed. */
.sm-aico.has-img .sm-aico-init { color: #1f1f1f; }
/* R137 — a section host that hides itself really goes: `.qn-sec` sets `display: flex`, which beats
   the browser's own `[hidden] { display: none }`, so "Your strategies" with no rules still held a
   14-px band open on Home. */
.qn-sec[hidden] { display: none; }
/* The asset header stacks; the icon sits above the ticker rather than beside it. */
.sm-asset-head .sm-aico { width: 34px; height: 34px; margin-bottom: 2px; }
.sm-asset-head .sm-aico-init { font-size: 12px; }

/* ── a Past-trades row folds its replay open (2026-08-17) ─────────────────────
   The row became a <button> so it is keyboard reachable; these keep it looking
   exactly like the div it replaced rather than like a control. */
.sm-hist-row {
  appearance: none; font: inherit; text-align: left; cursor: pointer;
  background: none; border: none; width: 100%;
}
.sm-hist-row:disabled { cursor: default; }
.sm-hist-row:focus-visible { outline: 2px solid var(--accent, #4c8dff); outline-offset: -2px; border-radius: 10px; }
.sm-hist-host:empty { display: none; }

/* ── draggable level tags (2026-08-16) ────────────────────────────────────────
   `.grab` is added by `wireDragLines`, on the same pass that attaches the
   handlers — NOT by the markup that draws the tag (changed 2026-08-17). Every
   visible promise below therefore means "this one is actually wired": a tag the
   wiring never reached renders as a plain `.sm-lvltag` and is honestly inert,
   instead of looking grabbable and doing nothing, which is how two drag bugs
   stayed invisible. `touch-action: none` is what makes a touch drag work at all
   — without it the browser claims the gesture for scrolling before pointermove
   ever fires. */
.sm-lvltag.grab { cursor: grab; touch-action: none; }
.sm-lvltag.grab:active, .sm-lvltag.dragging { cursor: grabbing; }
/* A bigger invisible target than the 16px tag: a stop line is a small thing to
   catch with a thumb, and missing it is how a drag becomes a scrub. */
.sm-lvltag.grab rect { stroke: transparent; stroke-width: 10px; paint-order: stroke; }
.sm-lvltag.dragging rect { opacity: 1; }
.sm-lvltag.grab:focus-visible { outline: 2px solid var(--accent, #4c8dff); outline-offset: 1px; }
.smx.smx-shake { animation: smx-shake .34s cubic-bezier(.36, .07, .19, .97); }
.smx-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; z-index: 2; }

/* CRT scanlines + vignette + animated grain */
.smx-crt { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.smx-lines { position: absolute; inset: 0; background: repeating-linear-gradient(0deg, rgba(0, 0, 0, .20) 0 1px, transparent 1px 3px); mix-blend-mode: multiply; }
.smx-vig { position: absolute; inset: 0; box-shadow: inset 0 0 80px 20px rgba(0, 0, 0, .55); border-radius: 16px; }
.smx-grain {
  position: absolute; inset: -50%; pointer-events: none; z-index: 3; opacity: .08;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: smx-grain .5s steps(4) infinite;
}
.smx-full .smx-grain { opacity: .09; }

/* power-on line-expand flash (full cinematic only) */
.smx-power {
  position: absolute; inset: 0; z-index: 6; pointer-events: none; transform-origin: center; opacity: 0;
  background: radial-gradient(closest-side, rgba(220, 255, 240, .95), rgba(0, 212, 126, .4) 55%, transparent);
}
.smx-power.smx-power-play { animation: smx-power .55s ease-out forwards; }

/* centered readout */
.smx-read { position: absolute; left: 50%; top: 46%; transform: translate(-50%, -50%); text-align: center; pointer-events: none; z-index: 4; }
.smx-count { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 46px; font-weight: 700; color: #eaf7f0; letter-spacing: -.02em; line-height: 1; text-shadow: 0 0 24px rgba(0, 212, 126, .5); font-variant-numeric: tabular-nums; }
.smx-lbl { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 9.5px; letter-spacing: .26em; color: #7e8c84; text-transform: uppercase; margin-top: 6px; }

/* bottom telemetry strip */
.smx-telem { position: absolute; left: 0; right: 0; bottom: 0; padding: 20px 16px 18px; z-index: 4; background: linear-gradient(transparent, rgba(11, 12, 13, .55) 34%, rgba(11, 12, 13, .92)); }
.smx-status { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; font-weight: 700; letter-spacing: .12em; color: #00d47e; text-transform: uppercase; }
.smx-detail { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11px; color: #7e8a84; margin-top: 7px; min-height: 14px; letter-spacing: .02em; }
.smx-bar { height: 3px; border-radius: 3px; background: rgba(255, 255, 255, .06); margin-top: 11px; overflow: hidden; }
.smx-bar-fill { display: block; height: 100%; width: 0%; border-radius: 3px; background: linear-gradient(90deg, #00d47e, #7fe9c0); box-shadow: 0 0 10px rgba(0, 212, 126, .6); }
@media (prefers-reduced-motion: reduce) { .smx-grain, .smx-power { animation: none; } }
:root[data-theme="light"] .smx-lines { background: repeating-linear-gradient(0deg, rgba(0, 0, 0, .05) 0 1px, transparent 1px 3px); }
:root[data-theme="light"] .smx-vig { box-shadow: inset 0 0 80px 20px rgba(22, 62, 38, .12); }
:root[data-theme="light"] .smx-grain { opacity: .05; }
:root[data-theme="light"] .smx-full .smx-grain { opacity: .06; }
:root[data-theme="light"] .smx-power { background: radial-gradient(closest-side, rgba(255, 255, 255, .95), rgba(0, 143, 4, .3) 55%, transparent); }
:root[data-theme="light"] .smx-count { color: #10271a; text-shadow: 0 0 24px rgba(0, 143, 4, .3); }
:root[data-theme="light"] .smx-lbl { color: #5f6e64; }
:root[data-theme="light"] .smx-telem { background: linear-gradient(transparent, rgba(243, 246, 243, .55) 34%, rgba(243, 246, 243, .95)); }
:root[data-theme="light"] .smx-status { color: #008f04; }
:root[data-theme="light"] .smx-detail { color: #5f6e64; }
:root[data-theme="light"] .smx-bar { background: rgba(0, 0, 0, .08); }
:root[data-theme="light"] .smx-bar-fill { background: linear-gradient(90deg, #008f04, #35b24b); box-shadow: 0 0 10px rgba(0, 143, 4, .35); }
@media (prefers-reduced-motion: reduce) {
  .gsheet, .gsheet-scrim { transition: none; }
  .gsheet { transform: none; }
}


/* ── 聪明钱 Smart Money screen (docs/SMART_MONEY.md) ─────────────────────────
   Robinhood-minimal: hairline rows + whitespace, big numbers; --ok/--err are the
   only color, reserved for PnL and the candles. All data comes from the
   control-plane cache — this screen never spends a token. */
/* ── 聪明钱 scanner boot sequence (plugins/smartmoney/web/smartmoneyScanner.ts) ─
   A full-height square targeting grid that plays IN FRONT of the board while it
   loads: a beam sweeps top→bottom, wallet blips light with asset logos, the bot
   floor drops, and the #1 target locks — then it fades to the real board. Theme-
   adaptive instrument surface (light overrides at the end of this block; the canvas
   palette flips in smartmoneyScanner.ts). Full cinematic plays once per app
   session; warm reopens are load-gated. Skipped under reduced motion. */
.smx {
  position: relative; height: 78vh; border-radius: 16px; overflow: hidden;
  background: #0b0c0d;
  transition: opacity .5s ease, transform .6s cubic-bezier(.4, 0, .2, 1), filter .5s ease;
}

/* Light-theme scan (owner 2026-07-17: the scan adapts too) — same instrument in
   daylight. The canvas palette flips in smartmoneyScanner.ts; the chrome here. */
:root[data-theme="light"] .smx { background: #f3f6f3; }

/* ═══ ROUND 2 (2026-08-17): the last plugin-prefixed blocks ═══════════════
   The boot cinematic's .smx-* (smartmoneyScanner.ts moved into the plugin), the
   .gate and .gsheet connect sheet (secretDrop.ts moved with it), and every
   other .sm- and .qn- block core no longer renders. Core web now renders NO
   plugin-prefixed class: slide-to-confirm and pull-to-refresh were renamed to
   .slide- and .ptr- because they are generic interactions, and the plugin mount
   point and screen flag became .plugin-mount / .plugin-screen-open. */

.smx.smx-done { opacity: 0; transform: scale(1.04); filter: blur(6px); pointer-events: none; }
/* ═══════════════════════════════════════════════════════════════════════════
   THE WATCH LIST — the assets the owner picked, on Home under Top traders

   Owner PRD §4b (2026-08-18). One foldable card per watched asset and the scan
   button on the header — and, since 2026-08-19, nothing else: the suggestions
   row under the cards was deleted with the rest of the scan-era furniture.

   IT BORROWS THE SEARCH ROW'S ANATOMY ON PURPOSE. `.qn-result-l/-sym/-vol/-r/
   -px/-ch` are the app's existing "asset with its three numbers" row, and the
   owner's screenshot of the section is that row. Re-styling a lookalike would
   have given the same asset two different typographies on one screen.

   NO RAW COLOUR IN THIS BLOCK — every value is a token, so dark mode is free.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ═══ THE HEADER STRIP (owner ruling 2026-08-19) ════════════════════════════
   `WATCH LIST · 9 of 15 watched` … `next scan in 12:04` [✨]

   The count says what the LIST is and belongs to the heading; the right-hand
   slot says what the SCAN is doing and changes by the second. They were the
   other way round — a count sat beside the button where a status belongs. */
.qn-wl-head-r { display: flex; align-items: center; gap: 10px; flex: 0 1 auto; min-width: 0; }
/* Rides the overline, in sentence case: the heading is an 11px uppercase
   tracked label and a count set in it would shout a number nobody needs to
   read twice. The separator is punctuation, so it lives in CSS rather than in
   a translated string. */
.qn-wl-count { font-size: 11.5px; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--text-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
.qn-wl-count::before { content: "·"; margin: 0 6px; }
/* THE STATUS. Quiet, tabular so a ticking countdown does not jitter, and
   ALLOWED TO SHRINK — at 390px the strip is title, count, status and a 28px
   button, and the one of those that may give ground is the status. It
   ellipsises rather than wrapping the button onto a second line. */
.qn-wl-status {
  font-size: 11.5px; font-weight: 500; color: var(--text-faint);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* ── THE SCAN BUTTON: SMALL, DARK, SQUARE (owner 2026-08-19, on a screenshot
   of the round purple one it replaces) ────────────────────────────────────────
   IT BORROWS THE SHARED TRIGGER'S SIZE AND BEHAVIOUR AND NOT ITS COLOUR. The
   28px box, the centred glyph, the disabled and focus rules all come from
   `.qn-review-cta` / `.qn-review-cta-icon` — the Positions header's own trigger
   — so the two "go and work now" buttons on this screen stay the same physical
   control. What the owner ruled differently is the SURFACE: subdued and square,
   the section-header idiom, rather than the accent-tinted circle. Both values
   are existing tokens, so it is theme-aware for free. */
/* The heading half of the strip: it holds the title AND the count now, so it
   needs a floor of its own or a long count would squeeze the status to nothing
   before it gave up any of its own space. */
.qn-sec-watchlist .qn-sechead > :first-child { display: flex; align-items: baseline; min-width: 0; flex: 0 1 auto; }
.qn-wl-scan {
  margin: 0; border-radius: 9px;
  background: var(--bg-elev-2); color: var(--text);
}
/* NOT WHILE IT IS WORKING. A disabled button still matches `:hover` and `:active` — CSS does not
   stop matching them, only the click stops arriving — so without these guards the control would go
   on lighting up under the pointer while refusing every press, which reads as broken rather than
   as busy. */
.qn-wl-scan:hover:not(:disabled) { background: var(--bg-elev-3); }
.qn-wl-scan:active:not(:disabled) { background: var(--bg-elev-3); }
/* ── DISABLED WHILE A SCAN RUNS, AND THE ANIMATION STAYS FULLY VISIBLE ────────
   Owner 2026-08-24: "when the scan is happening, the scan button should not be clickable" and
   "it should show the animation that is loading as well". `.qn-review-cta:disabled` drops the
   whole control to 0.5 opacity, which is right for a control that simply cannot be used and wrong
   here: the breathing glyph IS the message, and a half-faded one reads as a dead button rather
   than a working one. The BOX still dims, so the target still says "not now"; the glyph does not. */
.qn-wl-scan:disabled { cursor: default; }
.qn-wl-scan.working:disabled { opacity: 1; background: var(--bg-elev-2); color: var(--text-dim); }
.qn-wl-scan.working:disabled .material-symbols-rounded { opacity: 1; color: var(--text); }
/* ── WHILE IT IS WORKING, THE GLYPH BREATHES ──────────────────────────────────
   Owner 2026-08-19: the icon itself plays the loading animation for the run's
   duration. It is `qn-breathe` — the app's OWN icon-button loading idiom, the
   one `.qn-autobtn.running svg` already wears — at the same 1.15s cadence as
   the watch card's own reading dot, so the header and the cards read as one
   thing happening rather than two.
   THE BUTTON ITSELF DOES NOT MOVE. Animating the whole control makes a thing
   you are about to tap unstable under the finger; the glyph carries the state
   and the target holds still. Reduced motion gets the state without the
   animation rather than nothing at all. */
.qn-wl-scan.working .material-symbols-rounded { animation: qn-breathe 1.15s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .qn-wl-scan.working .material-symbols-rounded { animation: none; opacity: 0.6; }
}
/* ═══ THE SCAN EXPERIMENT CONTROL ══════════════════════════════════════════
   A quiet two-column setting directly under the scan header: words take the
   flexible column and the switch keeps its hit target. Every colour is a live
   theme token, so light and dark states are the same component rather than a
   light-mode patch. */
.qn-wl-experiment {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 14px;
  margin: 0 0 10px; padding: 11px 12px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elev-1);
}
.qn-wl-exp-copy { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.qn-wl-exp-title { font-size: 12.5px; line-height: 1.4; font-weight: 650; color: var(--text); }
.qn-wl-exp-note { font-size: 11.5px; line-height: 1.4; color: var(--text-faint); }
.qn-wl-exp-sw {
  position: relative; flex: none; width: 44px; height: 26px; padding: 0; border: none;
  border-radius: 999px; background: var(--bg-elev-3); cursor: pointer;
}
.qn-wl-exp-knob {
  position: absolute; top: 4px; left: 4px; width: 18px; height: 18px; border-radius: 50%;
  background: var(--text-faint); transition: left .14s, background .14s;
}
.qn-wl-exp-sw.on { background: var(--sm-up); }
.qn-wl-exp-sw.on .qn-wl-exp-knob { left: 22px; background: var(--sm-on-up); }
.qn-wl-exp-sw:disabled { cursor: wait; opacity: 0.62; }
.qn-wl-exp-sw:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qn-wl-exp-error { margin: -4px 2px 10px; font-size: 11.5px; line-height: 1.4; color: var(--sm-down); }
@media (max-width: 430px) {
  .qn-wl-experiment { grid-template-columns: minmax(0, 1fr) 44px; gap: 10px; padding: 10px; }
}
/* Said once, quietly, and only while the list really is the seed: the owner
   should never think they picked these. */
.qn-wl-seeded { font-size: 12px; line-height: 1.45; color: var(--text-faint); padding: 0 2px 8px; }
.qn-wl-cards { display: flex; flex-direction: column; gap: 6px; }
.qn-wl-card { border-radius: 12px; background: var(--sm-card); border: 1px solid var(--border); overflow: hidden; }
/* ═══ THE ROW IS A GRID WITH RESERVED COLUMNS (owner 2026-08-19) ═════════════
   *"a better design for these watch list cards so that the layout doesn't feel
   awkward when it's loading"*.

   WHAT WAS AWKWARD, precisely. `.qn-result` is `justify-content: space-between`
   over three variable-width children, and the verdict slot changed size between
   states — a 9px dot became a 28px pill. So when a scan landed, every price
   column in the list physically shifted, eight rows jumping at eight different
   moments. Nothing was loading badly; the geometry was measuring its own
   contents.

   FOUR TRACKS, AND ONLY ONE OF THEM CAN CHANGE SIZE:

     40px    the leading avatar — M3's two-line list-item leading element
     1fr     symbol + volume, the only flexible track, so it absorbs everything
     84px    price + change, wide enough for $12,345,678 and for $0.00137 at
             this size, so the number's box does not depend on the number
     32px    the verdict, THE RULE THIS BLOCK EXISTS FOR: 32px whether it holds
             a check, a cross, a breathing dot, or nothing at all

   Three of the four are fixed, so the row's shape is decided by the stylesheet
   and never by whether a value has arrived. That is what makes a skeleton
   honest here: it can occupy exactly the box its value will, because the box
   was never the value's to choose.

   ONE RIGHT EDGE FOR EVERY PRICE, for free: the last two tracks are fixed, so
   the price column ends at the same x on every card in the list whatever the
   digits are. The screenshot's ragged right edge was the old `flex: 0 0 auto`. */
.qn-wl-row {
  display: grid;
  grid-template-columns: 40px 1fr 84px 32px;
  align-items: center;
  gap: 12px;
  /* M3 two-line list item: 72dp minimum, 16dp horizontal inset. */
  min-height: 72px;
  padding: 12px 16px;
  border-radius: 0;
  /* `.qn-result` sets `justify-content: space-between` for a flex row; on a
     grid it would distribute the TRACKS and undo everything above. */
  justify-content: stretch;
}
/* M3's state layer, kept from `.qn-result` — but `.qn-result` also sets
   `outline: none` on focus, which leaves a keyboard user with no idea which row
   they are on. These rows are buttons. The ring is not optional. */
.qn-wl-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.qn-wl-row:active { background: color-mix(in srgb, var(--text) 11%, transparent); }
/* 40dp leading avatar (M3), up from 26px. */
.qn-wl-ico { width: 40px; height: 40px; flex: none; border-radius: 999px; }
/* Track 2. `min-width: 0` is what lets a long name ellipsis instead of pushing
   the fixed tracks off the row — the one way a grid can still be shoved. */
.qn-wl-words { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* M3 title-small for the ticker, body-small for the volume beneath it. */
.qn-wl-row .qn-result-sym { font-size: 14px; font-weight: 600; letter-spacing: 0.1px; line-height: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qn-wl-row .qn-result-vol { font-size: 12px; font-weight: 400; line-height: 16px; color: var(--text-faint); margin-top: 0; font-variant-numeric: tabular-nums; }
/* Track 3. Right-aligned on the shared edge, and tabular so the digits do not
   jitter left and right as a price ticks. */
.qn-wl-nums { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; min-width: 0; font-variant-numeric: tabular-nums; }
.qn-wl-row .qn-result-px { font-size: 14px; font-weight: 600; line-height: 20px; font-variant-numeric: tabular-nums; }
/* M3 label-medium. The percentage carries the only colour on the row — no
   direction tinting of the whole card (owner: the delta is enough). */
.qn-wl-row .qn-result-ch { font-size: 12px; font-weight: 500; line-height: 16px; margin-top: 0; font-variant-numeric: tabular-nums; }
/* ═══ TRACK 4: 32px IN EVERY STATE, INCLUDING EMPTY ═════════════════════════
   The whole point. The box exists on an idle card with nothing in it, so a
   verdict arriving fills a space that was already there. */
.qn-wl-verdict { width: 32px; height: 32px; display: grid; place-items: center; }
.qn-wl-chip { margin-left: 0; }
/* READING = A PULSE AND NOTHING ELSE. A chart still out has no answer, and any
   icon in this slot would be one. Reuses the app's own breathing keyframe. */
.qn-wl-dot {
  flex: none; width: 9px; height: 9px; margin-left: 0; border-radius: 999px;
  background: var(--text-dim); animation: qn-breathe 1.15s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) { .qn-wl-dot { animation: none; opacity: 0.7; } }
/* ═══ A MISSING NUMBER IS A SHAPE, NOT AN ABSENCE ═══════════════════════════
   Each skeleton is the exact line box of the value it stands in for — same
   height, same line, inside the same fixed track — so the number replacing it
   moves nothing. `.sm-skel` is the app's own loading idiom and brings the
   pulse and its reduced-motion rule with it. */
.qn-wl-skel { display: block; border-radius: 4px; }
.qn-wl-skel-px { height: 20px; width: 68px; }
.qn-wl-skel-ch { height: 16px; width: 44px; }
.qn-wl-skel-vol { height: 16px; width: 92px; }
/* NOT REPORTED, and not going to be. A dash is the honest mark for a value the
   venue does not carry — never a zero, which is a measurement nobody took. It
   sits on the same line as the number it replaces. */
.qn-wl-none { color: var(--text-faint); font-variant-numeric: tabular-nums; }
.qn-wl-row .qn-result-px.qn-wl-none, .qn-wl-row .qn-result-ch.qn-wl-none { color: var(--text-faint); }
/* The expanded state reads as a tonal surface (M3), which is also the only
   thing on this row that may signal it — a chevron would need a fifth track and
   a track that appears only sometimes is the jump this block just removed. */
.qn-wl-card[data-open="true"] { background: var(--bg-elev-1); }
.qn-wl-card[data-open="true"] .qn-wl-row { background: color-mix(in srgb, var(--text) 4%, transparent); }
.qn-wl-open { display: flex; flex-direction: column; gap: 8px; padding: 0 16px 12px; }
/* One short owner sentence. Exact failure detail stays on the banked receipt;
   this surface never renders model working-out. */
.qn-wl-read { font-size: 12.5px; line-height: 1.5; color: var(--text-dim); }
.qn-wl-quiet { color: var(--text-faint); }
/* WHAT WAS DECIDED, AND WHY — the boxes the agent filled in and its own reason, in one list of
   label and value. The markup shipped on 2026-08-24 with no rule of its own, so every box came out
   as a bare block in the body font: the label and the value ran together on one line and the whole
   list read as a paragraph. Two columns wide, stacked narrow, and the value carries the weight
   because the value is the answer. */
.qn-wl-form {
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px 10px; border-radius: 10px;
  background: var(--bg-elev-2); border: 1px solid var(--border);
}
.qn-wl-frow { display: grid; grid-template-columns: minmax(0, 136px) minmax(0, 1fr); gap: 2px 12px; align-items: baseline; }
.qn-wl-flab { font-size: 11.5px; line-height: 1.5; color: var(--text-faint); }
/* `anywhere` because a value can be one long unbroken price or a citation-free sentence, and a
   card that scrolls sideways on a phone is the failure this section already fixed once.
   `max-width` because the agent's reason is a paragraph: on a 1900px screen the value column is
   over 1500px wide and one line of prose runs the whole way across, which is a line nobody can
   read back to the start of. 80 characters is the measure the rest of this app's prose uses. */
.qn-wl-fval { font-size: 12.5px; line-height: 1.5; color: var(--text); overflow-wrap: anywhere; max-width: 80ch; }
@media (max-width: 520px) { .qn-wl-frow { grid-template-columns: minmax(0, 1fr); } }
/* THE CONTROL THAT OPENS THE AGENT'S OWN WORDS. Same shape as "stop watching" below — a text
   button with a 44px target — because they sit in the same fold and a second button idiom here
   would read as a second kind of action. */
.qn-wl-words-btn {
  align-self: flex-start; display: inline-flex; align-items: center;
  appearance: none; background: none; border: none; min-height: 44px; padding: 0;
  font: inherit; font-size: 12px; color: var(--text-dim); cursor: pointer; text-decoration: underline;
}
.qn-wl-words-btn:hover { color: var(--text); }
.qn-wl-words-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE QUOTATION. `pre-wrap` is what makes "exactly as written, paragraph breaks and all" true on
   the screen rather than only in the text node — without it the agent's paragraphs collapse into
   one run and the card shows a tidied version of something presented as a quotation. */
.qn-wl-said {
  font-size: 12px; line-height: 1.55; color: var(--text-dim); white-space: pre-wrap;
  overflow-wrap: anywhere; padding: 8px 10px; border-radius: 10px;
  background: var(--bg-elev-2); border: 1px solid var(--border);
}
/* ≥44px, both of the buttons inside the fold. The arrow button measured 31px
   and the "stop watching" link 20px — under the 44pt iOS and 48dp M3 minimums,
   on the two controls that actually change something. */
.qn-wl-go {
  align-self: flex-start; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  appearance: none; font: inherit; font-size: 12.5px; font-weight: 620; cursor: pointer;
  min-height: 44px; padding: 0 16px; border-radius: 999px; border: 1px solid color-mix(in srgb, var(--sm-up) 40%, transparent);
  background: color-mix(in srgb, var(--sm-up) 12%, transparent); color: var(--sm-up);
}
.qn-wl-go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qn-wl-goico { font-size: 16px; line-height: 1; }
.qn-wl-golab { line-height: 1; }
.qn-wl-drop {
  align-self: flex-start; display: inline-flex; align-items: center;
  appearance: none; background: none; border: none; min-height: 44px; padding: 0;
  font: inherit; font-size: 12px; color: var(--text-faint); cursor: pointer; text-decoration: underline;
}
.qn-wl-drop:hover { color: var(--text-dim); }
.qn-wl-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qn-wl-empty { font-size: 12.5px; line-height: 1.5; color: var(--text-faint); padding: 6px 2px; }
/* THE SERVER'S OWN SENTENCE, shown as it was written — "You are watching 15
   assets, which is the limit (15). Remove one to add NEAR." Never clamped: the
   number at the end is the part that answers the question. */
.qn-wl-refusal { font-size: 12.5px; line-height: 1.5; color: var(--sm-down); padding: 8px 2px 2px; }

/* THE "MOVING TODAY, NOT ON YOUR WATCH LIST" ROWS ARE GONE (owner 2026-08-19:
   "removing all these"). `.qn-wl-sugs`, `-sugh`, `-sug`, `-sug-l`, `-sugwhy`,
   `-sugage` and `.qn-wl-star` went with them. The star that adds an asset lives
   on the asset's own screen — the rule directly below. */

/* ── The star on the asset screen (owner PRD §4b: top right) ──────────────── */
/* A ROW, NOT A SECOND ABSOLUTE BOX. The share button is hidden on most assets —
   it appears only once a shareable play exists — so anything positioned
   relative to it would float over empty space most of the time. A flex row
   pinned to the corner is correct whichever of the two is present. */
.sm-asset-acts { position: absolute; top: 4px; right: 2px; display: flex; align-items: center; gap: 6px; }
.sm-asset-acts .sm-share-btn { position: static; }
.sm-watch-btn {
  width: 38px; height: 38px; display: grid; place-items: center; flex: none;
  background: var(--sm-card); border: 1px solid var(--border); border-radius: 999px;
  color: var(--text-dim); cursor: pointer;
}
.sm-watch-btn:hover { color: var(--text); }
/* Watched: filled, in the app's own accent — the one state that must read at a
   glance from across the screen. */
.sm-watch-btn.on { color: var(--sm-up); border-color: color-mix(in srgb, var(--sm-up) 42%, transparent); background: color-mix(in srgb, var(--sm-up) 12%, transparent); }
/* HOLLOW vs FILLED is the FILL axis of the variable font, not a second glyph:
   the self-hosted subset is a fixed list of icon NAMES, and a name outside it
   renders as its raw ligature text ("star_outline") in the corner of the
   screen. The axis is in the subset; the second name would not be. */
.sm-watch-ico { font-size: 20px; line-height: 1; font-variation-settings: "FILL" 0, "wght" 500, "GRAD" 0, "opsz" 24; }
.sm-watch-btn.on .sm-watch-ico { font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 24; }

/* ── HOME ON A PHONE: POSITIONS | WATCH LIST AS TWO TABS ─────────────────────
 *
 * Owner ruling 2026-08-21. Stacked, these two sections are about two screens of
 * scrolling on a narrow phone, and the one a person opens the app to see — what
 * am I actually in — is the one below the fold. So on narrow widths they become
 * two tabs, Positions first on every load.
 *
 * ═══ WHY A CONTAINER QUERY AND NOT A MEDIA QUERY ═══
 *
 * `.qn-wrap` is capped at 980px and centred, so on a 1900px screen the CONTAINER
 * is still ~980px wide. A `@media (max-width: 520px)` rule would ask the wrong
 * element entirely: it would be false on a wide screen for the right reason and
 * true on a narrow one by luck, and it would stop being either the moment the
 * wrap is nested in something narrower. `qnwrap` is the thing whose width
 * decides whether these sections fit side by side, so `qnwrap` is what is asked
 * — the same container the 860px wide layout above already keys off.
 *
 * The bar is display:none outside this block, so the wide layout renders the
 * same sections in the same order it did before this existed.
 */
.qn-hometabs { display: none; }
@container qnwrap (max-width: 520px) {
  .qn-hometabs { display: flex; gap: 8px; padding: 2px 0; }
  /* Half each, so neither label's length decides how much of the bar it gets. */
  .qn-hometab {
    flex: 1 1 0; min-width: 0; text-align: center; white-space: nowrap;
    font-size: 13px; font-weight: 560; color: var(--text-dim);
    background: var(--bg-elev-1); border: none; border-radius: 999px;
    padding: 11px 12px; line-height: 1; cursor: pointer;
    transition: background .14s, color .14s;
  }
  .qn-hometab.on { background: var(--sm-accent, var(--accent)); color: var(--bg); font-weight: 640; }
  /* ONE AT A TIME. Both stay mounted and keep polling — only the display flips,
     so a tab tap never costs the watch list its tick or refetches its rows. */
  .qn-cols.qn-hometab-positions > .qn-sec-watchlist { display: none; }
  .qn-cols.qn-hometab-watch > .qn-side { display: none; }
}

/* ── The coin's face on a position row (owner 2026-08-21) ─────────────────────
 *
 * The watch list's tile is 40px because its rows are cards with room to spare.
 * A position row is a two-line strip at 14px padding, and 40px there crowds the
 * numbers on the narrow wide-column layout — so the same tile is drawn a size
 * down. It is the SAME element and the same fallback; only the diameter differs,
 * and the letters follow it through the knob `.sm-aico-init` already reads
 * rather than a second rule repeating that class name.
 */
/* TOP-ALIGNED, unlike the watch list's. A watch row is one line and centring reads right there;
   a position row wraps to two or three (`2× · wants $76,400 · 1.4% away · TP … · SL …`), and a
   centred tile then floats level with the numbers instead of the ticker it names. Lined up with
   the symbol, which is what the eye goes to first. */
.qn-run-ico { width: 34px; height: 34px; flex: none; border-radius: 999px; --sm-aico-init-size: 12px; align-self: flex-start; margin-top: 1px; }

/* ═══════════════════ THE PLAYBOOK TAB ═══════════════════
   One action-first card per lesson. Shut, the card contains only the two server-authored action
   lines (or the untouched legacy sentence), evidence metadata, and the switch. Its action surface
   is also the fold control; every other fact and control stays inside the fold.

   NO NEW CUSTOM PROPERTIES. The mockup's --accent/--on-accent pair is the app's own mono
   inversion: var(--text) on var(--bg) is white-on-near-black in dark and near-black-on-white in
   light, which is exactly what "mono accent for Add and Save, never the green" asks for. The
   green stays where it means something — the switch that says a rule is live. */
.sm-tab-n { margin-left: 5px; font-variant-numeric: tabular-nums; }

.qn-rul-srow { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
.qn-rul-search { flex: 1; min-width: 0; background: var(--bg-elev-2); color: var(--text); border: none;
  border-radius: 999px; padding: 11px 15px; font: inherit; font-size: 14px; }
.qn-rul-search::placeholder { color: var(--text-faint); }
.qn-rul-add { flex: none; background: var(--text); color: var(--bg); border: none; border-radius: 999px;
  padding: 11px 17px; font: inherit; font-size: 14px; font-weight: 700; white-space: nowrap; cursor: pointer; }

.qn-rul-status:empty { display: none; }
.qn-rul-status { color: var(--warn); font-size: 13px; padding: 4px 0 10px; }
.qn-rul-totals { display: flex; flex-wrap: wrap; gap: 5px 9px; margin: 0 0 10px; color: var(--text-faint);
  font-size: 11px; font-variant-numeric: tabular-nums; }
.qn-rul-totals:empty { display: none; }

.qn-rul-cards { display: flex; flex-direction: column; gap: 10px; }
.qn-rul-card { background: var(--bg-elev-1); border-radius: 16px; padding: 13px 14px; }
/* An off rule is dimmed, not hidden: off keeps its history, and a rule you cannot see is a rule
   you cannot turn back on. */
.qn-rul-card.off { opacity: .55; }

/* The action/meta surface is the invisible fold button. The switch stays a sibling so changing
   reach can never also open the card. No caret or “Details” word is visible on a shut row. */
.qn-rul-summary-row { display: flex; align-items: flex-end; gap: 12px; }
.qn-rul-summary { min-width: 0; flex: 1 1 auto; padding: 0; border: 0; background: none; color: inherit;
  font: inherit; text-align: left; cursor: pointer; }
.qn-rul-summary:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; border-radius: 6px; }
.qn-rul-actions { display: grid; gap: 9px; }
.qn-rul-action { min-width: 0; }
.qn-rul-action-label { color: var(--text-faint); font-size: 10.5px; font-weight: 700; line-height: 1.25; }
.qn-rul-action-text { margin-top: 2px; color: var(--text); font-size: 14px; line-height: 1.42;
  overflow-wrap: anywhere; }
.qn-rul-summary .qn-rul-meta { margin-top: 9px; justify-content: flex-start; }
.qn-rul-details { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.qn-rul-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.qn-rul-detail-text { margin-top: 8px; color: var(--text-dim); font-size: 12.5px; line-height: 1.45;
  overflow-wrap: anywhere; }
.qn-rul-details-title { margin-top: 11px; color: var(--text-faint); font-size: 11.5px; }

.qn-rul-tagrow { display: flex; align-items: center; flex-wrap: wrap; gap: 7px 10px; margin-bottom: 8px; }
/* ── PROVENANCE IS THE QUIETEST THING ON THE CARD ──
   It was the loudest: a saturated green MANUAL outranked both the rule and whether the rule is
   reaching the agent, which are the two things somebody opened this screen to read. Worse, on a
   switched-off card the green tag still read as "active" against the dimmed treatment — the card
   contradicted itself (dede, live, 2026-08-22).

   So both tags take one neutral treatment and are told apart by the word, which is the only thing
   that actually differs. This drops the mockup's blue/green pair deliberately: that distinction
   cost more than it bought, and colour on this card now means one thing — the switch is green
   because the rule is live. */
.qn-rul-tag { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 8px;
  color: var(--text-faint); background: color-mix(in srgb, var(--text) 8%, transparent); }
.qn-rul-meta { min-width: 0; display: flex; flex: 1 1 210px; flex-wrap: wrap; justify-content: flex-end;
  gap: 3px 8px; color: var(--text-faint); font-size: 11px; font-variant-numeric: tabular-nums; }
.qn-rul-meta > * { min-width: 0; }
.qn-rul-coin { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-dim); }
.qn-rul-meta > * + *::before { content: "·"; margin-right: 8px; color: var(--text-faint); }
.qn-rul-score.unknown { color: var(--text-faint); }

.qn-rul-rule { margin: 0; color: var(--text); font-size: 14px; line-height: 1.42; overflow-wrap: anywhere; }
.qn-rul-rule.muted { color: var(--text-dim); }

.qn-rul-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; min-height: 22px; }
/* ── WHETHER THIS RULE IS REACHING THE AGENT (PM ruling 05) ──
   The bottom-left slot used to hold a call count. It cannot hold one honestly until rule ids ride
   to the trade row, so it holds the thing that is real and does differ per rule.

   THE TWO STATES MUST BE TELLABLE APART WITHOUT READING THEM. Same size, same place, different
   weight and different colour: "shown" is the ordinary state and stays quiet; "elsewhere" is the one
   the owner can do something about — retire something older, or switch a rule off — so it takes
   the same amber every other actionable-but-not-broken thing in this app takes. A rule that is off
   renders nothing here at all; its switch already says so. */
.qn-rul-reach { font-size: 12.5px; color: var(--text-faint); }
.qn-rul-reach.shown { color: var(--text-dim); }
.qn-rul-reach.elsewhere { color: var(--warn); font-weight: 600; }

/* ── THE RESULT SPLIT, ONLY AFTER THE CARRY GATE ──
   A total alone is rule age. The three outcomes lead; the total is quiet context beside them.
   Compliance is not coloured like profit: red/green remain on the actual dollar result in the
   unfolded rows. Amber keeps its existing job — a rule that has not reached the agent needs
   attention, just like a queued rule. A rule awaiting the first post-creation trade stays neutral:
   there was no opportunity to record, so painting its honest zero amber would invent a defect. */
.qn-rul-calls { flex: 1 1 0; min-width: 0; max-width: calc(100% - 70px); display: flex; align-items: baseline; gap: 5px;
  padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; }
button.qn-rul-calls { cursor: pointer; }
button.qn-rul-calls:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; border-radius: 4px; }
.qn-rul-split { min-width: 0; display: flex; flex: 1 1 auto; flex-wrap: wrap; gap: 2px 6px;
  font-size: 11.5px; font-variant-numeric: tabular-nums; }
.qn-rul-metric { color: var(--text-dim); white-space: nowrap; }
.qn-rul-metric.broke { color: var(--text); font-weight: 650; }
.qn-rul-metric.unjudged { color: var(--text-faint); }
.qn-rul-call-state { color: var(--text-dim); font-size: 12.5px; }
.qn-rul-calls.missing-call .qn-rul-call-state,
.qn-rul-calls.never-shown.elsewhere .qn-rul-call-state { color: var(--warn); font-weight: 600; }
.qn-rul-total { flex: none; color: var(--text-faint); font-size: 10.5px; white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.qn-rul-total::before { content: "·"; margin-right: 4px; }
.qn-rul-caret { flex: none; color: var(--text-faint); font-size: 14px; line-height: 1; }
.qn-rul-foot.has-calls .qn-rul-paused { flex: 1 0 100%; order: 3; }

/* The mockup's unfolded ledger: four factual columns, newest first from the renderer. No “Show
   all” control exists until there is a real route for the rows beyond the server's recent-eight. */
.qn-rul-sub { margin-top: 11px; background: var(--bg-elev-2); border-radius: 12px; overflow: hidden; }
.qn-rul-subhead { padding: 9px 12px; border-bottom: 1px solid var(--border); color: var(--text-faint);
  font-size: 11.5px; line-height: 1.35; }
.qn-rul-call { display: grid; grid-template-columns: 44px 54px minmax(0, 1fr) auto; align-items: center;
  gap: 8px; padding: 9px 12px; font-size: 12.5px; font-variant-numeric: tabular-nums; }
.qn-rul-call + .qn-rul-call { border-top: 1px solid var(--border); }
.qn-rul-call-symbol { color: var(--text); font-weight: 700; font-variant-numeric: normal; overflow: hidden;
  text-overflow: ellipsis; }
.qn-rul-call-date { color: var(--text-faint); white-space: nowrap; }
.qn-rul-call-outcome { min-width: 0; color: var(--text-faint); }
.qn-rul-call-outcome.broke { color: var(--sm-down); font-weight: 600; }
.qn-rul-call-outcome.followed { color: var(--text-dim); }
.qn-rul-call-money { min-width: 54px; text-align: right; font-weight: 700; white-space: nowrap; }
.qn-rul-call-money.up { color: var(--sm-up); }
.qn-rul-call-money.down { color: var(--sm-down); }
.qn-rul-call-money.none { color: var(--text-faint); font-weight: 500; }

/* ═══ WHY THIS LESSON EXISTS — the write-time snapshot ═══
   This surface exists only inside the card's outer fold. It gets the same raised treatment as the
   trade ledger because it is the same kind of thing — frozen receipts.
   EVERY COLOUR IS A THEME TOKEN, so light and dark come from the same rules. Amber is deliberately
   NOT used for the open question: amber means "this needs attention" everywhere else on this
   screen, and "we have not measured which threshold is better" is a caveat, not a defect. */
.qn-rul-why { margin-top: 9px; width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 7px 2px 0; border: 0; border-top: 1px solid var(--border); background: none;
  color: inherit; font: inherit; text-align: left; cursor: pointer; }
.qn-rul-why-label { color: var(--text-faint); font-size: 11.5px; }
.qn-rul-why.open .qn-rul-why-label { color: var(--text-dim); }
.qn-rul-why:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; border-radius: 4px; }
.qn-rul-why-body { margin-top: 9px; background: var(--bg-elev-2); border-radius: 12px; padding: 11px 12px;
  font-size: 12.5px; }
.qn-rul-why-line { color: var(--text-dim); line-height: 1.45; }
/* The threshold disagreement. A left rule marks it as an aside on the numbers under it. */
.qn-rul-why-open { margin-top: 8px; padding-left: 9px; border-left: 2px solid var(--border);
  color: var(--text-faint); font-size: 12px; line-height: 1.4; }
.qn-rul-why-open-label { color: var(--text-dim); font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; }
.qn-rul-why-open-text { margin-top: 2px; overflow-wrap: anywhere; }
/* Receipts stack rather than squeeze into an eight-column table. This keeps entry, exit, best,
   kept, result and the story visible at 390px without handing horizontal overflow to the page. */
.qn-rul-why-scroll { margin: 7px -12px 0; padding: 0 12px; overflow-x: hidden; }
.qn-rul-why-table { min-width: 0; }
.qn-rul-receipt { padding: 9px 0; }
.qn-rul-receipt + .qn-rul-receipt { border-top: 1px solid var(--border); }
.qn-rul-receipt-head { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline; gap: 7px; }
.qn-rul-receipt-coin { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text); font-weight: 700; }
.qn-rul-receipt-date, .qn-rul-receipt-outcome { color: var(--text-faint); font-size: 11px; }
.qn-rul-receipt-story { margin-top: 4px; color: var(--text-dim); font-size: 12px; line-height: 1.4;
  overflow-wrap: anywhere; }
.qn-rul-receipt-facts { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px;
  margin-top: 8px; font-variant-numeric: tabular-nums; }
.qn-rul-why-cell { min-width: 0; color: var(--text-dim); }
.qn-rul-receipt-fact { display: flex; min-width: 0; flex-direction: column; gap: 1px; }
.qn-rul-receipt-label { color: var(--text-faint); font-size: 9.5px; }
.qn-rul-receipt-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text-dim); font-size: 11px; }
.qn-rul-why-cell.money { font-weight: 650; white-space: nowrap; }
.qn-rul-why-cell.money.up { color: var(--sm-up); }
.qn-rul-why-cell.money.down { color: var(--sm-down); }
.qn-rul-why-cell.money.none { color: var(--text-faint); font-weight: 500; }
.qn-rul-why-subhead { margin-top: 12px; color: var(--text-faint); font-size: 11.5px; }
.qn-rul-why-when { margin-top: 8px; color: var(--text-faint); font-size: 11px; line-height: 1.4; }
.qn-rul-sharpening { margin-top: 3px; }
.qn-rul-sharpen { padding: 5px 0; }
.qn-rul-sharpen + .qn-rul-sharpen { border-top: 1px solid var(--border); }
.qn-rul-sharpen-text { margin-top: 2px; color: var(--text-dim); font-size: 12px; line-height: 1.4;
  overflow-wrap: anywhere; }
.qn-rul-sharpen-changed { margin-top: 2px; color: var(--text-faint); font-size: 11px; line-height: 1.4;
  overflow-wrap: anywhere; }

@media (max-width: 420px) {
  .qn-rul-meta { flex-basis: 100%; justify-content: flex-start; }
  .qn-rul-coin { max-width: 100%; }
  .qn-rul-receipt-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 8px; }
}
/* The route's own sentence about counting, once, above the list. Quiet: it explains a blank, it is
   not itself news. */
/* An explanation, not a footnote. It was 12px faint text floating above the list and read as a
   stray implementation note; it is the only thing standing between a reader and inventing their own
   reason for a missing number, so it gets a surface and the dim weight the rest of the app gives a
   real sentence. */
.qn-rul-note { font-size: 12.5px; line-height: 1.45; color: var(--text-dim); background: var(--bg-elev-1);
  border-radius: 12px; padding: 10px 12px; margin-bottom: 10px; }
.qn-rul-note:empty { display: none; }
/* Brain house rules reach the agent and can never be listed here (PM ruling 06 R15). Same quiet
   treatment as the note — it is a boundary of the screen, not an alert. NO `:empty` rule, and that
   is deliberate: this element is never built when there is nothing to say, so a hiding rule would
   be covering for a bug rather than a state. */
.qn-rul-brain { font-size: 12px; color: var(--text-faint); padding: 0 2px 10px; }
/* ZERO IS A WARNING, NOT A DIM NOTHING. A rule at zero calls after a week means the wiring is
   broken, and that is the single most useful thing this screen can tell anybody. */
.qn-rul-paused { font-size: 12px; color: var(--text-faint); }
.qn-rul-sw { width: 38px; height: 22px; border-radius: 999px; background: var(--bg-elev-2); border: none;
  position: relative; flex: none; padding: 0; cursor: pointer; }
.qn-rul-knob { position: absolute; top: 4px; left: 4px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--text-faint); transition: left .14s, background .14s; }
.qn-rul-sw.on { background: var(--sm-up); }
.qn-rul-sw.on .qn-rul-knob { left: 20px; background: var(--sm-on-up); }
.qn-rul-dots { color: var(--text-faint); font-size: 17px; line-height: 1; letter-spacing: 1px;
  background: none; border: none; padding: 0 2px; cursor: pointer; }

/* Two lines now, not one: what the guess is, and what confirming it does. The buttons drop under
   them rather than competing for the same row — at 390px a sentence that explains itself plus two
   controls does not fit on one line, and squeezing it was how the old version ended up saying only
   half of what it needed to. */
.qn-rul-ask { margin-top: 11px; background: var(--bg-elev-2); border-radius: 12px; padding: 10px 12px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 9px; font-size: 12.5px; color: var(--text-dim); }
.qn-rul-ask-said { flex: 1 1 100%; min-width: 0; }
.qn-rul-ask b { color: var(--text); font-weight: 700; }
/* The reason it is worth answering, quieter than the guess itself. */
.qn-rul-ask-why { color: var(--text-faint); margin-top: 2px; }
.qn-rul-yes { margin-left: auto; font: inherit; font-weight: 700; color: var(--text); background: none; border: none; cursor: pointer; }
.qn-rul-no { font: inherit; font-weight: 600; color: var(--text-faint); background: none; border: none; cursor: pointer; }

.qn-rul-menu { background: var(--bg-elev-2); border-radius: 12px; padding: 6px; margin: 10px 0 0 auto; max-width: 185px; }
.qn-rul-mi { display: block; width: 100%; text-align: left; padding: 10px 12px; font: inherit; font-size: 13.5px;
  border-radius: 8px; background: none; border: none; color: var(--text); cursor: pointer; }
.qn-rul-mi.del { color: var(--sm-down); }

  gap: 8px; border-bottom: 1px solid var(--border); }
  background: none; border: none; cursor: pointer; }

.qn-rul-empty { padding: 28px 18px; text-align: center; background: var(--bg-elev-1); border-radius: 16px; }
.qn-rul-empty-b { font-size: 15px; font-weight: 700; color: var(--warn); margin-bottom: 5px; }
.qn-rul-empty-s { font-size: 13px; color: var(--text-faint); }

.qn-rul-sheet { padding: 10px 16px 20px; }
.qn-rul-grab { width: 32px; height: 4px; border-radius: 2px; background: var(--bg-elev-2); margin: 0 auto 14px; }
.qn-rul-sheet-title { font-size: 13px; font-weight: 700; color: var(--text-dim); margin-bottom: 10px; }
.qn-rul-field { width: 100%; background: var(--bg-elev-2); color: var(--text); border: none; border-radius: 12px;
  padding: 13px 14px; font: inherit; font-size: 14px; min-height: 76px; resize: vertical; }
.qn-rul-field::placeholder { color: var(--text-faint); }
.qn-rul-hint { font-size: 12px; color: var(--text-faint); margin-top: 10px; }
.qn-rul-acts { display: flex; gap: 8px; margin-top: 14px; }
.qn-rul-btn { flex: 1; padding: 12px; border: none; border-radius: 999px; font: inherit; font-size: 14px;
  font-weight: 700; cursor: pointer; background: var(--bg-elev-2); color: var(--text); }
.qn-rul-btn.go { background: var(--text); color: var(--bg); }
.qn-rul-btn:disabled { opacity: .6; cursor: default; }
.qn-rul-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
/* ═══════════════════ END THE PLAYBOOK TAB ═══════════════════
   The two pins in `playbook-tab-ui-test` that read "this block" used to slice
   from the opening banner to the END OF THE FILE, which was the same thing only
   while nothing had been appended below it. R52 appended the trade sheet and
   both went red on rules that are not the playbook's. This marker is where the
   block actually ends; put a new section AFTER it. */

/* ═══ WHAT THE DECIDER DID WITH THE FACTS AGAINST IT (R82) ═══
   Under the trade log, in the Playbook's own section idiom (`qn-review`), because
   it is the same kind of thing: a record of decisions already made. No colour of
   its own — the existing text tokens and one border carry the whole board, so a
   half that reads worse is not painted red. The two halves sit side by side
   because the comparison IS the board; they stack under 380px rather than
   shrinking the numbers, and the board is not drawn at all when nothing has been
   recorded (see quant.ts — a zero here would claim a measurement nobody made). */
/* ═══ A PROPOSAL WAITING FOR THE OWNER'S YES (R112) ═══
   The one card on this screen that ASKS something, so it is the one card with a border: everything
   else here is a record. Still no new colour — the yes takes the app's up token, the no is an
   outline, and a refusal takes the down token's text. */
.qn-pend { border: 1px solid var(--border); border-radius: 16px; padding: 12px 14px 14px; }
.qn-pend-words { font-size: 12.5px; line-height: 1.5; color: var(--text); padding: 2px 0 8px; }
.qn-pend-left { font-size: 11.5px; color: var(--text-faint); padding-bottom: 10px; }
.qn-pend-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.qn-pend-btn {
  appearance: none; cursor: pointer; font: inherit; font-size: 14px; font-weight: 700;
  padding: 12px; border-radius: 999px; border: 1px solid var(--border); background: none; color: var(--text);
}
.qn-pend-btn.yes { background: var(--sm-up); border-color: var(--sm-up); color: var(--sm-on-up); }
.qn-pend-btn:disabled { cursor: default; opacity: 0.5; }
.qn-pend-answer { font-size: 12.5px; line-height: 1.45; color: var(--text-dim); padding-top: 6px; }
.qn-pend-answer.bad { color: var(--sm-down); }
.qn-pend-answer[hidden] { display: none; }

/* ═══ WHAT WAS ASKED FOR, AND WHAT HAPPENED (R108) ═══
   The quietest list on the screen: it is a record, not an announcement, and most of its rows are
   the engine declining to do something. No colour of its own — a refusal is not an error, and
   painting it red would make an ordinary "already open, not placed again" read as a fault. The one
   that DID place gets the ordinary text colour so it stands out of the dim ones by weight alone. */
.qn-plc-list { display: flex; flex-direction: column; gap: 6px; }
.qn-plc-row { font-size: 11.5px; line-height: 1.45; color: var(--text-faint); }
.qn-plc-row.done { color: var(--text-dim); }
.qn-plc-more { font-size: 11px; color: var(--text-faint); padding-top: 6px; }
/* R123 — one plain line per attempt, and the engine's own sentence behind "why". Nothing new in
   colour: --text-faint, --text-dim and --border are already declared for both themes. */
/* R124 — the one line under the figures that says waiting, filled, or not placed. */
.qn-done-fill { font-size: 12px; line-height: 1.45; color: var(--text-dim); padding-top: 10px; }
.qn-plc-lead { font-size: 11.5px; line-height: 1.45; color: var(--text-dim); padding: 2px 0 8px; }
.qn-plc-say { display: block; }
.qn-plc-why {
  background: none; border: none; padding: 2px 0 0; margin: 0; cursor: pointer;
  font: inherit; font-size: 11px; color: var(--text-dim); text-decoration: underline;
}
.qn-plc-full {
  font-size: 11px; line-height: 1.5; color: var(--text-faint);
  margin-top: 4px; padding: 8px 10px; border-radius: 10px; border: 1px solid var(--border);
}

/* R122 addition — the board waits until there are enough finished trades to say anything; this is
   the one quiet line that stands in for it. M3 body-medium, the same as the other explanations. */
.qn-dq-soon { font-size: 14px; line-height: 20px; color: var(--text-dim); }
.qn-dq-lead { font-size: 12px; line-height: 1.45; color: var(--text-dim); padding: 2px 0 8px; }
.qn-dq-halves { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.qn-dq-half {
  display: flex; flex-direction: column; gap: 3px;
  padding: 10px; border-radius: 12px; border: 1px solid var(--border);
}
.qn-dq-label { font-size: 11px; font-weight: 700; letter-spacing: 0.01em; color: var(--text); }
.qn-dq-value { font-size: 12px; line-height: 1.4; color: var(--text-dim); }
.qn-dq-note { font-size: 11px; line-height: 1.5; color: var(--text-faint); padding-top: 6px; }
@media (max-width: 380px) { .qn-dq-halves { grid-template-columns: 1fr; } }

/* ── THE TRADE SHEET (R52-TRADE-SHEET) ──────────────────────────────────────
   Owner, 2026-08-25: tapping a card in the "what top trades are doing" strip
   opens a slide-up with the trader, the asset, the chart and the position —
   "overall follow material 3 design princple, fit the rest of the style of the
   app, minimal, only show info that are vital".

   NOTHING NEW IN COLOUR AND NOTHING NEW IN MOTION. Every value below is a token
   this stylesheet already declares (--text, --text-dim, --text-faint, --border,
   --sm-card, --sm-up, --sm-down, --bg), so both themes come out right without a
   second palette; the slide-up, its reduced-motion opt-out and the desktop
   centring belong to `.sm-sheet` and are shared with every other sheet. The
   pill, the avatar, the chip row and the coin icon are the app's own components
   (.sm-follow-pill / .sm-hexav / .sm-tfrow / .sm-aico) and are only positioned
   here, never restyled. */
/* PACKED: the app's 8-dp rhythm between rows and nothing else (owner, 2026-08-26: *"there is tons
   of white space out here. please make them packed"*). It was 14. */
.qn-ts { display: flex; flex-direction: column; gap: 8px; padding: 0 20px; }
/* Every row is its own height — except the chart, which grows into whatever is left of the sheet
   (R125). The 160-px floor is the drawing's, and nothing is reserved above it. */
.qn-ts > * { flex: none; }
/* …and it does not take everything that is left, either (owner, 2026-08-26: "the chart doesn't
   have to be that big"). It was 374.8 px of an 844-px phone — 44% of the sheet for the picture and
   the rest shared by the wallet, the coin, the windows, the position and the two buttons. A third
   of the viewport is the cap, with the 160-px floor still under it. */
.qn-ts > .qn-ts-chart { flex: 1 1 auto; min-height: 160px; max-height: min(280px, 34dvh); }



/* 1 · the trader. The name grows, the pill never does. */
.qn-ts-who { display: flex; align-items: center; gap: 10px; }
.qn-ts-whobtn { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 9px; background: none; border: none; padding: 0; cursor: pointer; color: var(--text); font: inherit; text-align: left; }
.qn-ts-av { width: 32px; height: 32px; border-radius: 10px; font-size: 10px; }
.qn-ts-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
.qn-ts-chev { flex: none; color: var(--text-faint); font-size: 17px; line-height: 1; }
.qn-ts-follow { margin-top: 0; }

/* 2 · the asset. Icon and name left, price and the change right. */
.qn-ts-asset { display: flex; align-items: center; gap: 10px; padding: 11px 12px; border-radius: 14px; background: var(--sm-card); }
.qn-ts-aleft { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; }
.qn-ts-aico { width: 26px; height: 26px; flex: none; }
.qn-ts-aname { font-size: 14.5px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qn-ts-badge { flex: none; font-size: 10.5px; font-weight: 600; letter-spacing: 0.02em; color: var(--text-dim); border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px; white-space: nowrap; }
.qn-ts-aright { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.qn-ts-px { font-size: 14.5px; font-weight: 650; font-variant-numeric: tabular-nums; }
/* A button because it does something: one tap swaps % for $. It is sized like
   the text it replaces so the row does not jump between the two. */
/* WHEN THAT PRICE WAS TRUE (R99). Quiet, under the number, and it wraps inside the right-hand
   column rather than pushing the coin's own name out of the row. */
.qn-ts-pxage { font-size: 10.5px; line-height: 1.3; color: var(--text-faint); text-align: right; max-width: 150px; }
.qn-ts-pxage[hidden] { display: none; }
.qn-ts-chg { background: none; border: none; padding: 0; cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text-dim); min-width: 62px; text-align: right; }
.qn-ts-chg.up { color: var(--sm-up); }
.qn-ts-chg.down { color: var(--sm-down); }

/* 3 · the chart, laid out like the app's own position chart (owner, 2026-08-26:
   "make chart elements and design similar to this"): the line/candle icons in
   the chart's top-right corner, the windows in a row along the bottom. */
/* The icons live in a band of their own at the top, so they can never sit on
   the price scale the chart draws down its right edge. */
/* HALF THE PHONE (owner, 2026-08-26): the sheet is full height now and the chart is the thing he
   opened it for. The HEIGHT is the renderer's — `{ viewportShare: 0.5 }` on the call, so the SVG
   itself is half the viewport and the box is exactly as tall as its drawing. A `min-height` here
   would have reserved space the aspect-locked SVG then refused to fill, which is the empty band
   the first attempt left under the line. */
/* The icons ride IN the chart's corner (below), so no band is reserved above the drawing. */
.qn-ts-chart { width: 100%; position: relative; min-height: 160px; padding-top: 0; }
.qn-ts-svg { width: 100%; display: block; }
/* The icons ride ON the chart, top right — the position chart's own corner. The
   control inside is `.sm-taseg`, shared, and nothing here restyles it. */
.qn-ts-modehost { position: absolute; top: 2px; right: 2px; z-index: 1; }
/* NO PRICE SCALE DOWN THE RIGHT (owner, 2026-08-26, on a screenshot of this chart): the only price
   on it is the green badge for where the coin is now. That is `{ axisLabels: false }` on the call
   in quant.ts, not a rule here — the markup belongs to the shared renderer and hiding another
   component's output from this block is the thing this sheet's CSS is pinned never to do. */
/* WHAT THE CHART IS DOING WHEN IT IS NOT A CHART. A box that draws nothing and
   says nothing was the owner's screenshot on 2026-08-26; this is the sentence
   that replaces it, centred in the space the chart would have filled. */
.qn-ts-chartnote {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 0 24px; font-size: 12.5px; line-height: 1.45; color: var(--text-faint);
}
.qn-ts-chartnote[hidden] { display: none; }
.qn-ts-ctrl { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
/* The chips themselves are `.sm-tfbtn`, exactly as the asset screen draws them —
   this block defines only its own row, and restyles nothing the sheet
   already defined. `.sm-tfrow` is not reused because its 12px bottom margin was
   written for a chart header and an override here would be this block reaching
   into another component. */
.qn-ts-tfrow { display: flex; gap: 6px; flex-wrap: wrap; }
/* Why the two exit prices are where they are, in the engine's own words — under the chart, quiet,
   full width of the row (R111). */
.qn-ts-why { flex: 1 0 100%; font-size: 11.5px; line-height: 1.45; color: var(--text-faint); order: 2; }
.qn-ts-why[hidden] { display: none; }
/* The asset's own address, under its name — a button because it copies. */
.qn-ts-acol { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.qn-ts-aaddr { display: flex; }
.qn-ts-aaddr[hidden] { display: none; }
.qn-ts-copy {
  appearance: none; border: none; background: none; padding: 0; cursor: pointer; font: inherit;
  font-size: 11px; font-variant-numeric: tabular-nums; color: var(--text-faint); text-align: left;
}
.qn-ts-copy:hover { color: var(--text-dim); }

/* 4 · the position. */
.qn-ts-pos { border-radius: 14px; background: var(--sm-card); padding: 12px 14px 12px; }
/* ═══ THE CARD THE OWNER SENT (2026-08-26; R142) ═══
   The money first and biggest: what the position is worth now on the left, what it has made or
   lost on the right, each with its own smaller line under it. Then a divider, then the facts that
   do not move. Four equal label-and-value rows made the money read as one more row. */
.qn-ts-postop { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 2px 0 12px; }
.qn-ts-posside { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.qn-ts-posside.right { align-items: flex-end; text-align: right; }
.qn-ts-poscap { font-size: 11.5px; line-height: 1.2; color: var(--text-dim); }
.qn-ts-posval { font-size: 24px; line-height: 1.15; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.qn-ts-posamt { font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.qn-ts-pospnl { font-size: 17px; line-height: 1.2; font-weight: 700; font-variant-numeric: tabular-nums; }
.qn-ts-pospct { font-size: 13px; font-weight: 650; font-variant-numeric: tabular-nums; }
.qn-ts-posside.up .qn-ts-pospnl, .qn-ts-posside.up .qn-ts-pospct { color: var(--sm-up); }
.qn-ts-posside.down .qn-ts-pospnl, .qn-ts-posside.down .qn-ts-pospct { color: var(--sm-down); }
.qn-ts-posside.none .qn-ts-pospnl, .qn-ts-posside.none .qn-ts-pospct { color: var(--text-faint); }
/* The divider, and the smaller facts under it — left, then right, wrapping only if a third joins. */
.qn-ts-posfacts { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 16px; padding-top: 10px; border-top: 1px solid var(--border); }
.qn-ts-posfact { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.qn-ts-posk { font-size: 12.5px; color: var(--text-dim); }
.qn-ts-posv { font-size: 13.5px; font-weight: 650; font-variant-numeric: tabular-nums; }
/* The one thing this sheet cannot say, said plainly rather than as a dash. */
.qn-ts-gap { font-size: 11.5px; line-height: 1.45; color: var(--text-faint); padding: 6px 0 2px; border-top: 1px solid var(--border); margin-top: 4px; }

/* ── THE OWNER'S OWN ORDER, FROM THE TRADE SHEET (R110) ─────────────────────
   Two buttons at the bottom of the sheet, then a full-height step over it for the amount, then the
   confirmation. No new colour: Long and Short are the app's own up/down tokens, and everything else
   is text, border and card. */
/* ═══ AT THE BOTTOM OF THE SHEET, FROM THE FIRST FRAME (owner, 2026-08-26; R111) ═══
   It sat in the flow after the position block, which on an 844-px phone is below the fold — the
   owner opened the sheet and saw no buttons at all. `sticky` inside the sheet's own scroll box
   pins it to the box's bottom edge whatever the content does, and the sheet's box already ends
   above the app's dock (`.sm-sheet-scrim { bottom: var(--dock) }`), so this needs no knowledge of
   the composer and does not touch the stacking rule R91 pinned. Opaque, because the list scrolls
   underneath it. */
/* R125 — the "why there is no result" sentence is its own full-width line under the position
   block, in M3 body-medium (14/20). It was the value cell of a row whose label names a NUMBER,
   and on a 480-px sheet it wrapped into four short lines beside "Up or down so far". */
.qn-ts-posnote { font-size: 14px; line-height: 20px; color: var(--text-dim); padding-top: 2px; }
.qn-ts-cta {
  position: sticky; bottom: 0; z-index: 2;
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  /* The sheet's own bottom padding is gone (R125), so the row owns the safe-area inset — and
     nothing sits between the buttons and the bottom edge of the window. */
  /* …and it stays on the bottom edge now that the chart no longer takes every spare pixel (R142):
     `auto` hands the leftover height to this row's top margin, so a shorter chart leaves its space
     between the position card and the buttons rather than under them. */
  padding: 8px 0 max(8px, env(safe-area-inset-bottom)); margin-top: auto; background: var(--bg);
}
.qn-ts-go {
  appearance: none; border: none; cursor: pointer; font: inherit; font-size: 15px; font-weight: 700;
  padding: 15px 12px; border-radius: 999px; color: var(--sm-on-up);
}
.qn-ts-go.up { background: var(--sm-up); }
.qn-ts-go.down { background: var(--sm-down); }

/* The step itself: it fills the sheet it is opened over, so nothing behind it can be tapped. */
.qn-ord {
  position: absolute; inset: 0; z-index: 3; display: flex; flex-direction: column; gap: 12px;
  padding: 14px 16px 18px; background: var(--bg-elev); border-radius: inherit; overflow-y: auto;
}
.qn-ord-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.qn-ord-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.qn-ord-ico { width: 38px; height: 38px; border-radius: 11px; }
.qn-ord-names { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.qn-ord-sym { font-size: 16px; font-weight: 700; }
.qn-ord-px { font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.qn-ord-x {
  appearance: none; border: 1px solid var(--border); background: none; cursor: pointer; color: var(--text-dim);
  width: 34px; height: 34px; border-radius: 999px; font-size: 18px; line-height: 1; flex: 0 0 auto;
}
/* The amount is the biggest thing on the screen, and the two facts under it are the quote's. */
.qn-ord-mid { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 22px 0 10px; }
.qn-ord-amt { font-size: 52px; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.qn-ord-size { font-size: 14px; font-weight: 700; color: var(--accent); }
.qn-ord-liq { font-size: 12.5px; color: var(--text-dim); }
.qn-ord-avail { font-size: 12px; color: var(--text-faint); }
.qn-ord-size[hidden], .qn-ord-liq[hidden], .qn-ord-avail[hidden] { display: none; }
.qn-ord-levrow { display: flex; align-items: center; gap: 12px; }
.qn-ord-lev { flex: 1 1 auto; accent-color: var(--accent); }
.qn-ord-levval { font-size: 17px; font-weight: 800; color: var(--accent); font-variant-numeric: tabular-nums; min-width: 58px; text-align: right; }
.qn-ord-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.qn-ord-key {
  appearance: none; border: none; background: none; cursor: pointer; color: var(--text);
  font: inherit; font-size: 23px; font-weight: 600; padding: 12px 0; border-radius: 12px;
}
.qn-ord-key:active { background: var(--sm-card); }
/* Slide to confirm: the knob is the control, the words are the label. */
/* The slide row is pinned to the bottom of the amount screen the same way Long/Short are pinned to
   the sheet's (R118, coco item 5): the keypad scrolls under it, it never leaves the screen. */
.qn-ord-slide {
  position: sticky; bottom: 0; z-index: 2;
  display: flex; align-items: center; gap: 10px; padding: 6px;
  border-radius: 999px; background: var(--sm-card); opacity: 0.55;
}
.qn-ord-slide.pricing .qn-ord-slidew { color: var(--text-faint); }
.qn-ord-slide.again { opacity: 1; }
.qn-ord-slide.again .qn-ord-slidew { color: var(--warn); }
.qn-ord-exits { font-size: 12px; color: var(--text-dim); }
.qn-ord-exits[hidden] { display: none; }
.qn-ord-slide.ready { opacity: 1; }
.qn-ord-slide.refused .qn-ord-slidew { color: var(--sm-down); }
.qn-ord-knob {
  appearance: none; border: none; cursor: pointer; width: 54px; height: 44px; border-radius: 999px;
  background: var(--text); color: var(--bg); font-size: 18px; flex: 0 0 auto;
}
.qn-ord-knob:disabled { cursor: default; opacity: 0.5; }
.qn-ord-slidew { flex: 1 1 auto; text-align: center; font-size: 14px; font-weight: 600; color: var(--text-dim); padding-right: 54px; }

/* The confirmation. One card, the share row, Continue. */
.qn-done { align-items: stretch; }
.qn-done-title { text-align: center; font-size: 15px; font-weight: 700; padding: 6px 0 10px; }
.qn-done-card { border-radius: 18px; background: var(--sm-card); border: 1px solid var(--border); padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.qn-done-top { display: flex; align-items: center; gap: 12px; }
.qn-done-ico { width: 46px; height: 46px; border-radius: 12px; }
.qn-done-names { display: flex; flex-direction: column; gap: 2px; }
.qn-done-sym { font-size: 20px; font-weight: 800; }
.qn-done-dir { font-size: 14px; font-weight: 700; }
.qn-done-dir.up { color: var(--sm-up); }
.qn-done-dir.down { color: var(--sm-down); }
.qn-done-figs { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.qn-done-figk { font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); }
.qn-done-figv { font-size: 21px; font-weight: 800; font-variant-numeric: tabular-nums; }
.qn-done-when { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.qn-done-share { display: flex; flex-direction: column; gap: 10px; align-items: center; padding-top: 6px; }
.qn-done-sharet { font-size: 13px; color: var(--text-dim); }
.qn-done-sharebtns { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.qn-done-sharebtn {
  appearance: none; cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 600;
  border: 1px solid var(--border); background: none; color: var(--text-dim); padding: 9px 16px; border-radius: 999px;
}
.qn-done-continue {
  appearance: none; cursor: pointer; font: inherit; font-size: 15px; font-weight: 700;
  border: 1px solid var(--border); background: none; color: var(--text); padding: 14px; border-radius: 999px; width: 100%;
}



/* Copyright (c) 2026 Roy Liu. All rights reserved.
   SPDX-License-Identifier: LicenseRef-Proprietary */
/* ═══ nmo — THE PLUGIN'S OWN STYLESHEET (v2) ══════════════════════════════
   Appended to the core stylesheet at build time by scripts/build-web.mjs when
   plugins/nmo/web is on disk. The browser still loads one /styles.css: no second
   request, no flash of unstyled cards, no load-order question at runtime. Plugin
   absent → none of this ships.

   EVERY SELECTOR IN HERE STARTS `.nmo-`, and every one is inside `.nmo-root`,
   the only element this plugin mounts. Nothing below can reach a core element:
   no `.composer-wrap`, no `body`, no `.transcript`. That is not tidiness — it is
   the rule the composer's survival rests on (decision 4). The Smart Money screen
   owns a `body.qn-composer-off` block that collapses the composer; nmo has no
   equivalent and must never grow one.

   THE RISE IS ON THE HEAD AND THE BODY, NOT ON THE ROOT. A `transform` on an
   ancestor makes it the containing block for `position: fixed` descendants, and
   this screen has one — the Follow modal, which has to cover the screen from
   wherever the tapped row happens to be. Keeping `.nmo-root` transform-free
   keeps that overlay honest.

   COLOUR COMES FROM THE APP'S TOKENS, not from hexes: `--bg-elev-1`, `--text`,
   `--text-faint`, `--border`, `--radius-*`, `--ok`, `--err`, `--warn` all flip
   with the theme, so both themes are one set of rules. */

/* ═══ THE SHEET IS OUTSIDE `.nmo-root`, AND IT NEEDS THE SAME TOKENS ═══════════
   The coin sheet is the one element this plugin puts on `document.body`, which
   is what lets it cover the app's own chrome — and it therefore inherits NOTHING
   from `.nmo-root`. Every `var(--nmo-up)` inside it resolved to nothing: an
   invalid value on a presentation attribute is dropped, so the line lost its
   stroke and the gradient's stops fell back to black. That is the "small grey
   plot" in the owner's screenshot, and no amount of chart geometry would have
   fixed it. The tokens are declared for both, once. */
.nmo-root,
.nmo-sheet {
  --nmo-up: var(--ok);
  --nmo-down: var(--err);
  --nmo-late: var(--warn);
  /* A thin wash IN the text colour: light on the dark ground, dark on the light
     one, with no per-theme block. */
  --nmo-card-bg: var(--bg-elev-1);
  --nmo-card-bg-2: color-mix(in srgb, var(--text) 9%, transparent);
  --nmo-line: color-mix(in srgb, var(--text) 9%, transparent);
  --nmo-tint-a: var(--grad-a);
  --nmo-tint-b: var(--grad-c);
  /* ═══ THE CHART'S OWN COLOURS (owner's recording, RB10b) ═══
     The mint the line is drawn in, the wash under it, the dotted grid behind it
     and the grey the axis is written in. Named here so the chart is one set of
     values rather than a hex per shape. */
  --nmo-chart-green: #5ce59a;
  --nmo-chart-wash: color-mix(in srgb, var(--nmo-chart-green) 18%, transparent);
  --nmo-chart-grid: color-mix(in srgb, var(--text) 12%, transparent);
  --nmo-chart-halo: color-mix(in srgb, var(--text) 22%, transparent);
  /* How far the Follow modal stops short of the bottom, so the composer is never
     underneath it (decision 4). */
  --nmo-composer-room: 180px;

  display: flex;
  flex-direction: column;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 0 12px;
  color: var(--text);
  /* NOTHING HERE MAY ESTABLISH A CONTAINING BLOCK. `transform`, `filter`,
     `perspective`, `contain`, `container-type` and `will-change` naming any of
     them all make this element the containing block for every `position: fixed`
     descendant — which would pin the Follow modal inside the scrolling list
     again, whatever the row's index. `container-type: inline-size` was doing
     exactly that until 2026-08-27; the card and stats queries below are plain
     media queries now, which cost nothing here because the screen is one column
     at a fixed max-width. */
}
:root[data-theme="light"] .nmo-root, :root[data-theme="light"] .nmo-sheet { --nmo-card-bg: #ffffff; }

/* ── a number that just changed (decision 24) ─────────────────────────
   THE FLASH IS COLOUR AND NOTHING ELSE. No size, no weight, no position: a list
   that reflows every two seconds cannot be read, and the numbers move while the
   person is looking straight at them. The two tokens are the app's own, so the
   direction reads the same here as everywhere else.

   The class is added by the screen and taken off again after ~600 ms; the
   transition is what makes it fade rather than blink. `will-change` is
   deliberately absent — it would promote every number on screen to its own
   layer for a colour change. */
.nmo-num { transition: color 220ms ease-out; }
/* ═══ ONLY THE DIGITS THAT CHANGED MOVE (owner, RB10 riders) ═══
   Each character of a live number is its own cell; the ones that changed turn
   over like an odometer wheel and the rest stay perfectly still. The cells are
   inline-block so a transform applies to them, and the number keeps its exact
   text — nothing here changes what `textContent` reads. */
.nmo-roll { display: inline-block; }
.nmo-rolling { animation: nmo-roll 250ms cubic-bezier(0.2, 0, 0, 1) both; }
@keyframes nmo-roll {
  from { transform: translateY(-0.9em); opacity: 0.15; }
  to { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .nmo-rolling { animation: none; }
}
.nmo-flash-up { color: var(--ok); }
.nmo-flash-down { color: var(--err); }
/* A NUMBER NOBODY HAS REFRESHED IS NOT A LIVE ONE. When the price provider has
   stopped answering, the last true value stays on screen — dimmed, so it reads
   as "this is what we last knew" rather than "this is what it is now". It never
   flashes: a flash claims something just moved. */
.nmo-stale { opacity: 0.55; }
@media (prefers-reduced-motion: reduce) {
  .nmo-num { transition: none; }
}

.nmo-head, .nmo-body { animation: nmo-rise 320ms var(--ease, cubic-bezier(0.2, 0, 0, 1)) both; }
@keyframes nmo-rise {
  from { transform: translateY(14px); opacity: 0; }
  to { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .nmo-head, .nmo-body { animation: none; }
}

/* ── the header: picture, balance, Deposit, bell, × (decision 12) ─────
   IT IS THE FIRST THING ON THE PAGE. The app's title bar stands down for this
   view (it declares it supplies its own top row), so nothing sits above this.
   The safe-area inset is the notch's, not a design choice — without it the row
   would hide under it on a phone. */
/* THE PICTURE AND THE BALANCE KEEP THE LEFT; the buttons take only what they
   need. `min-width: 0` lets the balance shrink instead of shoving them off. */
/* ONE LINE, ALWAYS. Wrapped, it reads as "10,000" above "sUSD" — two numbers
   where there is one, on the row a person checks first. `min-width: 0` lets it
   shrink a step rather than shove the buttons off the row. */
/* The three buttons are one group, packed to the right with tight gaps, so the
   balance keeps the room it needs. */

/* the three sections, as underlined text */



.nmo-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.nmo-body { min-height: 0; padding-bottom: 28px; }
.nmo-screen { display: block; }




.nmo-subtab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── the mascot avatar, wearing this genie's two tints ───────────────── */
/* ═══ THE MASCOT ALONE, ONE AND A HALF TIMES (owner, 29 Aug 2026 14:20) ═══════
   Twice the size with a tinted disc was tried and taken back ("too big, no need
   for the green circle"). The owner then asked for 1.5x with the text area's
   width kept: the picture is 66 on a feed post (44 × 1.5), still 30 in a reply,
   still no disc. The picture keeps its centre (x = 38) by hanging 11px into the
   post's left gutter, so the thread line still runs through its middle; since
   2026-08-29 15:57 it is a column of the post's own grid rather than the first
   thing in the name row.

   The gradient stays declared for nothing else that used it? Nothing else does:
   `--nmo-tint-a` and `--nmo-tint-b` are still what the genie's own two colours
   are called and are used elsewhere; only this disc drew them as a circle. */
.nmo-av {
  position: relative; flex: none; border-radius: 50%;
  background: none; padding: 0; display: grid; place-items: center;
}
.nmo-av-face { width: 100%; height: 100%; border-radius: 50%; display: block; }
/* ═══ THE GENIE'S CANVAS IS BIGGER THAN ITS BOX, ON PURPOSE ══════════════════
   The trader's picture fills its 46px box. The genie is drawn, and a drawing
   needs room around it to move in — a jump throws the ball about twice its own
   radius upward. So the canvas hangs outside the box on all four sides and the
   ball inside it is drawn exactly one box wide: the two circles come out the
   same size, which is what the owner asked for four times on 31 Aug.

   The size and the offsets are set on the element in nmo.ts, not here, because
   the same number also tells the mascot how big to draw the ball and the two
   must not drift.

   No rounded corners: a canvas' corners are transparent already, and rounding
   would cut into the overflow. No clicks: the overflow lies over the posts above
   and below and must never take a tap meant for one of them. */
.nmo-av-genie { border-radius: 0; pointer-events: none; }
.nmo-av-sm { width: 30px; height: 30px; }
/* 30% smaller, and the genie's face matches the trader's exactly (owner, 31 Aug): "roaster
   trader pfp should reduce size 30%" and "genie avatar ... should be the same size as the trader
   pfp". 66 x 0.7 = 46. Both the trader picture and the genie's feed avatar use this one class and
   nothing else does, so they cannot drift apart. */
.nmo-av-md { width: 46px; height: 46px; }
.nmo-av-lg { width: 64px; height: 64px; }

/* ── a post ──────────────────────────────────────────────────────────── */
/* ═══ A POST IS TWO COLUMNS: THE PICTURE, AND EVERYTHING ELSE ═══════════════
   (owner, 29 Aug 2026 15:57, two screenshots.)

   Column one is the picture, top-aligned, running the whole height of the post.
   Column two is the name row, then the words, then the card, then the lines and
   the action row — every one of them starting at the same x as the name.

   This replaces a flex header with the name centred against the picture plus a
   56px margin on each row below it: the words began under the PICTURE and 7px
   left of the name, because two numbers that had to agree did not. */
/* WHERE THE TWO COLUMNS ACTUALLY SIT (owner, 29 Aug 2026 16:11).

   The post's own left padding is the picture's left edge: 5px. So the picture
   runs 5 → 71 and its centre is 38, where the thread line runs, with no negative
   margin pulling it there. Column two then starts at 5 + 66 + 8 = 79, which is
   exactly where the name has always started — so the words moved up beside the
   name without moving right. The padding is 16px on the other three sides. */
.nmo-post {
  padding: 14px 16px 14px 5px; border-bottom: 1px solid var(--nmo-line); position: relative;
  display: grid; grid-template-columns: 66px minmax(0, 1fr); column-gap: 8px; align-items: start;
}
.nmo-post > * { grid-column: 2; min-width: 0; }
/* The picture spans every row it can reach, so the row beside it is only as tall
   as the name — and it is placed AFTER the "reposted by" line and BEFORE the
   name row, so those two share a row whether or not there is a line above. */
.nmo-post > .nmo-av { grid-column: 1; grid-row: span 99; }
/* ═══ WHO REPOSTED IT IS ABOUT THE WHOLE POST (cc, pass 9) ═══════════════════
   So it takes a row of its own across BOTH columns, above the picture. It was
   `order: -1` in the content column, which put it in row one there and pushed
   the genie's name down to row two while the picture still started at row one —
   so on a repost, and only on a repost, the name was no longer level with the
   top of the picture. Full width in row one; the picture starts in row two. */
.nmo-post > .nmo-reposted { grid-column: 1 / -1; }
/* The name and the time start level with the top of the picture. */
.nmo-post-h { display: flex; gap: 8px; align-items: start; }
/* The name, and under it who asked for the post when somebody did. */
/* ═══ THE NAME, AND THE MOVE BESIDE IT (owner, 31 Aug 2026) ═══
   A trader's post carries a small pill immediately after the name. The row is a
   flex line so the two sit together on one baseline; with a single child — every
   other post — it lays out exactly as it did before.
   The name takes the room and gives it up first: the pill is two short words and
   must never be the thing that gets cut, because it is what says whose post this
   is now that the grey caption has gone. */
.nmo-who-wrap { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; }
.nmo-who { font-size: 14px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Green on a dark green ground, red on a dark red ground — the owner's picture.
   The ground is mixed from the same token as the text so the pair moves together
   with the theme and never becomes light-on-light. `flex: none` so it keeps its
   width while the name shrinks. */
.nmo-side-pill {
  flex: none; font-size: 11px; font-weight: 700; line-height: 1;
  padding: 3px 7px; border-radius: 999px; white-space: nowrap;
}
.nmo-side-buy { color: var(--nmo-up); background: color-mix(in srgb, var(--nmo-up) 18%, transparent); }
.nmo-side-sell { color: var(--nmo-down); background: color-mix(in srgb, var(--nmo-down) 18%, transparent); }
/* A grey `.nmo-side-closed` belongs here when a trader's position is tracked and
   the record can say a sell closed one. It is deliberately NOT written yet: a
   rule no element can carry is dead the day it lands, and this file deletes
   those. One row in `SIDE_PILL` and one rule here, together or not at all. */
/* The market value at the trade, under the amount. It draws only when the record
   carries `atMcUsd`; today it never does, so this rule waits for cc's field
   rather than reserving a blank line for it. */
.nmo-at-mc { font-variant-numeric: tabular-nums; }
.nmo-asked { font-size: 12.5px; color: var(--text-faint); }
.nmo-post-h .nmo-num { font-size: 13px; color: var(--text-faint); white-space: nowrap; }
.nmo-reposted { font-size: 12.5px; color: var(--text-faint); margin-bottom: 4px; }
/* Post text is the Trade screen's BODY size (14px), not a step above it — the
   owner's note on build 7 was that this screen read larger than the rest. */
.nmo-thesis { margin: 4px 0 10px; font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; }

/* THE PICTURE AND THE SYMBOL START LEVEL (decision 30). The card is two text
   lines beside a 36px picture, and it is only as tall as they are — centring
   them left empty space under the words on every card that had one line. */
.nmo-card {
  background: var(--bg-elev-1); border-radius: 12px; padding: 10px;
  display: grid; grid-template-columns: 36px minmax(0, 1fr) auto auto; gap: 10px; align-items: start;
}
.nmo-tok { width: 36px; height: 36px; border-radius: 10px; object-fit: cover; }
.nmo-tok-letters {
  display: grid; place-items: center; font-family: var(--font-display);
  font-weight: 700; font-size: 13px; color: var(--bg);
  background: linear-gradient(135deg, var(--grad-a), var(--grad-c));
}
/* The names column is a stack: symbol, then the coin's name under it. */
.nmo-card-names { min-width: 0; display: block; }
.nmo-sym { font-weight: 700; font-size: 14px; }
/* The card's own name line — under the symbol, quieter, and cut with an ellipsis
   rather than pushing the card wider. Its whole text is on the line's title. */
.nmo-card-names .nmo-coin-name {
  font-size: 12.5px; color: var(--text-faint); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* the delay line under the symbol — the number this product lives or dies on */
/* ═══ A LONG HANDLE WRAPS RATHER THAN RUNNING OFF THE CARD (31 Aug 2026) ═══
   Measured at 390px: "5.2 s behind @changeratethenar" showed 31% of itself and
   "11.9 s behind @quietwhale · past the 3 s target" 93%, both cut by the card's
   edge with no ellipsis rule to end them cleanly — so they stopped mid-word.
   A handle is one unbreakable word, and a word wider than its column overflows
   however much the line is allowed to wrap.
   `anywhere` lets an over-long word break so the line stays inside the card. It
   is deliberately NOT `text-overflow: ellipsis` with `nowrap`: that would fit by
   throwing away the end of the line, and the end of this line is "· past the 3 s
   target" — the warning the card exists to give. Nothing is lost and nothing is
   shrunk; a long handle simply takes two lines. */
.nmo-full {
  font-size: 11.5px; color: var(--text-faint); line-height: 1.3;
  overflow-wrap: anywhere; min-width: 0;
}
.nmo-full .nmo-num { color: var(--text-dim); font-weight: 500; }
.nmo-full.nmo-late, .nmo-full.nmo-late .nmo-num { color: var(--nmo-late); }
.nmo-kv { text-align: right; }
.nmo-k { font-size: 11px; color: var(--text-faint); }
/* The market cap the trader's own trade happened at — a third, quiet line under
   the amount, because $2,400 into a $45M coin and into a $200K one are two
   different decisions and the amount alone does not say which. */
.nmo-at { font-size: 11px; color: var(--text-faint); line-height: 1.3; padding-top: 1px; }
/* MONEY IS NOT CODE. Every number a person reads is the app's display face with
   tabular figures — the columns still line up, and it stops reading like a
   terminal. Only the contract address and the address input stay monospace,
   because those are compared character by character. */
.nmo-v { font-family: var(--font-display); font-variant-numeric: tabular-nums; font-weight: 600; font-size: 14px; }
.nmo-up { color: var(--nmo-up); }
.nmo-down { color: var(--nmo-down); }
.nmo-closed { color: var(--text-faint); font-size: 12.5px; font-family: var(--font-ui); }
.nmo-num { font-family: var(--font-display); font-variant-numeric: tabular-nums; }
.nmo-late { color: var(--nmo-late); }

/* ── a callout the genie is reacting to (decision 11) ────────────────── */
/* SET APART ON PURPOSE: italic, behind a rule, so a reader can see at a glance
   that these are somebody else's words and not the genie's — the same reason
   they are rendered as text and never as markup. */
.nmo-callout { margin: 0 0 10px; padding-left: 10px; border-left: 3px solid var(--nmo-card-bg-2); }
/*
 * A REPLY IS A POST OF THE SAME FAMILY (owner, 31 Aug 2026).
 *
 * These replies used to borrow `.nmo-callout`, which insets 10px and draws a 3px rule down the
 * left. The owner asked for the rule gone and the picture back in line: "no need to have a line
 * here, pfp should be center aligned as rest of the pfps". The inset was what pushed it out of
 * line, so there is no inset and no border here — only the gap that separates a reply from the
 * post it answers.
 */
.nmo-replies { margin: 0 0 10px; }
.nmo-replied { display: block; }

.nmo-callout-who { font-size: 12.5px; color: var(--text-faint); margin-right: 6px; }
.nmo-callout-q { font-style: italic; font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; }
/* The coin this post is about is on another chain (decision 32). Quiet, the same
   size as the rest of the small print — it is a fact about the coin, not a
   warning about the post. */
.nmo-otherchain { font-size: 12.5px; color: var(--text-faint); padding-top: 2px; }

/* ── the action row (decision 10) ────────────────────────────────────── */
.nmo-acts { display: flex; align-items: center; gap: 18px; margin-top: 10px; padding: 0 2px; font-size: 12.5px; color: var(--text-faint); }
/* The action row and the delay line are META — the Trade screen's smaller
   step (12.5px), the same one .nmo-sub already uses. */
.nmo-act {
  display: inline-flex; align-items: center; gap: 5px; background: none; border: 0;
  padding: 0; font: inherit; font-size: 12.5px; color: inherit; cursor: pointer;
}
.nmo-act.is-on { color: var(--nmo-up); }
.nmo-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
.nmo-mc { margin-left: auto; font-weight: 600; color: var(--text-dim); }

/* ── comments: a person's own words ──────────────────────────────────── */
.nmo-comments { margin-top: 10px; border-top: 1px solid var(--nmo-line); padding-top: 8px; }
.nmo-comment { display: flex; gap: 10px; align-items: flex-start; padding: 6px 0; }
.nmo-comment-body { flex: 1; min-width: 0; }
.nmo-comment-who { font-size: 13px; font-weight: 600; }
.nmo-comment-text { font-size: 13.5px; line-height: 1.4; overflow-wrap: anywhere; }
.nmo-comment .nmo-num { font-size: 12px; color: var(--text-faint); }
.nmo-comment-form { display: flex; gap: 8px; align-items: center; padding-top: 6px; }
.nmo-comment-input {
  flex: 1; min-width: 0; font: inherit; font-size: 13.5px; color: var(--text);
  background: var(--bg-elev-1); border: 1px solid var(--nmo-line);
  border-radius: 999px; padding: 7px 12px;
}
.nmo-comment-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.nmo-comment-send { font-size: 13px; padding: 6px 12px; }

/* ── the "New posts" pill and the thread line (decision 12) ──────────── */
.nmo-newbar {
  margin: 12px 16px 4px; background: var(--bg-elev-1); border: 0; border-radius: 14px;
  padding: 10px 14px; display: flex; align-items: center; justify-content: center; gap: 12px;
  font: inherit; font-size: 14px; color: var(--nmo-up); font-weight: 600; width: calc(100% - 32px); cursor: pointer;
}
.nmo-newbar:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-stack { display: flex; }
.nmo-stack .nmo-av { margin-left: -8px; border: 2px solid var(--bg); }
.nmo-stack .nmo-av:first-child { margin-left: 0; }

.nmo-thread { border-bottom: 1px solid var(--nmo-line); }
.nmo-thread .nmo-post { border-bottom: 0; }
/* each post draws its own segment: from under its picture to its end */
.nmo-thread .nmo-post::after {
  /* under the picture: 14px padding + 66px picture + 4px */
  content: ""; position: absolute; left: 37px; top: 84px; bottom: 0; border-left: 2px solid var(--nmo-card-bg-2);
}
.nmo-thread .nmo-post:last-child::after { display: none; }
.nmo-thread .nmo-post + .nmo-post::before, .nmo-thread .nmo-more + .nmo-post::before {
  content: ""; position: absolute; left: 37px; top: 0; height: 14px; border-left: 2px solid var(--nmo-card-bg-2);
}
.nmo-more {
  display: block; width: 100%; text-align: left; font: inherit; font-size: 14px; font-weight: 500;
  color: var(--text-faint); background: none; border: 0; padding: 6px 16px 6px 72px; cursor: pointer; position: relative;
}
.nmo-more::before { content: ""; position: absolute; left: 37px; top: 0; bottom: 0; border-left: 2px dotted var(--nmo-card-bg-2); }
.nmo-more.is-open::before { border-left-style: solid; }
.nmo-more:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── rows: traders and following ─────────────────────────────────────── */
.nmo-row {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) auto auto; gap: 10px;
  align-items: center; padding: 12px 16px; border-bottom: 1px solid var(--nmo-line);
}
.nmo-row-f { grid-template-columns: minmax(0, 1fr) auto; }
.nmo-sheet-list .nmo-row-f { grid-template-columns: 30px minmax(0, 1fr) auto; padding: 10px 0; }
.nmo-rk { color: var(--text-faint); font-size: 13px; }
.nmo-row-mid { min-width: 0; }
.nmo-hd { font-weight: 600; }
.nmo-sub { font-size: 12.5px; color: var(--text-faint); display: flex; gap: 10px; flex-wrap: wrap; align-items: baseline; }
.nmo-good { color: var(--nmo-up); }
.nmo-bad { color: var(--nmo-down); }
.nmo-pnl { text-align: right; font-weight: 600; }
.nmo-lnk {
  color: var(--text-dim); text-decoration: underline; text-underline-offset: 2px;
  cursor: pointer; font: inherit; font-size: 12.5px; background: none; border: 0; padding: 0;
}
.nmo-lnk:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.nmo-switch {
  width: 44px; height: 26px; border: 0; border-radius: 999px; background: var(--nmo-card-bg-2);
  position: relative; flex: none; cursor: pointer;
}
.nmo-switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--text); transition: transform 200ms var(--ease, ease);
}
.nmo-switch.is-on { background: var(--nmo-up); }
.nmo-switch.is-on::after { transform: translateX(18px); background: var(--bg); }
.nmo-switch.is-off, .nmo-switch:disabled { opacity: 0.4; cursor: not-allowed; }
.nmo-switch.is-pending { opacity: 0.6; }
.nmo-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .nmo-switch::after { transition: none; } }

/* ── the overlay: the Follow modal and the two header sheets ─────────── */
/* IT STOPS SHORT OF THE COMPOSER. The scrim covers the screen so the modal is
   where the eye already is whatever the row's index — the 2026-08-27 bug was a
   settings sheet appended at the END of a hundred-row list — but the bottom of
   the viewport belongs to the composer and is left alone. */
.nmo-scrim {
  /* THE BOTTOM EDGE IS THE SCRIM'S OWN, NOT ITS PADDING'S. Padding moved the
     modal inward while the scrim itself still covered the composer and took
     every tap over it. The inset ends the element above that band. */
  position: fixed; inset: 0 0 var(--nmo-composer-room) 0;
  z-index: 40; background: rgba(0, 0, 0, 0.6);
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
}
.nmo-modal {
  width: 100%; max-width: 420px; max-height: 100%; overflow-y: auto;
  background: var(--bg-elev); border-radius: var(--radius); padding: 16px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.nmo-modal-h { margin: 0 0 4px; font-size: 17px; font-family: var(--font-display); font-weight: 600; }
.nmo-hint { font-size: 13px; color: var(--text-faint); margin: 0 0 6px; }
.nmo-modal-acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
.nmo-set {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  padding: 10px 0; border-top: 1px solid var(--nmo-line); font-size: 14px;
}
.nmo-set:first-of-type { border-top: 0; }
.nmo-val {
  display: inline-flex; align-items: center; gap: 2px; background: var(--nmo-card-bg-2);
  border-radius: 8px; padding: 4px 10px;
  font-family: var(--font-display); font-variant-numeric: tabular-nums; font-size: 13px;
}
.nmo-val-input {
  width: 5.5ch; min-width: 4ch; font: inherit; color: var(--text); background: none;
  border: 0; padding: 0; text-align: right; -moz-appearance: textfield;
}
.nmo-val-input::-webkit-outer-spin-button, .nmo-val-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.nmo-val-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.nmo-val-unit { color: var(--text-faint); }
.nmo-seg { display: inline-flex; background: var(--nmo-card-bg-2); border-radius: 999px; padding: 3px; font-size: 12px; }
.nmo-seg-b { font: inherit; border: 0; background: none; padding: 3px 10px; border-radius: 999px; color: var(--text-faint); cursor: pointer; }
.nmo-seg-b.is-on { background: var(--text); color: var(--bg); font-weight: 600; }
.nmo-seg-b:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.nmo-sheet-list { max-height: 50vh; overflow-y: auto; }
.nmo-activity-quote { overflow-wrap: anywhere; }

/* ── Profile: the total-balance chart and the tiles (decision 13) ────── */
.nmo-balance { margin: 12px 16px 0; background: var(--bg-elev-1); border-radius: var(--radius-md); padding: 12px; }
.nmo-balance-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
/* The Trade screen sets its hero total in the display face at a title size.
   This is that, one step down — the number rides a one-line header here, not a
   36px-padded hero, so 28px is the size that fits beside Deposit and the bell. */
.nmo-balance-v {
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  font-size: 28px; font-weight: 500; line-height: 1.05; letter-spacing: -0.8px;
}
/* ═══ THESE TWO ARE NOT INSIDE THE NUMBER (aa, second read of e7a83851) ═══
   They were written between the opening brace of `.nmo-balance-v` and its
   declarations. A selector inside a declaration block is not a rule — the
   browser dropped both, so the cash-and-coins line and the chart's caption were
   drawn at the page's ordinary size and colour instead of small and muted. They
   are siblings of the number in the DOM, and now they are siblings here. */
.nmo-balance-split { font-size: 12.5px; color: var(--text-faint); padding: 2px 0 6px; font-variant-numeric: tabular-nums; }
.nmo-balance-caption { font-size: 12px; color: var(--text-faint); padding: 6px 0 0; }
/* How many of the coins the total counts were last valued a while ago. */
.nmo-balance-old { font-size: 12px; color: var(--text-faint); padding: 2px 0 0; }
.nmo-balance-d { font-size: 12.5px; color: var(--text-faint); }
.nmo-balance-d .nmo-num { font-weight: 600; }
.nmo-chart { display: block; width: 100%; height: 160px; margin: 10px 0 8px; }
.nmo-ranges { display: flex; gap: 6px; }

/* The holdings are the same cards as the feed, in one quiet column directly
   under the balance chart. The wrapper owns spacing only; card geometry stays
   on `.nmo-card`, so Profile cannot become a fifth card shape. */
.nmo-profile-portfolio { display: grid; gap: 8px; padding: 12px 16px 0; }
.nmo-profile-portfolio > .m3-sectionlabel { margin: 0; }
.nmo-profile-empty { margin: 0; color: var(--text-faint); font-size: 13px; }


.nmo-range:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* ═══ THE CHOSEN TIME FRAME IS A DARK PILL, NOT A GREEN ONE (owner's recording) ══
   Core's chip paints the chosen one in the up-colour, which put a second green on
   a screen whose green means "the price went up". In the video it is a plain dark
   pill with white text: chosen, and saying nothing about money. */
.nmo-coin-chart .nmo-range { color: var(--text-faint); background: none; }
.nmo-coin-chart .nmo-range.is-on {
  color: var(--text); background: var(--bg-elev-2, color-mix(in srgb, var(--text) 12%, transparent));
  font-weight: 600;
}
/* The bell is a promise we have not made yet: it is drawn, and it is off. */
.nmo-chart-alert { color: var(--text-faint); opacity: 0.5; cursor: not-allowed; display: inline-flex; align-items: center; gap: 5px; background: none; border: 0; font: inherit; font-size: 12.5px; }
.nmo-chart-divider { width: 1px; align-self: stretch; background: var(--nmo-line); margin: 0 4px; }
.nmo-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 10px 16px 0; }
.nmo-stat { background: var(--bg-elev-1); border-radius: 12px; padding: 10px 12px; min-width: 0; }
.nmo-stat-wide { grid-column: 1 / -1; display: flex; justify-content: space-between; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.nmo-stat-k { font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); font-weight: 600; }
.nmo-stat-v { font-family: var(--font-display); font-variant-numeric: tabular-nums; font-size: 18px; font-weight: 600; margin-top: 2px; white-space: nowrap; }
.nmo-stat-empty { color: var(--text-faint); }
.nmo-stat-s { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; }
.nmo-stat-s .nmo-num { color: var(--text-dim); }

/* ── notes, refusals, and the words when something is missing ────────── */
.nmo-trouble { margin: 2px 0; }
/* A REFUSAL IS THE SERVER'S SENTENCE, printed as it arrived. */
.nmo-refusal {
  margin: 10px 0 0; padding: 10px 12px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--nmo-late) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--nmo-late) 34%, transparent);
  color: var(--text); font-size: 13.5px;
}
.nmo-screen > .nmo-refusal { margin: 10px 16px; }

/* ── narrow screens ──────────────────────────────────────────────────── */
/* A step down for the balance rather than a second line, and tighter buttons so
   the row still fits on the narrowest phone. */
/* ═══ 380 MISSED EVERY PHONE ANYBODY OWNS (measured 31 Aug 2026) ═══
   This layout — three columns, the second value on its own row — is the answer
   to a card with no room, and it was switched on below 380px. An iPhone is 390.
   So the phone the owner actually holds got the four-column layout meant for a
   desktop panel, the coin-name column was squeezed to whatever the two value
   blocks left it, and "Useless Coin With A Deliberately Long Name" showed 45% of
   itself on the Profile list.
   Measured at 390: the name column had about 132px of the ~290 the name wants.
   440 is above the widest phone in common use (430), so every phone gets the
   roomy layout and the 520-wide desktop panel keeps the four-column one.
   WHAT LOST THE ROOM: nothing was taken from anything. The second value block
   ("Net P&L" on a trade card) stops sharing line one and takes a full-width row
   of its own underneath, which is what this rule was written to do. */
@media (max-width: 440px) {
  /* ═══ EVERY VALUE BLOCK OFF LINE ONE, NOT JUST THE LAST (31 Aug 2026) ═══
     The last block already dropped to its own row here. The FIRST one stayed
     beside the name and took 42-90px of a 249px line, which left the name column
     103-143px. Measured at 390: "Apple xStock Tokenized Equity" showed 48% of
     itself and "Wrapped Wrapped Wrapped Ether" 39% — five pixels short of the
     50% this file calls a stub, on the very name whose photograph ("Apple
     xStock" cut to "A..") is why the measurement exists.
     With both blocks on their own rows the name column is the whole line minus
     the icon: 203px, and those two names read 69% and 76%.
     WHAT IT COSTS, measured at 390 rather than guessed: a card that HAS a first
     value block gains one row, 35-50px of height — 7 of the 15 feed cards, and
     all 3 Profile holdings. A card without one — the other 8 — gains 10px of
     name width and NO height at all. */
  .nmo-card { grid-template-columns: 36px minmax(0, 1fr); }
  .nmo-card .nmo-kv { grid-column: 2 / -1; text-align: left; }
  /* ═══ A TRADER'S CARD IS TWO-SIDED: IDENTITY LEFT, NUMBERS RIGHT ═══
     (owner, 31 Aug 2026, from his reference)
     The rule above stacks every value block under the name, which is right for
     the feed and Profile cards — it is what gave their coin names room back at
     phone width. On a TRADER'S card it put "Amount sold" and its number below
     the coin name on the left, and his reference is explicit: picture and name
     on the left, labelled values right-aligned on the SAME ROW, the way
     "Position 33.00" and "Net P&L -66.98" sit as columns at the right edge.
     So this card keeps the row. Two `auto` columns because the market value at
     the trade becomes a SECOND right-aligned column beside the first when the
     record carries it; with one value the second column is zero wide, and with
     none the whole block is absent — no label over an empty space. */
  .nmo-card.nmo-card-leader { grid-template-columns: 36px minmax(0, 1fr) auto auto; }
  .nmo-card.nmo-card-leader .nmo-kv { grid-column: auto; text-align: right; }
  /* ═══ THE TWO SIDES NEED A REAL BOUNDARY, NOT A SHARED ROW AND HOPE ═══
     (bb's painted-position check, 1 Sep 2026)
     Measured at 390x844: the symbol painted from x=148.0 to x=208.5 while
     "Amount bought" began at x=170.8 — the identity running 37.7px straight
     THROUGH the label on the same row. The cause is arithmetic, not styling:
     "Amount bought" and "MC when bought" want about 180px of the ~250 a phone
     card has, so the `1fr` identity track was squeezed to nothing and its text
     simply painted outside it.
     Clipping the identity is not an answer: the symbol is what says which coin
     this is. The card therefore reaches 56px left into the post's unused avatar
     gutter. At 390px that grows its content box from 249px to 305px. Two 60px
     value columns can then wrap their labels while leaving 119px for the symbol,
     all on the same row. The number comes from the post grid's 66px picture
     column minus its 10px card gap; it is reclaimed room, not viewport overflow.

     WHAT THE NAME LINE LEAVING CHANGED, measured at 390 on 1 Sep 2026: nothing
     across. The name sat UNDER the symbol, so dropping it took a row off the
     card's height and gave the columns no width at all. Both labels still wrap:
     the 60px column holds "Amount" over "sold", and "MC when" over "sold".
     Ending that wrap needs 90px a column — "MC when bought" is the widest label
     at 89.1px — and 90px columns inside today's 325px card squeeze the identity
     to 59px, where LANTERN (66.7px) wraps to four lines instead. Both fit only
     if the card also reaches 64px rather than 56px: 333px wide, 67px of
     identity, every label and every symbol up to 7 characters on one line. That
     is a change to the card's width and the owner has not asked for it. */
  /* ═══ AND WIDE ENOUGH THAT THE LABELS DO NOT STACK (owner, 1 Sep 2026) ═══
     A 60px column wrote "Amount" over "sold" and "MC when" over "sold" — the
     stacked look the owner has objected to four times, in miniature. Measured
     at 390, the labels want: Amount sold 65.9, MC when sold 74.0, Amount
     bought 81.1, MC when bought 89.1. So the column is 90px: the widest label
     on one line, with nothing to spare and nothing wasted.
     Two 90px columns need 8px more room than two 60px ones left over, so the
     reach into the post's picture gutter goes from 56px to 64px — 333px of
     card, 67px of identity, which measures every symbol up to seven characters
     on one line. A longer symbol wraps; the owner took that price knowingly. */
  .nmo-post > .nmo-card.nmo-card-leader {
    width: calc(100% + 64px);
    margin-left: -64px;
  }
  .nmo-card.nmo-card-leader {
    grid-template-columns: 36px minmax(0, 1fr) 90px;
  }
  .nmo-card.nmo-card-leader[data-nmo-has-trade-mc="1"] {
    grid-template-columns: 36px minmax(0, 1fr) 90px 90px;
  }
  .nmo-card.nmo-card-leader .nmo-kv { width: 90px; max-width: 90px; }
  /* ═══ AND IT MUST NOT SLIDE UNDER THE TRADER'S OWN PICTURE ═══════════════
     (measured 1 Sep 2026, from the screen the owner photographed)

     A negative margin reserves NOTHING. The picture's column runs the whole
     height of the post, so a card pulled 64px left goes under it — and on a
     trader's post with no words of its own the card is the very next thing
     after the name row, level with the picture. Measured at 390: the coin's
     36px art painted at x 38-74 against a picture at 18-64, twenty-six pixels
     of it underneath. That is what the owner photographed and what the deploy
     check reported as "the trader's picture paints 54px in a 46px box": not
     one picture too big, but two pictures inside one measurement.

     The selector is the fact itself — a card that DIRECTLY follows the name
     row is a card with no words above it. A card under a thesis is already
     clear and is not moved.

     TWO NUMBERS, AND WHY THIS ONE. Sixteen pixels clears the picture, which is
     what a person sees. Twenty-four also clears the eight-pixel margin the
     painted-size check measures through, so that check reads the picture alone
     again — 46 in 46 — instead of reading whatever else is near it. The eight
     extra pixels are invisible under a name row; a deploy gate that is red for
     a reason nobody can act on is not. If that check is ever narrowed to the
     picture's own box, this can go back to 16.

     ONE RULE FOR BOTH CARDS. Every card that reaches into the gutter carries
     `nmo-card-ref` — the trader's, the genie's position card and the "did not
     copy" note — so naming that class covers all three and cannot be forgotten
     when the next one is written. Measured wordless at 390: the trader's coin
     art sat 26px under the picture, the position card's 18px. Both clear. */
  .nmo-post > .nmo-post-h + .nmo-card.nmo-card-ref { margin-top: 24px; }
  .nmo-card.nmo-card-leader .nmo-k {
    white-space: normal; overflow-wrap: anywhere; line-height: 1.15;
  }
  /* The symbol is the whole left side of this card now — the coin's longer name
     was dropped from it on 1 Sep 2026 — so it may wrap rather than be cut. */
  .nmo-card.nmo-card-leader .nmo-sym {
    overflow: visible; text-overflow: clip; white-space: normal; overflow-wrap: anywhere;
  }

  /* ═══ THE GENIE'S OWN POSITION CARD IS TWO-SIDED TOO (owner, 1 Sep 2026) ═══
     He photographed his position card with "Position" and "Net P&L" STACKED
     under the coin, one above the other, and sent his reference: the picture,
     the symbol with the coin's name under it on the left, and the two labelled
     values side by side at the right on the SAME band — the whole card two
     text lines tall.

     Every rule that made that shape was written for `.nmo-card-leader`, the
     TRADER's card, so this one — a different class on the same screen — kept
     falling through to the stacking rule above. Measured at 390 before the
     change: 269px wide, 158.3px tall, both values below the name.

     THE NAME STAYS HERE. It went from the trader's card because two long
     labels needed the room; these labels are "Position" (42px) and "Net P&L"
     (42.7px), and they leave it.

     WHY THE COLUMNS ARE SIZED BY THEIR CONTENT rather than fixed like the
     trader's: money must never wrap. A fixed 82px column breaks "−$1,454.99"
     (81.4px) across two lines, and "$135," over "990.00" is a number a person
     can misread. Content-sized columns give each value exactly what it needs —
     measured at 390: NOMI's card comes out 60.5px tall with every value on one
     line, which is the two-line card he asked for. The 100px cap is the
     backstop: past it the value wraps rather than pushing the coin off the
     card, and the identity keeps whatever is left.

     The 56px reach into the post's picture gutter is the same reclaimed room
     the trader's card takes, and it puts the two cards' left edges in one line
     down the feed. NOT for the trader's card: it has its own widths above, and
     `:not()` here keeps the two from fighting over which was written last. */
  .nmo-post > .nmo-card.nmo-card-ref:not(.nmo-card-leader) {
    width: calc(100% + 56px);
    margin-left: -56px;
  }
  .nmo-card.nmo-card-ref:not(.nmo-card-leader) {
    grid-template-columns: 36px minmax(0, 1fr) auto auto;
  }
  .nmo-card.nmo-card-ref:not(.nmo-card-leader) .nmo-kv {
    grid-column: auto; text-align: right; max-width: 100px;
  }
  /* Nothing here paints through anything else: past its column a label or a
     value breaks onto a second line instead of running on. */
  .nmo-card.nmo-card-ref:not(.nmo-card-leader) .nmo-k,
  .nmo-card.nmo-card-ref:not(.nmo-card-leader) .nmo-v {
    overflow-wrap: anywhere;
  }
  /* THE SYMBOL ENDS IN AN ELLIPSIS RATHER THAN BREAKING. Breaking it is what
     the rule above would do, and a six-figure position measured at 390 left
     83px of identity and painted "BONE" over "R" — a symbol split down two
     lines is a coin nobody can read, and it cost the card the third line the
     owner asked it not to have. Cut, it stays one line and the card stays two.
     The name under it was already cut this way. */
  .nmo-card.nmo-card-ref:not(.nmo-card-leader) .nmo-sym {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
  }

  .nmo-stats { grid-template-columns: 1fr; }
}

/* ── the reference card under a thesis-only post ──────────────────────────
   What the post is ABOUT, in the smallest specific form: the coin, and either
   the trade it passed on, the behaviour it measured, or the position it holds.
   No market cap — a thesis-only post has no trade of its own to price. */
/* ONE SHAPE, ONE RULE (owner, 2026-08-29 15:36). The reference card used to
   carry its own copy of the grid and a sentence line of its own; it is the same
   card as the trade card — picture, symbol over name, and who traded on the
   right — so it is the same rule. */
.nmo-fact { white-space: nowrap; }
/* A coin with no readable name: its address, and why there is no name. */
.nmo-mint { font-family: var(--font-mono); }
.nmo-unknown { font-size: 12px; font-weight: 400; color: var(--text-faint); margin-left: 6px; }
/* Which of the coin's checks could not be read — one plain sentence, quiet. */
.nmo-ref-why { font-size: 13px; color: var(--nmo-late, var(--warn)); }
/* A stale LIST has sentence spacing and the warning colour. Stale numeric
   values use the shared opacity rule above and keep their card line box; this
   used to add six pixels of line padding to every old Position and P&L value,
   pushing the value glyphs back over their own labels in Profile. */
.nmo-stale:not(.nmo-num):not(.nmo-v) {
  font-size: 13px; color: var(--nmo-late, var(--warn)); padding: 6px 0 2px;
}

/* The reference card takes the position card's shape, plus the address row. */
.nmo-coin-name { font-size: 13px; color: var(--text-faint); }
.nmo-mintrow {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 10px;
  font-size: 12px; color: var(--text-faint); padding-top: 6px;
}
/* An ADDRESS is not prose — it is copied and compared character by character,
   which is the one place the mono face earns its keep. */
.nmo-mintrow .nmo-mint { font-family: var(--font-mono); font-size: 11.5px; }
.nmo-chartlink { text-decoration: none; }

/* Follow a wallet nobody has ranked: one input above every sub-tab. */
.nmo-addrbox { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 4px 0 10px; }
.nmo-addr {
  flex: 1 1 200px; min-width: 0; font: inherit; font-size: 14px; font-family: var(--font-mono);
  /* Typed and pasted addresses, same reason as above. */
  color: var(--text); background: var(--bg-elev-1); border: 1px solid var(--nmo-line);
  border-radius: 12px; padding: 9px 12px;
}
.nmo-addr:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-addrbox .nmo-refusal { flex: 1 0 100%; }



/* ── label-small section headers, as the app writes them ────────────────── */
/* The PAPER badge: a tonal chip, the same shape Smart Money's regime tag wears. */
.nmo-badge {
  font-size: 10px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-dim); background: var(--bg-elev-1); border-radius: 999px; padding: 3px 9px;
}

/* ── icons: the app's font where the subset has the glyph, a path where not ── */
.nmo-ic { font-size: 18px; line-height: 1; color: inherit; }
.nmo-ic-drawn {
  width: 18px; height: 18px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.nmo-act .nmo-ic-drawn, .nmo-lnk .nmo-ic-drawn { flex: none; }

/* ── what this screen alone needs (type comes from core's shared chrome) ───
   The chip rows, range chips, section labels and empty state are core's
   .app-chip / .app-rangechip / .app-sectionlabel / .app-empty — nothing here
   sets a font-size for them, which is the pin. */
.nmo-tabs { padding: 8px 0 10px; }
.nmo-subtabs { padding: 2px 0 10px; }
.nmo-ranges { padding: 10px 0 2px; }
/* The PAPER tag beside the balance label. */
.nmo-badge {
  font-size: 10px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-dim); background: var(--bg-elev-1); border-radius: 999px; padding: 3px 9px;
}

/* ── the header row: core's top-bar box, and only what is left over ───────
   `.m3-toprow` gives the padding, gap and height; `.m3-toprow-title` the 14px/600
   the session title uses; `.m3-textbtn` Deposit; core's own `.icon-btn` the bell
   and the ×. Nothing here sets a font-size for any of them — that is the pin. */
.nmo-head { flex: none; padding-top: env(safe-area-inset-top); }
.nmo-bal { font-variant-numeric: tabular-nums; flex: 0 1 auto; min-width: 0; }
.nmo-tabs, .nmo-subtabs, .nmo-ranges { padding-left: 16px; padding-right: 16px; }
.nmo-tabs { padding-bottom: 10px; }
.nmo-subtabs { padding-bottom: 10px; }
.nmo-ranges { padding-top: 10px; }
/* The two icon buttons keep core's 40px box; only the drawn glyph is ours. */
/* The glyph size, named once: the row's right-edge pull is derived from it and
   from core's button size, so neither is written twice. */
.nmo-top { --m3-toprow-glyph: 20px; }
.nmo-bell .nmo-ic, .nmo-close .nmo-ic { font-size: var(--m3-toprow-glyph); }
.nmo-bell .nmo-ic-drawn, .nmo-close .nmo-ic-drawn { width: var(--m3-toprow-glyph); height: var(--m3-toprow-glyph); }
/* A quiet action inside a dialog — the modal's Cancel. */
.nmo-cta-quiet { background: none; }

/* ── the Coin screen (decision 17) ────────────────────────────────────────
   Somewhere you WENT from a card, not a panel: it takes the body, and the app's
   back brings you out of it before it closes the screen. Type and chips come
   from core's shared chrome; only what has no token is here. */
.nmo-coin-top { display: flex; align-items: center; gap: 10px; padding: 4px 0 10px; }
.nmo-coin-head { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 12px; align-items: center; }
/* ═══ THE ADDRESS UNDER THE SYMBOL (owner, RB10 riders 2) ═══
   The symbol and the coin's long name share one line, the name muted and
   ellipsised when it will not fit — its whole text is on the row's title — and
   the short address with its copy icon sits directly beneath, where fomo puts
   it and where a person looks for it. */
.nmo-sym-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.nmo-sym-line .nmo-coin-name { font-size: 12.5px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.nmo-coin-names .nmo-mintrow, .nmo-card-names .nmo-mintrow { padding: 1px 0 0; }
.nmo-coin-head .nmo-tok { width: 44px; height: 44px; }
.nmo-coin-names { min-width: 0; }
.nmo-coin-facts { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; font-size: 13px; }
.nmo-coin-mc { font-variant-numeric: tabular-nums; }
/* A market cap we already knew rather than one we just read: dimmed, because
   showing it the way a fresh one is shown says it is today's. */
.nmo-coin-old { opacity: 0.55; }
.nmo-coin-delayed { font-size: 11.5px; color: var(--text-faint); line-height: 1.3; text-align: right; }
.nmo-coin-holders { display: flex; flex-wrap: wrap; gap: 8px; font-size: 12.5px; color: var(--text-faint); padding: 8px 0 2px; }
.nmo-coin-you { color: var(--text-dim); }
.nmo-coin-chart { position: relative; padding: 6px 0 2px; }
.nmo-coin-chart-h { display: flex; justify-content: flex-end; }
/* THE PLOT IS AS WIDE AS THE ROW IT IS IN (owner, RB10). Its viewBox is built at
   the row's measured width and its height matches the box below, so the drawing
   fills the element instead of being letterboxed into a 272-pixel strip in the
   middle of a 1700-pixel sheet — which is what a 340×200 viewBox does inside a
   160-pixel-tall box on a wide screen. */
.nmo-coin-line { width: 100%; height: 200px; display: block; }
.nmo-coin-nochart { text-align: left; }

/* ── the chart, like the reference (RB9) ──────────────────────────────
   THE AXIS IS THE ONLY TEXT ON THE PLOT, and it is muted so the line stays the
   thing you look at. The pulse is a CSS animation with a one-second period —
   the owner asked for the circle to pulse every second, and a JS timer for that
   would be one more thing to leak. */
/* BIG ENOUGH TO READ ON A PHONE. Nine pixels was unreadable at arm's length. */
.nmo-chart-tick { font-size: 12px; fill: var(--text-faint); font-variant-numeric: tabular-nums; }
/* THE LINE ITSELF: thin and mint, the way the recording draws it, with only a
   breath of colour under it. A 2-pixel stroke that does not scale with the box,
   so it stays a hairline whatever width the row turns out to be. */
.nmo-chart-path { stroke: var(--nmo-chart-green); stroke-width: 2; fill: none; }
/* A FADE, NOT A BLOCK. The recording has the mint breathing out under the line
   and gone before the bottom of the plot; a flat wash read as a solid slab. */
.nmo-chart-fill { fill: url(#nmo-chart-fade); }
/* The faint dotted grid behind it — small, low contrast, never competing. */
.nmo-chart-grid { fill: var(--nmo-chart-grid); }
.nmo-chart-now { stroke: var(--nmo-chart-green); stroke-width: 1; stroke-dasharray: 4 4; opacity: 0.75; }
.nmo-chart-pill rect { fill: var(--nmo-chart-green); }
.nmo-chart-pill text { font-size: 12px; fill: #062a17; font-weight: 700; font-variant-numeric: tabular-nums; }
/* The end of the line: a solid mint dot inside a darker translucent ring that
   grows and fades once a second. */
.nmo-chart-dot { fill: var(--nmo-chart-green); }
.nmo-chart-pulse { fill: var(--nmo-chart-halo); animation: nmo-chart-pulse 1s ease-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes nmo-chart-pulse {
  0% { opacity: 0.45; transform: scale(0.7); }
  70% { opacity: 0; transform: scale(1.8); }
  100% { opacity: 0; transform: scale(1.8); }
}
@media (prefers-reduced-motion: reduce) {
  .nmo-chart-pulse { animation: none; }
}

/* ═══ THE EXACT PLOT SLIDES; ITS SHAPE NEVER INTERPOLATES ═══
   (owner's chart-feel video, 1 Sep 2026)
   One SVG group owns every time-positioned shape. JavaScript moves that group
   left at the chart's real pixels-per-millisecond rate, then swaps the exact
   path when a reading lands. There is no opacity transition: the deployed
   cross-fade was measured in a lower-clarity state for 60.7% of a minute. */
.nmo-chart-slide { will-change: transform; }
/* A trade has no price of its own, so it may wake the real line but never move
   it. Buy and sell use the same motion; colour already says the side. */
@keyframes nmo-chart-order-wake {
  0% { filter: brightness(1); }
  45% { filter: brightness(1.65) drop-shadow(0 0 3px var(--nmo-chart-green)); }
  100% { filter: brightness(1); }
}
.nmo-chart-order { animation: nmo-chart-order-wake 420ms ease-out; }
@keyframes nmo-chart-breakout {
  0% { filter: brightness(1); }
  45% { filter: brightness(2.2) drop-shadow(0 0 5px var(--nmo-chart-green)); }
  100% { filter: brightness(1); }
}
.nmo-chart-rise .nmo-chart-dot { animation: nmo-chart-breakout 520ms ease-out; }
/* A price rise kicks the SCREEN FRAME, not the drawing. Moving `.nmo-sheet` or
   `.nmo-chart` would move the line and every trader mark with it, turning a
   presentation flourish into false chart geometry. This transparent edge is a
   sibling overlay painted by the sheet itself; its two-pixel shake cannot
   change the position of any SVG node below it. */
@keyframes nmo-screen-rise-shake {
  0%, 100% { opacity: 0; transform: translateX(0); }
  18% { opacity: 0.55; transform: translateX(-2px); }
  38% { opacity: 0.42; transform: translateX(2px); }
  58% { opacity: 0.3; transform: translateX(-1px); }
  78% { opacity: 0.18; transform: translateX(1px); }
}
.nmo-sheet.nmo-screen-rise::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--nmo-chart-green) 65%, transparent),
    inset 0 0 24px color-mix(in srgb, var(--nmo-chart-green) 12%, transparent);
  opacity: 0;
  animation: nmo-screen-rise-shake 260ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .nmo-chart-order, .nmo-chart-rise .nmo-chart-dot { animation: none; }
  .nmo-sheet.nmo-screen-rise::after { display: none; animation: none; }
}

/* ── the trades beside the chart (RB9 §3.5) ───────────────────────────
   Newest at the top, seven of them, each a real swap with its real size. A new
   row slides in; nothing is ever invented to fill the column. */
/* ═══ THE TRADES RIDE OVER THE PLOT, NOT BESIDE IT (owner's recording, RB10b) ══
   In the video the buys and sells are drawn ON the chart's left edge and the
   line runs the whole width underneath them. A column beside the plot took the
   width away from the thing it was next to. */
.nmo-coin-chart-row { position: relative; --nmo-marks-h: 0px; }
.nmo-coin-plot { width: 100%; }
/* ═══ THE COUNTS ABOVE THE CHART OWN THAT BAND, AND THE LANE STARTS UNDER IT ══
   These two lines are drawn IN THE FLOW at the top of the plot, on purpose: a
   count that floated would be positioned against whatever ancestor happened to
   be positioned. The trades lane beside them is absolute against the ROW, and
   its `top` was a flat 12px chosen when the chart was the first thing in the
   row. Once a count is drawn the chart is no longer first, and 12px lands in
   the middle of the count — "1 trader move before this chart" and "No trades
   showing yet." were painted on top of each other, 14px of overlap across the
   whole 106px the sentence is wide.

   So the row measures its own top band and the lane starts below it. The height
   is `:has()`, not JavaScript, because the counts live inside `.nmo-coin-plot`
   and the six-second tick REPLACES that whole element — a number written on the
   row by the builder would go stale the first time a trade landed in a hole,
   and going stale here means going back to overlapping. `:has()` re-answers on
   its own every time the plot is swapped.

   `line-height` is stated rather than left to `normal` for the same reason the
   variable exists at all: this is the number the offset below is made of, and a
   font that rounds it differently would put the sentence back over the count. */
.nmo-marks-before,
.nmo-marks-ingap { margin: 0; font-size: 11.5px; line-height: 14px; }
.nmo-coin-chart-row:has(.nmo-marks-before) { --nmo-marks-h: 14px; }
.nmo-coin-chart-row:has(.nmo-marks-ingap) { --nmo-marks-h: 14px; }
.nmo-coin-chart-row:has(.nmo-marks-before):has(.nmo-marks-ingap) { --nmo-marks-h: 28px; }
.nmo-coin-trades {
  position: absolute; left: 0; top: var(--nmo-marks-h); bottom: 20px; width: 110px;
  overflow: hidden; pointer-events: none; z-index: 1;
}
/* The lane existing but carrying no row must not look like a missing feature.
   This sentence describes only what is on screen; it does not turn a pending,
   refused or honestly empty read into a claim that the coin never traded. */
.nmo-trades-empty {
  position: absolute; left: 2px; top: 12px; width: 106px; z-index: 1;
  margin: 0; color: var(--text-faint); font-size: 11.5px; line-height: 1.3;
  background: var(--bg); padding: 2px 3px; border-radius: 3px;
  pointer-events: none;
}
/* ═══ A VERTICAL MARQUEE (owner: "real time and doing vertical marquee") ═══
   Every row rides upward at one speed from the moment it arrives. It is one
   animation per row, started when the row is made — not a list that re-sorts, so
   nothing ever jumps and a row arriving does not move the ones already going. */
.nmo-trade-lane { position: absolute; inset: 0; }
@keyframes nmo-trade-rise {
  from { transform: translateY(0); opacity: 0; }
  8% { opacity: 1; }
  82% { opacity: 1; }
  to { transform: translateY(calc(-1 * var(--nmo-rise, 180px))); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .nmo-trade { animation-duration: 1ms !important; }
}
/* A tab nobody is looking at composites nothing. */
.nmo-marquee-still .nmo-trade { animation-play-state: paused; }
/* A ROW READS OVER THE LINE. The chart runs underneath it, so the text carries
   its own dark backing rather than relying on whatever is behind it. */
.nmo-trade {
  position: absolute; left: 0; bottom: 0;
  font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums;
  white-space: nowrap; display: flex; gap: 5px; align-items: baseline;
  padding: 1px 6px 1px 2px; border-radius: 4px;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  animation: nmo-trade-rise var(--nmo-rise-ms, 18000ms) linear both;
}
.nmo-trade-arrow { font-size: 10px; }
.nmo-trade-noamount { color: var(--text-faint); }
/* SEVERAL SWAPS ADDED UP, not one person's trade. Muted, because the word is a
   caveat on the amount beside it and must not compete with it. */
.nmo-trade-sum { color: var(--text-faint); font-size: 9px; align-self: center; }
.nmo-trade-buy { color: var(--nmo-up); }
.nmo-trade-sell { color: var(--nmo-down); }
/* Nothing known: said UNDER the chart, never as a row in the marquee — a row
   there would read as a trade that happened. */
.nmo-trades-none { font-size: 12px; color: var(--text-faint); padding: 4px 0 0; }

/* ── the Coin screen as a sheet over everything (RB9 §3.1) ────────────
   IT COVERS THE COMPOSER. That is the point: the reference slides a sheet up
   over the whole app, and ours sat under the tabs with the composer still
   showing. `position: fixed` on an element appended to <body> is the only way to
   be above a composer this plugin must never style — see decision 4. */
/* ═══ ABOVE THE APP'S OWN FULL-SCREEN CHROME (owner, RB10) ═══════════════════
   THE SHEET OPENED ON DESKTOP AND NOT ON A PHONE, and the reason is one number.
   The plugin renders inside `#session-overlay` (index.html:262 → .session-panel
   → #transcript → .plugin-mount). That overlay is `position: fixed; inset: 0;
   z-index: 80` (src/web/styles.css:1431) — and only INSIDE
   `@media (min-width: 900px)` is it dropped to `position: relative; z-index: 1`
   (src/web/styles.css:1673). So under 900px the app's own chrome is a
   full-screen box at 80 and this sheet, at 60 on the body beside it, was painted
   underneath it. It opened every time; nobody could see it.
   It is not a phone-versus-desktop thing at all: narrowing a desktop window
   below 900px reproduces it exactly.
   100 clears the session overlay (80) and stays under the app's own menus and
   drawers (200 and up), which are the things that SHOULD cover this.

   THE PANEL IS THE BASE AND THE PHONE IS THE OVERRIDE, which is the wrong way
   round from how it reads but the right way round for this screen's own rule:
   nothing here may be styled only above a width, because a laptop would then
   depend on a query to be styled at all. So the wide case is what every reader
   gets, and a narrow one is handed the full-width band on top of it. A typo in
   the query leaves a phone with a 520-wide panel that still fits — a bad look,
   not a bare screen. */
.nmo-sheet {
  position: fixed; z-index: 100;
  left: 50%; right: auto; bottom: 0; transform: translateX(-50%);
  width: 520px; max-width: calc(100% - 32px);
  height: min(90vh, 860px);
  background: var(--bg-elev-1, var(--bg)); color: var(--text);
  border-radius: 18px 18px 0 0; box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.45);
  display: flex; flex-direction: column; overflow: hidden;
  animation: nmo-sheet-up-wide 260ms var(--ease, cubic-bezier(0.2, 0, 0, 1)) both;
  /* ═══ AND NOTHING UNDER THE BAR: THE BAR'S EDGE IS THE SHEET'S EDGE ═══
     THIS OVERRIDES the token block at the top of this file — `.nmo-root,
     .nmo-sheet` (line 34), which sets `padding: 0 0 12px`. That padding was
     written for `.nmo-root`, the screen inside the tab, which wants a little
     room under its last card. The sheet joined that selector only to get the
     `--nmo-*` colours (it is on `document.body` and inherits nothing from
     `.nmo-root`), and it took the padding with it: 12 pixels of the sheet's own
     ground under the Buy/Sell bar, with the list at the top and at the bottom
     alike. That is the gap in the owner's deploy-3 screenshot, measured 29 Aug
     2026. Same specificity as the block above, further down the file, so this
     wins. `.nmo-trade-bar` below already leaves room for the phone's own bottom
     bar inside its own padding; the sheet must add nothing to that. */
  padding: 0;
}
@keyframes nmo-sheet-up-wide {
  from { transform: translate(-50%, 100%); }
  to { transform: translateX(-50%); }
}
@keyframes nmo-sheet-up { from { transform: translateY(100%); } to { transform: none; } }
/* A phone: the whole width, a band off the bottom edge, as it is designed. */
@media (max-width: 640px) {
  .nmo-sheet {
    left: 0; right: 0; transform: none;
    width: auto; max-width: none; height: 90vh;
    animation-name: nmo-sheet-up;
  }
}
.nmo-sheet-grab { flex: none; padding: 8px 0 4px; display: flex; justify-content: center; cursor: grab; }
.nmo-sheet-grab::before { content: ""; width: 36px; height: 4px; border-radius: 999px; background: var(--text-faint); opacity: 0.5; }
.nmo-sheet-body { flex: 1 1 auto; overflow-y: auto; padding: 0 16px 24px; }
.nmo-sheet-scrim { position: fixed; inset: 0; z-index: 99; background: rgba(0, 0, 0, 0.5); }

/* ═══ BUY AND SELL, ON THE SHEET'S FLOOR (RB11; owner, 29 Aug 2026 17:3x) ════
   It is the sheet's own last child, after the scrolling list — so the list
   scrolls above it and it sits on the bottom edge. It does not move, and nothing
   holds it there.

   IT WAS STUCK TO THE BOTTOM OF THE LIST INSTEAD, and the list was padded by the
   bar's height so the last post could pass it. A stuck element measures its
   bottom from the list's CONTENT edge, so that padding lifted the bar a whole
   bar-height plus 24px off the sheet's floor, with the post text running under
   it — the gap in the owner's two screenshots.

   It carries the sheet's own ground behind it, the phone's safe area is part of
   its padding, and it takes the 16px at the sides the list used to give it.

   These five were written inline in `web/coinTrade.ts` while this file belonged
   to another lane; they are here now and the module is free of them. */
.nmo-trade-bar {
  position: static; flex: none;
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--bg-elev-1, var(--bg));
}
/* IT WRAPS ON A NARROW PHONE. Four sizes, Confirm and Cancel do not fit across
   360 pixels, and a row that overflows puts Confirm off the edge of the screen. */
.nmo-trade-row { display: flex; flex-wrap: wrap; gap: 8px; }
/* THE TWO SIDES TAKE THE SCREEN'S OWN COLOURS, the same green and red every
   number on it uses — the buy is the filled one, because it is the thing a
   person came here to do. */
.nmo-trade-bar .nmo-trade-buy {
  background: var(--nmo-up); color: var(--bg); border: 0; font-weight: 700;
}
.nmo-trade-bar .nmo-trade-sell {
  background: none; color: var(--nmo-down); border: 1px solid var(--nmo-down); font-weight: 600;
}
.nmo-trade-bar .nmo-trade-buy, .nmo-trade-bar .nmo-trade-sell {
  border-radius: 999px; padding: 11px 14px; font: inherit; font-size: 14px; cursor: pointer;
}
.nmo-trade-bar button:disabled { opacity: 0.5; cursor: not-allowed; }
.nmo-trade-chip {
  border-radius: 999px; padding: 6px 12px; font: inherit; font-size: 12.5px; cursor: pointer;
  background: none; color: var(--text-faint); border: 1px solid var(--nmo-line);
}
.nmo-trade-chip.nmo-trade-chip-on { color: var(--text); background: var(--bg-elev-2, color-mix(in srgb, var(--text) 12%, transparent)); }
.nmo-trade-confirm { background: var(--nmo-up); color: var(--bg); border: 0; border-radius: 999px; padding: 11px 14px; font: inherit; font-size: 14px; font-weight: 700; cursor: pointer; }
.nmo-trade-cancel { background: none; color: var(--text-faint); border: 0; font: inherit; font-size: 13px; cursor: pointer; }
.nmo-trade-receipt { font-size: 12.5px; color: var(--text-dim); }
.nmo-trade-refusal { font-size: 12.5px; color: var(--nmo-down); }
.nmo-trade-say { min-height: 1px; }
@media (prefers-reduced-motion: reduce) {
  .nmo-sheet { animation: none; }
}
/* WHOSE CHART THIS IS. A line drawn from our own price reads is not the pool's
   history, and the caption says so and when ours starts — otherwise the left
   edge reads as the day the coin began. Same faint small print as everything
   else that is a note rather than a number. */
.nmo-coin-since { font-size: 11.5px; color: var(--text-faint); padding-top: 4px; margin: 0; }
/* The value now, at the end of the line. */
.nmo-coin-pill {
  position: absolute; right: 0; top: 30px;
  font-size: 11.5px; font-variant-numeric: tabular-nums;
  background: var(--bg-elev-2); border-radius: 999px; padding: 3px 9px;
}
/* A trade on the line: filled for a buy, hollow for a sell, so the two read
   apart without relying on colour alone. */
/* A trade on the line, as the recording draws it: a filled disk in the side's
   colour with a dark ring around it, and the trader's initial inside. */
.nmo-mark { stroke-width: 2.5; vector-effect: non-scaling-stroke; stroke: var(--bg); }
.nmo-mark-buy { fill: var(--nmo-chart-green); }
.nmo-mark-sell { fill: var(--nmo-down); }
.nmo-mark-letter { font-size: 9px; font-weight: 700; fill: #062a17; text-anchor: middle; pointer-events: none; }
.nmo-mark-sell + .nmo-mark-letter { fill: var(--bg); }
.nmo-mark-by-callout { opacity: 0.7; }
/* A MARK THAT IS SOMEBODY'S FACE. The picture is the circle's fill, so the ring
   has to become a stroke in the side's colour rather than the fill it is on a
   plain mark — otherwise the colour would cover the face it is meant to ring. */
.nmo-mark-face { stroke-width: 2.5; }
.nmo-mark-face.nmo-mark-buy { stroke: var(--nmo-up); }
.nmo-mark-face.nmo-mark-sell { stroke: var(--nmo-down); }
.nmo-own-trade-mark {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}
.nmo-own-trade-mark-buy { stroke: var(--nmo-up); }
.nmo-own-trade-mark-sell { stroke: var(--nmo-down); }
.nmo-avgline { stroke: var(--text-faint); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.nmo-coin-settings { display: flex; flex-wrap: wrap; gap: 8px; padding: 8px 0 2px; }
.nmo-coin-toggle {
  font: inherit; font-size: 12.5px; color: var(--text-dim); background: var(--bg-elev-1);
  border: none; border-radius: 999px; padding: 6px 12px; cursor: pointer;
}
.nmo-coin-toggle.is-on { background: var(--sm-chip-on); color: var(--text); }
.nmo-coin-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The expanded position card's second row of numbers. */
.nmo-card-more { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 18px; padding-top: 10px; }
.nmo-coin-ask { width: 100%; margin: 16px 0 4px; }
/* A card is a door: it says so on hover and focus, and nothing else changes. */
.nmo-card[role="button"] { cursor: pointer; }
.nmo-card[role="button"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── how a post came about (rulebook rule 3) ──────────────────────────────
   One label under the card: copied @x · not copied: … · on its own · asked by
   the owner. Four different claims, and only one of them means the genie acted. */
.nmo-label { font-size: 12.5px; color: var(--text-faint); padding-top: 6px; }
.nmo-label-copied { color: var(--nmo-up); }
.nmo-label-notcopied { color: var(--nmo-late, var(--warn)); }
/* Line or candles, when the wire carries what candles need. */
.nmo-coin-shape { display: flex; gap: 8px; flex: 1 0 100%; padding-bottom: 4px; }
.nmo-candle-wick { stroke-width: 1; vector-effect: non-scaling-stroke; }
.nmo-candle-wick.nmo-up, .nmo-candle.nmo-up { stroke: var(--nmo-up); fill: var(--nmo-up); }
.nmo-candle-wick.nmo-down, .nmo-candle.nmo-down { stroke: var(--nmo-down); fill: var(--nmo-down); }
.nmo-coin-reply { width: 100%; margin-top: 8px; }
.nmo-coin-share { flex: none; }
/* Copy one trade: a control on the action row, not a second Follow. */
.nmo-act-copy { gap: 4px; }
.nmo-copy-note { color: var(--text-faint); }

/* ── what a reply is answering (rule 6) ───────────────────────────────────
   A reply read on its own is an answer with the question missing, so the
   comment sits above the genie's line — as somebody's own words, quoted. */
.nmo-replyto {
  margin: 2px 0 8px; padding: 8px 10px; border-left: 2px solid var(--nmo-line);
  font-size: 12.5px; color: var(--text-faint); background: var(--bg-elev-1);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.nmo-replyto-who { font-weight: 600; margin-right: 6px; color: var(--text-dim); }
.nmo-replyto-q { quotes: none; overflow-wrap: anywhere; }

/**
 * The risk lines under a card (decision 20). Small, faint, plain — the same
 * colour as the rest of the small print. NEVER --nmo-down, --warn or --nmo-late:
 * in paper the door let the copy through, so a fact about who holds the coin is
 * a fact and not a warning, and red would report a decision nobody made.
 */
.nmo-risk { font-size: 12.5px; color: var(--text-faint); padding-top: 4px; line-height: 1.35; }


/* ═══ THE THREE TABS UNDER THE COIN'S CHART (wednesday, 1 Sep 2026) ══════════
   The owner asked for "holders (with count) / feed / about", the selected one
   underlined and white and the others grey. The bar is its own row under the
   chart; the panel below it is whatever that tab draws.

   Numbers on this tab are laid out label-left, value-right, because that is the
   shape his reference pictures use for a list of facts and it reads down the
   values rather than across the labels. */
.nmo-coin-tabs { display: flex; gap: 18px; padding: 10px 16px 0; border-bottom: 1px solid var(--nmo-line); }
.nmo-coin-tab {
  appearance: none; background: none; border: 0; padding: 6px 0 10px; cursor: pointer;
  color: var(--text-faint); font: inherit; font-weight: 600;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
/* The chosen tab is white with a white line under it; the other two stay grey. The
   line is written out in full rather than as `border-bottom-color`, because the
   whole declaration is what says this tab is underlined — a lone colour relies on
   the transparent border above still being there to give it a width. */
.nmo-coin-tab.is-on { color: var(--text); border-bottom: 2px solid var(--text); }
.nmo-coin-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-coin-panel { padding-top: 4px; }
/* All three panels are built and kept; the two nobody is looking at are hidden
   rather than thrown away, so nothing loses the reader's place. Stated here so a
   `display` on the class above can never quietly outrank the browser's own rule. */
.nmo-coin-panel[hidden] { display: none; }

.nmo-ct-list { padding: 2px 16px 8px; }
.nmo-ct-row { display: flex; align-items: baseline; gap: 8px; padding: 7px 0; }
/* The dotted leader his reference pictures use between a label and its value. It
   is decoration, so it is a background on a spacer rather than characters a screen
   reader would have to read out. */
.nmo-ct-row::after {
  content: ""; order: 1; flex: 1 1 auto; min-width: 12px; align-self: center; height: 1px;
  background-image: radial-gradient(circle, var(--text-faint) 0.5px, transparent 0.5px);
  background-size: 4px 1px; background-repeat: repeat-x; opacity: 0.5;
}
.nmo-ct-v { order: 2; }
.nmo-ct-k { color: var(--text-faint); flex: none; }
.nmo-ct-v { text-align: right; }
/* A number we do not have reads as a sentence, never as a dash or a zero. */
.nmo-ct-none { color: var(--text-faint); }
.nmo-ct-note, .nmo-ct-read { color: var(--text-faint); font-size: 12px; padding: 0 16px 6px; }

.nmo-ct-addr { display: flex; align-items: center; gap: 8px; padding: 2px 16px 12px; }
/* The contract address on the About tab is a `.nmo-mint` like every other contract
   address in the product, and takes its monospace face from that one rule. Only the
   size and the wrapping are this tab's business — the same shape as
   `.nmo-mintrow .nmo-mint` above. */
.nmo-ct-addr .nmo-mint { font-size: 12px; overflow-wrap: anywhere; min-width: 0; }
.nmo-ct-copy { appearance: none; background: none; border: 0; padding: 4px 0; margin-left: auto;
  color: var(--accent); font: inherit; cursor: pointer; flex: none; }

.nmo-ct-empty { padding: 14px 16px 18px; }
.nmo-ct-count { margin: 0 0 8px; }
.nmo-ct-why { margin: 0; color: var(--text-faint); }
