/* 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;
  /* ═══ THE MONO FACE, AND WHY THIS LINE CHANGED ═══════════════════════════
     It read `--font-mono: "Google Sans Code", var(--font-mono)`. A custom
     property defined in terms of ITSELF is invalid at computed-value time, so
     every `font-family: var(--font-mono)` in this product fell back to whatever
     it inherited. Measured 1 Sep 2026: the coin screen's contract address
     painted in Google Sans Text — a proportional face, which is why an address
     never lined up the way an address should.

     And the face it named was never there to reach: "Google Sans Code" is not
     requested in index.html and has no @font-face. It could not have resolved
     even with the self-reference removed.

     So this names the mono face the product ACTUALLY loads — JetBrains Mono,
     already fetched for the pre-auth landing — and then the system's own mono
     stack, which is what a fallback list is for. */
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ═══ THE MATERIAL 3 TYPE SCALE ═══════════════════════════════════════════
     Fifteen named roles, each one a size, a line height, a weight and a letter
     spacing that belong together. Use a ROLE, never a number.

     WHY THIS EXISTS (owner, 1 Sep 2026: "the font design should follow material
     3 principle"). Before it, every size in this product was a bare number
     chosen one at a time. The cost was measured the same day: the profile
     picture went from 66px to 41px across three changes and not one font moved
     with it, because nothing said which text was a title and which was a label
     — there was only 13px, 14px, 12.5px, and no way to tell what any of them
     meant. A role is a decision about what a piece of text IS, and that
     survives a redesign; a bare 13px does not.

     HOW TO USE ONE, both lines together:

         font: var(--m3-title-small);
         letter-spacing: var(--m3-title-small-tracking);

     The `font` shorthand carries weight, size, line height and family in one
     declaration. It also RESETS font-variant, so anything that needs
     `font-variant-numeric: tabular-nums` must declare it AFTER the shorthand,
     not before.

     The families are ours, not Material's: display roles take --font-display
     (Google Sans Flex), everything else --font-ui (Google Sans Text). The
     sizes, line heights, weights and tracking are Material 3's own. */
  --m3-display-large: 400 57px/64px var(--font-display);
  --m3-display-large-tracking: -0.25px;
  --m3-display-medium: 400 45px/52px var(--font-display);
  --m3-display-medium-tracking: 0px;
  --m3-display-small: 400 36px/44px var(--font-display);
  --m3-display-small-tracking: 0px;

  --m3-headline-large: 400 32px/40px var(--font-display);
  --m3-headline-large-tracking: 0px;
  --m3-headline-medium: 400 28px/36px var(--font-display);
  --m3-headline-medium-tracking: 0px;
  --m3-headline-small: 400 24px/32px var(--font-display);
  --m3-headline-small-tracking: 0px;

  --m3-title-large: 400 22px/28px var(--font-ui);
  --m3-title-large-tracking: 0px;
  --m3-title-medium: 500 16px/24px var(--font-ui);
  --m3-title-medium-tracking: 0.15px;
  --m3-title-small: 500 14px/20px var(--font-ui);
  --m3-title-small-tracking: 0.1px;

  --m3-body-large: 400 16px/24px var(--font-ui);
  --m3-body-large-tracking: 0.5px;
  --m3-body-medium: 400 14px/20px var(--font-ui);
  --m3-body-medium-tracking: 0.25px;
  --m3-body-small: 400 12px/16px var(--font-ui);
  --m3-body-small-tracking: 0.4px;

  --m3-label-large: 500 14px/20px var(--font-ui);
  --m3-label-large-tracking: 0.1px;
  --m3-label-medium: 500 12px/16px var(--font-ui);
  --m3-label-medium-tracking: 0.5px;
  --m3-label-small: 500 11px/16px var(--font-ui);
  --m3-label-small-tracking: 0.5px;
}

/* ---- 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 — and, since a chip is left out
   while its own screen is the one on the page, that includes the case where the
   screen you are looking at owned the last chip. There is NO "current" chip
   state to paint: the door you are already through is not drawn at all. See
   `mountPluginChips` in plugins.ts. */
.plugin-chips[hidden] { display: none; }
.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; }
/* ═══ A SCREEN THAT ASKED FOR THE BAR TO STAND DOWN (#323) ═══
   Owner, 14 Sep 2026, on the For You deck: no ask bar and no Chill · credits · Top up strip while
   the cards are up, so a card fits the phone. The class is set only by src/web/composerAway.ts, on
   a screen's request, and a build without that screen never sets it. Out of flow while away, back
   in flow when it returns — never lifted over the page (composer-reach-test). */
body.composer-away .composer-wrap,
body.composer-away .usage-hud,
body.composer-away .context-hint { display: none; }
/* …AND THE BOTTOM EDGE IT KEPT CLEAR BECOMES THE SCREEN'S (#328). With the bar gone, the scroller's own bottom is
   the bottom of the page, so it keeps the phone's home bar clear itself — env(safe-area-inset-bottom), 0 on a phone
   without one — plus 12px, so a control at the very end of the screen (the For You tray) is never under the home
   indicator and never touches the screen's edge. */
body.composer-away .transcript { padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
/* 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; }
/* The other two M3 button roles, for the bottom of a sheet rather than the end
   of a row of icons: 40px tall, which is the size the spec gives a button that
   is the thing being asked for. The outlined one is the way out or the way
   sideways, the filled one is the answer. They live here, beside the text
   button, because a button role belongs to the app and not to whichever screen
   needed it first. */
.m3-outlinedbtn, .m3-filledbtn {
  font: inherit; font-size: 14px; font-weight: 560; line-height: 1;
  min-height: 40px; padding: 0 20px; border-radius: 999px; cursor: pointer;
  white-space: nowrap; text-align: center;
}
.m3-outlinedbtn { border: 1px solid var(--border, var(--bg-elev-2)); background: none; color: var(--text); }
.m3-outlinedbtn:hover { background: var(--bg-elev-1); }
.m3-filledbtn { border: none; background: var(--text); color: var(--bg); }
.m3-filledbtn:hover { opacity: 0.9; }
.m3-outlinedbtn:disabled, .m3-filledbtn:disabled { opacity: 0.5; cursor: default; }
.m3-outlinedbtn:focus-visible, .m3-filledbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* 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. */
/* THE COPY SHEETS ARE A THIRD DOOR (owner, 9 September 2026). They moved off
   `.nmo-overlay` inside the plugin's container and onto `document.body`, so that
   they cover the composer — and on the body they inherit nothing from
   `.nmo-root`. Same trap the coin sheet fell into and for the same reason: a
   token that resolves to nothing is not an error anywhere, it is a control drawn
   in the wrong colour. The layout properties at the foot of this block are
   cancelled again in `.nmo-copy-scrim` below; only the tokens are wanted here. */
.nmo-root,
.nmo-sheet,
.nmo-copy-scrim {
  /* ═══ THE POST'S LEFT COLUMN, DECLARED ONCE ═══════════════════════════════
     The picture's size, the post's padding and the thread line's thickness are
     the only numbers in this geometry that a person chooses. Everything else —
     the width of the picture column, where the line runs, where it starts —
     is arithmetic ON them, so it is written as arithmetic here rather than as
     a result copied into six rules and four comments.

     THIS IS NOT TIDINESS. The picture was cut from 66px to 46px on 31 Aug and
     the copies did not follow: the column stayed 66, so the line ran 10px right
     of the picture it was drawn for, started 24px below it instead of 4, and
     the post's left inset stopped matching its right. Three defects, all
     photographed, all from one number written in more places than one. With the
     line reading `--nmo-thread-x`, changing `--nmo-pfp` moves the picture, the
     column, the line and the line's top together, and a comment cannot disagree
     with the file because there is no second copy to disagree with. */
  --nmo-pfp: 41px;   /* pump.fun: 97px of 920 = 41.1 at 390 */
  --nmo-post-pad-x: 1px;
  --nmo-post-pad-y: 14px;
  --nmo-thread-stroke: 3px;   /* pump.fun: 7px of 920 = 3.0 */
  /* The picture's own centre, less half the line's thickness so the stroke
     straddles it — not the column's centre, which is the same thing only while
     the column is the picture's width, and was not for two days. */
  --nmo-thread-x: calc(var(--nmo-post-pad-x) + var(--nmo-pfp) / 2 - var(--nmo-thread-stroke) / 2);
  /* Just under the picture: the post's own top padding, the picture, and 4px. */
  --nmo-thread-top: calc(var(--nmo-post-pad-y) + var(--nmo-pfp) + 4px);
  --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;   /* the light theme's; the dark theme takes the Vivid gain (#424) */
  --nmo-chart-wash: color-mix(in srgb, var(--nmo-chart-green) 18%, transparent);
  --nmo-chart-grid: color-mix(in srgb, var(--text) 12%, transparent);
  /* ═══ THE RING AT THE END OF THE LINE ═══════════════════════════════════
     It was 22% of the TEXT colour, and its animation's strongest moment was
     45% opacity — so at its very best it painted about 9.9% of the text colour
     on a near-black plot, with no stroke, and it was fully gone for the last
     30% of every second. Technically animating, practically invisible.
     It is the LINE's own green now, not text grey, and strong enough to read
     while it is at its quietest. */
  --nmo-chart-halo: color-mix(in srgb, var(--nmo-chart-green) 70%, transparent);
  /* How far the Follow modal stops short of the bottom, so the composer is never
     underneath it (decision 4). */
  --nmo-composer-room: 180px;
  /* The side padding of `.nmo-sheet-body`, named because the coin's top bar has
     to cancel it to run edge to edge. Two rules needing the SAME number is what
     makes it a token — written twice, a change to one is a silent 16px seam in
     the other. */
  --nmo-sheet-pad-x: 16px;
  /* The wash at the top of a coin screen. One value, because two rules paint it
     — the sheet's grab handle and the full screen's back bar — and they must
     meet with no seam. 16% of the text colour: light on the dark ground, dark on
     the light one, no per-theme block. */
  --nmo-top-wash: color-mix(in srgb, var(--text) 16%, transparent);
  /* ═══ THE FOLLOW BLUE ═══════════════════════════════════════════════════
     One new colour, and it is deliberate: #4d62f5 is a blue this product does
     not own — `--accent` is #ffffff on dark and #000000 on light, so there is
     nothing to reuse. Named once so a light theme can answer it later.
     IT LIVES HERE, not on the trader screen's own block, because BOTH doors on
     to a trader need it: the sheet slides up on `document.body` inside
     `.nmo-sheet`, the page renders inside `.nmo-root`, and this is the one
     block both of them carry. Declared under either alone, the other door's
     Follow button asks for a colour that does not exist and paints as bare
     text — which is what it did.
     ONE BLUE, NOT TWO. This value used to be written twice — #4d62f5 on the
     trader band's small pill and #4762fa on the profile page — which is two
     answers to "what colour is Follow" three hundred lines apart. The mockup's
     own value wins and both controls read it from here. */
  --nmo-follow-bg: #4762fa;   /* the light theme's; the dark theme takes the Vivid violet (#424) */
  --nmo-follow-fg: #ffffff;

  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; }

/* ═══ THE VIVID BASE (#424, design v55 sections 02 and 03) ════════════════════
   The owner, 16 Sep: "update the ui / ux accordingly … vivid only". The design's
   Vivid palette is THE palette: there is no Calm one and no switch.

   IT IS DONE BY REDECLARING THE APP'S OWN TOKENS on the nmo containers, not by
   rewriting two thousand rules. Every rule in this file already reads `--bg-elev-1`,
   `--text`, `--accent`, `--ok`, `--m3-title-small` and friends, so answering those
   names differently inside nmo moves the whole plugin at once, and nothing outside
   the containers below can see the new answers — the rest of Genie is untouched.

   THE TYPE ROLES ARE WRITTEN OUT AGAIN, NOT INHERITED. `--m3-body-small` is declared
   on :root as `400 12px/16px var(--font-ui)`, and a `var()` inside a custom property
   is substituted where it is DECLARED. So changing `--font-ui` here would not change
   the face inside any role: every role must be redeclared beside it.

   DARK ONLY. The design has no light version; the light theme keeps its own nmo
   values until the owner asks for one.

   Every door the plugin puts on the page's body is listed, because a body-mounted
   element inherits nothing from `.nmo-root` (the lesson of the coin sheet above). */
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet, .nmo-sheet-scrim, .nmo-sheet-overlays,
  .nmo-copy-scrim, .nmo-copy-sheet, .nmo-deck-asklayer, .nmo-chainmenu) {
  /* Ground and surfaces: true black, then three near-neutral steps. */
  --bg: #000000;
  --bg-elev: #0e0e11;
  --bg-elev-1: #0e0e11;
  --bg-elev-2: #17171b;
  --bg-elev-3: #202025;
  /* Hairlines, never shadows. */
  --border: rgba(255, 255, 255, .12);
  --border-strong: rgba(255, 255, 255, .28);
  --shadow-sm: none; --shadow-md: none; --shadow-lg: none;
  /* White ink at 100, 66 and 42 percent. */
  --text: #ffffff;
  --text-dim: rgba(255, 255, 255, .66);
  --text-faint: rgba(255, 255, 255, .42);
  /* Violet runs the interface: the line, the selected range, the main button. */
  --accent: #a15cff;
  --accent-2: #a15cff;
  --on-accent: #0b0016;
  --accent-dim: rgba(161, 92, 255, .26);
  --grad: var(--accent);
  --grad-h: var(--accent);
  --sm-chip-on: rgba(161, 92, 255, .26);
  /* Money: a saturated green and red. No third colour — the old amber is the quiet ink. */
  --ok: #1ee07f;
  --err: #ff4f64;
  --warn: rgba(255, 255, 255, .66);
  --danger: #ff4f64;
  /* The design's own names for the roles, for the shared parts at the foot of this file. */
  --nmo-violet: #a15cff;
  --nmo-on-violet: #0b0016;
  --nmo-orchid: #d05cff;            /* moments only: a win, a copy, a streak day */
  --nmo-selected: rgba(161, 92, 255, .26);
  --nmo-on-selected: #f1e6ff;
  --nmo-loss-container: rgba(255, 79, 100, .16);
  --nmo-on-loss-container: #ffb3bf;
  --nmo-outline: rgba(255, 255, 255, .28);
  --nmo-outline-variant: rgba(255, 255, 255, .12);
  /* The plugin's own colour names, answered from the palette. */
  --nmo-card-bg: #0e0e11;
  --nmo-card-bg-2: #17171b;
  --nmo-line: rgba(255, 255, 255, .08);
  --nmo-late: rgba(255, 255, 255, .66);
  --nmo-tint-a: #3a1c7a;
  --nmo-tint-b: #a15cff;
  --nmo-chart-green: #1ee07f;
  --nmo-follow-bg: #a15cff;
  --nmo-follow-fg: #0b0016;
  --nmo-top-wash: #17171b;

  /* Type: Google Sans Flex on the Material 3 scale; Google Sans Code for addresses and times. */
  --font-ui: "Google Sans Flex", "Google Sans Text", "Roboto Flex", Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-display: "Google Sans Flex", "Google Sans Text", "Roboto Flex", Roboto, system-ui, sans-serif;
  --font-mono: "Google Sans Code", "JetBrains Mono", "Roboto Mono", ui-monospace, Menlo, monospace;
  --m3-display-large: 400 57px/64px var(--font-display);
  --m3-display-medium: 400 45px/52px var(--font-display);
  --m3-display-small: 400 36px/44px var(--font-display);
  --m3-headline-large: 400 32px/40px var(--font-display);
  --m3-headline-medium: 400 28px/36px var(--font-display);
  --m3-headline-small: 400 24px/32px var(--font-display);
  --m3-title-large: 400 22px/28px var(--font-ui);
  --m3-title-medium: 500 16px/24px var(--font-ui);
  --m3-title-small: 500 14px/20px var(--font-ui);
  --m3-body-large: 400 16px/24px var(--font-ui);
  --m3-body-medium: 400 14px/20px var(--font-ui);
  --m3-body-small: 400 12px/16px var(--font-ui);
  --m3-label-large: 500 14px/20px var(--font-ui);
  --m3-label-medium: 500 12px/16px var(--font-ui);
  --m3-label-small: 500 11px/16px var(--font-ui);
  font-family: var(--font-ui);
}

/* ── 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)) backwards; }
@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; }




/* ── 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, and on that day the feed picture was 66 (44 × 1.5) and hung 11px
   into the post's left gutter to keep its centre on the thread line.

   NEITHER OF THOSE NUMBERS IS TRUE ANY MORE and this paragraph is history, not
   a description: the picture is `--nmo-pfp` (cut 30% on 31 Aug), it is a column
   of the post's own grid rather than the first thing in the name row (29 Aug
   15:57), there is no negative margin pulling it anywhere, and the thread line
   runs at `--nmo-thread-x` because that is calculated from the picture rather
   than written beside it. A reply picture is still `.nmo-av-sm`.

   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; }
/* ═══ A TRADER WITH NO PICTURE GETS THEIR INITIALS, IN THE SAME CIRCLE ═══════
   (owner, 4 Sep 2026, on three screenshots of traders with no picture: initials.
   Until now such a row drew NOTHING — no image, no ring, a blank space.)

   THE LETTERS SIT IN THE CIRCLE A PICTURE WOULD HAVE FILLED, at its size and
   with its edge, because a row where some traders have pictures and some have
   letters must not look ragged. Same `width/height: 100%`, same `border-radius`,
   so the tile IS the circle rather than something drawn inside it.

   ═══ WHY `cqw` AND NOT `em`, MEASURED IN CHROME AT EVERY SIZE WE USE ═══

   The spec asked for `font-size: 0.4em`. `em` is relative to the element's OWN
   inherited font size, and `.nmo-av` sets none — so it resolves against the
   root's 16px and comes out the SAME in every circle:

     circle   29    30    40    44    46    64      <- the six sizes in this file
     0.4em   6.4   6.4   6.4   6.4   6.4   6.4  px  <- identical, and illegible
     ratio  .221  .213  .160  .145  .139  .100      <- the bigger the circle,
                                                       the smaller the letters look

   `cqw` is a percentage of the CONTAINER'S OWN WIDTH, which is what "four tenths
   of the circle" actually means:

     circle   29    30    40    44    46    64
     40cqw  11.6  12.0  16.0  17.6  18.4  25.6  px
     ratio   .40   .40   .40   .40   .40   .40      <- constant, by construction

   ONE RULE FOR SIX SIZES, AND FOR THE SEVENTH NOBODY HAS ADDED YET. A fixed
   pixel size per class would need six copies today — `.nmo-av-sm`, `.nmo-av-md`,
   `.nmo-av-lg`, and the 29px, 40px and 44px overrides further down this file —
   and the next size added would silently get the wrong letters.

   `container-type: inline-size` and not `size`: inline-size contains layout and
   the inline axis only, never paint, so the genie's canvas above still hangs
   outside its box the way that comment says it must. Every `.nmo-av` already has
   an explicit width from its size class, so nothing here can change a layout.

   The gradient and `color: var(--bg)` are the coin's letter tile's, `.nmo-tok-letters`
   below — same idea, so it must not be a second design. Change both together. */
.nmo-av { container-type: inline-size; }
.nmo-av-letters {
  width: 100%; height: 100%; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 700; font-size: 40cqw;
  letter-spacing: 0.02em; color: var(--bg);
  background: linear-gradient(135deg, var(--grad-a), var(--grad-c));
  user-select: none;
}
/* ═══ 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" — the 66 it was on 29 Aug, times 0.7. Both the trader picture and the genie's feed
   avatar use this one class and nothing else does, so they cannot drift apart.
   The size itself is `--nmo-pfp` at the top of this file, because the post's column and its
   thread line are calculated from it. Change it there and they follow. */
.nmo-av-md { width: var(--nmo-pfp); height: var(--nmo-pfp); }
.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 SIT, AND WHY BOTH NUMBERS MOVED ═══════════════
   (owner, 1 Sep 2026: "pfp needs to have equal margin to the left side of
   screen as well as right side of text area".)

   Measured on the deployed build at 390 before the change: the picture's left
   edge was 18px from the screen and the text's right edge 29px from it — 5px of
   post padding on the left against 16px on the right, on top of 13px the
   transcript adds to both. Eleven pixels out, and visible.

   So the padding is 16px on all four sides and the two insets are both 29px.

   THE COLUMN IS THE PICTURE'S OWN WIDTH, 46px, not 66. It was 66 when the
   picture was 66. The picture was cut 30% to 46 on 31 Aug and the column was
   left behind, which is what put the picture at the START of a column 20px
   wider than itself — and that is the whole of the thread-line bug below: a
   line drawn down the column's middle stopped being a line down the picture's
   middle. With the column the picture's own size the two are the same thing
   again and cannot drift apart.

   The genie's canvas still hangs 8px outside this column on each side, which is
   deliberate and unchanged: it overflows into the gap, takes no clicks, and the
   ball inside it is drawn one picture wide.

   WHAT IT COSTS: column two starts at 16 + 46 + 8 = 70 rather than 79, so the
   words and every card start 9px further left and are 9px wider — 278px rather
   than 269. The cards are the text column by definition, so they follow. */
.nmo-post {
  padding: var(--nmo-post-pad-y) var(--nmo-post-pad-x);
  border-bottom: 1px solid var(--nmo-line); position: relative;
  display: grid; grid-template-columns: var(--nmo-pfp) minmax(0, 1fr);
  column-gap: 11px; align-items: start;   /* pump.fun: 27px of 920 = 11.4 */
}
.nmo-post > * { grid-column: 2; min-width: 0; }
/* pump.fun: the card's left edge is the AVATAR's right edge (129 of 920 = 54.7),
   not the text column's start (156 = 66.1). It reaches back across the gap. */
.nmo-post > .nmo-card { margin-left: calc(-1 * 11px); }
/* 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; }
/* WHO WROTE IT — Material 3 title-small. It is the title of the post: the
   smallest of the three title roles, because a feed row is not a page heading.
   It was 14px/600, a number nobody could argue with because it did not say what
   it was. */
.nmo-who {
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ═══ THE Buy / Sell PILL'S RULES ARE GONE (#447, design v60) ═══════════════
   A green "Buy" and a red "Sell" sat immediately after the trader's name from
   31 Aug 2026. Design v60's post header is the picture, the name and a short age,
   with no tag of any kind: what the trade was is said in the sentence under the
   name, where it is words rather than a colour to learn. `.nmo-side-pill`,
   `.nmo-side-buy` and `.nmo-side-sell` had no other caller and went with it. */
/* "UNKNOWN ORIGIN", BESIDE SELL (owner, 14 Sep 2026; #310). A sale whose buy we never saw and whose
   origin nothing proved: the sentence the owner took off the card, said in two words. The look is
   design v14's chain tag (.tag: 500 12px/16px, 3px 8px, 6px corners, #c4c7c5 words on #2a2b2d),
   in the app's own tokens for those two colours so the light theme keeps it readable. The row's
   6px gap puts it after Sell; `flex: none` and `nowrap` keep it whole while the name gives up
   room, so it never wraps the name row. */
.nmo-origin-badge {
  flex: none; padding: 3px 8px; border-radius: 6px; white-space: nowrap;
  font: 500 12px/16px var(--font-ui); letter-spacing: normal;
  color: var(--text-dim); background: var(--bg-elev-2);
}
/* 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); }
/* WHEN — Material 3 label-medium. A timestamp labels the row, it does not say
   anything; label is the role for text that names rather than reads. Was 13px. */
.nmo-post-h .nmo-num {
  font: 500 11px/16px var(--font-ui); letter-spacing: var(--m3-label-medium-tracking);
  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. */
/* ═══ WHO IS BEING ANSWERED — label-medium (owner, 1 Sep 2026) ═══════════════
   "Replying to @poorgoat_" had NO RULE AT ALL, so it inherited the body's
   16px/1.5 and painted LARGER than the author's own name beside it, which the
   scale puts at title-small, 14px. Measured: 16px against 14px. A line naming
   who is being answered outranking the person speaking is a hierarchy inversion,
   and it was not a decision anyone made — it was the absence of one.

   It is a label about the row, not something a person reads, so: label-medium,
   12px, and the muted colour every other label on this screen carries. The space
   under it is the gap between two blocks, which is what separates it from the
   words it introduces. */
.nmo-replying {
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
  color: var(--text-faint); margin: 0 0 6px;
}
/* ═══ THE TEXT AREA OF A POST, DESIGNED AS ONE THING ════════════════════════
   (owner, 1 Sep 2026, four times on this one area: "optimize the copy/text area
   font style and size"; "why reply post text and normal post font different —
   should be the same"; "font style, size, line break, not right — redesign
   using material 3 principles"; and, of the shipped 16px, "now font too big".)

   THE SIZE IS SETTLED BY HIM: 16px was too big, so the words are body-medium,
   14px — where they were before, and now the checks lines join them there
   instead of sitting at 12.5. Same size, which is what "should be the same"
   asked for.

   THE PROBLEM THAT LEAVES is the one that made me reach for 16 in the first
   place: 14px on 20px leading, six wrapped lines, is what photographs as a wall.
   Going back to 14 without going back to the wall means the rhythm cannot come
   from the size. It comes from the two things he actually named — "style" and
   "line break":

   · STYLE. The genie's sentence is full-strength text; the checks under it are
     muted. Same role, same size, same family — different weight of voice. One is
     somebody speaking, the other is a list of measurements. That is a hierarchy
     a reader can see without anything being bigger, and it is deliberate where
     five identical blocks read as an accident.

   · LINE BREAK. Five statements with five equal gaps is not rhythm, it is a
     list pretending to be paragraphs. There is now ONE real break — 14px, where
     the speaking stops and the measuring starts — and the measurements sit 2px
     apart inside it, because they are one list and not five thoughts. Related
     things close, unrelated things apart, which is the whole of M3's grouping
     rule and the opposite of putting 10px between everything. */
.nmo-thesis {
  font: 400 12.5px/18px var(--font-ui); letter-spacing: var(--m3-body-medium-tracking);
  margin: 4px 0 12px; overflow-wrap: anywhere;
}

/* #475: THE TRADE ITSELF, ONE LINE ABOVE THE CARD (the design's feed post: "Bought $18,459 of musebook.").
   The same size and rhythm as a genie's own line above a card, with the figure in the display face so the
   number is what the eye lands on. It is data, not small print, so it keeps the body colour. */
.nmo-trade-line {
  font: 400 12.5px/18px var(--font-ui); letter-spacing: var(--m3-body-medium-tracking);
  margin: 4px 0 8px; overflow-wrap: anywhere;
}
.nmo-trade-line .nmo-trade-figure { font-family: var(--font-display); font-weight: 700; }

/* 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: 11px 14px;
  display: grid; grid-template-columns: 36px minmax(0, 1fr) auto auto; gap: 10px; align-items: start;
}
.nmo-tok { width: 36px; height: 36px; border-radius: 6px; 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; }
/* THE COIN'S SYMBOL — Material 3 title-small. It is the card's title: the one
   thing that says which coin this is. Was 14px/700; the role is 500, so it
   paints lighter than it did, and that is the role's decision rather than ours. */
/* ═══ THE COIN'S NAME IS A TITLE, SO IT TAKES THE TITLE ROLE ═══════════════
   (owner, 2 Sep 2026: "redesign using material 3 principle — important data:
   name, value of amount, value of mc".)
   Was `500 13px/20px`, a size written here beside a scale that already defines
   it. `--m3-title-small` IS 500 14px/20px, so this is the same decision said in
   the system's own words — and the name is now the same role as the two values
   it sits beside, which is what makes them read as peers. */
.nmo-sym { font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking); }
/* 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.
   Material 3 body-small: it is prose about the coin, not a label on it. Was
   12.5px with a 1.3 line height; the role brings 12px/16px. */
.nmo-card-names .nmo-coin-name {
  font: 400 11px/16px var(--font-ui); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* ═══ AND WHEN THAT LINE IS AN ADDRESS, IT IS MONO ══════════════════════════
   (owner, 1 Sep 2026: the contract address under every coin symbol.)

   The address wears BOTH classes on purpose — `nmo-coin-name nmo-mint` — because
   everything except the face is the name line's: the size, the muted colour, the
   ellipsis, the no-wrap. Only the face comes from `.nmo-mint`.

   WHY THIS SELECTOR AND NOT JUST `.nmo-mint`, which is what the next person will
   try: the rule above sets the face through the `font` SHORTHAND, and a
   shorthand sets font-family like any other declaration. Its selector is
   `.nmo-card-names .nmo-coin-name` — two classes, (0,2,0). `.nmo-mint` is one
   class, (0,1,0). The plain class LOSES, silently: the element carries
   `nmo-mint`, every rule looks right, and the address still paints in the
   proportional face. It needs at least the shorthand's specificity to win, so it
   is written as the same descendant pair plus the class it must beat, (0,3,0).

   Measured after adding it: the address paints in JetBrains Mono and gets
   NARROWER — 72.1px to 68.6px — so the value columns beside it gain that room.
   The tightest card is the genie's own sell card, whose headroom goes from 6.7px
   to 10.2px. That is the one to watch if this line ever grows. */
.nmo-card-names .nmo-coin-name.nmo-mint { font-family: var(--font-mono); }
/* 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; }
/* THE WORD OVER A NUMBER — Material 3 label-small, which is what it is: the
   smallest label, over the value it names. Was a bare 11px; the role is also
   11px but adds the 500 weight and 0.5px tracking every M3 label carries, so it
   paints WIDER. Measured at 390 after the change: "MC at sell" goes 52.0px to
   57.6, "Amount" 40.7 to 45.0. The value columns are sized by their content, so
   nothing wraps — the identity column pays it, and at a 313px card it can. */
/* SUPPORTIVE: a label role and the variant colour, which is what makes it
   supportive — not being bold, not being moved. Was `500 12.5px/16px` with the
   label-SMALL tracking, a pairing that belonged to no role. `--m3-label-medium`
   is 500 12px/16px and carries its own tracking. The two labels on a card are
   peers and share this rule, so neither can end up bigger than the other. */
.nmo-k {
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
  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; }
/* ═══ AND THE ONE THAT IS A SENTENCE GETS A ROW, NOT A COLUMN ═══════════════
   (friday, 11 Sep 2026, from #180's photograph of a feed card at 390 wide.)

   The rule above is written for a line of three or four words — "at $71.0K MC",
   "we saw it 4m later" — sitting under the figure it belongs to. The trader
   card's honest sentence about a holding it cannot value is twenty words, and
   it was drawn in the identity column: `minmax(0, 1fr)` between a 36px picture
   and two content-sized figure columns, i.e. whatever the money leaves over. On
   his 390 screen that was about 55px, and a sentence with words wider than 55px
   in it came out as a stack roughly one word tall per line, running past the
   column it was given.

   A paragraph is not a column, so it stops being one: `1 / -1` spans every
   track the card has at any width, which makes it the card's own second row,
   under the figures rather than beside them. The card's widths are read from
   the page and never restated here, so this survives the media query below —
   three tracks or four, `-1` is the end either way.

   THE TOP MARGIN IS THE BREAK BETWEEN THE FIGURES AND THE WORDS, the same 6px
   rhythm the card already uses between a value and the quiet line under it.
   `overflow-wrap: anywhere` is the same guard `.nmo-full` carries: a single
   word longer than the card — a coin's name, a long handle — breaks rather than
   painting over the card's edge. */
.nmo-card .nmo-card-why {
  grid-column: 1 / -1;
  margin-top: 6px;
  overflow-wrap: anywhere; min-width: 0;
}
/* 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. */
/* THE NUMBER ITSELF — Material 3 title-small, the same role as the coin's
   symbol, because on this card the money and the coin are the two titles. Was
   14px/600.
   THE ROLE DOES NOT SET THE FAMILY HERE, and that is deliberate: a value also
   wears `.nmo-num`, whose rule further down this file puts money in the display
   face, and it comes later so it wins. The role governs the size, the line
   height, the weight and the tracking; the money keeps its own face. Measured:
   14px/20px, 500, Google Sans Flex, tabular figures intact.
   `font-variant-numeric` comes AFTER the shorthand on purpose: `font` resets
   font-variant, so declaring the tabular figures first would silently lose them
   and the columns would stop lining up. */
/* IMPORTANT: the value takes a title role at full strength, the same one the
   coin's name takes, because the owner named them together — name, amount value,
   MC value. Was `500 15px/20px`; `--m3-title-small` is 500 14px/20px, which is
   the size the card was already being forced to inline for the one-row layout.
   The size was simply written down twice.

   `font-variant-numeric` STILL COMES AFTER THE SHORTHAND. `font` resets
   font-variant, so declaring the tabular figures first loses them and the columns
   stop lining up — and the role is a shorthand exactly like the old literal was.
   This bit me measuring it, before it could bite the card. */
.nmo-v {
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  font-variant-numeric: tabular-nums;
}
/* pump.fun: label bottom 663 to value top 677 = 14px of 920 = 5.9.
   `display: block` because a value is a <span> from `num()`, and an inline box
   ignores a vertical margin — the rule was applied and painting nothing. */
.nmo-k + .nmo-v { display: block; margin-top: 6px; }
/* #482: a figure we cannot back is an empty cell under its heading. It holds one line of the
   value's own height, so a card with a gap is the same shape as a card with both figures. */
.nmo-v.nmo-v-empty { display: block; min-height: 1lh; }
.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; }
.nmo-replied { display: block; }

/* ═══ THE LINE FROM A POST TO WHAT ANSWERS IT (owner, 1 Sep 2026) ═══
   A vertical line down the picture column, from the trader's post to the genie's
   reply under it — the shape in his reference picture.

   ═══ WHAT THIS LINE IS ALLOWED TO MEAN ═══
   A line between two posts is a claim that one caused the other, so every rule
   here is scoped INSIDE `.nmo-replied`. That wrapper is built by `paintThreads`
   only when the reply carries a real `repliesTo` id AND the post it names is on
   this page. It is never drawn from two posts being next to each other, sharing a
   coin, or arriving close together. A reply whose parent is off the page gets no
   wrapper and therefore no line — it stays a top-level post saying whose move it
   answers, and claims nothing about the post above it.

   The geometry is `.nmo-thread`'s, deliberately: both read `--nmo-thread-x`, the
   picture's own centre, and a run of updates already draws its line there. Two
   lines in two columns would read as two different kinds of relationship.

   THIS SENTENCE USED TO NAME A NUMBER and that is how it went wrong: it said
   "37px is the middle of the 66px picture column", which was true when written
   and false from 31 Aug, when the picture was cut to 46 and the column was not.
   It names the declaration now, so it cannot disagree with the file.

   A `.nmo-replied` can sit INSIDE a `.nmo-thread`, so `.nmo-thread`'s own
   descendant rules can land on these same cards. That is safe and checked: they
   set the identical declarations on the identical pseudo-element, so the two
   rules paint one line, never two. */
.nmo-replied > .nmo-post::after {
  /* THE PICTURE'S CENTRE AND THE PICTURE'S BOTTOM, both derived from its own
     size: left = 16px padding + 46/2 − 1 for the line's own 2px width = 38.
     top = 14px padding + 46px picture + 4 = 64. Both were written for a 66px
     picture and never moved when it was cut to 46 on 31 Aug: the line ran 10px
     right of centre and started 24px below the picture instead of 4. If the
     picture's size changes again, these two numbers change with it. */
  content: ""; position: absolute; left: var(--nmo-thread-x); top: var(--nmo-thread-top); bottom: 0;
  border-left: var(--nmo-thread-stroke) solid var(--nmo-card-bg-2);
}
/* Each answer picks the line up at its own top and carries it to its picture. */
.nmo-replied .nmo-replies > .nmo-post::before {
  content: ""; position: absolute; left: var(--nmo-thread-x); top: 0; height: var(--nmo-post-pad-y);
  border-left: var(--nmo-thread-stroke) solid var(--nmo-card-bg-2);
}
/* With more than one answer the line continues between them, and stops at the last. */
.nmo-replied .nmo-replies > .nmo-post:not(:last-child)::after {
  content: ""; position: absolute; left: var(--nmo-thread-x); top: var(--nmo-thread-top); bottom: 0;
  border-left: var(--nmo-thread-stroke) solid var(--nmo-card-bg-2);
}
/* One exchange, one card edge: the rule between a post and its own answer would
   cut the line it is supposed to be joined by. The last answer keeps its own. */
.nmo-replied > .nmo-post { border-bottom: 0; }

.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) ────────────────────────────────────── */
/* ═══ THE ROW HAS TO HOLD THE MARKET CAP ON ONE LINE ══════════════════════
   Measured on the deployed build at 390: five icons and their counts take
   103.1px, five 18px gaps take 90, and the market cap wants 72.5 for
   "$506.4K MC" — 265.6 of 269, so it fits by three pixels and then does not,
   because the value and its "MC" are two nodes with a space between them and
   the space is a place to break. The owner's screenshot has "$462.5K" on one
   line and "MC" alone under it.
   Fourteen-pixel gaps give the row 20px of real slack instead of three. */
.nmo-acts { display: flex; align-items: center; gap: 14px; margin-top: 10px; padding: 0 2px; font-size: 11px; 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; }
/* A number and the word for what it is a number OF are one thing: never the
   value on one line and "MC" alone on the next. `flex: none` so the row cannot
   squeeze it below its own words — if the row is ever too tight the icons give,
   and that is visible and fixable, where a silent break is neither. */
.nmo-mc { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--text-dim); white-space: nowrap; flex: none; }
/* ═══ THE FEED POST'S OWN ROW IS ONE VIOLET LINK NOW (#447, design v60) ══════
   Design v60 draws `.post .acts .mirror` in the key colour — `--lime` in its
   Vivid palette, which is #a15cff, the app's own `--nmo-violet`. Not a button
   and not a pill: a text link, 500 weight, sitting where the six icons were.
   The rules above stay because the coin screen's holder rows still use `.nmo-acts`
   and `.nmo-act`; what left is the FEED POST's use of them. */
.nmo-mirror-link {
  display: inline-flex; align-items: baseline; gap: 4px; background: none; border: 0; padding: 0;
  font: 500 13px/1.3 var(--font-ui); color: var(--nmo-violet, var(--accent)); cursor: pointer;
}
.nmo-mirror-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
/* The figure is the person's own copy amount and reads as part of the sentence, not as a
   separate number: same colour, same weight, no tabular jump. */
.nmo-mirror-link .nmo-num { font: inherit; color: inherit; }

/* ── 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 + 46px picture + 4px. The same two numbers
     as the reply line above, and they must stay the same: a `.nmo-replied` can
     sit INSIDE a `.nmo-thread`, so both rules land on the same pseudo-element of
     the same card. Equal declarations paint one line; different ones would paint
     two, offset. */
  content: ""; position: absolute; left: var(--nmo-thread-x); top: var(--nmo-thread-top); bottom: 0; border-left: var(--nmo-thread-stroke) 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: var(--nmo-thread-x); top: 0; height: var(--nmo-post-pad-y); border-left: var(--nmo-thread-stroke) 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;
}
/* The dotted connector through the fold is the SAME line: it continues the
   solid segment above it to the one below, so it takes the same 38px. In another
   column a run of updates would read as two relationships with a gap. */
.nmo-more::before { content: ""; position: absolute; left: var(--nmo-thread-x); top: 0; bottom: 0; border-left: var(--nmo-thread-stroke) 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);
}
/* The stored-AUM board is deliberately two lines at phone width: balance owns the
   first line, while the move, coverage and optional scorecard profit may wrap below
   it rather than writing through a trader's name or balance. */
.nmo-balance-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) max-content;
  grid-template-areas:
    "rank name now"
    ".    facts facts";
  column-gap: 10px;
  row-gap: 4px;
  align-items: baseline;
  padding: 10px 16px;
  border-bottom: 1px solid var(--nmo-line);
}
.nmo-balance-row > .nmo-rk { grid-area: rank; }
.nmo-balance-name {
  grid-area: name;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: var(--m3-title-medium);
  letter-spacing: var(--m3-title-medium-tracking);
}
.nmo-balance-now {
  grid-area: now;
  white-space: nowrap;
  text-align: right;
  font: var(--m3-title-small);
  letter-spacing: var(--m3-title-small-tracking);
}
/* A trader the scorecard has no profit for gets a dash here, and the dash is QUIET.
   The column now carries money, so its figures are green or red at full strength; a
   dash left at that strength reads as a value rather than as the absence of one. The
   same screen's other money column already says a missing figure in the faint colour
   (`.nmo-pnl-say`, "no trades"), and before this column carried the profit the dash
   sat in the faint line under the handle — so faint is what this screen has always
   used for "there is nothing to show", and this keeps it. Two classes, so it beats
   `.nmo-up` / `.nmo-down`; it never meets them anyway, since a row has a figure or a
   dash and never both.
   THE SEARCH ROWS TOO (#203). #302 kept this rule off them while their column still said
   "Balance now". It now says "Profit" and draws the same stored figure as the 7D rows,
   so a search row with no profit gets the same quiet dash: one screen, one meaning. */
.nmo-balance-row > .nmo-balance-now.nmo-none { color: var(--text-faint); }
.nmo-balance-facts {
  grid-area: facts;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  min-width: 0;
  color: var(--text-faint);
  font: var(--m3-body-small);
  letter-spacing: var(--m3-body-small-tracking);
}
.nmo-balance-head {
  padding-top: 6px;
  padding-bottom: 6px;
  color: var(--text-faint);
  font: var(--m3-label-small);
  letter-spacing: var(--m3-label-small-tracking);
}
.nmo-balance-head .nmo-balance-name,
.nmo-balance-head .nmo-balance-now { font: inherit; letter-spacing: inherit; }
.nmo-balance-source, .nmo-balance-short { margin: 8px 16px 0; }
/* The plain two-column row — the genie's own activity, and nothing on the
   Traders tab. Both trader lists are `.nmo-row-t` below. */
.nmo-row-plain { grid-template-columns: minmax(0, 1fr) auto; }
/* ═══ THE TRADERS LIST CARRIES HIS PICTURE (owner, 2 Sep 2026) ═════════════
   One more column than the plain row, between the rank and his name, because
   the row appends five things now and a grid with four columns would drop the
   fifth onto a second line.

   29px, not the 41 of `--nmo-pfp`: this is a list row of 12px padding with a
   rank beside it and a line of record under it, and the post's picture size
   would set the height of every row on the tab. The same 29 the trader band
   above already uses, so the two agree. Sized here rather than by giving
   `traderAvatar` a second size, because there is one drawing of a trader's
   face and screens say how big to paint it. */
/* ═══ THE MONEY COLUMN IS A FIXED WIDTH, AND THAT IS WHY THE ROWS ALIGN ═══
   (owner, 4 Sep 2026, photograph of his own screen: "it's truncated")

   It was `auto`, and EVERY ROW IS ITS OWN GRID — so each row's money column was
   as wide as whatever number happened to be in it, and what was left over went
   to the trader. Measured on his screenshot: row 1 holds "+$14.2M" and its record
   line broke into "trading" / "alongside" / "them", three lines of one word; row 2
   holds "+$8M", 18px narrower, and the same sentence fitted on one. Two rows of
   the same list, wrapping differently, because of a number in a different column.

   This is the same fault the cards had and the same fix (`--nmo-money-col`,
   3 Sep) — a column that is not text-width must not be sized by its text.

   72px: the widest a row draws is "+$14.2M" and "−$228.00"; at `--m3-title-small`
   in the display face with tabular figures those measure 62px, and 72 leaves the
   minus sign and a wider figure room without taking more from the name. */
/* ═══ BOTH TRAILING COLUMNS ARE FIXED, OR THE HEADING LIES ═══
   The Follow column was `auto`, so it was as wide as its word — 87px on a row
   reading "Following", 26px on the heading, whose cell is empty. Different widths
   means different column edges, and "PROFIT" painted 52px to the right of the
   numbers it names. A heading over the wrong column is worse than no heading.

   88px holds the widest word the button ever carries ("Following" at
   label-medium, 63px, plus its 24px of padding) and every button then paints the
   same width, so the actions line up down the list as well. */
.nmo-row-t { --nmo-trader-money: 72px; --nmo-trader-follow: 88px; }
.nmo-row-t > .nmo-follow-btn { width: 100%; }
/* ═══ A ROW WITH A SETTINGS BUTTON NEEDS ROOM FOR TWO CONTROLS ═════════════
   The width is asked for by the class the row wears when it HOLDS a gear, not by
   which list drew it — the two lists share one row and may not have two ideas of
   its shape.

   ═══ AND THE ROOM COMES OUT OF THE FIGURE, NOT OUT OF THE NAME ═══
   (measured in chromium at 390 wide, 9 Sep 2026)

   This was 122px — the full 88px button plus the 32px gear and its 2px gap — and
   every one of those 34 extra pixels came out of the NAME, because the name is the
   only track that flexes. Measured: the name cell went from 58px on a board row to
   24px on a followed one, and "Unipcs" was cut to "Un…". A list whose names are
   unreadable is not the list this row was merged to produce.

   The arithmetic at 390 wide. The row has 332px inside its padding. Fixed: 26 for
   the position, 40 for the picture, 4 gaps of 12. That leaves 218 for the name, the
   figure and the buttons.
     · 107 for the pair. "Unfollow" is 49px at label-medium and 取消跟随 is 49px too,
       plus the button's 24px of padding is 73 — the 88px default is sized for the
       longest word the BOARD's button carries ("Following"), which this one never
       says. 73 + 2 + 32 = 107.
     · 63 for the figure AND, under it, the word saying whether copying is on.
       Everything that goes there is one line of it: "+$12.9M" is 60px, "no trades"
       55, "watching" 52, 正在跟单 51. One 24px line and one 16px line is 40, which
       is exactly the height the picture already sets, so the row does not grow.
     · 48 left for the name, against the board's 58. Not equal, and it cannot be:
       there is no room at this width for the board's name AND a second control. It
       is twice what it was. */
.nmo-row-t.nmo-has-gear { --nmo-trader-follow: 107px; --nmo-trader-money: 63px; }
.nmo-row-t > .nmo-follow-pair { grid-area: follow; width: 100%; }
.nmo-row-t > .nmo-follow-pair > .nmo-follow-btn { flex: 1 1 auto; }
/* ═══ TWO LINES, SO THE RECORD IS NOT COMPETING WITH THE MONEY ═══
   The name, the money and the button share the top line; the record runs under
   all three. Named areas rather than counted lines, because the heading row uses
   this same grid and a label that drifts one column off is worse than no label.
   Measured on a 430px screen: the record's cell goes 121px -> 308px. */
.nmo-row-t {
  grid-template-columns:
    26px auto minmax(0, 1fr) var(--nmo-trader-money) var(--nmo-trader-follow);
  grid-template-areas:
    "rank face name money follow"
    "rank face sub  sub   sub";
  row-gap: 2px;
}
.nmo-row-t > .nmo-rk { grid-area: rank; }
.nmo-row-t > .nmo-av { grid-area: face; }
/* ═══ A LONG HANDLE IS CUT OFF, NOT ALLOWED TO RUN INTO THE MONEY ═══
   (owner, 4 Sep 2026, second photograph: "dumbcrayoneater" printed straight
   through "+$8.2M")

   `min-width: 0` lets the grid TRACK shrink; it does nothing about the text
   inside it. A handle is one unbreakable word, so with nothing to clip it the
   button simply painted past its own cell and over the column next door — which
   is worse than truncation, because two things are then unreadable instead of
   one being shortened.

   Every level has to carry it: the cell, the button inside, and the span inside
   that. `text-overflow` only acts on a block box that is itself clipping, so
   putting it on any one of the three alone leaves the other two to overflow. */
.nmo-row-t > .nmo-tr-name { grid-area: name; min-width: 0; overflow: hidden; }
.nmo-row-t .nmo-hd-open {
  display: block; max-width: 100%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nmo-row-t .nmo-hd-open > span {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nmo-row-t > .nmo-pnl { grid-area: money; }
.nmo-row-t > .nmo-follow-btn, .nmo-row-t > .nmo-head-spacer { grid-area: follow; }
.nmo-row-t > .nmo-tr-sub { grid-area: sub; }
/* The rank and the face sit against the top line, not centred over both. */
.nmo-row-t > .nmo-rk, .nmo-row-t > .nmo-av { align-self: center; }
/* An empty record cell must not leave a 2px gap the rows below do not have. */
.nmo-tr-sub:empty { display: none; }
/* ═══ THE HANDLE LINE ══════════════════════════════════════════════════════
   One handle, cut off rather than wrapped, and nothing beside it. It does not
   wrap: `.nmo-sub` wraps by default and would push the row taller the moment a
   handle grew.

   BOTH CLASS NAMES, because `.nmo-sub` below sets `flex-wrap: wrap` and is LATER in
   this file — one class against one class is a tie, and the last one wins. Measured
   in chromium at 390 wide: the handle line painted 42px instead of 16 and the row
   96px instead of 70. */
.nmo-sub.nmo-tr-handle { flex-wrap: nowrap; gap: 6px; min-width: 0; }
.nmo-tr-handle > .nmo-tr-at {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ═══ WHETHER COPYING IS ON, UNDER THE FIGURE ══════════════════════════════
   (owner, 9 Sep 2026: the Following list may keep whether copying is on, and
   nothing else that is not a problem. Where it goes was measured, 9 Sep.)

   It sat on the handle's line and could not stay there: at 390 wide a row with
   a settings button leaves 48px for the name and the handle, and "copying" is
   45px of it and does not shrink, so the handle painted at 0px and disappeared.

   So the money column carries both, stacked and right-aligned. That column is
   already one of the two the two lists may differ in. The figure is one line
   and the word is one 16px line under it, which together fit inside the 40px
   the picture sets, so the row is still the board's height. */
.nmo-row-t > .nmo-pnl-cell {
  grid-area: money; min-width: 0;
  display: flex; flex-direction: column; align-items: stretch;
}
.nmo-row-state {
  flex: none; white-space: nowrap; text-align: right;
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint); font-weight: 400;
}
.nmo-row-state.nmo-good { color: var(--nmo-up); }
/* ═══ WHERE WE CANNOT YET SAY, THE FIGURE COLUMN SAYS SO IN WORDS ══════════
   Never a zero, which claims a result nobody has, and never a dash on its own,
   which says nothing at all. Body-small in the faint colour, right-aligned
   under the same column as the numbers: two lines of it at this size fit
   inside the 40px the picture already sets, so a row saying this is exactly as
   tall as a row carrying a figure. */
.nmo-pnl-say {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint); font-weight: 400;
}
/* ═══ M3 LIST DENSITY, AND WHY THE FACE GREW (owner, 3 Sep 2026) ═══════════
   29px was chosen on 2 Sep against a row whose trailing control was a 26px-tall
   toggle. The trailing control is now a 32px button, so the row's own height is
   set by the button rather than by the picture, and the 29 that was sized to
   keep rows short is now just a small face in a row that is no shorter for it.

   40px is M3's leading avatar on a two-line list item, and two lines is what
   this row is: a handle and a record line under it. PHOTOGRAPHED, not derived:
   with the 12px padding either side and the 1px rule under it the row paints
   65px — inside M3's 72px two-line item and well above the 48px minimum touch
   target. A row whose record line wraps to two lines paints 91px, which is the
   record earning the room rather than the picture taking it.

   The 29 above the list (the trader band) is NOT changed with it. That band is a
   horizontal strip of faces where the face IS the row; this is a list item where
   the face is the leading element beside a name. M3 sizes those differently and
   so do we. */
.nmo-row-t > .nmo-av { width: 40px; height: 40px; }
/* The gap either side of the leading avatar is M3's 16px, not the row's 10px:
   the face is the leading element and the name is the headline, and M3 puts 16
   between them. The rank keeps the tighter gap — it is a label ON the row. */
.nmo-row-t { column-gap: 12px; }
.nmo-sheet-list .nmo-row-plain { grid-template-columns: 30px minmax(0, 1fr) auto; padding: 10px 0; }
/* ═══ THE WAY BACK OFF A TRADER'S PAGE ═════════════════════════════════════
   (owner, 4 Sep 2026: "whats this")

   `.nmo-back` had no rule, so it painted as the browser's own grey button with
   the word "Traders" sitting in it — which is a large part of why that page did
   not read as a page. M3's text button: no container until you touch it, the
   label in label-large, the glyph leading it at the text's own size. */
.nmo-back {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 0 0 4px -8px; padding: 6px 8px; border: 0; border-radius: 999px;
  background: none; color: var(--text-dim); cursor: pointer;
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
@media (hover: hover) { .nmo-back:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); } }
.nmo-back:active { background: color-mix(in srgb, var(--text) 12%, transparent); }
.nmo-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-back .nmo-ic { font-size: 20px; }

/* ═══ THE COLUMN HEADINGS (owner, 4 Sep 2026) ══════════════════════════════
   "i dont know what the numbers there are for, there's category tile, like name,
   pnl, etc." — the rank and the money had no headings, so the rank read as an
   unexplained 1, 2, 3 and the money never said whose it was.

   It IS a row (`.nmo-row-t`), so it inherits the grid and every label sits over
   the column it names. What it is not is a list item: no hover, no press, no
   40px picture, and label-small in the faint colour so it reads as a heading and
   never as the first trader. */
.nmo-row-head {
  padding-top: 6px; padding-bottom: 6px;
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  color: var(--text-faint); text-transform: uppercase;
}
/* The rank and money cells already carry their own role; inside the heading they
   take the heading's, or "#" and "Profit" would paint as a rank and a number. */
.nmo-row-head .nmo-rk, .nmo-row-head .nmo-pnl, .nmo-row-head .nmo-tr-name {
  font: inherit; letter-spacing: inherit; color: inherit; font-weight: 500;
}
/* THE HEADING'S ACTION CELL IS EMPTY AND MUST LOOK IT. It first borrowed
   `.nmo-follow-btn` to be the right width and painted a small empty ring at the
   end of the heading — the button's own rule sits later in this file and won the
   border back. It is a plain cell now, and the column's width comes from
   `--nmo-trader-follow` rather than from anything it is wearing. */
.nmo-head-spacer { border: 0; background: none; pointer-events: none; }
/* The heading has no picture and no second line, so neither takes any room. */
.nmo-row-head > .nmo-av { width: 40px; height: 0; }
/* ═══ THE ROW IS TOUCHABLE, SO IT HAS A STATE LAYER ════════════════════════
   M3's rule: anything that responds to a press shows it. The handle inside is
   its own button and keeps its own focus ring; this is the row's own hover, at
   the 8% M3 gives a hover state layer. Pointer-coarse devices never hover, and
   the rule costs them nothing. */
@media (hover: hover) {
  .nmo-row-t:hover, .nmo-row-plain:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
  /* …except the heading, which is not a trader and does nothing when pressed. */
  .nmo-row-head:hover { background: transparent; }
}
/* ═══ THE TRADERS LIST, ON THE TYPE SCALE (owner, 1 Sep 2026) ══════════════
   The tab he taps second. Each row is a rank, a handle, a line of record under
   it, and a number at the right. Roles chosen from what each one IS:

   · the rank is a label on the row — label-medium;
   · the handle is the row's title, the one thing that says whose row it is —
     title-MEDIUM, not the title-small a post's author takes. Measured first:
     the handle had no size of its own and inherited the body's 16px at 600
     weight, so title-small would have shrunk the row's own identity from 16px
     to 14px, which nobody asked for. Title-medium is the 16px title role, so
     the size the row was designed around is kept and now has a name;
   · the record line under it is prose about the trader — body-small;
   · the number at the right is the row's other title — title-small, the same
     role the money takes on a card, and `.nmo-num` still gives it the display
     face and its tabular figures.

   WHAT IT COSTS, and it is the thing to watch: every M3 label carries the 500
   weight and 0.5px tracking, so the rank and the record paint wider than the
   bare sizes did. A handle is one unbreakable word beside a number, so this is
   measured painted, not assumed — see the report. */
.nmo-rk {
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
  color: var(--text-faint);
}
.nmo-row-mid { min-width: 0; }
.nmo-hd { font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking); }
/* ═══ THE HANDLE IS A BUTTON, AND A BARE BUTTON PAINTS ITSELF GREY ═══
   (owner saw it, 3 Sep 2026: every handle on a lighter grey block the width of the text,
   reading as text that had been accidentally selected.)

   `nmo.ts` makes the handle a real <button> so it is reachable by keyboard and announced
   as something to press, which is right. Nothing in this sheet reset the button chrome,
   so the browser's own fill showed through — measured on the running screen as
   background rgb(107,107,107) with 1px 6px of padding. The row's own background is the
   only one that belongs behind a handle. */
.nmo-hd-open {
  background: none; border: 0; padding: 0; margin: 0;
  color: inherit; font: inherit; letter-spacing: inherit;
  text-align: left; cursor: pointer; -webkit-appearance: none; appearance: none;
}
.nmo-sub {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  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); }
/* Text-align and weight only: the role comes from `.nmo-num` on the value
   itself, which is where the money's own face is decided. */
.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; }

/* ═══ THE FOLLOW BUTTON, WHICH REPLACED THE GREEN TOGGLE ═══════════════════
   (owner, 3 Sep 2026: "dont use toggle, just call it follow")

   `.nmo-switch` stood here — a 44×26 sliding pill, green when on. It is gone,
   and every screen that drew it now draws this. See `followButton` in nmo.ts for
   why the button won; this is only how it is painted.

   IT ALSO HAD NO CSS AT ALL UNTIL NOW. `.nmo-follow-btn` shipped on 1 Sep for the
   coin bar and no rule was ever written for it, so that button has been painting
   as the browser's own default grey box — the same fault `.nmo-hd-open` was
   written for two days ago, one element along. Now that every screen uses it,
   there is one rule and one look.

   M3, and each choice is a role rather than a colour picked by eye:

   · SHAPE is the full round of an M3 button, not the 8px of a box. A pill is
     what M3 gives an action; the rectangles on this screen are containers.
   · NOT FOLLOWING is the OUTLINED button — the low-emphasis action. It sits on
     a hundred rows at once and outlined is the one M3 style that repeats that
     often without the list turning into a wall of filled buttons.
   · FOLLOWING is the FILLED TONAL button. Tonal is M3's answer for "this is on
     and it is not the loudest thing here", which is exactly a row you already
     follow: it must read as done at a glance and must not out-shout the P&L
     beside it. The old toggle used --nmo-up, the same green as a gain, so a
     followed trader and a profitable one were the same colour.
   · A BLOCKED TRADER keeps the outline at M3's 38% disabled opacity, and the
     word inside says which of the two reasons it is — the old switch said it
     only to a screen reader.
   · STATE LAYERS on hover and press, because an M3 button without one reads as
     a picture of a button. */
/* ═══ AND IT GAVE THE NAME ITS ROOM BACK (owner, 4 Sep 2026) ═══
   84px minimum and 14px sides painted 95.6px — more than twice the 44px toggle
   it replaced, all of it taken from the column holding the trader's name and
   record. "Following" is the widest word it ever holds and it does not need a
   minimum on top of its own padding: at label-medium with 12px sides it paints
   ~80px, and the row's name column gets the difference. */
.nmo-follow-btn {
  flex: none; height: 32px; padding: 0 12px;
  border-radius: 999px; border: 1px solid var(--nmo-line); background: transparent;
  color: var(--text); cursor: pointer; white-space: nowrap;
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background-color 120ms var(--ease, ease), border-color 120ms var(--ease, ease);
}
.nmo-follow-btn:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.nmo-follow-btn:active { background: color-mix(in srgb, var(--text) 12%, transparent); }
/* Filled tonal: a container fill and no outline, so the two states differ by
   WEIGHT and not only by colour — legible to somebody who cannot tell them apart. */
.nmo-follow-btn.is-on {
  background: var(--nmo-card-bg-2); border-color: transparent; font-weight: 620;
}
.nmo-follow-btn.is-on:hover { background: color-mix(in srgb, var(--text) 15%, transparent); }
.nmo-follow-btn.is-on:active { background: color-mix(in srgb, var(--text) 19%, transparent); }
.nmo-follow-btn.is-pending { opacity: 0.6; }
/* M3's disabled state is the label at 38% on no container. `cursor: default`,
   not `not-allowed`: the trader is not an error, we simply cannot copy them. */
.nmo-follow-btn:disabled { opacity: 0.38; cursor: default; }
.nmo-follow-btn:disabled:hover, .nmo-follow-btn:disabled:active { background: transparent; }
.nmo-follow-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .nmo-follow-btn { 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;
}
/* M3 DIALOG SHAPE: the 28px extra-large corner, and the 24px padding that goes
   with it. `--radius` is this app's container radius and a dialog is not a
   container — M3 gives the dialog its own, larger, corner precisely so it reads
   as lifted off the page rather than as another panel on it. */
.nmo-modal {
  width: 100%; max-width: 420px; max-height: 100%; overflow-y: auto;
  background: var(--bg-elev); border-radius: 28px; padding: 24px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
/* M3's dialog headline is headline-small (24px). It is not taken here: this
   dialog's title carries a HANDLE, which is one unbreakable word, and at 24px a
   long one pushes the dialog wider than the 420 it is allowed and then wraps to
   three lines above a two-line question. Title-large is the next role down, it
   is still a headline role, and it holds "Following dumbcrayoneater" on one line
   at 420px. `overflow-wrap` is the floor under a handle longer than any we have
   seen — it breaks the word rather than the box. */
.nmo-modal-h {
  margin: 0 0 8px;
  font: var(--m3-title-large); letter-spacing: var(--m3-title-large-tracking);
  font-family: var(--font-display); overflow-wrap: anywhere;
}
/* BODY SMALL for a hint on either sheet. It was Body Medium, the same size and
   weight as the fold's own field labels underneath it, so a line explaining the
   sheet and a line naming a setting stood at exactly the same height. A hint is
   the quietest thing on a sheet and now reads that way. */
.nmo-hint {
  margin: 0 0 12px; color: var(--text-faint);
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}
/* THE ONE HINT THAT IS NOT A HINT: the copy sheet's first line is the amount the
   trade will use. It sits directly above the "one trade only" note, and a figure
   and a caveat at one size would be two lines of the same voice. */
.nmo-copy-amount {
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}
.nmo-modal-acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }

/* ═══ THE COPY SHEET: ONE AMOUNT, ONE SWITCH, AND FOUR NUMBERS BEHIND A LINK ═══
   (owner, 8 September 2026, when it replaced a dialog with two tick boxes; redrawn
   to design v60 by #432 on 16 September, when the three preset cards, the list of
   numbers they opened and the sliders of More settings went, with their rules.)

   A MATERIAL BOTTOM SHEET, not the dialog above. The dialog is centred because
   it asks something short; this one is a form a thumb works down, so it comes up
   from the bottom edge and keeps its top corners.

   ═══ AND IT COVERS THE COMPOSER ═══ (the owner, 9 September 2026, on the
   "Copying theveeman" sheet: "this screen should be a slide up modal from the
   bottom, covering the prompt composer".)

   IT USED TO STOP SHORT OF IT, at `--nmo-composer-room`, the way `.nmo-scrim`
   above still does — that inset was written for the Follow modal, whose whole
   argument is that the composer stays reachable behind a one-line question.
   This is not that. It is a form somebody works down and answers, and a live
   prompt box glowing under it is a second thing to do at the same time. So the
   scrim runs the full height now, exactly like the coin sheet's, and the sheet
   comes up from the real bottom edge with the same 260ms slide.

   THE COIN SHEET'S OWN BOX WOULD NOT DO. `.nmo-sheet` is a fixed-height screen —
   `height: min(90vh, 860px)` — which is right for a coin, because a coin always
   fills it. Two questions do not fill nine tenths of a phone, and a sheet padded
   out to a height its content cannot reach is a sheet with a hole in the middle.
   So this box hugs its content up to a ceiling and borrows everything else: the
   layer, the scrim, the keyframe and the timing.

   TWO FACES SHARE THESE RULES: the confirm face (`-ask`) and the settings face
   (`-more`). They are the SAME element wearing a different class — Change
   settings refills the sheet rather than opening a second one. */
.nmo-copy-scrim {
  position: fixed; inset: 0;
  /* 99, the coin sheet's scrim, not 40: on `document.body` this has to clear the
     app's own `#session-overlay` (z-index 80 under 900px wide) and stay under the
     app's menus and drawers at 200. The long comment on `.nmo-sheet` has the
     whole story; the two numbers must not drift apart. */
  z-index: 99; background: rgba(0, 0, 0, 0.6);
  display: flex; align-items: flex-end; justify-content: center;
  /* The token block at the top of this file hands this element the `--nmo-*`
     colours and takes its own layout with them. None of that layout is wanted.
     `flex-direction` IS PART OF THAT LAYOUT and cancelling it is not optional:
     that block says `column`, and in a column the two lines above swap meaning —
     `justify-content: center` stops running left-to-right and starts centring
     TOP TO BOTTOM. Measured in a browser at 390 wide on 9 Sep 2026, the sheet
     floated with 122px of dimmed screen under it instead of sitting on the
     bottom edge. Everything else about it was right, which is why no check that
     reads class names caught it. */
  flex-direction: row;
  max-width: none; margin: 0; padding: 0;
}
.nmo-copy-sheet {
  width: 100%; max-width: 480px;
  /* A CEILING, NOT A HEIGHT. The two questions are short and stay short; More
     settings is taller than a phone and scrolls inside `.nmo-copy-body`. 90vh is
     the coin sheet's own ceiling, so the strip of page left showing above the two
     sheets is the same strip. `dvh` wins where a browser knows it, so the sheet
     ends at the bottom of what is actually visible rather than under the URL bar. */
  max-height: 90vh; max-height: 90dvh;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--bg-elev); border-radius: 28px 28px 0 0;
  box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.5);
  /* THE PHONE'S OWN BOTTOM BAR, PAID FOR ONCE. On the sheet itself rather than on
     the row of buttons, because the picker face has no buttons and still sits on
     the bottom edge — put on `.nmo-copy-acts` this would have been a band of
     nothing under three quarters of the faces and a home bar through the last one. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  /* The coin sheet's own entrance, by name, so the two cannot drift: 260ms out of
     the bottom edge. `nmo-sheet-up` ends at `transform: none`, which is what this
     box wants at every width — it is never translated sideways. */
  animation: nmo-sheet-up 260ms var(--ease, cubic-bezier(0.2, 0, 0, 1)) both;
}
/* A face with nothing to press hides its row rather than leaving an empty band of
   padding on the sheet's floor. */
.nmo-copy-acts[hidden] { display: none; }
/* THE HANDLE IS A BUTTON, NOT A DECORATION — see `bottomSheet` in nmo.ts. The
   bar is 32×4, M3's size, drawn rather than written so no font decides whether
   the way out of the sheet is visible. The button is the whole width and 28px
   tall: design v60 puts the bar 10px from the top edge and the name 14px under
   it (#432). */
.nmo-copy-grab {
  flex: none; height: 28px; width: 100%; border: 0; background: none; cursor: pointer;
  display: flex; align-items: flex-start; justify-content: center; padding: 10px 0 0; box-sizing: border-box;
}
.nmo-copy-grab::before {
  content: ""; width: 32px; height: 4px; border-radius: 999px;
  background: var(--text-faint); opacity: 0.6;
}
.nmo-copy-grab:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; border-radius: 28px 28px 0 0; }
.nmo-copy-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 20px 8px; }
/* The buttons never scroll away: a person who has read the sheet must not have to
   find the answer button. */
.nmo-copy-acts { flex: none; display: flex; gap: 12px; padding: 12px 20px 28px; }
.nmo-copy-acts .nmo-cta { min-height: 56px; font: var(--m3-title-medium); letter-spacing: .15px; }
/* Start copying, or Save, is the wider of two — it is the one being asked for. */
.nmo-copy-back, .nmo-copy-cancel { flex: 1 1 0; }
.nmo-copy-go { flex: 1.6 1 0; }
/* On the confirm face the link sits over one full-width button. */
.nmo-copy-sheet-ask .nmo-copy-body { padding-bottom: 0; }
.nmo-copy-sheet-ask .nmo-copy-acts { flex-direction: column; gap: 0; padding-top: 0; }
.nmo-copy-sheet-ask .nmo-copy-go { flex: none; width: 100%; }

.nmo-copy-head {
  display: flex; align-items: center; gap: 12px; padding: 0 0 14px;
  border-bottom: 1px solid rgba(161, 92, 255, .28);
}
.nmo-copy-head .nmo-av { width: 40px; height: 40px; }
.nmo-copy-words { flex: 1 1 auto; min-width: 0; }
/* The name on one line, cut with an ellipsis rather than wrapped (design v60). */
.nmo-copy-title {
  margin: 0; font: var(--m3-title-large); letter-spacing: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nmo-copy-sub {
  margin: 0; color: var(--text-dim);
  font: var(--m3-body-medium); letter-spacing: .25px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A wallet with no name (#309) says so, small, under "Paper copy". */
.nmo-copy-wallet {
  margin: 2px 0 0; color: var(--text-faint); overflow-wrap: anywhere;
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}
/* A wallet with no name and no picture (#309): the face circle filled in the colour of the card's
   own action circles, with core's Wallet icon in it, instead of letters made from its address. */
.nmo-av.nmo-av-wallet { background: var(--bg-elev-3); color: var(--text-dim); }
.nmo-av-wallet .nmo-ic { font-size: 50cqw; }

/* ── the confirm face (#453, design v63 #cs3s): one amount, one track ──── */
/* The amount, large, with the settings face's own Per trade track under it. The figure IS the
   field: tapping it selects the number and opens the number pad. */
.nmo-copy-amt { margin: 18px 0 2px; text-align: center; }
.nmo-copy-amt-n {
  display: block; width: 100%; box-sizing: border-box; padding: 0; border: 0; background: none;
  text-align: center; color: var(--text); font: 500 45px/52px var(--font-display); letter-spacing: -.5px;
  font-variant-numeric: tabular-nums; cursor: text;
}
.nmo-copy-amt-n:focus { outline: none; color: var(--nmo-violet, var(--accent)); }
.nmo-copy-amt-l {
  display: block; color: var(--text-dim); font: var(--m3-body-medium); letter-spacing: .25px;
}
.nmo-copy-amt .nmo-copy-row-bare { border-top: 0; padding: 4px 4px 10px; }
/* The safety net: a card that is the switch, its line under its name. */
.nmo-copy-net {
  display: flex; align-items: center; gap: 16px; width: 100%; box-sizing: border-box; text-align: left;
  padding: 14px 16px; border-radius: 16px; cursor: pointer; color: inherit;
  background: var(--bg-elev-2); border: 1px solid var(--nmo-outline-variant, var(--nmo-line));
}
.nmo-copy-net-w { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.nmo-copy-net-t { font: var(--m3-title-medium); letter-spacing: .15px; }
.nmo-copy-net-d { color: var(--text-dim); font: var(--m3-body-medium); letter-spacing: .25px; }
.nmo-copy-fig { color: var(--text); font-weight: 500; }
.nmo-copy-up { color: var(--ok); }
.nmo-copy-down { color: var(--err); }
/* The switch the card and the Copying label carry: M3's 52×32 track, a 16px handle off and a
   24px one on, so the state is a size and a position as well as a fill. */
.nmo-sw-track {
  flex: none; position: relative; width: 52px; height: 32px; box-sizing: border-box;
  border: 2px solid var(--nmo-outline, var(--text-faint)); border-radius: 999px; background: transparent;
  transition: background-color 120ms var(--ease, ease), border-color 120ms var(--ease, ease);
}
.nmo-sw-track::after {
  content: ""; position: absolute; top: 50%; left: 6px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--nmo-outline, var(--text-faint)); transform: translateY(-50%);
  transition: left 120ms var(--ease, ease), width 120ms var(--ease, ease), height 120ms var(--ease, ease);
}
:is(.nmo-copy-net, .nmo-copy-master, .nmo-grule-switch).is-on .nmo-sw-track {
  background: var(--nmo-violet, var(--text)); border-color: var(--nmo-violet, var(--text));
}
:is(.nmo-copy-net, .nmo-copy-master, .nmo-grule-switch).is-on .nmo-sw-track::after {
  left: 22px; width: 24px; height: 24px; background: var(--nmo-on-violet, var(--bg));
}
@media (prefers-reduced-motion: reduce) {
  .nmo-sw-track, .nmo-sw-track::after { transition: none; }
}
/* `.nmo-copy-val` is left out on purpose: it draws its own outline and ring the moment it takes
   the keyboard, and a second outline over that is two indicators for one state. */
:is(.nmo-copy-net, .nmo-copy-master, .nmo-copy-sl, .nmo-copy-amt-n,
  .nmo-copy-link, .nmo-copy-stop):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The quiet link to the settings face. */
.nmo-copy-link {
  display: block; margin: 14px auto 18px; height: 40px; padding: 0 12px;
  border: 0; background: transparent; border-radius: 999px; cursor: pointer;
  color: #c9a6ff; font: var(--m3-label-large); letter-spacing: .1px;
}
.nmo-copy-link:disabled { opacity: 0.5; cursor: default; }

/* ── the settings face (#432, design v60 #cp2): a switch and four numbers ── */
/* The Copying switch, with its label over it, at the right of his name. */
.nmo-copy-master {
  flex: none; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--text-dim); font: var(--m3-label-small); letter-spacing: .5px;
}
.nmo-copy-master:disabled { opacity: 0.5; cursor: default; }
.nmo-copy-rows { display: flex; flex-direction: column; margin-top: 4px; }
/* ── slide or type (#453, design v63 #cs1s): a row is a name, a value, a track ──
   Measured off the design page: a 12px track, its filled part the one violet, and a 4px × 32px
   handle with a ring of the sheet's own colour around it so it is legible on the fill. */
.nmo-copy-row { padding: 10px 0 6px; border-top: 1px solid var(--nmo-outline-variant, var(--nmo-line)); }
.nmo-copy-row:first-child { border-top: 0; }
.nmo-copy-row-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.nmo-copy-row-l { font: var(--m3-body-large); letter-spacing: .5px; }
/* The value, in a tonal field. It is an input at rest as well as while it is being typed in, so
   tapping it never rebuilds anything and the caret lands where the finger did. */
.nmo-copy-val {
  flex: none; width: 96px; height: 36px; box-sizing: border-box; padding: 0 12px; border-radius: 10px;
  text-align: right; font: var(--m3-title-medium); letter-spacing: .15px; font-variant-numeric: tabular-nums;
  background: var(--bg-elev-2); border: 1px solid transparent; cursor: text; color: var(--text);
}
/* Being typed into: the tonal fill gives way to an outline and the design's soft ring around it,
   so the field reads as the one thing on the sheet taking input. */
.nmo-copy-val:focus {
  outline: none; border-color: var(--nmo-violet, var(--accent)); background: transparent;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--nmo-violet, var(--accent)) 18%, transparent);
}
.nmo-copy-val.nmo-copy-up { color: var(--ok); }
.nmo-copy-val.nmo-copy-down { color: var(--err); }
.nmo-copy-val.is-off { color: var(--text-faint); font-weight: 400; }
/* The track. The fill is painted from `--nmo-sl-p`, which the screen sets as the handle moves —
   never a transition, because a fill that lags the finger reads as a dropped drag. */
.nmo-copy-sl {
  -webkit-appearance: none; appearance: none; display: block; width: 100%; height: 36px;
  margin: 2px 0 0; padding: 0; background: transparent; cursor: pointer; --nmo-sl-p: 0%;
}
.nmo-copy-sl::-webkit-slider-runnable-track {
  height: 12px; border-radius: 999px;
  background: linear-gradient(90deg, var(--nmo-violet, var(--accent)) 0, var(--nmo-violet, var(--accent)) var(--nmo-sl-p),
    var(--bg-elev-2) var(--nmo-sl-p), var(--bg-elev-2) 100%);
}
.nmo-copy-sl::-moz-range-track { height: 12px; border-radius: 999px; background: var(--bg-elev-2); }
.nmo-copy-sl::-moz-range-progress { height: 12px; border-radius: 999px; background: var(--nmo-violet, var(--accent)); }
.nmo-copy-sl::-webkit-slider-thumb {
  -webkit-appearance: none; width: 4px; height: 32px; margin-top: -10px; border: 0; border-radius: 2px;
  background: var(--nmo-violet, var(--accent)); box-shadow: 0 0 0 5px var(--bg-elev);
}
.nmo-copy-sl::-moz-range-thumb {
  width: 4px; height: 32px; border: 0; border-radius: 2px;
  background: var(--nmo-violet, var(--accent)); box-shadow: 0 0 0 5px var(--bg-elev);
}
/* The two ends of the range, quietly, with the middle kept for what the row has to say: the band
   while somebody is typing, or the one note a number past it earns. */
.nmo-copy-ends {
  display: flex; justify-content: space-between; align-items: center; gap: 8px; min-height: 18px;
  margin-top: -2px; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: .4px;
}
.nmo-copy-row.is-saying .nmo-copy-end { visibility: hidden; }
.nmo-copy-mid { color: var(--text-dim); text-align: center; }
.nmo-copy-mid.is-note { color: var(--text); }
/* Stop copying stands apart from Save and Cancel. */
.nmo-copy-stop {
  display: block; margin: 8px auto 0; height: 40px; padding: 0 12px;
  border: 0; border-radius: 999px; background: transparent; cursor: pointer;
  color: var(--err); font: var(--m3-label-large); letter-spacing: .1px;
}
/* 16px from Stop copying to the buttons, as the design has it: the body's and the button row's
   own paddings are that gap on this face. */
.nmo-copy-sheet-more .nmo-copy-body:has(> .nmo-copy-stop) { padding-bottom: 4px; }
/* A small caption under a row: the stopped-for-today line (#383). */
.nmo-more-cap {
  margin: 2px 0 0; color: var(--text-faint);
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}

/* ── Unfollow and the way back into the rules, side by side in one cell ─── */
.nmo-follow-pair { display: inline-flex; align-items: center; gap: 2px; }
.nmo-gear-btn {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  min-width: 32px; min-height: 32px; padding: 0 4px; font-size: 15px;
  border: 0; border-radius: 999px; background: none; color: var(--text-faint); cursor: pointer;
}
.nmo-gear-btn:hover { color: var(--text); }
.nmo-gear-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The same thing on his own page, sized to the Follow button it sits beside. */
.nmo-profile-gear {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  min-width: 33px; min-height: 33px; margin-left: 6px; font-size: 16px;
  border: 0; border-radius: 8px; background: var(--bg-elev-1); color: var(--text-faint); cursor: pointer;
}
.nmo-profile-gear:hover { color: var(--text); }
.nmo-profile-gear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* BODY MEDIUM for what the field is called, LABEL LARGE for the figure in it —
   the same 14px, one weight apart, which is the pairing used everywhere else on
   these screens for a word and the number it names. */
.nmo-set {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  padding: 10px 0; border-top: 1px solid var(--nmo-line);
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}
.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: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
}
.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; }
/* pump.fun: 1m -> 5m is 36 ref = 23.5 CSS, and the labels carry no tracking. */
.nmo-ranges { display: flex; gap: 24px; }

/* 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: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}

/* ═══ THE TWO TABS ACROSS THE TOP OF YOUR OWN PROFILE ═════════════════════════
   The same bar as the one under a coin's chart, and it is the same rules doing
   the work rather than a copy of them: the tabs carry `.nmo-coin-tabs` and
   `.nmo-coin-tab` for the look, and `.nmo-profile-tabs` / `.nmo-profile-tab`
   only as the name the suites find them by. Two names on one element, the way
   the portfolio card carries its marker beside the class that shapes it — a
   second set of measurements for one bar is a second thing to keep in step, and
   the owner would see the two bars drift apart. */
.nmo-profile-tabs { margin-bottom: 4px; }

/* ═══ ONE TRADER YOU FOLLOW ═══════════════════════════════════════════════════
   Material 3's two-line list item at 72px: his picture, his name with a line
   under it, and a round gear at the end. The height is a MINIMUM, not a fixed
   number — a long name wraps to a taller row rather than being cut off, because
   a clipped name is a name we have shown wrong. */
.nmo-pf-list { display: block; }
.nmo-pf-row {
  display: flex; align-items: center; gap: 16px;
  min-height: 72px; box-sizing: border-box; padding: 8px 16px;
}
/* The hand and the hover only where there is something to press. A row for a wallet
   nobody knows a handle for has no page to open, and a pointer over it would promise
   one — see `onOpen` in `profileFollowing.ts`. */
.nmo-pf-row[role="button"] { cursor: pointer; }
.nmo-pf-row[role="button"]:hover { background: var(--bg-elev-2); }
/* Inside the edge, so the ring is not clipped by the row above or below it. */
.nmo-pf-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.nmo-pf-av { flex: none; }
/* `min-width: 0` is what lets the name below actually shorten: without it a flex
   child refuses to go under its content's width and pushes the gear off the row. */
.nmo-pf-mid { flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; }
.nmo-pf-name {
  font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nmo-pf-rules { color: var(--text-faint); font-size: 13px; }
/* The separator is dimmer than the facts it sits between, so the line reads as
   three things rather than one run-on sentence. */
.nmo-pf-dot { opacity: 0.6; }
.nmo-pf-gear { flex: none; }
.nmo-pf-gear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-pf-absent { margin: 0; padding: 10px 16px; 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. */
/* pump.fun: 14 ref cap = 9.1 CSS cap = 13px, and no letter-spacing. */
.nmo-coin-chart .nmo-range { color: var(--text-faint); background: none; font-size: 13px; letter-spacing: normal; }
.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 Alert bell was here, drawn switched off because we had no alerts. The owner asked for
   it to go on 6 Sep 2026 and the settings icon now stands in its place, behind the same line. */
.nmo-chart-divider { width: 1px; align-self: stretch; background: var(--nmo-line); margin: 0 4px; }
/* THE CHART'S SETTINGS, AT THE END OF THE TIME-FRAME ROW (owner, 6 Sep 2026). It was a line of
   link text in a header row above the drawing; it is now an icon where the bell was. Sized to
   the chips beside it so the row reads as one row, and given a real touch target — an icon
   drawn at 18px is not an 18px button. */
.nmo-coin-gear {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; min-height: 32px; padding: 0 4px;
  color: var(--text-faint); background: none; border: 0; cursor: pointer;
}
.nmo-coin-gear:hover { color: var(--text); }
.nmo-coin-gear .nmo-ic { width: 18px; height: 18px; }
.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: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}
.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) {
  /* ═══ ONE STEP SMALLER ON A NARROW PHONE (owner, 2 Sep 2026) ══════════════
     He chose one row everywhere, and one row does not fit the narrowest card at
     the full title size: with his own numbers — Amount $15.0K, MC $17.7M, P&L
     −33.4% — the three columns want 167.5px against the 160.0px beside a "CTO".
     Over by 7.5. At 13px they want 156.6 and fit with 3.4px to spare, so the
     step buys 10.9px, measured.

     ONLY THE NUMBERS STEP. The labels stay on `--m3-label-medium`: they are
     already narrower than the numbers above them on this card, so shrinking them
     buys nothing and would only make the supportive text harder to read.

     13px IS ABOVE THE FLOOR. The smallest type this screen uses is 11px, so this
     is two steps clear of it.

     THIS BREAKPOINT IS THE CARD'S OWN, not a new number: it is where the card
     already changes shape for a narrow phone, a few lines below. What it is NOT
     is a verified mapping to a 269px card — see the note in the report; the
     269/313 widths this was measured against come from a comment in
     `closeCard.ts`, and I could not confirm them against a running screen. */
  /* EVERY ASSET CARD, NOT JUST THE TRADER'S (3 Sep 2026). This was scoped to
     `.nmo-card-leader`, and the genie's OWN position card is `.nmo-card-ref`
     without it — so on a narrow phone his card drew its values at 14px while the
     trader card beside it drew 13. One pixel apart on two cards the owner had just
     asked to be one template, and the step I added is what split them.
     `.nmo-card-ref` is the asset-card family: the trader card, his own position
     card, and the not-copied card all carry it. */
  .nmo-card.nmo-card-ref .nmo-v { font-size: 13px; }
  /* ═══ 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 the card's content line, leaving the
     name column well under half of it. Measured at 390 on 31 Aug, when that line
     was 249px: "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 content line
     minus the picture and its gap, and those two names read 69% and 76%.
     THE LINE'S WIDTH IS NOT WRITTEN HERE: it is the post's second grid column
     less this card's own padding, and it moved from 249 to 258 on 1 Sep when the
     picture column narrowed to the picture's width. The percentages above are
     the measurement of that day and are marked as such.
     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 CARD IS AS WIDE AS THE WORDS ABOVE IT (owner, 1 Sep 2026) ═══════
     "the status card needs to be of the same width of the text area."

     It used to reach LEFT into the post's picture gutter with a negative margin,
     to buy room for two 90px label columns. That is gone: the card's left edge
     now lines up with the post's own words, where his eye expects it, and the
     columns fit in what a phone card actually has.

     WHAT IT ACTUALLY HAS is not written here, because it is not a number this
     rule owns: it is the post's second grid column less this card's 10px padding
     on each side, and the post's second column is whatever is left after
     `--nmo-pfp` and the gap. It moved once already — the picture column narrowed
     to the picture's own width on 1 Sep and every card gained 9px without a line
     of this rule changing. Take the number from the page, not from here.

     WHAT DOES NOT CHANGE is the shape of the problem, and it is the reason this
     card is laid out the way it is: a column is as wide as the WIDER of its
     label and its number. Measured at 390: "$1,225.13" is 72.4px, "$135,990.00"
     is 90.5, "$12.3M" is 53.1 — while the labels were "Amount bought" at 81.1px
     and "MC when bought" at 89.1px. The LABELS were setting the widths, so the
     coin's own symbol was left 18.8px and the card had to be widened to survive
     it. That was the wrong end to pull.

     So the words gave way instead. "MC when bought" became "MC at buy" (53.5px)
     and "Amount bought" became "Amount" (40.7px) — both now narrower than the
     money under them, so neither costs the identity anything, and the symbol
     stays whole. The side is still said twice on the post: the pill beside the
     trader's name, and this card's own "MC at buy" / "MC at sell".

     The columns are sized by their content, so each is as wide as the money in
     it and no wider, and the identity takes what is left. A symbol wider than
     that is cut, not broken across lines. */
  /* THREE TRACKS, AND A FOURTH ONLY WHEN THERE IS SOMETHING IN IT. A fourth
     track with nothing in it is zero wide but its 8px gap is not, and that gap
     held the amount 8px off the card's right edge on every card whose record
     carries no market cap — an empty column reserved, which is the one thing
     this card must never do. */
  .nmo-card.nmo-card-leader {
    grid-template-columns: 36px minmax(0, 1fr) auto;
    column-gap: 8px;
  }
  .nmo-card.nmo-card-leader[data-nmo-has-trade-mc="1"] {
    grid-template-columns: 36px minmax(0, 1fr) auto auto;
  }
  .nmo-card.nmo-card-leader .nmo-kv {
    grid-column: auto; text-align: right; max-width: 100px;
  }
  /* Past its column a label or a number breaks onto a second line rather than
     painting over the coin beside it. */
  .nmo-card.nmo-card-leader .nmo-k,
  .nmo-card.nmo-card-leader .nmo-v {
    overflow-wrap: anywhere;
  }
  /* The symbol is cut, not broken: "BONE" over "R" is a coin nobody can read,
     and it costs the card a third line. */
  .nmo-card.nmo-card-leader .nmo-sym {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
  }

  /* ═══ NOTHING SLIDES UNDER THE PICTURE ANY MORE ═══════════════════════════
     A card pulled left with a negative margin reserves nothing, and the
     picture's column runs the whole height of the post — so on a post with no
     words of its own the card's coin art sat 26px underneath the trader's
     picture. That is what the deploy check read as "the trader's picture paints
     54px in a 46px box": two pictures inside one measurement. A 24px top margin
     used to hold it clear. With the cards back at text width nothing reaches
     into that column at all, so the margin is gone and those posts are 24px
     shorter again. */

  /* ═══ 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.

     THIS CARD IS TEXT WIDTH TOO (owner, 1 Sep 2026): "the status card needs to
     be of the same width of the text area". It reached into the post's picture
     gutter with a negative margin, and that is gone — its left edge lines up
     with the words above it. It can afford to: "Position" is 42px and "Net P&L"
     42.7px, both narrower than their own numbers, so the columns are sized by
     the money and the identity keeps enough for the symbol AND the coin's name
     under it. `:not()` keeps this off the trader's card, which has its own
     widths above. */
  /* THE COIN IS ALWAYS NAMED, EVEN WHEN THE MONEY IS ENORMOUS. At text width
     the picture and two money columns can take everything the card has:
     measured at 390, a position of $135,990.00 beside a profit of +$168,580.00
     wants 190px of value column, which was more than the card had to give, and
     the identity was squeezed to NOTHING — a card that does not say which coin
     it is about. Thirty pixels is the floor, which is a cut symbol rather than
     no symbol. It binds only when the two numbers together outrun the card: at
     $1,454.99 the identity comes out around 35px on its own and both numbers
     stay on one line. Past the floor it is the money that wraps, because a coin
     with no name is worse than a number on two lines.
     The card's own width is deliberately not restated here — see the trader
     card's rule above for why. */
  .nmo-card.nmo-card-ref:not(.nmo-card-leader) {
    grid-template-columns: 36px minmax(30px, 1fr) auto auto;
    column-gap: 8px;
  }
  .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; }

/* ═══ THE SEARCH UNDER THE ROUND BUTTON (#535, part D of #531; design v88 section 04b) ═══
   The design's .sp, .sr, .sp-top, .sp-none and .ghost, on the app's own tokens: its ink, ink-2 and ink-3 are
   --text, --text-dim and --text-faint; its raised and raised-2 are --bg-elev-2 and --bg-elev-3; its line-2 is
   --border. It replaced #187's "Find a trader" field on the Traders tab. A row and the larger card are buttons,
   so every rule that a button would bring along is put back to nothing here. */
.nmo-sp { padding: 0 16px 16px; box-sizing: border-box; }
.nmo-sp-hint { font: var(--m3-body-medium); color: var(--text-dim); margin: 6px 2px 14px; max-width: 34ch; }
.nmo-sp-ex { display: flex; flex-wrap: wrap; gap: 8px; }
.nmo-sp-chip {
  height: 32px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--border); background: none;
  display: inline-flex; align-items: center; font: var(--m3-label-large); color: var(--text-dim); cursor: pointer;
}
.nmo-sp-chip code { font: 500 13px var(--font-mono); }
.nmo-sp-h {
  display: flex; justify-content: space-between; align-items: baseline;
  font: var(--m3-title-small); color: var(--text-dim); margin: 10px 2px 2px;
}
.nmo-sp-h small { font: var(--m3-body-small); color: var(--text-faint); }
.nmo-sr {
  display: flex; align-items: center; gap: 12px; width: 100%; height: 64px; box-sizing: border-box;
  padding: 0; margin: 0; border: 0; background: none; color: inherit; text-align: left; font: inherit; cursor: pointer;
}
.nmo-sr-pic {
  width: 40px; height: 40px; border-radius: 10px; flex: none; overflow: hidden;
  display: grid; place-items: center; font: 600 13px var(--font-ui); color: #fff;
  background: linear-gradient(135deg, hsl(var(--nmo-sr-hue, 260) 35% 22%), hsl(var(--nmo-sr-hue, 260) 60% 58%));
}
.nmo-sr-pic.is-round { border-radius: 50%; }
.nmo-sr-pic img { width: 100%; height: 100%; object-fit: cover; }
.nmo-sr-mid { flex: 1; min-width: 0; }
.nmo-sr-nm { font: var(--m3-title-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-dim); }
.nmo-sr-nm b { color: var(--text); font-weight: 600; }
.nmo-sr-nm em { font-style: normal; font: var(--m3-body-medium); color: var(--text-faint); margin-left: 6px; }
.nmo-sr-sub { font: var(--m3-body-small); color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nmo-sr-sub code { font: 400 12px var(--font-mono); }
.nmo-sr-rt { text-align: right; flex: none; }
.nmo-sr-rt b { display: block; font: 500 15px var(--font-ui); font-variant-numeric: tabular-nums; color: var(--text); }
.nmo-sr-rt b.nmo-up { color: var(--nmo-up); }
.nmo-sr-rt b.nmo-down { color: var(--nmo-down); }
.nmo-sr-rt span { font: var(--m3-body-small); color: var(--text-faint); }
.nmo-sr-rt span.nmo-up { color: var(--nmo-up); }
.nmo-sr-rt span.nmo-down { color: var(--nmo-down); }
.nmo-sp-said { font: var(--m3-body-medium); color: var(--text-dim); margin: 4px 2px 8px; }
.nmo-sp-said.is-quiet { color: var(--text-faint); }
.nmo-sp-said.is-after { margin-top: 14px; }
.nmo-sp-top {
  display: block; width: 100%; box-sizing: border-box; text-align: left; font: inherit; color: inherit; cursor: pointer;
  border: 0; background: var(--bg-elev-2); border-radius: 20px; padding: 14px 16px; margin: 0 0 6px;
}
.nmo-sp-top .nmo-sr { height: auto; }
.nmo-sp-top-foot { display: flex; gap: 18px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--nmo-line); }
.nmo-sp-top-foot div { font: var(--m3-body-small); color: var(--text-faint); min-width: 0; }
.nmo-sp-top-foot b { display: block; font: 500 15px var(--font-ui); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nmo-sp-none { font: var(--m3-body-large); color: var(--text-dim); margin: 26px 2px 8px; max-width: 30ch; }
.nmo-sp-none-tip { font: var(--m3-body-medium); color: var(--text-faint); margin: 0 2px; max-width: 34ch; }
/* Still searching: rows breathe in the surface colour where coins will land. No spinner. */
.nmo-sp-ghost { display: flex; align-items: center; gap: 12px; height: 64px; }
.nmo-sp-ghost i { display: block; background: var(--bg-elev-3); border-radius: 8px; animation: nmo-sp-breathe 1.6s ease-in-out infinite; }
.nmo-sp-ghost > i:first-child { width: 40px; height: 40px; border-radius: 10px; flex: none; }
.nmo-sp-ghost-lines { flex: 1; }
.nmo-sp-ghost-lines i { height: 12px; margin: 5px 0; width: 60%; }
.nmo-sp-ghost-lines i + i { width: 38%; height: 10px; }
@keyframes nmo-sp-breathe { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .nmo-sp-ghost i { animation: none; } }
/* The plain field the search is tried in until the round button (#537) lands: one rounded field under the tabs. */
.nmo-search-preview-field {
  display: block; width: calc(100% - 32px); height: 48px; box-sizing: border-box; margin: 4px 16px 8px; padding: 0 16px;
  border: 0; border-radius: 999px; background: var(--bg-elev-2); color: var(--text); font: var(--m3-body-large); outline: none;
}
.nmo-search-preview-field::placeholder { color: var(--text-dim); opacity: 1; }

/* ═══ THE TRADERS LIST'S ROW (#445, design v60 "Traders · top 30 by 7-day profit") ═══
   One line: the rank in the code face, a 40px picture (his initials on the design's violet when there is none),
   his name cut with an ellipsis, and the last cell with the small board and the figure 12px apart. 64 tall with
   8px above and below, no line between rows, no headings over them. Top 30 and Following share it; a Following
   row leaves the rank cell empty. The only second line a row may have is a real problem, under the name. */
.nmo-balance-row.nmo-trader-line {
  grid-template-columns: 24px 40px minmax(0, 1fr) auto;
  grid-template-areas: none;
  column-gap: 16px; row-gap: 0;
  align-items: center;
  min-height: 64px; box-sizing: border-box; padding: 8px 16px;
  border-bottom: 0;
}
/* The older list's named cells (`.nmo-balance-row > .nmo-rk` and the rest) do not apply: each child takes the next cell. */
.nmo-balance-row.nmo-trader-line > * { grid-area: auto; }
.nmo-trader-line > .nmo-rk { font: 500 14px/20px var(--font-mono); color: var(--text-faint); }
.nmo-trader-line > .nmo-av { width: 40px; height: 40px; }
.nmo-trader-line > .nmo-av .nmo-av-letters {
  background: linear-gradient(135deg, #2c2a4a, #6f63c8); color: #ffffff;
  font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
.nmo-trader-line > .nmo-balance-name { font: var(--m3-body-large); letter-spacing: .5px; }
.nmo-trader-money { display: flex; align-items: center; justify-content: flex-end; gap: 12px; min-width: 66px; }
.nmo-trader-money .nmo-balance-now {
  min-width: 66px; text-align: right; white-space: nowrap;
  font: var(--m3-title-medium); letter-spacing: .15px; font-variant-numeric: tabular-nums;
}
/* "no trades" on a Following row, in the faint small words the old row used. */
.nmo-trader-money .nmo-balance-now.nmo-pnl-say {
  font: var(--m3-body-medium); letter-spacing: .25px; color: var(--text-faint);
}
/* "watching", under the figure of a follow that copies nothing. */
.nmo-trader-figure { display: flex; flex-direction: column; align-items: flex-end; }
/* NO SECOND LINE ON ANY TRADER ROW (owner, 17 Sep 2026). `.nmo-trader-problem` drew a red sentence
   under the name on the Following list; the row builder has no slot to pass one any more, so the
   rule that placed it is gone with it rather than left waiting for a caller. */
.nmo-trader-line[role="button"] { cursor: pointer; }
.nmo-trader-line[role="button"]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }



/* ── 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; }
/* An icon nobody can draw. It is a crossed-out box on purpose and it is dimmed on
   purpose: the whole failure of the old behaviour was that a missing icon looked
   like NOTHING, so it survived every review. This looks broken, which is what it is. */
.nmo-ic-drawn.is-missing { color: var(--text-faint); opacity: 0.7; }

/* ── 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-ranges { padding: 9px 0 2px; }   /* pump.fun 14 ref = 9.1 */
/* 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); }
/* ═══ THE NAV SECTION IS PINNED, AND GETS OUT OF THE WAY (owner, 1 Sep 2026) ═══
   "when user scrolls up, this nav section slides up and hide; when user scrolls
   down, this nav section slides down and show." The bar was not pinned at all
   before this — it was a plain block at the top of a 5,013px page inside the
   app's transcript, so it scrolled away on the first swipe and stayed away.

   `top` is NOT 0. The transcript carries 24px of padding above its content, and a
   sticky bar written `top: 0` pins to the padding box: the bar sat 24px down and
   the feed scrolled through the band above it, printing a post's name over the
   nav. That 24px is the HOST'S and is measured in nmo.ts, which sets
   `--nmo-nav-inset` on the element; a host with no padding falls back to 0px.

   The background must be opaque — the bar now passes over the feed, and it was
   transparent. `--bg-elev` is what the chat column behind it already paints.

   MEASURED AND HANDED OVER BY TUESDAY, section 7 of
   scratchpad/reports/tuesday-nav-hides-on-scroll.md. `z-index: 4` is checked
   rather than picked: everything that must sit above the nav is `position:
   fixed` far above it — the Follow modal's scrim at 40, the coin sheet at 100
   and its scrim at 99 — and nothing in the plugin sits between 4 and 40.

   AND THE FILL ON THE ENTRY ANIMATION IS `backwards`, NOT `both` (line 136):
   `both` holds the animation's final transform after it ends, and a held
   `transform: none` silently beats the slide below — the class would go on and
   off and the bar would not move. */
.nmo-head {
  position: sticky;
  top: calc(-1 * var(--nmo-nav-inset, 0px));
  z-index: 4;
  background: var(--bg-elev);
  transition: transform 180ms var(--ease, cubic-bezier(0.2, 0, 0, 1));
}
.nmo-head-away { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) { .nmo-head { transition: none; } }
.nmo-bal { font-variant-numeric: tabular-nums; flex: 0 1 auto; min-width: 0; }
.nmo-tabs, .nmo-ranges { padding-left: 16px; padding-right: 16px; }
.nmo-tabs { 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. */
/* ═══ THE BAR IS A WASH, TOP TO BOTTOM (owner, 1 Sep 2026) ══════════════════
   "learn from pumpfun, make this nav bar area with some color gradient."

   MEASURED OFF HIS PICTURE (265.png, 626px wide), not chosen. Sampled at five x
   positions across the bar — 25, 150, 300, 340, 600 — and every one reads the
   same colour at the same y, so the gradient is PURELY VERTICAL with no
   horizontal component at all.

       top of the bar    rgb(38,42,43)   +31 over the page below it
       bottom of the bar rgb(16,16,18)   +7.7 over the page below it
       the page below    rgb(9,9,9)

   IT CARRIES NO COLOUR. Both ends are about 6% saturation — hsl(192,6%,16%) at
   the top and hsl(240,6%,7%) at the bottom, a whisper of cyan drifting to a
   whisper of blue. Shown either swatch on its own a person would say "dark
   grey". So "some color gradient" is a light-to-dark WASH, and copying a hue
   out of it would be inventing something that is not in his picture.

   SO OURS IS A WASH IN THE TEXT COLOUR, which is the idiom this file already
   uses for `--nmo-card-bg-2` and `--nmo-line`: light on the dark ground, dark on
   the light one, no per-theme block, and NO NEW COLOUR IN THE PRODUCT. The two
   percentages are his two lifts converted onto our ground: over `--bg-elev-1`
   (36 of 255) a mix of N% of `--text` (227) lands at 36 + 1.91N, so +31 is 16%
   and +7.7 is 4%.

   IT ENDS LIFTED, NOT AT NOTHING, because his does — his bar's bottom is still
   7.7 above the page, then a 1px rule at rgb(41,41,44), then the page. We have
   no such rule; the 4% step at our bar's foot is what draws the edge instead.

   FULL BLEED: his wash runs the whole width, so this cancels the sheet body's
   side padding and puts it back inside, rather than sitting in a 16px-inset box
   the way every other child of that body does. */
.nmo-coin-top {
  display: flex; align-items: center; gap: 10px;
  margin-inline: calc(-1 * var(--nmo-sheet-pad-x));
  padding: 4px var(--nmo-sheet-pad-x) 10px;
  background: linear-gradient(
    180deg,
    var(--nmo-top-wash) 0%,
    color-mix(in srgb, var(--text) 4%, transparent) 100%);
}
/* ═══ THE AGENT'S PICTURE IS THE SIZE OF THE COIN'S ═════════════════════════
   (owner, 1 Sep 2026: "icon of agent should be as big as asset".)
   Read off the live page, not chosen: the coin's image renders 44 x 44, while
   this one rendered 30 x 30 when the coin was opened from a genie's card and
   41 x 41 from a trader's. Both now match the coin at 44. The size class the
   markup asks for is overridden here rather than changed, because the same
   classes size a reply's picture and a comment's, which are not this. */
.nmo-coin-top > .nmo-av { width: 44px; height: 44px; }
/* ═══ SHARE AND FOLLOW SIT ON THE RIGHT ═════════════════════════════════════
   (owner: "the share and follow button should be aligned to the right".)
   The markup already puts a spacer before them — `nmo.ts` appends
   `<span class="nmo-sp">` and the comment there says "everything after the
   spacer sits hard against the right edge". It never did: `.nmo-sp` had NO RULE
   ANYWHERE, so it was a zero-width span that pushed nothing.
   Measured on the live page before this: share 155.3-195.3, follow 205.3-272.5,
   with the bar's own right edge at 374 — 101.5px of empty space to their right.
   Scoped to this bar: a bare `.nmo-sp` is used elsewhere and this is not a
   licence for every spacer in the plugin to start growing. */
.nmo-coin-top > .nmo-sp { flex: 1 1 auto; }
/* ═══ ROOM BETWEEN THE AGENT AND THE COIN (owner, 1 Sep 2026) ══════════════
   "give more margin between the agent and the asset." Measured on the live page
   at 390: `.nmo-coin-top` ends at y=158.4 and `.nmo-coin-head` begins at 158.4 —
   A GAP OF EXACTLY ZERO. The two blocks are touching, which is the crowding in
   his 286.png. 16px is the spacing scale's step for separating two distinct
   blocks; it is a scale step, not a measurement off a picture, because there is
   no reference for this one. */
.nmo-coin-head { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 16px;   /* pump.fun 25 ref = 16.3 */ align-items: center; margin-top: 16px; }
/* ═══ 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; }
/* pump.fun: the coin's title is 17 ref cap = 11.1 CSS cap = 15px. */
.nmo-coin-head .nmo-sym { font-size: 15px; line-height: 20px; }
/* pump.fun: the address line is 14 ref cap = 9.1 CSS cap = 13px. */
.nmo-coin-head .nmo-mint, .nmo-coin-names .nmo-mintrow .nmo-mint { font-size: 13px; }
/* body-small: it is prose about the coin beside the symbol, the same role the
   card's name line takes. Was 12.5px. */
.nmo-sym-line .nmo-coin-name {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  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; border-radius: 12px; }   /* pump.fun 18 ref = 11.8 */
.nmo-coin-names { min-width: 0; }
/* THE PRICE AND WHAT THE COIN IS WORTH — title-small, because on this screen
   they are what the header is about, the same role the money takes everywhere
   else. `.nmo-num` inside still carries the display face and tabular figures.
   Was a bare 13px. */
.nmo-coin-facts {
  font: 500 15px/20px var(--font-ui); letter-spacing: var(--m3-title-small-tracking);
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
}
.nmo-coin-mc { font-variant-numeric: tabular-nums; }
/* ═══ THE WINDOW THE PERCENT COVERS ═══
   It sits inside the coloured percent, so without this it would be painted in the up or
   down colour and read as part of the money. It is not money: it is the name of the window,
   the same word the chip below it carries. Small and faint, so the number stays the thing
   you look at. */
.nmo-chg-row { display: flex; align-items: baseline; gap: 4px; justify-content: flex-end; }
.nmo-chg-range {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  color: var(--text-faint);
}
/* 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; }
/* ═══ WHY THE PRICE IS OLD, ON A ROW OF ITS OWN (owner, 9 Sep 2026) ═══
   It used to live in `.nmo-coin-facts`, the header grid's `auto` track, and it is a
   whole sentence — "Price delayed — the price service has nothing current for this
   coin · last known 16h ago". An `auto` track is as wide as its widest child, the
   names column beside it is `minmax(0, 1fr)` and gives way first, and the sentence was
   drawn on top of the address line and its copy icon.

   It is now a sibling of the header, directly under it, with the whole row to itself,
   so there is nothing for it to sit on top of. Body-small rather than label-small: it
   no longer labels the number above it, it is prose about the coin, which is the role
   the holders line below it already takes and the size that line already uses. It
   reads from the left like every other sentence on the sheet, and it wraps. */
.nmo-coin-delayed {
  font: 400 12px/16px var(--font-ui); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint); text-align: left; padding: 6px 0 0;
}
/* Who holds it — body-small: facts about the coin, read rather than scanned. */
.nmo-coin-holders {
  font: 400 12px/16px var(--font-ui); letter-spacing: var(--m3-body-small-tracking);
  display: flex; flex-wrap: wrap; gap: 8px; color: var(--text-faint); padding: 14px 0 2px;
}
.nmo-coin-you { color: var(--text-dim); }
.nmo-coin-chart { position: relative; padding: 6px 0 2px; }
/* `.nmo-coin-chart-h` was here — a row holding only the "Chart settings" link, above the
   drawing. The link became an icon in the time-frame row on 6 Sep 2026 and the row went with
   it: a box holding nothing is still a box, and it was pushing the drawing down. */
/* 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. */
/* ═══ AS TALL AS HIS (owner, 1 Sep 2026: "make chart taller") ══════════════
   Measured off 299.png, 602 px wide. His screen is 440 css — his recording is
   1320 px for the same screen at 3x — so F = 440/602 = 0.7309.

   The plot's height comes from his own axis rather than from a bounding box,
   because the box's edges are a judgement and the rungs are not: his eight rungs
   run $33.60M down to $33.25M with centres at y=317.5 and y=783.5, which is
   seven gaps of 66.6 ref px = 48.7 css each. Seven gaps is 340.6 css between the
   outer rungs, and one further gap of air (half above the top rung, half below
   the bottom) makes the plot 389.3 css. friday, working from the ladder's ends
   in the video instead, got 394. The two agree within 1.2%, so 390 it is.

   WHAT THIS COSTS, measured on the live sheet at 390 x 844: the chart block goes
   from 263.3 to 488.3 and 153 px of the sheet drops below the fold — the money
   card, the transactions row and the call to action. The sheet body gains a
   scroll it did not have (670 -> 823). Nothing above the chart moves.
   The trades column beside it needs no change: `.nmo-coin-trades` is absolute
   with BOTH top and bottom set, so it stretches with the row on its own.

   THIS HEIGHT IS WRITTEN TWICE AND THE TWO COPIES MUST AGREE. The other one is
   `const h = 248` in `plugins/nmo/web/nmo.ts` (in `coinPlot`), which is the SVG's
   own coordinate height. Change one alone and the browser letterboxes the
   drawing — it centres the viewBox and paints grey bands down the sides, which
   the owner photographed once and which `nmo-ui-coin-test`'s "never letterboxed"
   check catches. monday left them at 165 rather than move one; they moved
   together on 2 Sep 2026, in one change.

   248 because the owner asked for the chart 1.5 times taller on 2 Sep 2026, and
   1.5 x 165 = 247.5. It replaces the pump.fun reference crop, which measured
   164.6. IT IS STILL TWO COPIES — see the longer note in `nmo.ts` for why, and
   for the single-source fix that is available and was not taken under time
   pressure. */
.nmo-coin-line { width: 100%; height: 248px; display: block; }
/* #483: the two position screens draw the design's 180px chart (v87 #apc6, #apc8), so the whole screen fits a
   390x844 phone with Buy and Sell right under "Ask Genie". Its other copy is POSITION_CHART_H in nmo.ts. */
.nmo-cs-sheet .nmo-coin-line { height: 180px; }
.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 — 12px is a floor, not a preference.
   An axis label is a number a person reads off the screen to find out what their
   money is doing, so its size is a readability floor and floors do not move to
   make something else fit. This was 10px for a while because the reference app
   measured 10px there and I copied the number across with the rest of its sizes.
   That was the wrong call twice over: it put a number below the floor on the
   screen, and it turned a suite red (`nmo-ui-coin-test.ts`, "the axis labels are
   readable on a phone", which reads this very declaration), which cost another
   pair of hands the ability to prove anything — a check that is already red
   proves nothing about the change under it.
   If the reference genuinely is smaller, that is a decision the owner takes
   deliberately, with the trade in front of him. It is not one a stylesheet takes
   quietly while copying a design. The rest of the copied sizes stand. */
.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; }
/* ═══ THE PRICE PILL SITS ON ITS LINE, NOT 2px UNDER IT ═════════════════════
   (owner, 1 Sep 2026, with a crop: the "current" pill is not centre-aligned
   with its value line.) Measured on the painted chart: the dashed line and the
   end dot both centre on 302.46; the pill box painted 294.46 -> 314.46, centre
   304.46. Exactly 2px low. Its own TEXT was already right — centred on 301.96.

   THE CAUSE IS A NUMBER IN nmo.ts:5927, NOT HERE. The box is placed at
   `lastY - 8` with `height: 20`, so its centre lands at `lastY + 2`; it should
   be `lastY - 10`. That file is tuesday's, so this corrects the paint by the
   measured 2px from the stylesheet, which is mine.

   IF TUESDAY FIXES THE SOURCE, THIS LINE MUST COME OUT IN THE SAME COMMIT —
   with both, the pill would sit 2px high instead. */
.nmo-chart-pill rect { fill: var(--nmo-chart-green); transform: translateY(-2px); }
.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 ring that breathes once a
   second and is READABLE THE WHOLE SECOND (owner, 1 Sep 2026).

   A STROKE, NOT A FILL. A filled disc behind a filled dot is mostly hidden by
   the dot; an outline is a ring a person can actually see, and it reads at a
   lower opacity than a wash needs.

   IT NEVER REACHES ZERO. The old one faded out at 70% and stayed out — a third
   of every second with nothing there at all, which is why he said it was not
   working. This one moves between two visible states, so there is no moment
   when the line's end is bare.

   ONE SECOND, CONSTANT, unchanged — that is the behaviour he asked to keep. No
   local-high splash here; that is a separate thing and not yet approved. */
.nmo-chart-dot { fill: var(--nmo-chart-green); }
.nmo-chart-pulse {
  fill: none;
  stroke: var(--nmo-chart-halo);
  stroke-width: 1.5;
  animation: nmo-chart-pulse 1s ease-in-out infinite;
  transform-box: fill-box; transform-origin: center;
}
@keyframes nmo-chart-pulse {
  0% { opacity: 0.85; transform: scale(1); }
  50% { opacity: 0.45; transform: scale(1.3); }
  100% { opacity: 0.85; transform: scale(1); }
}
@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 NEW LOCAL HIGH: THE SPLASH ON THE END-OF-LINE CIRCLE ════════════════
   (owner: "chart should flash, shake, and the current price circle should have
   an additional splash animation when the price breaks a local high".)

   THE FLASH AND THE SHAKE ALREADY EXIST above — `nmo-chart-breakout` and
   `nmo-screen-rise-shake`. The splash is the third thing and the only one that
   was missing. What was ALSO missing is the word LOCAL HIGH: all of it is gated
   in nmo.ts on `lastV > lastDrawnValue`, which is ANY upward tick. That is very
   close to the flash-on-every-trade the owner had removed. THE FIX IS THE GATE
   IN nmo.ts, NOT A CLASS NAME HERE: `.nmo-chart-rise` keeps its name, because
   two checks in `nmo-ui-coin-test` pin that exact selector text and a rename
   here turns them red for everyone sharing the tree. Renaming it to
   `-high` is worth doing, but it has to land WITH those two checks in one
   commit, not before them.

   THE NUMBERS COME FROM HIS RECORDING, not from taste. Measured on
   ScreenRecording_08-31-2026 23-52-45_1.MP4 (1320x2868, 60fps, a 440-wide
   screen at 3x), 160x160 px tracked on the end-of-line circle:

       his ring's cycle          1500 ms   (autocorrelation r = 0.69)
       his ring's outer radius   grows 1.6x within one cycle
       our constant pulse        1000 ms, scale 1 -> 1.3 (the owner set both)

   So the splash grows by HIS measured 1.6 on top of our own pulse's 1.3 peak:
   1.3 x 1.6 = 2.1. It runs 520 ms because that is the flash it fires beside,
   already in this file and already tuned against the same recording — a splash
   outlasting its own flash would read as two events.

   IT MOVES NOTHING. It is a `<circle>` of its own with `transform-box:
   fill-box`, so scale and opacity are all it touches: no layout, no geometry,
   and no transform on any ancestor that the line or a trader mark is
   positioned inside. */
@keyframes nmo-chart-splash {
  0% { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0; transform: scale(2.1); }
}
/* At rest it is not drawn at all, so a chart that never breaks a high carries
   no extra ink — and it cannot be mistaken for a second resting ring. */
.nmo-chart-splash { fill: none; stroke: var(--nmo-chart-halo); stroke-width: 1.5; opacity: 0;
  transform-box: fill-box; transform-origin: center; }
.nmo-chart-rise .nmo-chart-splash { animation: nmo-chart-splash 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 splash is hidden outright, not merely stopped: an animation set to
     `none` would leave the circle painted at its first keyframe, which is a
     second ring sitting permanently on the line's end. */
  .nmo-chart-splash { 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` was styled here with `.nmo-marks-ingap`, and had two rules of its own
   below. The "before this chart" badge is deleted from the screen on 6 Sep 2026 — the owner
   ruled that a trade beyond the chart is not shown at all — so nothing can carry that class
   and the rules can never match. The in-gap badge is untouched and keeps every rule it had:
   it is a different fact and he has not asked for it.

   The row's height variable no longer needs a two-badge case for the same reason: one badge
   is the most that can be drawn. */
.nmo-marks-ingap { margin: 0; font-size: 11.5px; line-height: 14px; }
.nmo-coin-chart-row:has(.nmo-marks-ingap) { --nmo-marks-h: 14px; }
/* ═══ EVERYONE'S ORDERS BESIDE THE PRICE LINE (#429, owner 16 Sep 2026, designs v48–v59) ═══
   Drawn on the trader's coin position sheet only (nmo.ts `coinChart`); every other chart has none, which
   the owner ruled on 4 and 8 Sep. A 92px strip over the left of the plot, IN FRONT of the price line as the
   design draws it (owner, 16 Sep 22:25: "follow same ui"; this replaces the 2 Sep "behind the line" rule for
   this strip), with no label (the 21:05 label is gone). The lane fades its top 38% out, which is where the
   oldest rows leave.
   The rows stand in one column pinned to the lane's bottom, 3px apart (`TRADE_ROW_GAP` in nmo.ts); a new
   order is added at the bottom and the column slides up over 0.6s. Nothing moves between orders.
   The bottom stops above the time-axis words, which the plot draws in its own bottom band. */
.nmo-coin-trades {
  position: absolute; left: 0; top: var(--nmo-marks-h);
  bottom: 30px; width: 92px;
  overflow: hidden; pointer-events: none;
  z-index: 1;
  display: flex; flex-direction: column;
}
/* ═══ THE COLUMN FLOATS OVER THE PLOT; THE PLOT RUNS FROM THE LEFT BORDER (#483, owner 17 Sep 21:11) ═══
   "The chart should be all the way from the left border, as per v86 design." In #apc6 and #apc8 the dot ground
   and the line run the card's full width, and the order figures float over the left 92px of it. So nothing
   reserves room for the column: it is laid over the plot, and it reads over the line because its figures are
   small and tight (11/16 in the code face, as the design sets them), not because of a plate behind them. This
   replaces friday's 19:05 "its own strip" ruling, which #483's second round built; he withdrew it. On a position
   screen the chart has no time words under it, so the column runs down to the design's 22px from the foot. */
.nmo-cs-sheet .nmo-coin-trades { bottom: 22px; }
.nmo-trade-lane {
  position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 38%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 38%, #000 100%);
}
.nmo-trade-col {
  position: absolute; left: 6px; bottom: 0;
  display: flex; flex-direction: column; gap: 3px;
  transition: transform .6s cubic-bezier(.2, .8, .2, 1);
  will-change: transform;
}
.nmo-trade {
  font-size: 11px; line-height: 16px; font-weight: 500; font-variant-numeric: tabular-nums;
  white-space: nowrap; display: flex; gap: 5px; align-items: center;
  opacity: .9;
}
/* A newly arrived order rises into place. The dark theme flashes it white instead (below, under #429). */
.nmo-trade-fresh { animation: nmo-trade-in .35s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes nmo-trade-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: .9; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .nmo-trade-col { transition: none; }
  .nmo-trade-fresh { animation: none; }
}
.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.` in front: the Buy and Sell buttons of the trade bar carry these same two class names. */
.nmo-trade.nmo-trade-buy { color: var(--nmo-up); }
.nmo-trade.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. */
/* body-small: a sentence saying there is nothing to list. */
.nmo-trades-none {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  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 `.nmo-root, .nmo-sheet` token block at the top of this
     file, which sets `padding: 0 0 12px`. (Named, not numbered: a line number in
     a comment is a fact about the file that every edit above it can falsify
     silently.) 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;
  }
}
/* ═══ THE COIN'S OWN SCREEN IS A SCREEN, NOT A SHEET ═══════════════════════
   The owner, 1 Sep 2026: "the screen should become full height of the screen,
   instead of part of the slide up modale." Built by wednesday, whose
   measurements these are; reviewed and landed here because this is my file.

   MEASURED OFF HIS REFERENCE, 1320x2868 at 3x = 440x956 css:
       0    – 62      the status bar / safe area
       62   – 109.5   the bar, 48px tall, a 1px rule at its foot
       110  – 866     the coin, the chart, the chips, the tabs, the posts
       867  – 919.7   one action button, 52.7px, floating over the list
       919.7– 956     36px under it, the home bar

   `nmo.ts` puts this class on and takes the grab handle and the scrim OFF the
   document when the coin screen is the coin's own — removed, not hidden, so a
   promise the screen cannot keep is not merely invisible.

   Nothing here touches the position screen: every rule for that is on the bare
   `.nmo-sheet` above and is unchanged. */
.nmo-sheet-full {
  left: 0; right: 0; top: 0;
  width: auto; max-width: none;
  transform: none;
  /* `vh` first for anything that does not know `dvh`; `dvh` then wins where it
     does, so the screen ends at the bottom of what is actually visible. */
  height: 100vh;
  height: 100dvh;
  border-radius: 0;
  box-shadow: none;
  /* ═══ AND THE ANIMATION HAS TO CHANGE WITH IT ═══
     NOT IN THE DRAFT, AND WITHOUT IT THIS SCREEN IS BROKEN ABOVE 640px.
     `.nmo-sheet` runs `nmo-sheet-up-wide` with `fill-mode: both`, and that
     keyframe ENDS at `transform: translateX(-50%)`. A held animation value beats
     a normal declaration, so the `transform: none` three lines up loses and a
     full-width screen sits half its own width off to the left. The phone query
     below 640px happens to swap in `nmo-sheet-up`, which ends at `none`, which
     is why a 390px check never sees it. Naming the animation here fixes it at
     every width instead of relying on a media query to hide it. */
  animation-name: nmo-sheet-up;
}
/* Belt as well as braces: `nmo.ts` takes the handle out of the document, and if
   a future edit ever only hides it, it must still not take up a row. */
.nmo-sheet-full .nmo-sheet-grab { display: none; }

/* ═══ AND THE BAR THAT REPLACES THE AGENT'S ════════════════════════════════
   Back arrow, the coin's name, then the actions hard against the right edge.

   THE WASH FOLLOWS THE BAR IT BELONGS TO. On the sheet it is painted on the
   grab handle; this screen has neither a `.nmo-coin-top` nor a handle, so the
   same wash goes here. Written as `--nmo-top-wash` so the two cannot drift: it
   was the same 16% literal in two rules, which is how a number goes stale.

   Same shape as `.nmo-coin-top`: it pulls out to the sheet's own edges with a
   negative inline margin and pays the padding back, so the wash reaches both
   sides rather than stopping inside the list's gutter. */
.nmo-coin-back {
  display: flex; align-items: center; gap: 8px;
  margin-inline: calc(-1 * var(--nmo-sheet-pad-x));
  padding: 12px var(--nmo-sheet-pad-x);
  padding-top: calc(12px + env(safe-area-inset-top, 0px));
  min-height: 53px;
  background: var(--nmo-top-wash);
}
/* The draft had a bare 17px, which is not on the type scale this product uses.
   `title-medium` is the role for a bar's title and is 16. */
.nmo-coin-back .nmo-back-name {
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The draft also carried an EMPTY rule for `.nmo-money-unread`. It is left out:
   the check on that name is a DOM query, not a stylesheet read, so the rule was
   needed by nothing, and an empty rule in a stylesheet reads as a contract that
   is not one. It inherits `.nmo-money`'s shape, which is what the draft's own
   comment says it wants. (`.nmo-money-unasked` stood beside it until 9 Sep 2026,
   for a card that said we had not asked what an unfollowed trader holds. We ask
   about everybody now, so the class is gone from the screen and from here.) */

.nmo-sheet-grab { flex: none; padding: 8px 0 4px; display: flex; justify-content: center; cursor: grab; }
/* ═══ THE WASH IS ONE SURFACE, AND IT STARTS AT THE SHEET'S EDGE ═══════════
   (owner, 1 Sep 2026, comparing ours with his: "theres color gap here".)

   I measured his flat first 17px, called it the grabber area rather than a
   designed stop, and left it out. The cause was right and the conclusion was
   wrong: it is flat there BECAUSE HIS WASH COVERS THE GRABBER TOO. Leaving it
   out is what put an unwashed strip between the sheet's top edge and the bar,
   and that strip is the gap he is pointing at.

   HIS PIXELS, at 1320 wide (a 440-wide screen at 3x), sampled down an empty
   column and at both edges:

       row below the sheet's rounded top edge   rgb(38,42,43)
       the flat run, 17.7 css px               rgb(38,42,43)
       around the grab handle                  rgb(38,42,43), the pill itself 24,24,27
       then it begins to fall                  37,41,42 -> 15,15,18
       the bar's foot                          a 1px rule, then the page

   No step anywhere between them, and the same colour at x=30 and x=1290 as in
   the middle — so it fills the rounded corners rather than stopping short.

   OURS IS THE SAME SHAPE, SPLIT OVER THE TWO ELEMENTS THAT OWN IT. The grabber
   takes the flat top; the bar below it starts at that identical colour and
   falls. They meet at 16% of the text colour with a measured 0px between them,
   so there is no seam to point at, and neither element needs to know the
   other's height — the wash ends at the bar's foot because the bar is what
   paints its own end.

   SCOPED with `:has()` to the sheet that actually carries a coin's top bar. The
   grabber belongs to every sheet in the product, and only this one was asked
   for a wash. */
.nmo-sheet:has(.nmo-coin-top) .nmo-sheet-grab { background: var(--nmo-top-wash); }
.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 var(--nmo-sheet-pad-x) 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; }
/* ═══ BUY FILLED VIOLET, SELL OUTLINED — NO GREEN AND NO RED (#478, design v86 §09) ═══
   The owner, 17 Sep: the coin screens had "green and red buttons", and that is the one thing on
   them that read as a different product. Green and red belong to money that has moved — a profit,
   a day's change, a board — never to a control. So the two sides take the interface's own violet:
   the buy is the filled one because it is the thing a person came here to do, and the sell is the
   same shape outlined. The colour no longer says which side; the word does. */
.nmo-trade-bar .nmo-trade-buy {
  background: var(--nmo-violet, var(--accent)); color: var(--bg); border: 0; font-weight: 700;
}
.nmo-trade-bar .nmo-trade-sell {
  background: none; color: var(--nmo-violet, var(--accent));
  border: 1px solid var(--nmo-violet, var(--accent)); 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)); }
/* ═══ THE BUY/SELL BAR ═══════════════════════════════════════════════════
   The word on a button is a label — that is exactly what M3's label roles are
   for — and label-large is the role for a button's text. It was 14px/700 on the
   confirm and 13px on the cancel, two numbers for one kind of thing.
   The receipt and the refusal under them are sentences, so body-small. */
/* THE BUTTON THAT DOES IT IS VIOLET TOO (#478). It was green — the last green control on
   these screens after Buy and Sell changed, and the biggest one: "Buy $50 of UBI" filling
   the foot in the colour this product uses for money that has gone UP. The word on it says
   what it does; the colour does not need to say it again, and green here read as a verdict
   on the trade somebody was about to make. */
.nmo-trade-confirm {
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
  background: var(--nmo-violet, var(--accent)); color: var(--bg); border: 0;
  /* pump.fun: 13 ref = 8.5 — a rounded rectangle, not a pill. 61 ref = 39.8 tall. */
  border-radius: 8px; min-height: 40px; padding: 0 14px; cursor: pointer;
}
.nmo-trade-cancel {
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
  background: none; color: var(--text-faint); border: 0; cursor: pointer;
}
.nmo-trade-receipt {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-dim);
}
.nmo-trade-refusal {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  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. */
/* label-small: it names how long we have watched, over the list below it. */
.nmo-coin-since {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  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); }
/* ═══ A TRADE ON THE LINE: A FILLED DISC WITH THE SIGN CUT OUT OF IT ═══════
   (owner's reference 283.png, 600px wide.)

   MEASURED, not chosen. That picture carries the same money card as 261.png and
   its footer is 553px wide in both, so both are at one scale and one picture-px
   is 0.647 screen-px.

       his disc            24px across      -> 15.5 css
       his sign's stroke    4px thick       ->  2.6 css
       his sign's length   12px, half the disc ->  7.8 css
       his buy fill        rgb(0,194,73)   #00C249
       his sell fill       rgb(255,80,34)  #FF5022

   THE SIGN IS A HOLE, NOT A COLOUR. Sampled through the middle of every mark,
   the inside of the bar reads rgb(6,5,17) — the plot's own ground — so the sign
   is the background showing through a filled disc, not a stroke painted on top.

   WHERE IT SITS: the disc's BOTTOM EDGE rests on the line, so its centre is one
   radius above the line's y at that time. Checked on all three marks in his
   picture and it holds within a pixel — buy 731 against a line at 730, sells
   641.5/645.5 against 642/646. It is not centred on the line, which is what we
   do today.

   OUR COLOURS ARE THE RIGHT HUE AND THE WRONG WEIGHT, so this uses our own
   tokens and the marks will read paler than his until someone decides otherwise:

       buy   his #00C249 vs `--nmo-up` #6dd58c    hue -4.7deg, sat -44.7, light +25.1
       sell  his #FF5022 vs `--nmo-down` #f28b82  hue -7.7deg, sat -18.8, light +16.3

   The hues are within 8 degrees, so his palette and ours agree about WHICH
   colours these are. Ours are far lighter and far less saturated. Matching him
   exactly would mean two new colours in the product, which is the owner's call
   and not one a stylesheet takes quietly. */
.nmo-own-trade-mark {
  fill: none;
  /* His 4 picture-px is 2.59; 2.5 was already here and is within a tenth. */
  stroke-width: 2.6;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}
/* The sign is the ground, so it reads as a hole in the disc. `--bg` is what
   `.nmo-mark` already uses to cut its own marks out of the line beneath them. */
.nmo-own-trade-mark-buy { stroke: var(--bg); }
.nmo-own-trade-mark-sell { stroke: var(--bg); }
.nmo-own-trade-disc { stroke: none; }
.nmo-own-trade-mark-buy .nmo-own-trade-disc { fill: var(--nmo-up); }
.nmo-own-trade-mark-sell .nmo-own-trade-disc { fill: var(--nmo-down); }

/* Reduced motion is not involved — a mark is a shape, it never animates. */
/* #516: the average-entry line could not be seen. Since c93c90a80 (2 Sep) removed the live-trade mark rules
   and left one body behind without its selector, the browser dropped THIS rule with it, and an SVG line with
   no stroke paints nothing. That leftover is gone; the line is the full ink now, dashed, and "his entry" /
   "your entry" sits over it with a ring of the sheet's colour so the words read over the dots and the line
   (design v86 section 09: 10px, the code face). */
.nmo-avgline { stroke: var(--text); stroke-opacity: .8; stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.nmo-avglabel {
  font: 500 10px var(--font-mono); letter-spacing: 0; fill: var(--text); fill-opacity: .85;
  stroke: var(--bg, #000); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; pointer-events: none;
}
.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 was an icon button on the action row; it is `.nmo-mirror-link` now (#447). */
.nmo-copy-note { color: var(--text-faint); }

/* ── a post on the coin's page (#478, design v86 #apc10) ──────────────────
   The same post the feed draws. Two things are different and both are about
   this screen: the quiet row ends in a violet "Mirror $50" instead of Copy,
   and a Follow chip sits beside the name at the size of a chip rather than
   the size of a button. */
.nmo-coin-post .nmo-acts { gap: 12px; }
.nmo-act-mirror {
  margin-left: auto;
  color: var(--nmo-violet, var(--accent));
  font-weight: 600;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: 11px;
}
.nmo-follow-btn.nmo-follow-mini {
  margin-left: 6px;
  padding: 2px 10px;
  font-size: 11px;
  min-height: 0;
  height: auto;
  border-radius: 999px;
}
.nmo-coin-feed-none { color: var(--text-faint); padding: 14px 2px; }

/* ── 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.
 */
/* ═══ THE CHECKS: THE SAME SIZE AS THE WORDS, IN A QUIETER VOICE ════════════
   Read off the served page: the post the owner kept pointing at has NO
   `.nmo-thesis` at all — its whole body is five of these, and they were 12.5px
   against a post's 16px. Both are `post.risk[]` sentences appended straight to
   the post, so they are its body, not a footnote under a card.

   SAME SIZE, which is what he asked for twice: body-medium, 14px, exactly the
   role the genie's own sentence takes.

   ONE STEP QUIETER, which is the part he has not been given yet. "Style" was
   one of his three words. A measurement is not somebody speaking, and the
   difference now lives in the colour rather than in the size — so the two read
   as two kinds of text without either being bigger.

   `--text-dim`, NOT `--text-faint`, and that was decided by looking at it: a
   post whose entire body is checks — which is exactly the post he keeps
   photographing — came out grey from top to bottom at faint, and a whole post in
   small-print grey is not a hierarchy, it is a post that looks switched off. Dim
   sits between the sentence's colour and the small print: clearly the post's own
   text, clearly a step below the speaking. If he wants them literally identical,
   delete the colour line and nothing else changes.

   NEVER A WARNING COLOUR. The door let this copy through, so drawing the fact
   like an error would report a decision nobody made. That has been the rule
   since this line existed and it is still checked.

   THE SPACING IS THE "LINE BREAK" HALF, and the numbers below are what it
   PAINTS, not what it declares. `.nmo-post` is a grid, so sibling margins do not
   collapse — they add. Measured at 390: the break above the first check is 20px
   (the reply line's own 6px plus this 14px) or 26px under a sentence, and the
   gap between checks is 2px. What matters is the ratio, and it is ten to one:
   one clear break where the speaking stops and the measuring starts, then a
   tight list. The 4px padding each line used to carry is gone — it made every
   gap 4px larger than the number written beside it. */
.nmo-risk {
  font: 400 12.5px/18px var(--font-ui); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text-dim);
  margin-top: 14px;
}
.nmo-risk + .nmo-risk { margin-top: 2px; }
/* The card gets its own air back: the words used to carry it in their bottom
   margin, and a post whose body is checks lines has no words. */
.nmo-risk + .nmo-card { margin-top: 12px; }


/* ═══ 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); }

/* ---------------------------------------------------------------------------
   The money card, under the chart on an agent's position screen.

   Every size here was read off the owner's picture (261.png, 590px wide) rather
   than chosen. Two things fix the scale. The card in that picture runs
   x=15..567 with its text at x=35..547 — 20px of padding on BOTH sides, which
   is what says the crop is a whole screen rather than an arbitrary cut. And
   553 picture-px of card is our 358px content column, so one picture-px is
   0.650 screen-px.

   Ink height became a font size by CALIBRATION, not by assuming a cap ratio: the
   same digit runs were painted in our own face at every size from 10 to 28 and
   photographed by the same code that read his picture, so the answer carries our
   antialiasing and our face's proportions instead of a textbook number. The
   ladder is scratchpad/cal.html.

       thing        his ink   x0.650   the size that paints it   role taken
       big money     22px     14.3     18px                      title-medium 16
       percent       18px     11.7     15px                      title-small  14
       a value       14px      9.1     11px                      label-small  11
       a label       16px     10.4     11px                      label-small  11
       Share         15px      9.8     13px                      label-large  14

   THE CARD IS FLATTER THAN IT LOOKS. His big money is only 1.2x his percent,
   not the 1.5x it reads as — it looks bigger because it is longer and leftmost,
   not because it is much taller. An earlier draft of this block had it at 24px
   on that misreading; the ladder is what caught it.

   Three of the five do not land on a role, because the scale is closed and has
   nothing at 18, 15 or 13. Each takes the nearest step and paints off his
   picture by: money 2px small, percent 1px small, Share 1px large. The two that
   land exactly are the label and the value — which share a role, and are told
   apart by colour and by the value keeping the display face, exactly as his
   picture tells them apart.
   --------------------------------------------------------------------------- */
/* ═══ IT HAS TO READ AS A CARD (owner, 1 Sep 2026) ═════════════════════════
   "redesign the position card, so that it stands out and looks material 3."

   THE BUG WAS MINE AND IT WAS ONE TOKEN. `--nmo-card-bg` is `--bg-elev-1`, and
   `.nmo-sheet` paints `--bg-elev-1` as well — the same rgb(35,36,37). The card's
   surface was therefore EXACTLY its own background, so the only thing separating
   it from the page was the grey Share strip at its foot. That is what he is
   looking at in 285.png: text on the page with a bar under it.

   I took `--nmo-card-bg` from the reference, where the card body genuinely is
   its page colour — I recorded that at the time and reused the token anyway.
   On his near-black ground that reads; on our lighter sheet it disappears.

   `--bg-elev-2` IS OUR ELEVATED SURFACE TOKEN, the next step above the sheet in
   both themes, so the separation comes from the scale rather than a colour typed
   here. The shadow is Material's level-1 pair, which is what does the work in
   the light theme where the surface step is smaller.

   PADDING IS 16, NOT THE 14 I MEASURED. His picture says 13.0 and the card token
   says 14; the owner has asked for Material here, and Material's card padding is
   16. That is a deliberate departure from the reference, not a drifted number. */
.nmo-money {
  background: var(--bg-elev-2);
  /* 16px is the shape scale's `large`, unchanged — his corner measured 14.7. */
  border-radius: 16px;
  padding: 16px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 3px 1px rgba(0, 0, 0, 0.15);
}
/* Row one: the money left, the percent right, sharing one baseline. */
.nmo-money-head { display: flex; align-items: baseline; gap: 12px; }
.nmo-money-total {
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
  /* The `font:` shorthand resets font-variant, so the face and the tabular
     figures have to be restated after it or the digits stop lining up. */
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
}
.nmo-money-pct {
  margin-left: auto;
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
}
/* Green up and red down are the plugin's own `.nmo-up` / `.nmo-down`, put on
   these two elements by the markup. Nothing new is defined here. */

/* Row two: one label-and-value pair flush left, one flush right, on one line.
   His picture leaves 25 picture-px between a label and its own value, which is
   16px, and 51 between the two rows' ink, which is 33. */
/* ═══ THE DOTTED RULE BETWEEN THE TWO ROWS ══════════════════════════════════
   I MISSED THIS WHEN I BUILT THE CARD. Both his pictures have it and I only
   looked for a solid line. In 261.png at y=84-86 there are 94 short runs with
   lifted pixels at x 42-44, 50-52, 58-60, 66-68 — a 3px dash on an 8px pitch.

   cc reads the same rule in 283.png as "5-6px dash / 2-3px gap". That is the
   dash and the gap the wrong way round: at y=943 the LIFTED runs are 3px wide
   (49-51) and the ground runs between them are 5-6px (43-48, 52-56). Same 8px
   pitch, so the pitch is agreed; it is a sparse dotted rule, not a nearly solid
   one. 8 picture-px is 5.2, and a 3px dash is 1.95.

   It sits INSIDE the padded column, not edge to edge like the Share footer. */
.nmo-money-rule {
  height: 1px; margin: 0;
  background-image: radial-gradient(circle, var(--text-faint) 0.5px, transparent 0.5px);
  background-size: 5.2px 1px; background-repeat: repeat-x; opacity: 0.5;
}
/* 9 and 10 are what PAINT cc's 15.9 from the headline's ink to the rule and
   13.3 from the rule to the detail ink, once both line boxes are accounted
   for. They are not cc's numbers copied across. */
.nmo-money-head + .nmo-money-rule { margin-top: 9px; }
.nmo-money-rule + .nmo-money-legs { margin-top: 10px; }
.nmo-money-legs {
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
  /* 23 and 9 are what PAINT his 33 and 12 between the ink, once the two rows'
     line boxes are accounted for. They are not his numbers copied across. */
  margin-top: 23px;
}
.nmo-money-leg { display: flex; align-items: baseline; gap: 16px; min-width: 0; }
.nmo-money-leg + .nmo-money-leg { margin-left: auto; }
.nmo-money-leg-k {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  color: var(--text-faint); flex: none;
}
.nmo-money-leg-v {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* An open position has no exit, so the right-hand pair is one sentence instead
   of a label and a number. It takes the same role and colour as a label and is
   a leg in its own right, so it lands on the same baseline at the same right
   edge — the row reads as finished rather than as a number that failed to
   arrive. Nothing is left where the second value would have been. */
.nmo-money-none {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  color: var(--text-faint);
}
/* WHAT WAS SOLD OUT BEFORE THE POSITION YOU ARE LOOKING AT. Its own line under
   row two, never inside it: it is a different piece of money from the position
   above and putting it in the same row would invite reading the two as one.

   It is quieter than row two — the label takes the faint colour every label on
   this card takes, and only the amount is coloured, because the amount is the
   fact. 12px is the same step row two takes from row one's rule, halved: this is
   an aside, and it should sit closer to the card it belongs to than the two rows
   sit to each other. */
.nmo-money-earlier {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin-top: 12px;
}
.nmo-money-earlier-k {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  color: var(--text-faint); flex: none;
}
.nmo-money-earlier-v {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
}

/* THE SHARE ROW IS AN ACTION, NOT A STRIP. It used to bleed to the card's edges
   with a negative margin, which is what his picture does — but a grey band
   welded to the foot of a card is the other half of why this did not read as a
   card, and the owner asked for Material. Material puts a card's actions inside
   its padding. So it keeps its tonal fill and gains a corner and a real touch
   height (40, Material's minimum; his strip measured 30) and sits in the padded
   column like everything else. DEPARTS FROM THE REFERENCE, deliberately. */
.nmo-money-share {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 16px 0 0; width: 100%; min-height: 40px; padding: 4px 12px;
  border: 0; border-radius: 8px;
  background: var(--nmo-card-bg-2); color: var(--text); cursor: pointer;
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
.nmo-money-share:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }


/* ═══ THE TRADER'S POSITION SCREEN ══════════════════════════════════════════
   Built from cc's measurements (scratchpad/reports/cc-trader-screen-measure.md)
   off 283.png. I did not re-measure the geometry; the font sizes are corrected
   and the correction is recorded in scratchpad/reports/monday-trader-screen-look.md.

   THE FOLLOW BLUE HAS MOVED to the block at the top of this file that both the
   app root and the sheet carry. It used to be declared here, on
   `.nmo-trader-screen` — A CLASS NOTHING IN THE CODEBASE EVER PUTS ON AN
   ELEMENT. So both Follow controls asked for a colour that was never declared
   anywhere, `background` and `color` fell away, and the buttons painted as
   transparent boxes with inherited grey text. That is the "Follow is plain
   text" the owner reported off the built sheet, and no amount of work in
   `agentProfile.ts` could have fixed it.
   The `Closed` badge's #151520 is NOT new: it is a +15 lift on his ground,
   which is what `--nmo-card-bg-2` already paints on ours. */

/* The band is full width and not inset — cc, "do not inset this band". 53.3
   tall, and the 16px sides are the sheet's own padding put back inside. */
/* His page's header: the picture, then his name and the line saying this is not
   his account, beside it rather than under it. The header had no rule at all
   while it drew two letters, so it stacked. */
.nmo-trader-head { display: flex; align-items: center; gap: 12px; }
.nmo-trader-head > .nmo-av { width: 44px; height: 44px; }
.nmo-trader-names { min-width: 0; }

.nmo-trader-top {
  display: flex; align-items: center; gap: 7px;
  margin-inline: calc(-1 * var(--nmo-sheet-pad-x));
  padding: 12px var(--nmo-sheet-pad-x); min-height: 53px;
}
/* 29px measured. `.nmo-av-sm` is already 30px, so if tuesday reaches for that
   instead the difference is one pixel and not worth a second rule. */
.nmo-trader-top .nmo-av, .nmo-trader-pic { width: 29px; height: 29px; flex: none; }
.nmo-trader-pic { border-radius: 50%; object-fit: cover; }
.nmo-trader-name {
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
  color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 69.6 x 26, right edge 16.25 from the screen — which is our own 16px padding,
   so `margin-left: auto` lands it there without a second number. cc gives no
   corner radius; 8px is mine and the one number here that is not measured. */
.nmo-trader-follow {
  margin-left: auto; flex: none;
  min-width: 70px; min-height: 26px; padding: 0 12px;
  border: 0; border-radius: 8px; cursor: pointer;
  background: var(--nmo-follow-bg); color: var(--nmo-follow-fg);
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
}
.nmo-trader-follow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The `Closed` badge under the symbol: 42.3 x 16.3, corner 3.9. */
.nmo-coin-closed {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 16px; padding: 0 6px; border-radius: 4px;
  background: var(--nmo-card-bg-2); color: var(--text-faint);
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
}

/* `4 transactions` on the left, what it cost on the right, one baseline.
   18.2 below the card, and the same 16px sides as everything else. */
.nmo-trader-tx {
  display: flex; align-items: baseline; gap: 12px;
  padding: 18px 0 0;
}
.nmo-trader-tx-count {
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  color: var(--text);
}
.nmo-trader-tx-spent {
  margin-left: auto; text-align: right;
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  color: var(--text-faint);
}


/* ═══ THE AGENT PROFILE SCREEN (owner, 1 Sep 2026) ══════════════════════════
   Built from 296.png and from what we can honestly fill —
   scratchpad/reports/monday-agent-profile-scope.md. Sizes are measured off that
   picture at F = 390/599 = 0.6511; the assumption behind that factor, and the
   3% it could be out by, are in the report.

   THIS SCREEN IS TWO SCREENS. Our genie has a paper ledger we wrote; a followed
   trader has only the swaps we happened to watch. Half of his picture cannot be
   filled for a trader at all. The rule here is NOT to leave those blank — a
   blank number reads as zero, and zero is a claim. Anything we cannot know is
   either absent with a sentence in its place (`.nmo-profile-absent`) or not
   built at all.

   NOT BUILT, AND DELIBERATELY: a `Perps` filter (this product has no perpetuals
   — every "perp" in the codebase is `perPocket` or `perPosition`), a second
   social handle (a Pump fact we do not hold), and a `Joined` date for a trader
   (we know when WE started watching, which is a different fact wearing the same
   word). There is no class for any of them, so none can be drawn by accident. */
.nmo-profile {
  /* THE FOLLOW BLUE IS NOT DECLARED HERE ANY MORE, and that was the whole bug.
     It was on this element — and the pinned Follow button is deliberately NOT
     inside this element, because a control stuck to the bottom of the scroller
     measures its bottom from the scroller's content edge. Custom properties
     only ever inherit DOWNWARDS, so the one element that needed the colour was
     the one element the colour could not reach: the button asked for a value
     nothing had declared, `background` and `color` fell away, and it painted as
     bare text on a transparent box. It is declared at the top of this file now,
     on the block both doors on to a trader carry. */
  padding: 0 var(--nmo-sheet-pad-x, 16px) 24px;
}
/* The scrolling body's action reserve is the only floor room on a profile with
   a dock. Keeping the profile's ordinary trailing padding as well left a second,
   misleading blank band after the final required card. */
.nmo-sheet:has(.nmo-profile-dock) .nmo-profile { padding-bottom: 0; }
/* The shared sheet body already supplies the trader's 16px mobile inset.
   Repeating it only on the v14 trader composition made it 32px narrower than
   the 390px artifact; our genie's existing profile keeps its established inset. */
.nmo-profile:has(.nmo-balance-hero-profile) { padding: 0 0 24px; }
/* Back on the left, the bell and the share on the right. */
.nmo-profile-top { display: flex; align-items: center; gap: 4px; padding: 4px 0 8px; }
.nmo-profile-top .nmo-sp { flex: 1 1 auto; }
/* The chevron is small, but its actual hit box follows the app's 48px icon-control floor. */
.nmo-profile-back {
  --icon-btn-size: 48px;
  flex: none; width: 48px; height: 48px;
}
.nmo-profile-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* ═══ THE TWO CONTROLS ON THE RIGHT ═════════════════════════════════════════
   48 SQUARE, and it is the button that is 48 — the drawing inside is 22. Every
   published minimum for a touch target is 44 or 48, and an icon-sized button is
   the commonest way to miss it; written here so neither control can drift under
   it while still looking right, because the drawing would not change.

   THE INERT STATE IS QUIET, NOT ABSENT. Both are unpressable today (there is
   nothing in this app to ask about one trader, and no address that reopens one),
   so they are drawn faint. They keep their focus ring: they are still in the tab
   order on purpose, so a keyboard reaches them and hears why they will not go. */
.nmo-profile-act {
  flex: none; width: 48px; height: 48px; border: 0; background: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; color: var(--text); cursor: pointer;
}
.nmo-profile-act[aria-disabled="true"] { color: var(--text-faint); cursor: default; }
.nmo-profile-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-profile-act-ico { width: 22px; height: 22px; }
.nmo-profile-act-ico path, .nmo-profile-act-ico line {
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ═══ THE VALUE CARD AT THE TOP OF A TRADER'S PAGE ══════════════════════════
   (owner's mockup, 8 Sep 2026, and his rule with it: no shiny colours and no
   borders — plain Material 3 on the tokens this app already owns.)

   SO THERE IS NOT ONE NEW COLOUR IN THIS BLOCK. The ground is the app's
   `--bg-elev-1` surface role; the line is the plugin's own up and down greens
   and reds; the faint words are `--text-faint`. No glow, no border, no ring.

   IT IS A FILLED CARD, WHICH IN MATERIAL 3 MEANS THE SHAPE CARRIES THE
   SEPARATION AND NOT A STROKE: the existing large corner role and a fill, with
   nothing drawn round the edge. A 1px outline here would be the first border on
   this screen. */
.nmo-tv-card {
  margin: 4px 0 0; padding: 16px; border-radius: var(--radius-lg);
  background: var(--bg-elev-1);
}
/* ═══ ONE HERO, TWO HEIGHTS ════════════════════════════════════════════════
   The profile and For You card share this component. The short form requests
   less surrounding copy, not another centred-number chart or another palette. */
.nmo-balance-hero {
  /* The identity sits on the outer container and the selected wallet history
     on the next tonal container.  These are app roles, not a local black mix:
     they remain distinct in both app themes. */
  display: flex; flex-direction: column;
  background: var(--bg-elev-1);
  border-radius: var(--radius-lg);
}
/* The trader identity belongs to the sheet's content inset. Only the selected
   balance history is a tonal surface below it, separated by the measured 12px. */
.nmo-balance-hero-profile {
  margin: 0;
  padding: 12px 0 0;
  background: transparent;
  border-radius: 0;
}
.nmo-balance-hero-profile .nmo-balance-hero-panel {
  margin-top: 12px;
  min-height: 320px;
  padding-bottom: 16px;
}
/* An unavailable selected history is a compact, honest answer.  The normal
   measured hero keeps its exact 320px plot geometry; this state has no plot to
   reserve and must not create an empty lower half before Verdict. */
.nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable {
  min-height: 0;
  padding-top: 14px;
  padding-bottom: 12px;
}
.nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable .nmo-tv-cannot { margin-top: 0; }
.nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable .nmo-balance-hero-controls { margin-top: 12px; }
.nmo-balance-hero-profile .nmo-balance-hero-controls {
  grid-template-columns: 196px minmax(0, 1fr);
  gap: 0;
  margin-top: 20px;
}
.nmo-balance-hero-panel {
  display: flex; flex-direction: column; min-width: 0;
  margin-top: 4px; padding: 8px 16px 6px; border-radius: var(--radius-md);
  background: var(--bg-elev-2);
}

.nmo-balance-hero-figures { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.nmo-balance-hero-figures .nmo-tv-value { margin-top: 10px; }
/* The plot is a deliberate row below the figure, never a background behind it.
   It starts at the balance figure's own left edge (#298) and its negative right
   margin reaches the hero's edge, leaving room at the lower edge for the dates. */
/* ═══ ONE PLOT HEIGHT FOR EVERY SURFACE THAT DRAWS THIS HERO ═══ (#334, owner 14 Sep 2026: "reduce the chart height to
   be same as trader profile"). His profile's 100px plot is the reference. The For You card reads this same number and
   never grows its own, so the two cannot drift apart again. */
.nmo-balance-hero { --nmo-hero-plot: 100px; }
.nmo-balance-hero .nmo-tv-chart {
  position: relative; height: 92px; flex: 0 0 92px; margin: 14px -16px 0 0;
}
/* The v14 profile plot is a measured 272px drawing, inset within its 356px
   balance surface. The short card continues to use the shared plot (see above). */
.nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart {
  width: 272px; height: 90px; flex: 0 0 90px;
  align-self: flex-start; margin: 50px 0 0 18px;
}
.nmo-balance-hero .nmo-tv-chart > .nmo-tv-line {
  /* Keep the final marker clear of the three hard-right value labels while the
     chart row itself still reaches the balance-panel edges. The line starts on this
     box's left edge (#298), so half its stroke paints outside it. */
  position: absolute; top: 0; right: auto; bottom: 16px; left: 0;
  width: calc(100% - 54px); height: calc(100% - 16px); z-index: 1; overflow: visible;
}
.nmo-balance-hero-guides {
  position: absolute; inset: 0 0 16px; display: flex; flex-direction: column; justify-content: space-between;
  pointer-events: none; z-index: 0;
}
.nmo-balance-hero-guide { position: relative; border-top: 1px solid var(--nmo-line); }
.nmo-balance-hero-guide-value {
  position: absolute; right: 0; top: -8px; padding-left: 4px; color: var(--text-faint);
  background: var(--bg-elev-2); font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  font-variant-numeric: tabular-nums;
}
.nmo-balance-hero-guide:last-child .nmo-balance-hero-guide-value { top: -12px; }
.nmo-balance-hero-dates {
  position: absolute; inset: auto 0 0; display: flex; justify-content: space-between; color: var(--text-faint);
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking); pointer-events: none; z-index: 2;
}
.nmo-balance-hero-details {
  margin-top: 2px; overflow: visible; text-overflow: clip; white-space: normal; overflow-wrap: anywhere;
  color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}
/* The accepted compact hierarchy spends its fixed space on identity, figures,
   plot, spans, source and copier context. Provider prose and chain controls
   remain available on the page below rather than burying Verdict and tabs. */
.nmo-balance-hero-profile .nmo-balance-hero-panel > .nmo-tv-thin { display: none; }
.nmo-balance-hero .nmo-tv-av { width: 40px; height: 40px; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-name { font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking); }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-cap { display: none; font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-balance-hero-profile .nmo-tv-value { font: var(--m3-headline-large); letter-spacing: var(--m3-headline-large-tracking); }
.nmo-balance-hero-actions { display: flex; align-items: center; gap: 4px; margin-left: auto; flex: none; }
.nmo-balance-hero-action {
  /* The tap box is Material's 48px minimum.  The painted circle stays compact
     inside it, so the identity line keeps the artifact's density. */
  position: relative; isolation: isolate; flex: none;
  display: inline-grid; place-items: center; width: 48px; height: 48px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: var(--text); cursor: pointer;
}
.nmo-balance-hero-action::before {
  content: ""; position: absolute; z-index: -1; width: 32px; height: 32px; border-radius: 50%;
  background: var(--bg-elev-3);
}
.nmo-balance-hero-action.is-genie { color: var(--accent); }
@media (hover: hover) {
  .nmo-balance-hero-action:not([aria-disabled="true"]):hover::before { background: var(--bg-elev-2); }
}
.nmo-balance-hero-action:not([aria-disabled="true"]):active::before { background: var(--nmo-card-bg-2); }
.nmo-balance-hero-action[aria-disabled="true"] { opacity: .65; cursor: default; }
.nmo-balance-hero-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-balance-hero-action-icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* HIS PICTURE FIRST, HIS NAME BESIDE IT (owner, 8 Sep 2026). The picture is the
   first child of the row, so it is the first thing in the block on the screen
   and the first thing a reader-aloud reaches. The two name lines take the rest
   of the row and the money below gets the card's whole width.

   Centred rather than top-aligned: the picture is 64px and the two lines
   together are about 50, so aligning at the top left the face hanging below the
   words. Everything under this row is a sibling in a column, so the picture
   still cannot overlap the drawing — that was the owner's earlier rule and it
   holds for the same reason as before. */
.nmo-tv-top { display: flex; align-items: center; gap: 8px; }
.nmo-tv-who { min-width: 0; flex: 1 1 auto; }

/* HIS NAME, ON ONE LINE. Title-large is the headline role — the same one the
   Follow question takes — and it is 22px against the subtitle's 14, so the two
   lines cannot be mistaken for each other. It is cut with an ellipsis only when
   the name genuinely will not fit the width left beside the picture; nothing
   here shortens a name that would have fitted. */
.nmo-tv-name {
  font: var(--m3-title-large); letter-spacing: var(--m3-title-large-tracking);
  color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* HIS HANDLE, UNDER THE NAME AND QUIETER. Body-medium and the faint grey: it is
   the supporting line, not a second heading. When we have no name for him the
   handle moves up to the line above and this one is NOT DRAWN — no empty row,
   and never the handle printed twice. */
.nmo-tv-cap {
  margin-top: 2px;
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The handle takes the size and colour of whichever line it is on. The plain
   `.nmo-profile-handle` rule below is for our genie's identity block, where the
   handle is a quiet line under a big name; here it is the line itself. */
.nmo-tv-name .nmo-profile-handle,
.nmo-tv-cap .nmo-profile-handle {
  font: inherit; letter-spacing: inherit; color: inherit; margin: 0;
}
/* `.nmo-tv-mark` REMOVED 8 Sep 2026, with the line it dressed: "Not @who — his
   public posts and trades, repeated here.", under his name. The owner removed
   that line from the card. A rule for an element nothing draws is a rule nobody
   can see is wrong, so it went with the element. */
/* `.nmo-tv-what` REMOVED 8 Sep 2026, with the caption it dressed: the small grey
   "In coins we watch" over the big figure. The owner removed it and nothing
   replaced it — the figure stands alone.
   AND THE GAP IT HELD OPEN MOVED DOWN, WHICH IS THE PART A DOM CHECK CANNOT SEE.
   The caption carried the 12px that separated the figure block from his name,
   and the figure only ever needed 2px because it sat right under the caption.
   Deleting the caption without moving that 12px would jam a 32px number against
   the name, and every check in the suite would still be green — the suites run
   without a cascade. The figure now carries the 12px itself. */
/* DISPLAY SMALL — the biggest thing on the page, and the only one at its size.
   It is the one text on this screen that stays at weight 400: at 36px the weight
   is doing nothing the size is not already doing, and a heavy 36 next to a 22
   name would read as two headlines arguing. The figures are all one width so the
   number does not jitter sideways when it ticks.
   THE 12px ABOVE IS THE CAPTION'S, INHERITED. #59 set this margin to 2px because
   the caption sat between the name and the figure and held the gap open; #56 then
   removed the caption, so the figure carries the gap itself. A 2px here would jam
   a 36px number against his name, and no check in the suite could see it. */
.nmo-tv-value {
  margin-top: 12px;
  font: var(--m3-display-small); letter-spacing: var(--m3-display-small-tracking);
  font-variant-numeric: tabular-nums; color: var(--text);
}
.nmo-tv-value.nmo-none { color: var(--text-faint); }
/* ═══ THE PROFIT LINE'S RULES ARE GONE WITH THE LINE ═══
   (owner via friday, 11 Sep 2026 16:55.)

   `.nmo-tv-change` and its parts — the money it moved, its percent, the span word —
   dressed the profit line under the balance. That line is on the record tab now, in
   its own words and its own classes, and no screen draws these. The typography rows
   that measured them went from `nmo-ui-geometry-test.ts` in the same change, which is
   the suite that caught them: it refuses a measurement naming a class nobody draws. */
.nmo-tv-thin {
  margin-top: 12px;
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
}
/* What stands where the big figure would have been when we could not value his
   coins. Body size, not figure size: it is a sentence and it must read as one,
   but it sits in the figure's place so the card keeps its shape and does not
   jump when a trader we can value is opened next. Full-strength text, because
   it is the card's actual answer and not a footnote to one. */
.nmo-tv-cannot {
  margin-top: 12px;
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text);
  max-width: 30ch;
}
.nmo-tv-stroke {
  fill: none; stroke: var(--nmo-up); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* A reconstructed point's incoming segment used to be drawn lighter (1.25px at 42%). #295 drew
   the balance hero as the design's one line, so no rule narrows the stroke by how a reading was
   come by; the hero keeps the word on the stroke's data-basis for a later mark. */
/* One real moment on a thin line. Drawn only while there are few enough of them
   to count, so a person can see that a week's line is three readings and not a
   continuous measurement. */
.nmo-tv-moment { fill: none; stroke: var(--nmo-up); stroke-width: 4; stroke-linecap: round; }
.nmo-tv-dot { fill: none; stroke: var(--nmo-up); stroke-width: 6; stroke-linecap: round; }
.nmo-tv-halo {
  fill: none; stroke: var(--nmo-up); stroke-width: 14; stroke-linecap: round; opacity: 0.22;
}
.nmo-tv-line.is-down .nmo-tv-stroke,
.nmo-tv-line.is-down .nmo-tv-moment,
.nmo-tv-line.is-down .nmo-tv-dot,
.nmo-tv-line.is-down .nmo-tv-halo { stroke: var(--nmo-down); }
/* ═══ PUT BACK BY THE FOLD, BECAUSE THE LINE IS STILL DRAWN ═══ (#218, 12 Sep 2026)

   #191's last commit — "retire the typography rows for the line the hero no
   longer draws" — took this rule out with the profit line's. One rule too many:
   the hero still appends `.nmo-tv-wash` (twice, in `valueLine` and in
   `serviceLineChart`), so what went was the fill of every RISING line, and an SVG
   shape with no fill declared paints SOLID BLACK. The two rules under it survived,
   which is why a falling line and a level one still looked right and nothing
   caught it: only a trader whose balance went up showed the black slab.

   PHOTOGRAPHED at 390 in #218's rehearsal of the fold, on @aurelius0121's rising
   line, before this went back. */
.nmo-tv-wash { stroke: none; fill: color-mix(in srgb, var(--nmo-up) 14%, transparent); }
.nmo-tv-line.is-down .nmo-tv-wash { fill: color-mix(in srgb, var(--nmo-down) 14%, transparent); }
/* ═══ AND A DRAWING THAT ENDED WHERE IT STARTED IS NEITHER ═══ (#108, 9 Sep 2026)

   The card had two colours and no way to say "level", so a line whose first and
   last moments are the same figure fell through to the gain colour — or worse,
   took the loss colour from the realised-profit figure above it, which is what
   the owner read on @unipcs. Both are the card asserting a direction nobody
   measured.

   So there is a third state and it is quiet on purpose: the app's own muted ink
   rather than a third hue, because a level line is the absence of a direction and
   inventing a colour for it would make it look like a finding. The dot and its
   halo follow the stroke, as they do for a fall, so the end of the drawing cannot
   be a different colour from the drawing. */
.nmo-tv-line.is-level .nmo-tv-stroke,
.nmo-tv-line.is-level .nmo-tv-moment,
.nmo-tv-line.is-level .nmo-tv-dot,
.nmo-tv-line.is-level .nmo-tv-halo { stroke: var(--text-dim); }
.nmo-tv-line.is-level .nmo-tv-wash {
  fill: color-mix(in srgb, var(--text-dim) 10%, transparent);
}
/* THE LINE IS THE DESIGN'S ON BOTH FORMS (#295): one 2.5px stroke for every reading, one 4px end
   dot, and the wash fading from 30% of the line's own ink to nothing (`serviceLineChart` draws the
   gradient in currentColor; these say which ink and that both forms paint with it). They sit after
   the flat tints above, which they must outrank. */
.nmo-balance-hero .nmo-tv-stroke { stroke-width: 2.5; }
.nmo-balance-hero .nmo-tv-dot { stroke-width: 8; }
.nmo-balance-hero .nmo-tv-line { color: var(--nmo-up); }
.nmo-balance-hero .nmo-tv-line.is-down { color: var(--nmo-down); }
.nmo-balance-hero .nmo-tv-line.is-level { color: var(--text-dim); }
.nmo-balance-hero .nmo-tv-line .nmo-tv-wash { fill: var(--nmo-wash-fade, none); }
/* A HELD READING (#296): a thin guide down through the reading, a dot on it in place of the end
   dot, and the reading's date where the span's change was. The marks exist only while a finger,
   the mouse or the arrow keys hold a reading, and they jump from reading to reading: no easing,
   with or without reduced motion. A long press on the chart selects nothing and opens no menu. */
.nmo-balance-hero .nmo-tv-chart[role="slider"] {
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
}
.nmo-balance-hero .nmo-tv-chart[role="slider"]:focus { outline: none; }
.nmo-balance-hero .nmo-tv-chart[role="slider"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
.nmo-balance-hero .nmo-tv-chart.is-holding .nmo-tv-dot { visibility: hidden; }
.nmo-balance-hero .nmo-tv-held-guide { fill: none; stroke: var(--text-faint); stroke-width: 1; transition: none; }
.nmo-balance-hero .nmo-tv-held-dot { fill: none; stroke: currentColor; stroke-width: 8; stroke-linecap: round; transition: none; }
.nmo-balance-hero-figures.is-holding :is(.nmo-tv-range, .nmo-balance-hero-range-line) { display: none; }
.nmo-balance-hero-held-when {
  margin-left: 10px; color: var(--text-dim); font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.nmo-balance-hero-profile .nmo-balance-hero-held-when { font: 500 15px/20px var(--font-ui); letter-spacing: normal; }
/* ═══ A HOLE IN THE LINE IS MARKED, AND SAYS WHY WHEN HELD ═══ (#335, the owner: "why the gap")
   A faint band where the line does not cross, with a dashed edge at each side, so the hole reads as
   something the chart chose to leave rather than a line that failed to draw. Nothing is drawn across
   it. Holding it darkens the band a little and lays one sentence over the top of the plot, on the
   card's own surface so the guides do not show through it; it goes with the hold. The profile's
   quiet hierarchy is kept: no sentence sits under the chart at rest. */
.nmo-balance-hero .nmo-tv-gap { fill: var(--text-faint); fill-opacity: 0.08; stroke: none; }
.nmo-balance-hero .nmo-tv-gap.is-held { fill-opacity: 0.18; }
.nmo-balance-hero .nmo-tv-gap-edge {
  fill: none; stroke: var(--text-faint); stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.7;
}
.nmo-balance-hero-gap-why {
  position: absolute; top: -6px; left: 0; right: 54px; z-index: 3; padding: 2px 4px;
  background: var(--bg-elev-2); color: var(--text); border-radius: 4px; pointer-events: none;
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}
/* On the profile the held dates sit in the figure's column just above the plot, so the sentence starts
   inside the plot instead of over them (measured on anselfang's month at 390, 15 Sep 01:40). */
.nmo-balance-hero-profile .nmo-balance-hero-gap-why { top: 26px; }
/* #318: a balance that is not the newest reading says which reading it is, in the hold's own type.
   A hold writes its own date there, so this one steps aside while a finger is down. */
.nmo-balance-hero-older-when {
  margin-left: 10px; color: var(--text-dim); font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.nmo-balance-hero-profile .nmo-balance-hero-older-when { font: 500 15px/20px var(--font-ui); letter-spacing: normal; }
.nmo-balance-hero-figures.is-holding .nmo-balance-hero-older-when { display: none; }
/* #391: what the figure leaves out, and the chain that kept a newer moment off the chart. Under the figures and
   outside the lines the profile hides, in the older date's colour, wrapping at 390. */
.nmo-balance-hero-coverage {
  margin-top: 4px; color: var(--text-dim); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}
/* …and the profile's column keeps the height it had at rest, so holding the chart never lifts the chart
   under the finger: at rest it is figure, change and date; held, the figure and the hold's one date. */
.nmo-balance-hero-profile .nmo-balance-hero-reading:has(> .nmo-balance-hero-older-when) { min-height: 76px; }

/* The dashed rule under the drawing, with the line's OWN first and last dates
   at its ends — not the span the pill names, which on a thin line is nowhere
   near what was actually drawn. */
/* The dashed rule under the drawing and its two dates went with the hero's trim
   (owner via friday, 16:55): on his own card they read "Aug 2026 … Aug 2026", and
   where the drawing stops is said in words instead. */


/* ── the four spans, as the card's bottom row ──────────────────────────────
   Equal widths, so the row reads as one control and not as four buttons of
   whatever width their words happen to be. The chosen one is the tonal grey
   this screen already uses for a chosen thing; no blue, no fill, no border. */
.nmo-tv-pills { display: flex; gap: 6px; margin-top: 14px; }
.nmo-tv-pill {
  flex: 1 1 0; min-width: 0; min-height: 32px; padding: 0 4px;
  border: 0; border-radius: 999px; cursor: pointer;
  background: none; color: var(--text-faint);
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
.nmo-tv-pill.is-on { background: var(--nmo-card-bg-2); color: var(--text); }
.nmo-tv-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Feed also uses the generic `.nmo-tv-pill` contract above. The shared balance
   hero needs 48px targets, but that treatment belongs only to its own controls:
   letting it escape changes Feed's two 32px selected tabs. */
.nmo-balance-hero-controls {
  display: grid; grid-template-columns: minmax(0, 1fr); align-items: center; gap: 2px;
  margin-top: 6px; min-width: 0;
}
.nmo-balance-hero .nmo-tv-pills { gap: 4px; margin: 0; flex: none; }
.nmo-balance-hero .nmo-tv-pill {
  /* The whole button—not an invisible wrapper—is 48px. Its tonal pill is a
     compact child layer inside that target. */
  position: relative; isolation: isolate; flex: 0 0 48px; width: 48px; height: 48px; min-height: 48px; padding: 0;
  border-radius: var(--radius-lg); background: none;
}
.nmo-balance-hero .nmo-tv-pill::before {
  content: ""; position: absolute; z-index: -1; inset: 8px 0; border-radius: var(--radius-lg);
  background: transparent;
}
.nmo-balance-hero .nmo-tv-pill[aria-pressed="true"] { color: var(--text); font-weight: 700; }
.nmo-balance-hero .nmo-tv-pill[aria-pressed="true"]::before {
  /* A selected range is a navigation state, not a gain. It borrows the app's
     existing positive role only as a tonal state layer, so it stays legible in
     either theme without inventing a local green. */
  background: color-mix(in srgb, var(--nmo-up) 28%, var(--bg-elev-3));
}
@media (hover: hover) {
  .nmo-balance-hero .nmo-tv-pill:not([aria-disabled="true"]):hover::before { background: var(--bg-elev-2); }
}
.nmo-balance-hero .nmo-tv-pill:not([aria-disabled="true"]):active::before { background: var(--bg-elev-2); }
.nmo-balance-hero .nmo-tv-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The chain switch row (#209, #218) went with #438: the trader service's history has no split by chain. */

/* ── the feed's two lists, in the trader card's own clothes ────────────────
   `.nmo-tv-pills` above IS the shape — equal widths, the chosen one filled —
   so nothing about the pill is restated here and the two rows cannot drift
   apart. This only says where the row sits: first under the tab row, with no
   space of its own above it (the tab row's 12px is the gap, #435), and the
   posts starting below it.
   THE TRADERS TAB'S "Top 30 (7 days) | Following" IS THIS SAME ROW (#445), so each half keeps its words on one line:
   at 390 wide in English and Chinese both fit with room to spare (the card look suite measures it). */
.nmo-feedpills { margin: 0 0 12px; }
.nmo-feedpills .nmo-tv-pill { white-space: nowrap; }

/* ── For you's switch: the top 100 traders, or everyone (owner, 11 Sep 2026) ──
   A smaller two-choice control under the list pills. It hugs its words rather than
   filling the row, so it reads as a setting of the list above it and not as a third
   list. The chosen choice wears the pill's own tonal grey. The line under it is the
   screen's quiet small text, and it holds a line's height before the answer lands so
   the posts under it do not jump when it fills. */
.nmo-feedscope { margin: -4px 0 12px; }
.nmo-feedscope-switch {
  display: inline-flex; gap: 2px; padding: 2px;
  border-radius: 999px; background: var(--nmo-card-bg);
}
.nmo-feedscope-choice {
  min-height: 28px; padding: 0 12px;
  border: 0; border-radius: 999px; cursor: pointer;
  background: none; color: var(--text-faint);
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
}
.nmo-feedscope-choice.is-on { background: var(--nmo-card-bg-2); color: var(--text); }
.nmo-feedscope-choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-feedscope-line { min-height: 18px; margin-top: 6px; color: var(--text-faint); font: var(--m3-body-small); }

/* ── "Following" with NOBODY FOLLOWED ──────────────────────────────────────
   (owner, 9 Sep 2026: "too much info, make it minimal")

   One line, three rows, one quiet link. Four things and no fifth.

   ═══ IT SITS AT THE TOP, IT DOES NOT FLOAT ═══
   The 40px of padding this had put a sentence and a lone button in the middle of
   an otherwise blank page, which is exactly what he photographed and called
   broken. Content that hangs in the middle of empty space reads as a failure
   whatever it says. This starts under the tab row and runs DOWN from it, the way
   a list does, because that is what the reader is expecting to find here.

   Only the line is centred. The rows below are a list and lists are ranged left. */
.nmo-following-none {
  display: flex; flex-direction: column; align-items: center;
  padding: 20px 24px 16px;
  text-align: center;
}
.nmo-following-none-say {
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text-faint);
}

/* ═══ THE THREE SHARE THE TRADERS LIST'S OWN LEFT EDGE ═══
   No padding of their own. The rows are `.nmo-row-t`, whose first column is the
   26px position cell — empty here, and kept precisely so the picture, the name
   and the handle start where they start on the board and on the Following list.
   A container that inset them would put the same three people at three different
   edges on three screens, which is the fault #102 exists to stop. */
.nmo-following-none-rows { display: flex; flex-direction: column; }

/* ═══ NO FIGURE, SO NO COLUMN FOR ONE ═══
   These rows carry no money, and 72px of reserved emptiness is not a blank cell —
   it is a column the row is holding open for a number that is never coming, and
   it takes that width off the name. The name gets it instead, which is what makes
   a long one ellipsise late rather than early. The rank cell STAYS: that one is
   holding the left edge, which is a thing worth holding.

   These three are the one trader row still drawn by the older builder; #479 says why. */
.nmo-row-t.nmo-row-s { --nmo-trader-money: 0px; }
.nmo-following-none-nofig { display: none; }

/* ── "See all traders" ─────────────────────────────────────────────────────
   QUIET GREY AND NOT A BUTTON'S CLOTHES. The three Follow buttons above are the
   offer; this is the way past them. In the accent colour, underlined, it competed
   with all three and won — which is the opposite of what a screen offering three
   people should do. It reads as a way out, and it is found when it is looked for. */
.nmo-following-none-all {
  display: block; margin: 4px auto 0;
  border: 0; background: none; padding: 12px 8px; cursor: pointer;
  color: var(--text-faint); text-decoration: none;
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
.nmo-following-none-all:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── "Following" with people in it who have been QUIET ─────────────────────
   The state next door, and it is a different fact: there IS something behind
   you. It says whose feed this is and when they last moved, and the feed's own
   "Look further back" stays under it — see `followingNoPosts`. */
.nmo-following-quiet {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 28px 24px 8px;
  text-align: center;
}
.nmo-following-quiet-faces { justify-content: center; }
.nmo-following-quiet-who {
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text);
}
.nmo-following-quiet-when {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
}

/* ── one line under the card ───────────────────────────────────────────────
   The numbers bold, the words after them faint. Parts we have no number for are
   not in the markup at all, so this line is often just the followers. */
.nmo-profile-social {
  display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap;
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text-faint);
}
/* The number's ROLE is set once, on `.nmo-profile-count-n` below; here it only
   takes the full-strength colour against the faint words around it. It used to
   carry a second weight of its own, which is how one fact ended up written in
   two places and at two weights. */
.nmo-profile-social .nmo-profile-count-n { color: var(--text); }
/* THE DOT IS BETWEEN THE PARTS, NOT AFTER EACH — so a trader we hold only one
   count for gets a bare "214 followers" and not a line with a dangling
   separator on it. It is drawn by the stylesheet rather than appended as text
   so that a part we have no number for takes its dot away with it: the markup
   simply has one fewer child, and `:not(:first-child)` follows. */
.nmo-profile-social .nmo-profile-count:not(:first-child)::before {
  content: "·"; margin-right: 8px; color: var(--text-faint);
}

/* ── who this is ─────────────────────────────────────────────────────────── */
.nmo-profile-id { display: flex; align-items: flex-start; gap: 12px; }
/* 82 ref px across = 53. */
.nmo-profile-av { width: 53px; height: 53px; border-radius: 50%; flex: none; object-fit: cover; }

/* ═══ FOLLOW, PINNED TO THE FLAT SAFE-AREA FLOOR ════════════════════════════
   (owner's mockup, 8 Sep 2026, second screen: the page scrolled and the button
   still there.)

   IT IS THE SHEET'S OWN LAST CHILD, a sibling of the scrolling list and not
   inside it — the same arrangement `.nmo-trade-bar` above uses, and for the
   measured reason written beside that bar: a control STUCK to the bottom of a
   scrolling list measures its bottom from the list's content edge, so the
   list's own padding lifts it a whole bar-height off the floor. Nothing here
   holds it down; it is simply the last thing in the box.

   The compact profile ends on a flat surface, not a fade or raised shadow. The
   scroll area reserves room for the whole safe-area dock before it reaches it. */
.nmo-profile-dock {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 14px var(--nmo-sheet-pad-x) calc(14px + env(safe-area-inset-bottom, 0px));
  min-height: calc(80px + env(safe-area-inset-bottom, 0px)); box-sizing: border-box;
  background: var(--bg-elev-1, var(--bg));
}
/* The dock is part of the same flat profile plane. Reserve its height plus a
   visible 16px exit so the last reachable question row cannot sit beneath it. */
.nmo-sheet:has(.nmo-profile-dock) { box-shadow: none; }
.nmo-sheet:has(.nmo-profile-dock) .nmo-sheet-body {
  padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px));
}
/* 52px, the app's blue, and no border on either state. FOLLOWED IS THE OUTLINED
   one — Material 3's own pairing for an action that is available but is not the
   one the screen is steering towards. */
.nmo-profile-follow {
  flex: 1 1 auto; min-height: 48px; padding: 0 20px;
  border: 0; border-radius: 24px; cursor: pointer;
  /* A full-width floor action uses the shared Material filled surface. Follow's
     blue token remains for its compact entry points rather than dominating a
     whole phone-width dock. */
  background: var(--text); color: var(--bg);
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.nmo-profile-follow.is-on {
  background: none; color: var(--text);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 24%, transparent);
}
.nmo-profile-follow:not(:disabled):hover {
  background: color-mix(in srgb, var(--text) 92%, var(--bg));
}
.nmo-profile-follow:not(:disabled):active {
  background: color-mix(in srgb, var(--text) 84%, var(--bg));
}
.nmo-profile-follow.is-on:not(:disabled):hover {
  background: color-mix(in srgb, var(--bg-elev-2) 92%, var(--text));
}
.nmo-profile-follow.is-on:not(:disabled):active {
  background: color-mix(in srgb, var(--bg-elev-2) 84%, var(--text));
}
.nmo-profile-follow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The way into the copy rules for this one trader, and only once he is
   followed. Round, the same 52 tall as the button beside it. */
.nmo-profile-gear {
  flex: none; width: 48px; height: 48px; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--nmo-card-bg-2); color: var(--text); cursor: pointer;
}
/* Its own screen is not built yet, so it says so and takes no press. */
.nmo-profile-gear:disabled { cursor: default; color: var(--text-faint); }
.nmo-profile-gear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The drawing inside it is three sliders, not a gear — the rule keeps the older name
   because three other lanes are editing this screen tonight and a rename here would
   collide with all of them for no gain a reader can see. Both the rails (lines) and the
   handles (circles) are stroked and hollow, so the handle is a ring on the rail. */
.nmo-profile-gear-ico { width: 22px; height: 22px; }
.nmo-profile-gear-ico circle, .nmo-profile-gear-ico line {
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round;
}
.nmo-profile-name {
  font: var(--m3-title-large); letter-spacing: var(--m3-title-large-tracking);
  color: var(--text); margin: 10px 0 0;
}
/* A trader has an @handle. OUR GENIE HAS A NAME AND NO HANDLE — the markup
   simply omits this element for a genie rather than inventing one. */
.nmo-profile-handle {
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text-faint); margin: 2px 0 0;
}

/* ── the counts, and the stats under them ────────────────────────────────── */
.nmo-profile-counts { display: flex; gap: 18px; margin-top: 14px; }
.nmo-profile-count {
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text-faint);
}
/* LABEL LARGE — the number, against BODY MEDIUM for the word after it. Same size,
   one weight apart, which is what makes "214 followers" read as a figure and its
   noun rather than as two words. It was at 700, heavier than anything else on the
   page bar nothing, which is why the counts row was shouting over the name. */
.nmo-profile-count-n {
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  color: var(--text); margin-right: 5px;
}
/* Average hold, trades, joined — an icon and a phrase each, wrapping on a
   narrow phone rather than being cut. A TRADER GETS FEWER OF THESE than our
   genie does, and the row is built from whatever is known; it is never padded
   out with an empty one. */
.nmo-profile-stats {
  display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 12px;
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
}
.nmo-profile-stat { display: inline-flex; align-items: center; gap: 5px; }
/* WHOSE COUNT IT IS. A trader's trade count is the provider's count of every
   trade they have ever made; ours is the trades we watched. They are two
   different numbers and this is what stops them being read as one. */
.nmo-profile-stat-src { color: var(--text-faint); opacity: 0.75; }

/* ── the portfolio value ─────────────────────────────────────────────────────
   CORRECTED 6 Sep 2026. This used to read "A PLACE, NOT A DRAWING", and said we
   store one point-in-time total and no series for anyone. That was true when the
   sheet was written and stopped being true: `/api/nmo/balance` answers up to
   three hundred points and the drawer above has been drawing them all along, so
   the box was saying we had no history six inches from a screen showing it.
   NOW IT IS THE EMPTY STATE. Our own genie's line is drawn here when there are
   at least two readings; with fewer there is nothing a line could honestly join,
   and the sentence saying why comes back. A TRADER STILL HAS NO SLOT AT ALL — we
   watch their swaps, never their balance.
   The slot keeps its height either way, so the screen does not jump when the
   first line arrives. It is 104px because his chart band measures 160 ref
   px = 104. */
.nmo-profile-chart-slot {
  min-height: 104px; margin: 18px 0 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 8px 10px;
  border-radius: 12px; background: var(--nmo-card-bg-2);
}
.nmo-profile-chart-slot .nmo-profile-absent { margin: 0; text-align: center; }
/* The drawing fills the box's width; its own viewBox carries the shape. */
.nmo-profile-chart-slot svg { display: block; width: 100%; height: 80px; }
/* Something a reader has to be told about the drawing — today, readings we left
   out. Quiet, under it, and drawn only when there is something to say. */
.nmo-profile-chart-note {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  text-align: center; color: var(--text-faint);
}

/* ── a thing we cannot know, said rather than left blank ─────────────────────
   THE MOST IMPORTANT RULE ON THIS SCREEN. An empty number reads as zero, and
   zero is a claim we cannot make. Wherever our genie has a figure and a trader
   does not, the trader gets one of these sentences in its place — never a dash,
   never a blank, never a 0. */
.nmo-profile-absent {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint); margin: 10px 0 0;
}

/* ── total cash: OUR GENIE ONLY ──────────────────────────────────────────────
   `cashUsd` is a real field on our own paper account. For a trader we watch
   swaps, not balances, so there is no such number and this block is not built —
   the markup omits it and puts an `.nmo-profile-absent` sentence in its place. */
.nmo-profile-cash { display: flex; align-items: center; gap: 14px; margin-top: 20px; }
.nmo-profile-cash-icon {
  width: 46px; height: 46px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--nmo-card-bg-2); color: var(--text-dim);
}
.nmo-profile-cash-label {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
}
/* TITLE LARGE, over a BODY SMALL label. It is the second-biggest figure on our own
   genie's page and it is not the portfolio value, so it sits a clear two roles
   below it rather than at a size of its own invention. */
.nmo-profile-cash-value {
  font: var(--m3-title-large); letter-spacing: var(--m3-title-large-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* ── the first row of a TRADER's list of coins ───────────────────────────────
   The mockup has a Cash figure and its share of the portfolio here. We have
   never read a followed trader's wallet and nothing on the wire carries either
   number, so this row says that in words where the figure would have been —
   and beside it goes the one real figure we do hold, which is not cash and is
   never called cash. Same shape as a coin row so it reads as the first item in
   the list rather than as a banner above it. */
.nmo-profile-cash-row {
  display: grid; grid-template-columns: 46px minmax(0, 1fr) auto;
  gap: 12px; align-items: center; padding: 12px 0;
}
.nmo-profile-cash-side { min-width: 0; }
.nmo-profile-cash-row .nmo-profile-cash-label {
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  color: var(--text);
}
.nmo-profile-cash-why {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
}
/* The line that names the other answer and why it is bigger (#81). Fainter and
   smaller than the sentence above it, because it is an explanation of a figure
   rather than a figure of its own — a reader who does not care about the gap
   should be able to skip it, and one who does should find it under the number
   it is about. It wraps: it is a sentence and not a label. */
.nmo-profile-cash-gap {
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  color: var(--text-faint); opacity: 0.85;
  margin-top: 2px; max-width: 42ch;
}
/* The dollars on this row are a HOLDING, so they are drawn in the ordinary text
   colour like every other holding's figure. There is deliberately no up and no
   down here: the red this row used to be drawn in belonged to the netting that
   has moved to its own block, and colour is what made it read as a balance in
   the red. */
.nmo-profile-cash-row .nmo-profile-pos-right { max-width: 42%; }

/* ── money in and out, which is not cash and not a balance ───────────────────
   Its own block under the value card, headed in words, with the figure under
   the heading and the sentence that names its window under that. Nowhere near
   the list of things he holds. */
.nmo-profile-netspend { margin-top: 14px; }
.nmo-profile-netspend-head {
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  color: var(--text);
}
.nmo-profile-netspend-value {
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
  font-variant-numeric: tabular-nums; margin-top: 2px;
}
/* The plugin's own up and down, the same as every other signed figure on this
   screen. No new green, no new red. */
.nmo-profile-netspend-value.nmo-up { color: var(--nmo-up); }
.nmo-profile-netspend-value.nmo-down { color: var(--nmo-down); }
.nmo-profile-netspend-why {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint); margin-top: 2px;
}

/* ── positions ───────────────────────────────────────────────────────────── */
.nmo-profile-pos-head { display: flex; align-items: center; gap: 10px; margin-top: 24px; }
.nmo-profile-pos-title {
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
  color: var(--text);
}
.nmo-profile-pos-count { color: var(--text-faint); font-variant-numeric: tabular-nums; }
.nmo-profile-toggle {
  margin-left: auto; display: inline-flex; gap: 2px; padding: 3px;
  border-radius: 999px; background: var(--nmo-card-bg-2);
}
.nmo-profile-toggle-btn {
  border: 0; background: none; cursor: pointer; border-radius: 999px;
  min-height: 22px; padding: 0 12px; color: var(--text-faint);
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
}
.nmo-profile-toggle-btn.is-on {
  background: color-mix(in srgb, var(--nmo-follow-bg) 28%, transparent);
  color: var(--text);
}
.nmo-profile-toggle-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* All and Tokens. THERE IS NO PERPS CHIP and no class for one: we hold no
   perpetuals, and a filter with nothing behind it is a promise we cannot keep.
   With only two of them, whether this row is worth drawing at all is tuesday's
   call — `All` and `Tokens` select the same rows today. */
.nmo-profile-filters { display: flex; gap: 10px; margin-top: 14px; }
.nmo-profile-filter {
  border: 1px solid var(--nmo-line); background: none; cursor: pointer;
  border-radius: 999px; min-height: 26px; padding: 0 14px; color: var(--text-dim);
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
.nmo-profile-filter.is-on { background: var(--nmo-card-bg-2); color: var(--text); border-color: transparent; }
.nmo-profile-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* One coin: its picture, its name over how much of it is held, and on the right
   what it is worth over the percent. */
.nmo-profile-pos {
  display: grid; grid-template-columns: 46px minmax(0, 1fr) auto;
  gap: 12px; align-items: center; padding: 12px 0;
}
.nmo-profile-pos-tok { width: 46px; height: 46px; border-radius: 50%; flex: none; }
/* ── THE COIN'S PICTURE WITH ITS CHAIN MARK (design #pf5) ──
   The wrapper takes the picture's own place and size, and the picture fills it; the mark is a 16px dot on
   the bottom-right corner with a ring in the colour behind it, so it reads as sitting on top of the
   picture rather than as part of it. The colour per chain is the chain's own; anything we have no colour
   for takes the design's blue, and the dot always carries the chain's name for a reader. */
.nmo-profile-pos-tokwrap { position: relative; display: block; }
.nmo-profile-pos-tokwrap > :first-child { width: 100%; height: 100%; border-radius: inherit; }
.nmo-profile-pos-chainmark {
  position: absolute; right: -2px; bottom: -2px; width: 16px; height: 16px; border-radius: 50%;
  background: #5b8cff; box-shadow: 0 0 0 2px var(--bg);
}
.nmo-profile-pos-chainmark[data-nmo-chain-mark="solana"] { background: #9945ff; }
.nmo-profile-pos-chainmark[data-nmo-chain-mark="ethereum"] { background: #627eea; }
.nmo-profile-pos-chainmark[data-nmo-chain-mark="bsc"] { background: #f0b90b; }
.nmo-profile-pos-chainmark[data-nmo-chain-mark="base"] { background: #0052ff; }
.nmo-profile-pos-chainmark[data-nmo-chain-mark="arbitrum"] { background: #12aaff; }
.nmo-profile-pos-chainmark[data-nmo-chain-mark="polygon"] { background: #8247e5; }
.nmo-profile-pos-chainmark[data-nmo-chain-mark="avalanche"] { background: #e84142; }
.nmo-profile-pos-chainmark[data-nmo-chain-mark="tron"] { background: #eb0029; }
/* BODY LARGE for the leading line of a coin row, and that is the choice made for
   every one of them. The alternative was Title Medium, which is the same 16px at
   weight 500 — but the section heading "Positions" directly above IS Title Medium,
   and a heading with rows under it at exactly its own size and weight is the fault
   this whole pass is about. Body Large keeps the 16px and drops to weight 400, so
   the heading still leads its own list.
   Its supporting line is BODY MEDIUM: a size down, and the pair reads as a name
   with a quantity under it rather than as two lines of equal standing. */
.nmo-profile-pos-name {
  font: var(--m3-body-large); letter-spacing: var(--m3-body-large-tracking);
  color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A holdings row carrying the "Unknown origin" badge (#310): the name gives up room, the badge does not. */
.nmo-profile-pos-name-badged { display: flex; align-items: center; gap: 6px; min-width: 0; }
.nmo-profile-pos-name-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nmo-profile-pos-amount {
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.nmo-profile-pos-right { text-align: right; display: flex; flex-direction: column; gap: 2px; }
/* LABEL LARGE — the figure at the right-hand end of a row, the same role the
   report tab gives the rank at the right-hand end of its rows. It is a size below
   the coin's name beside it and a weight above it, so the eye can run down the
   money column without the names competing. */
.nmo-profile-pos-value {
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  color: var(--text);
}
.nmo-profile-pos-pct {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  font-variant-numeric: tabular-nums;
}
/* Direction is the plugin's own up and down. No new green, no new red. */
.nmo-profile-pos-pct.nmo-up { color: var(--nmo-up); }
.nmo-profile-pos-pct.nmo-down { color: var(--nmo-down); }
/* #395: a coin the stored coin-safety answer says cannot be sold. A small dot in the down colour by
   the name; its words are on the element for a finger resting on it and for a screen reader. */
.nmo-profile-pos-unsellable {
  display: inline-block; width: 8px; height: 8px; margin-left: 6px; border-radius: 50%;
  background: var(--nmo-down); vertical-align: middle;
}
.nmo-profile-pos.is-openable { cursor: pointer; }
.nmo-profile-pos.is-openable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
/* #395: the Portfolio tab. The total leads in the row figure's display face; each chain is a quiet
   heading with its own subtotal, and the coins under it are the holdings list's own rows. */
.nmo-portfolio-total { margin: 4px 0 6px; color: var(--text-faint); font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking); }
.nmo-portfolio-total-usd {
  font: var(--m3-title-large); letter-spacing: var(--m3-title-large-tracking);
  font-family: var(--font-display); font-variant-numeric: tabular-nums; color: var(--text);
}
.nmo-portfolio-total-over { font-variant-numeric: tabular-nums; }
.nmo-portfolio-chain { margin-top: 14px; }
.nmo-portfolio-chain-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding-bottom: 4px; border-bottom: 1px solid var(--nmo-line);
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking); color: var(--text-dim);
}
.nmo-portfolio-chain-usd { font-variant-numeric: tabular-nums; color: var(--text-faint); }
/* And on the figure itself (11 Sep 2026, #153). `agentProfile.ts` puts the direction on a signed
   figure "the same colour as the percent beside it", but `.nmo-profile-pos-value` sets the text
   colour after `.nmo-up` and `.nmo-down`, so the class never showed: measured in a real browser
   at 390px, a transfer sale's +$39K and a closed row's −$328 both drew rgb(227, 227, 227) while
   that row's percent drew red. These two rules make the figure take the colour it was given. */
.nmo-profile-pos-value.nmo-up { color: var(--nmo-up); }
.nmo-profile-pos-value.nmo-down { color: var(--nmo-down); }
/* A value we could not price is a sentence, never a zero: `unrealised` is null
   when we have no current price or nothing is held, and both of those are
   different from "it is worth nothing". */
.nmo-profile-pos-unpriced {
  color: var(--text-faint);
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}
/* The word under a transfer sale's collected money (owner's pick, 11 Sep 2026). It stands where
   the percent would, at the percent's size, in the faint colour every label takes. */
.nmo-profile-pos-said {
  color: var(--text-faint);
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}
/* Nothing on this screen animates, so there is no reduced-motion rule to write;
   an empty one would be theatre. */

/* ═══ THE SETTINGS BUTTON ON THE TAB ROW (owner, 4 Sep 2026) ═══

   ANCHORED RIGHT WITHOUT MOVING THE THREE TABS. `margin-left: auto` in a flex row eats the
   space AFTER the last tab, so Feed, Traders and Profile keep the positions they had and the
   button sits at the end. Stated here once rather than in the call site: `nmo.ts` sets no
   position for it at all.

   IT MUST NOT READ AS A FOURTH TAB, so it takes none of `.nmo-tab`'s chip shape — no pill,
   no border, no selected state. It is an icon on the same line, which is what was asked for. */
.nmo-tabs .nmo-tab-settings {
  margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 4px; cursor: pointer;
  color: var(--text-faint);
}
.nmo-tabs .nmo-tab-settings:hover { color: var(--text); }

/* The settings screen. One column, the same rhythm as the rest of the app. */
.nmo-settings { display: flex; flex-direction: column; gap: 8px; }
.nmo-set-chain {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  width: 100%; text-align: left; padding: 14px;
  border: 1px solid var(--nmo-line); border-radius: 16px;
  background: none; color: var(--text); cursor: pointer;
}
.nmo-set-chain.is-on { border-color: var(--text); }
/* A row we cannot serve is quiet and takes no pointer — the sentence inside it says why, so
   the look never has to carry the meaning on its own. */
.nmo-set-chain:disabled { cursor: default; opacity: 0.55; }

/* ═══ THE DAILY STOP, IN SETTINGS (#434) ═══
   Design v60's settings row (.cp-row, .cp-step): the name with a one-line hint on the left, and the
   number between − and + in a 40px pill on the right. A loss is drawn in the loss red; Off is quiet. */
.nmo-settings > .m3-sectionlabel { margin: 16px 0 0; }
.nmo-limit-row {
  display: grid; grid-template-columns: 1fr auto; align-items: center; column-gap: 14px;
  min-height: 56px; padding: 6px 0;
}
.nmo-limit-l { min-width: 0; }
.nmo-limit-t { display: block; font: var(--m3-body-large); letter-spacing: .5px; color: var(--text); }
.nmo-limit-hint {
  display: block; font: var(--m3-body-small); letter-spacing: .4px; color: var(--text-faint);
}
.nmo-limit-step {
  display: inline-flex; align-items: center; height: 40px; box-sizing: border-box; border-radius: 999px;
  background: var(--bg-elev-2); border: 1px solid var(--nmo-outline-variant, var(--nmo-line));
}
.nmo-limit-b {
  width: 40px; height: 38px; padding: 0; border: 0; border-radius: 999px; background: none; cursor: pointer;
  color: var(--text-dim); font: 500 18px/1 var(--font-display, var(--font-ui)); text-align: center;
}
.nmo-limit-b:disabled { opacity: 0.38; cursor: default; }
.nmo-limit-b:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-limit-v {
  min-width: 64px; text-align: center; color: var(--text);
  font: var(--m3-title-medium); letter-spacing: .15px; font-variant-numeric: tabular-nums;
}
.nmo-limit-v .nmo-num { font: inherit; }
.nmo-limit-v.nmo-limit-down { color: var(--err); }
.nmo-limit-off { color: var(--text-faint); font-weight: 400; }
.nmo-limit-trouble { grid-column: 1 / -1; margin: 6px 0 0; }

/* ═══ GENIE'S RULES, IN SETTINGS (#454) ═══
   The same row shape as the daily stop above it — the line on the left, one control on the right —
   so the whole screen reads as one list and a rule does not look like a different kind of thing from
   a limit. The date under each line is the quiet ink the rest of the app uses for a provenance. */
.nmo-grules { display: block; }
.nmo-grule-row {
  display: grid; grid-template-columns: 1fr auto; align-items: center; column-gap: 14px;
  min-height: 56px; padding: 6px 0; width: 100%; box-sizing: border-box;
  margin: 0; border: 0; background: none; text-align: left; color: inherit;
}
.nmo-grule-tap { cursor: pointer; }
.nmo-grule-tap .nmo-ic { color: var(--text-faint); }
.nmo-grule-l { min-width: 0; }
.nmo-grule-t { display: block; font: var(--m3-body-large); letter-spacing: .5px; color: var(--text); }
.nmo-grule-since {
  display: block; font: var(--m3-body-small); letter-spacing: .4px; color: var(--text-faint);
}
.nmo-grule-switch {
  display: inline-flex; align-items: center; padding: 0; margin: 0; border: 0; background: none; cursor: pointer;
}
.nmo-grule-switch:disabled { opacity: .38; cursor: default; }
:is(.nmo-grule-switch, .nmo-grule-tap, .nmo-grule-askbtn, .nmo-grule-askforget,
  .nmo-learned-all):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Nothing to show is one quiet line, never a heading over empty space. */
.nmo-grule-none { margin: 8px 0 4px; font: var(--m3-body-medium); color: var(--text-faint); }

/* The small sheet a preference row opens: the badge's name, Show fewer / Show more, and Forget
   under them. Three states and no fourth — the three the server already holds. */
.nmo-grule-asklayer { position: fixed; inset: 0; z-index: 60; }
.nmo-grule-askscrim { position: absolute; inset: 0; background: rgba(0, 0, 0, .44); }
.nmo-grule-asksheet {
  position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box;
  padding: 8px 20px calc(20px + env(safe-area-inset-bottom, 0px));
  border-radius: 28px 28px 0 0; background: var(--bg-elev-3, var(--bg-elev-2));
}
.nmo-grule-askgrab {
  display: block; width: 32px; height: 20px; margin: 0 auto 4px; padding: 0;
  border: 0; background: none; cursor: pointer;
}
.nmo-grule-askgrab::before {
  content: ""; display: block; width: 32px; height: 4px; border-radius: 999px;
  background: var(--nmo-outline, var(--text-faint)); opacity: .55;
}
.nmo-grule-asktitle {
  margin: 4px 0 16px; font: var(--m3-title-medium); letter-spacing: .15px; color: var(--text); text-align: center;
}
.nmo-grule-askrow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.nmo-grule-askbtn {
  -webkit-appearance: none; appearance: none; box-sizing: border-box; min-width: 0; width: 100%; height: 56px; margin: 0;
  display: inline-flex; align-items: center; justify-content: center; padding: 0 12px;
  border: 1px solid var(--nmo-v14-outline, var(--nmo-outline)); border-radius: 999px; cursor: pointer;
  background: transparent; color: var(--text); font: var(--m3-title-medium); letter-spacing: .15px;
  -webkit-tap-highlight-color: transparent;
}
/* The one it is on now, so the sheet says where the person already stands before they change it. */
.nmo-grule-askbtn.is-on {
  background: var(--nmo-violet, var(--accent)); color: var(--nmo-on-violet, var(--on-accent, #fff)); border-color: transparent;
}
.nmo-grule-askforget {
  display: block; width: 100%; height: 48px; margin: 12px 0 0; padding: 0;
  border: 0; background: none; cursor: pointer; color: var(--err);
  font: var(--m3-label-large); letter-spacing: .1px;
}
/* The quiet link beside "What Genie has learned", to the whole list (#454). */
.nmo-learned-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.nmo-learned-head > .m3-sectionlabel { margin-bottom: 0; }
.nmo-learned-all {
  flex: none; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--accent); font: var(--m3-label-large); letter-spacing: .1px;
}

/* ═══ THE TRADER'S REPORT TAB — a six-sided chart, then six rows ═══════════════
   (owner, 8 Sep 2026: percentile positions only. No stars, no grade, no score
   out of a hundred, and no horizontal rules anywhere on this page.)

   The chart sits ON THE PAGE, not in a card: it is the whole point of the tab
   and a box around it would make it look like one fact among several. The rows
   underneath are cards, because each of them IS one fact among six. */
.nmo-report { padding: 4px 0 12px; }
/* Full width, up to a point, and then centred. The names are drawn INSIDE the
   picture, so they are measured in the picture's own units and grow with it —
   at the sheet's full 520px the 12px the owner asked for would land on screen
   at 17px, which is bigger than the row headings underneath. On a phone the
   sheet is narrower than this cap and the chart really is edge to edge; on a
   wide screen it stops growing and sits in the middle. */
.nmo-report-radar {
  display: block; width: 100%; max-width: 380px; margin: 0 auto;
  height: auto; overflow: visible;
}
/* The outer edge is a position of 100 — the best anyone in the cohort managed. */
.nmo-report-web { fill: none; stroke: var(--nmo-chart-grid); stroke-width: 1; }
/* Halfway, dotted: the middle of the cohort, so a reader can see at a glance
   which side of it each corner falls on without reading a single number. */
.nmo-report-mid { fill: none; stroke: var(--nmo-chart-grid); stroke-width: 1; stroke-dasharray: 3 4; }
.nmo-report-spoke { stroke: var(--nmo-chart-grid); stroke-width: 1; opacity: 0.6; }
.nmo-report-shape { fill: var(--nmo-chart-wash); stroke: var(--nmo-chart-green); stroke-width: 2; }
.nmo-report-vertex { fill: var(--nmo-chart-green); }
/* The names are part of the drawing, not a legend beside it — and their SIZE is part of the
   drawing too, so it is set on each name in the drawing's own units and is deliberately not
   here. It used to be `font-size: 12px`, which measured the names in screen pixels while
   everything around them was measured in the box's units: on a narrow phone the shape shrank
   with the box and the names did not, until the long ones ran out over both edges. Putting a
   pixel size back on this rule would bring that straight back.

   LABEL MEDIUM, AND ONLY TWO THIRDS OF IT. The role's weight (500) and tracking
   (0.5) are taken here, because those are the same however big the drawing is
   drawn. Its SIZE is not, and must not be: a pixel size on this rule is the bug
   described above, so the 12.5 in `traderReport.ts` stands in for the role's 12px
   and shrinks with the shape. */
.nmo-report-axis {
  fill: var(--text-faint);
  font-weight: 500; letter-spacing: var(--m3-label-medium-tracking);
}
/* A measure we could not take is named more quietly than one we could. The dimming
   is not the only thing that says so — the corner has no dot and the shape steps
   round it — but it is the one a reader sees without looking for it. */
.nmo-report-axis.is-hollow { opacity: 0.45; }
.nmo-report-caption {
  margin: 2px 0 14px; text-align: center;
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
}
.nmo-report-absent {
  margin: 12px 2px; color: var(--text-faint);
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}

.nmo-report-rows { display: flex; flex-direction: column; gap: 8px; }
.nmo-report-row { background: var(--nmo-card-bg-2); border-radius: 14px; overflow: hidden; }
.nmo-report-row-head {
  appearance: none; background: none; border: 0; width: 100%; cursor: pointer;
  display: flex; align-items: center; gap: 10px; padding: 12px 14px;
  color: var(--text); font: inherit; text-align: left;
}
.nmo-report-row-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.nmo-report-row-left { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
/* TITLE MEDIUM — the title of a row, and the largest thing inside one. It was
   weight 600 at whatever the page's own size happened to be, which came out at
   exactly the tab label above it; now it is one full role clear of it. */
.nmo-report-row-name {
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
}
/* How much of the record the figure rests on. BODY SMALL and grey because it
   qualifies the title above it rather than competing with it — two sizes down
   and a weight down, so the pair reads as a title with a footnote. */
.nmo-report-row-cover {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint);
}
/* LABEL LARGE for the standing at the right-hand end. A size below the title it
   belongs to and the same weight, which is what stops it reading as a second
   title on the same line. */
.nmo-report-row-rank {
  flex: 0 0 auto; font-variant-numeric: tabular-nums;
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
.nmo-report-row-rank.is-good { color: var(--nmo-up); }
.nmo-report-row-rank.is-fair { color: var(--warn); }
.nmo-report-row-rank.is-poor { color: var(--nmo-down); }
/* NO POSITION IS GREY, and grey is not a shade of bad. A measure we could not take
   has to look unlike every measure we could, in either direction.

   IT CARRIES NO WEIGHT OF ITS OWN ANY MORE. It used to drop to 500 against the
   600 of the rule above, which was the only way to look quieter when everything
   was hand-set; now the whole row is on the scale and Label Large IS 500, so the
   colour does the saying and there is one weight for one role. */
.nmo-report-row-rank.is-none { color: var(--text-faint); }
.nmo-report-row-chev {
  flex: 0 0 auto; color: var(--text-faint); font-size: 18px; line-height: 1;
  transform: rotate(90deg); transition: transform 140ms ease;
}
.nmo-report-row.is-open .nmo-report-row-chev { transform: rotate(270deg); }
.nmo-report-row-body { display: none; padding: 0 14px 12px; }
.nmo-report-row.is-open .nmo-report-row-body { display: block; }
/* BODY MEDIUM — the sentence a row opens to say. A size under the title that
   opened it, and the only running prose on the page. */
.nmo-report-row-says {
  margin: 0; color: var(--text-faint);
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}

/* The trader sheet's two tabs. The coin screen's control, class for class, with
   the one difference the owner asked for: NO RULE UNDER THE ROW on this page.
   The labels never wrap — "Positions · 137" broken over two lines would move the
   chart under it by a line and back again as the count arrives. */
.nmo-profile-tabs { border-bottom: 0; padding-left: 2px; padding-right: 2px; margin-bottom: 6px; }
/* ═══ THE TAB LABEL IS TITLE SMALL, AND THAT IS THE OWNER'S COMPLAINT ═══════
   (owner, 8 Sep 2026: "the title of each card is of the same size as the tab")

   He was right, and it was not close: the shared tab rule takes the page's own
   font and puts weight 600 on it, which lands at 16px/600 — and the report rows
   underneath were 16px/600 as well. Two things a centimetre apart, identical, so
   neither one led. A tab is a control that names where you are; a row title is
   the content. Title Small (14/20, weight 500) sits one clear step under the row
   titles' Title Medium (16/24, weight 500), which is the step the eye reads as
   "this is the chrome and that is the thing".

   SCOPED TO THIS ROW ON PURPOSE. `.nmo-coin-tab` is shared with the coin sheet,
   which is not this task's screen and has its own owners this week. */
.nmo-profile-tabs .nmo-coin-tab {
  white-space: nowrap;
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
}

/* ═══════════════════════════════════════════════════════════════════════════
   FOR YOU — THE DECK (#101, 9 September 2026)

   The tab is one card, the real next cards stacked behind it (#334), and three round
   buttons: Pass, his full profile, Copy (#308, owner 14 Sep 2026: "3 buttons in
   line, below the card … use icon, not text … there should be no button
   highlighted"). Pass and Copy do exactly what the swipes do.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ ONE HEIGHT, READ BY BOTH FACES ═══════════════════════════════════════
   THE WHOLE FLIP DEPENDS ON THIS ONE NUMBER. The front and the back are two
   absolutely-positioned faces of the same box, and both take their height from
   `--nmo-deck-face` on the deck — so the page under the card cannot jump at the
   halfway point of the turn.

   SINCE #308 THE NUMBER IS MEASURED, NOT WRITTEN HERE. The card is a short
   profile now and its answer is taller than the screen: 635px for the design's
   front, with 491px between the tab row and the composer at 390 by 844. So
   `fitDeck` in `forYouDeck.ts` sets it to the taller face's whole answer, less
   whatever would make the page scroll, and each face scrolls inside the card.
   The 560px here is only what a document with no layout keeps. */
.nmo-deck {
  --nmo-deck-face: 560px;
  /* Design v14's ink names, answered from the Vivid palette since #424: gain, loss, the interface
     violet and its selected container. The old amber is the quiet ink; there is no third colour. */
  --nmo-v14-green: #1ee07f; --nmo-v14-amber: rgba(255, 255, 255, .66); --nmo-v14-dot-red: #ff4f64;
  --nmo-v14-red-rank: #ff4f64; --nmo-v14-primary: #a15cff; --nmo-v14-outline: rgba(255, 255, 255, .28);
  --nmo-v14-green-chosen: rgba(161, 92, 255, .26);
  position: relative;
  display: flex; flex-direction: column; align-items: stretch;
  /* No padding under the buttons: every pixel more is a pixel the card does not get. */
  padding: 0 3px;
}
/* THE TRAY SITS AT THE BOTTOM (#328; the owner's ruling again on #334, 14 Sep 2026 23:0x: "make the 3 button tray close
   to the bottom of the screen"). The screen's body keeps 28px under its content and the root 12px more; under the deck
   both go, so the tray comes down to the bottom margin the app keeps under a screen whose ask bar stands down (12px
   above the phone's home bar), and the pile takes the room — the card is centred in it, never grown into it (fitDeck in
   forYouDeck.ts). For one evening (#334, 21:5x to 23:0x) the tray followed the card with the screen empty below; the
   owner ruled that away, so do not bring it back. */
.nmo-root:has(.nmo-deck) { padding-bottom: 0; }
.nmo-body:has(> .nmo-screen > .nmo-deck) { padding-bottom: 0; }
/* The design is dark only; the light theme takes the app's own light roles, as the profile does. */
:root[data-theme="light"] .nmo-deck {
  --nmo-v14-green: var(--ok); --nmo-v14-amber: var(--warn); --nmo-v14-dot-red: var(--err);
  --nmo-v14-red-rank: var(--err); --nmo-v14-primary: #0b57d0; --nmo-v14-outline: var(--border);
  --nmo-v14-green-chosen: color-mix(in srgb, var(--ok) 14%, var(--bg));
}
/* The tab row already says "For you"; the heading stays for a screen reader only, as the design
   draws no title over the card. */
.nmo-deck-title {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

/* The pile. The next cards sit behind in the same box, so revealing one costs no
   layout — and `position: relative` is what the stamps hang off. It is the room
   between the tab row and the tray (`--nmo-deck-stage`, measured by fitDeck), and
   each card's place and height in it are set there too (#334): the card in front
   centred, the ones behind at their own heights. Without a layout it is the card. */
.nmo-deck-pile { position: relative; height: var(--nmo-deck-stage, var(--nmo-deck-face)); flex: none; }
.nmo-deck-card {
  position: absolute; left: 0; right: 0; top: var(--nmo-card-top, 0px); height: var(--nmo-deck-face);
  border-radius: 24px; background: transparent;
  border: 0;
  /* THE FINGER OWNS SIDEWAYS, THE BROWSER OWNS DOWNWARDS. `pan-y` is what leaves
     ordinary scrolling alive under a horizontal swipe — the canvas note's "up and
     down beyond the flip stay free" is this one declaration, and since #308 it is
     also what scrolls a long answer inside the card. */
  touch-action: pan-y;
  perspective: 900px;
  will-change: transform;
  transition: transform 200ms cubic-bezier(.2, .8, .2, 1);
}
/* Held: no easing, because the card must sit exactly where the finger is. */
.nmo-deck-card.is-dragging { transition: none; }
/* Flying off after a release. Longer than the settle, and it is the only place a
   card leaves the screen under its own power. */
.nmo-deck-card.is-gone { transition: transform 260ms ease-out; opacity: .2; }
/* SCENERY: THE NEXT CARDS, STACKED BEHIND (#334). Cards the person cannot touch, each narrower
   about its own lower edge (`--nmo-stack-scale`, from DECK_STACK_SHRINK). The part of a taller
   card that would poke out above the front one is clipped (`--nmo-stack-clip`) — and shown again
   the moment a swipe lifts the card in front.

   THEY READ AS CARDS BY THEIR OWN SURFACES, NOT BY BEING DARKENED (owner, 14 Sep 2026, on the
   first stack: "the background deck looking ugly"). Darkened copies of the front made three
   dark bands under the card that read as a smudge or its shadow. So each card behind has its
   own opaque surface, lighter than the screen and stepping down towards it the further back it
   is, a hairline edge around its rounded corners, and a small shadow of its own in place of the
   front's deep one; the front's sheen is not repeated. Opaque, never faded: a translucent card
   would show the one behind it through it. */
.nmo-deck-card.is-under {
  height: var(--nmo-card-h, var(--nmo-deck-face));
  transform-origin: 50% 100%; transform: scale(var(--nmo-stack-scale, .95));
  clip-path: inset(var(--nmo-stack-clip, 0px) 0 0 0);
  pointer-events: none; transition: none;
}
.nmo-deck-card.is-under .nmo-deck-face {
  border-color: rgba(255, 255, 255, .12); box-shadow: none;
}
.nmo-deck-card.is-under .nmo-deck-sheen { display: none; }
.nmo-deck-card.is-behind-1 .nmo-deck-face { background: #202025; }
.nmo-deck-card.is-behind-2 .nmo-deck-face { background: #17171b; }
.nmo-deck-card.is-behind-3 .nmo-deck-face { background: #0e0e11; }
:root[data-theme="light"] .nmo-deck-card.is-under .nmo-deck-face { border-color: var(--border); box-shadow: 0 4px 10px rgba(0, 0, 0, .08); }
:root[data-theme="light"] .nmo-deck-card.is-behind-1 .nmo-deck-face { background: var(--bg-elev-1); }
:root[data-theme="light"] .nmo-deck-card.is-behind-2 .nmo-deck-face { background: var(--bg-elev-2); }
:root[data-theme="light"] .nmo-deck-card.is-behind-3 .nmo-deck-face { background: var(--bg-elev-3); }
/* A swipe lifting the front uncovers the first card behind whole, as the card it is about to be. */
.nmo-deck-pile:has(> .nmo-deck-card.is-dragging, > .nmo-deck-card.is-gone) > .nmo-deck-card.is-behind-1 { clip-path: none; }
.nmo-deck-pile:has(> .nmo-deck-card.is-dragging, > .nmo-deck-card.is-gone) > .nmo-deck-card.is-behind-1 .nmo-deck-face {
  background: var(--bg-elev); border-color: rgba(255, 255, 255, .16); box-shadow: none;
}
.nmo-deck-pile:has(> .nmo-deck-card.is-dragging, > .nmo-deck-card.is-gone) > .nmo-deck-card.is-behind-1 .nmo-deck-sheen { display: block; }

/* The card's one face sits in this box. It turned over until #374, when a swipe up began opening his full
   profile instead and the back went. */
.nmo-deck-inner { position: relative; height: 100%; }
/* ═══ ONE FACE IS THE DESIGN'S CARD ═══ (#308)
   A tonal surface, 24px corners, a hairline border and a deep shadow, with a 1px
   sheen along its top edge. The surface and the sheen stay still; the page inside
   (`.nmo-deck-scroll`) scrolls a long answer, and never the page around it. */
.nmo-deck-face {
  position: absolute; inset: 0;
  height: 100%; overflow: hidden;
  box-sizing: border-box;
  background: var(--bg-elev);
  border: 1px solid rgba(255, 255, 255, .16); border-radius: 28px;
  box-shadow: none;
}
:root[data-theme="light"] .nmo-deck-face { border-color: var(--border); box-shadow: 0 8px 24px rgba(0, 0, 0, .12); }
.nmo-deck-sheen {
  position: absolute; left: 24px; right: 24px; top: 0; height: 1px; z-index: 2; pointer-events: none;
  background: linear-gradient(90deg, rgba(161, 92, 255, 0), rgba(161, 92, 255, .35), rgba(161, 92, 255, 0));
}
.nmo-deck-scroll {
  height: 100%; box-sizing: border-box; padding: 16px;
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  /* The words cut at the card's lower edge are what say there is more; a bar would also take
     the design's width on a desktop browser. */
  scrollbar-width: none;
}
.nmo-deck-scroll::-webkit-scrollbar { display: none; }
.nmo-deck-page { display: flex; flex-direction: column; gap: 14px; }

/* ═══ WHY HE IS ON THIS CARD ═══ (#382, rule 6)
   ONE LINE under the verdict box, and the only thing added to the front since #373. It is
   deliberately the quietest text on the card: it explains the card, it is not part of what
   the card says about him, and a reason set as loud as a figure would read as a claim. */
.nmo-deck-because {
  flex: none; padding-top: 2px; color: var(--text-faint);
  font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking);
}
/* A reason they asked FOR gets the app's own accent on its first word's weight; a reason they
   asked to see less of stays plain. Both are dim: neither is news about the trader. */
.nmo-deck-because[data-nmo-because="more"] { color: var(--text-dim); }

/* ═══ THE QUESTION SLIDING UP OVER THE DECK ═══ (#382, rule 3; design v60 by #433)
   v60's sheet (phones #ak1 and #ak2, the .ask-* rules): the highest surface with 28px top corners and a
   faint wash at the top saying who is speaking — violet for a preference, red for the warning — a
   handle, a small line with a dot, the card's own pill, a 24/32 question, the evidence as faces over a
   sentence, and two full-width 56px round buttons side by side. The colours are the dark block's
   further down; these are the shapes, which hold in both themes.

   ON THE SCREEN'S BOTTOM EDGE, NOT THE DECK'S (#394). The layer is on the page's body, fixed to the
   screen, as the trader panel's sheet is. Drawn inside `.nmo-deck` it ended where the deck's box
   did — 47px above the bottom at 390 by 844 with a 34px home bar, inset 13px each side, header and
   tabs undimmed — which is the band under the sheet in the owner's picture. 99 is the copy sheet's
   scrim: over the app's own `#session-overlay` (80), under its menus and drawers (200), and under
   the trader panel (100).

   ON THE BODY IT INHERITS NOTHING FROM THE SCREEN (#433). Its buttons were the plain `.nmo-cta`
   class, which has no rule anywhere, so on staging they drew as the browser's own grey boxes. Every
   part here has its own rule, and the dark block lists the layer among the doors that get the
   screen's colours and type roles. The bottom padding is v60's 34px, which is the home bar's own
   height on the phone the design is drawn for, and the bar's height wherever a phone reports more. */
.nmo-deck-asklayer {
  position: fixed; inset: 0; z-index: 99;
  display: flex; align-items: flex-end; justify-content: center;
  --nmo-v14-outline: rgba(255, 255, 255, .28);
}
:root[data-theme="light"] .nmo-deck-asklayer { --nmo-v14-outline: var(--border); }
.nmo-deck-askscrim { position: absolute; inset: 0; background: rgba(0, 0, 0, .62); }
:root[data-theme="light"] .nmo-deck-askscrim { background: rgba(0, 0, 0, .25); }
.nmo-deck-asksheet {
  /* The copy sheet's width: the whole of a phone, and a column rather than a banner on a desk. */
  position: relative; z-index: 1; width: 100%; max-width: 480px; box-sizing: border-box;
  display: block;
  padding: 10px 20px max(34px, env(safe-area-inset-bottom, 0px));
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--accent) 14%, transparent) 0,
      color-mix(in srgb, var(--accent) 4%, transparent) 64px, transparent 110px) no-repeat,
    var(--bg-elev);
  color: var(--text);
  border: 1px solid var(--border); border-bottom: 0;
  border-radius: 28px 28px 0 0;
  box-shadow: 0 -8px 24px rgba(0, 0, 0, .12);
  font-family: var(--font-ui, inherit);
}
.nmo-deck-asklayer.is-never .nmo-deck-asksheet {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--danger) 14%, transparent) 0,
      color-mix(in srgb, var(--danger) 4%, transparent) 64px, transparent 110px) no-repeat,
    var(--bg-elev);
}
/* The handle is a real button: it is one of the ways out, and a way out only a mouse can find
   is not a way out on a phone. It is drawn as v60's 32 by 4 bar 10px under the sheet's top edge,
   with its 24px touch reaching up to that edge, and the next line 20px under the bar. */
.nmo-deck-askgrab {
  display: block; width: 64px; height: 24px; padding: 0; margin: -10px auto 10px;
  border: 0; background: none; cursor: pointer;
}
.nmo-deck-askgrab::before {
  content: ""; display: block; width: 32px; height: 4px; margin: 10px auto;
  border-radius: 2px; background: var(--nmo-v14-outline);
}
/* Who is speaking: "Genie noticed", or "Before you copy" on the warning, after a small glowing dot. */
.nmo-deck-askkicker {
  display: flex; align-items: center; gap: 8px; margin: 0 0 12px;
  color: var(--text-dim); font: var(--m3-label-medium); letter-spacing: .5px;
}
.nmo-deck-askdot {
  flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 90%, transparent);
}
.nmo-deck-asklayer.is-never .nmo-deck-askdot {
  background: var(--danger); box-shadow: 0 0 8px color-mix(in srgb, var(--danger) 90%, transparent);
}
/* The card's own pill (`badgePill`), styled by the card's own rules, which list this holder too. */
.nmo-deck-askpill { display: flex; }
.nmo-deck-asktitle {
  margin: 14px 0 10px; color: var(--text);
  font: var(--m3-headline-small); letter-spacing: 0; text-wrap: balance;
}
/* The evidence, two lines: the faces, then the sentence (owner: "the icon and reasoning into 2 different lines"). */
.nmo-deck-askwho { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0 0 24px; }
.nmo-deck-askfaces { display: flex; flex: none; padding-left: 8px; }
.nmo-deck-askface {
  flex: none; width: 30px; height: 30px; box-sizing: border-box; margin-left: -8px; overflow: hidden;
  display: grid; place-items: center;
  border: 2px solid var(--bg-elev); border-radius: 999px;
  color: #fff; font: 600 10px var(--font-ui, inherit);
  background: linear-gradient(135deg, #3a1c7a, #a15cff);
}
.nmo-deck-askface:nth-child(2) { background: linear-gradient(135deg, #2c2a4a, #6f63c8); }
.nmo-deck-askface:nth-child(3) { background: linear-gradient(135deg, #1f3a3a, #3fb58f); }
.nmo-deck-askface-img { display: block; width: 100%; height: 100%; object-fit: cover; }
.nmo-deck-askwhy {
  margin: 0; color: var(--text-dim);
  font: var(--m3-body-medium); letter-spacing: .25px;
}
.nmo-deck-askname { font-weight: 500; color: var(--text); }
/* Two full-width round buttons, side by side: the quiet one outlined on the left, the other filled. */
.nmo-deck-askrow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.nmo-deck-askbtn {
  -webkit-appearance: none; appearance: none; box-sizing: border-box; min-width: 0; width: 100%; height: 56px; margin: 0;
  display: inline-flex; align-items: center; justify-content: center; padding: 0 12px;
  border: 0; border-radius: 999px; cursor: pointer; text-align: center;
  font: var(--m3-title-medium); letter-spacing: .15px;
  -webkit-tap-highlight-color: transparent;
}
.nmo-deck-askbtn.is-outlined { background: transparent; color: var(--text); border: 1px solid var(--nmo-v14-outline); }
.nmo-deck-askbtn.is-filled { background: var(--accent); color: var(--on-accent, #fff); }
.nmo-deck-askbtn.is-warning { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }
/* EQUAL WEIGHT ON THE WARNING (#454). Both answers are outlined; the never one keeps red words and a
   red edge, so it reads as the cautious answer without being the loud one that pulls the thumb. */
.nmo-deck-askbtn.is-outlined.is-warning-words {
  color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}
/* The hover shade only under a real mouse: the sheet rises where a thumb just pressed Pass or Copy, and a phone can
   keep that press as a hover on whatever is under it now. */
@media (hover: hover) and (pointer: fine) {
  .nmo-deck-askbtn:hover { box-shadow: inset 0 0 0 999px color-mix(in srgb, currentColor 8%, transparent); }
}
.nmo-deck-askbtn:active { box-shadow: inset 0 0 0 999px color-mix(in srgb, currentColor 12%, transparent); }
/* A Chinese line breaks at its punctuation or its spaces, never inside a word (#430's rule): on the 390 phone the
   warning's reason broke 仓位 in two. `anywhere` still breaks a line that has nowhere else to go. */
:root:lang(zh) .nmo-deck-askwhy { word-break: keep-all; overflow-wrap: anywhere; }
.nmo-deck-askbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* It rises rather than appearing. Under reduced motion the class is never added and the sheet
   is simply there, which is what that setting asks for. */
.nmo-deck-asklayer.is-rising .nmo-deck-asksheet { animation: nmo-deck-askrise 220ms cubic-bezier(.05, .7, .1, 1); }
.nmo-deck-asklayer.is-rising .nmo-deck-askscrim { animation: nmo-deck-askfade 220ms linear; }
@keyframes nmo-deck-askrise { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes nmo-deck-askfade { from { opacity: 0; } to { opacity: 1; } }

/* ═══ WHAT GENIE HAS LEARNED, AS CHIPS ═══ (#382, rule 4)
   Filled means "more of these"; crossed through means "fewer". The crossing is drawn on the
   words alone, so the chip stays readable and the state is visible at a glance — and the chip
   also SAYS which side it is on in words, because a line through text is a rule a person has
   to already know, and a screen reader gets nothing at all from it. */
.nmo-learned { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 8px; }
/* The same pill the amount chips on the copy sheet are, so a chip here reads as the same kind of
   thing and is obviously something to press. Measured at 390 with the crossed-out state drawn on
   a transparent surface: the words alone read as struck-through text and not as a control at all,
   so the outline is the app's own line rather than the fainter one first used here. */
.nmo-learned-chip {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 0 14px; border-radius: 999px; cursor: pointer;
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
  border: 1px solid var(--nmo-line); background: transparent; color: var(--text);
  transition: background-color 120ms var(--ease, ease), border-color 120ms var(--ease, ease);
}
/* Filled means "more of these". It KEEPS ITS OUTLINE: with the border taken away, the dark green
   on a dark surface read at 390 as plain text with no pill at all, and the one state the design
   calls "filled" was the one that looked like nothing. */
.nmo-learned-chip.is-more {
  background: var(--nmo-v14-green-chosen); border-color: var(--nmo-v14-green); color: var(--text);
}
.nmo-learned-chip.is-fewer { background: var(--nmo-card-bg-2); }
.nmo-learned-chip.is-fewer .nmo-learned-words { text-decoration: line-through; opacity: .75; }
.nmo-learned-side {
  color: var(--text-faint); text-transform: lowercase;
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
}
.nmo-learned-chip.is-more .nmo-learned-side { color: var(--text-dim); }

/* ═══ THE THREE ROUND BUTTONS ═══ (#308)
   The design's values: 16px under the pile, three outlined circles 28px apart, 60, 48
   and 60 across, every one the same outline on the card's own surface, so none is the
   highlighted one. The design's two drawn card edges under the card are gone (#334): the
   real next cards behind it are the pile now, and there are none behind the last card.
   A 60px button is 62 across its outline (content-box), as the design draws it. */
.nmo-deck-actions {
  display: flex; align-items: center; justify-content: center; gap: 28px;
  flex: none; margin: 16px 13px 0;
}
.nmo-deck-act {
  flex: none; display: grid; place-items: center; box-sizing: content-box;
  width: 60px; height: 60px; padding: 0; border-radius: 50%;
  border: 1px solid var(--nmo-v14-outline); background: var(--bg-elev); color: var(--text);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.nmo-deck-act.is-profile { width: 48px; height: 48px; color: var(--text-dim); }
.nmo-deck-act-icon { display: block; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.nmo-deck-act.is-profile .nmo-deck-act-icon { stroke-width: 1.8; }
@media (hover: hover) {
  .nmo-deck-act:not([aria-disabled="true"]):hover { background: var(--bg-elev-2); }
}
.nmo-deck-act:not([aria-disabled="true"]):active { background: var(--bg-elev-2); }
.nmo-deck-act[aria-disabled="true"] { opacity: .65; cursor: default; }
.nmo-deck-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ═══ THE TWO STAMPS ═══════════════════════════════════════════════════════
   On the CARD and not on a face, so they do not turn over with it. `--stamp` is
   set from the drag as a number between nought and one, which is what makes the
   stamp fade in with the finger rather than switch on at a threshold. */
.nmo-deck-stamp {
  position: absolute; top: 22px; z-index: 3;
  padding: 4px 12px; border-radius: 10px;
  font: 500 28px/32px var(--font-ui); letter-spacing: 2px;
  opacity: 0; pointer-events: none;
  transform: rotate(-12deg);
}
/* The deck design's COPY stamp (#308): its green on a dark green wash with a soft glow. PASS is the
   same stamp in the design's red. */
.nmo-deck-stamp.is-copy {
  left: 22px; color: var(--nmo-v14-green); border: 3px solid var(--nmo-v14-green);
  background: rgba(29, 58, 40, .55); box-shadow: 0 0 24px rgba(109, 213, 140, .35);
}
.nmo-deck-stamp.is-pass {
  right: 22px; color: var(--nmo-v14-red-rank); border: 3px solid var(--nmo-v14-red-rank); transform: rotate(12deg);
  background: rgba(65, 14, 11, .55); box-shadow: 0 0 24px rgba(255, 180, 171, .3);
}
.nmo-deck-card.is-copying .nmo-deck-stamp.is-copy { opacity: var(--stamp, 1); }
.nmo-deck-card.is-passing .nmo-deck-stamp.is-pass { opacity: var(--stamp, 1); }

/* WHERE IN THE PILE THIS CARD IS, SAID OUT LOUD. It stays out of the picture rather
   than out of the page. Not `display: none`, which would take it away from a screen
   reader too. */
.nmo-deck-where {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

/* The honest states: one sentence, and where there is somewhere to go, a way on. */
.nmo-deck-said {
  margin: 24px 8px 0; text-align: center; color: var(--text-faint);
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}
/* THE WAY ON IS THE APP'S TONAL BUTTON (#456). It was drawn with `nmo-cta`, a class no rule styles, and on
   4536 it came out as the browser's own grey box. This is v60's `.btn.tonal.sm`: 40px, fully round, the
   selected container and its ink, no outline. The light theme has no selected role, so it mixes the accent. */
.nmo-deck-toTraders {
  -webkit-appearance: none; appearance: none; box-sizing: border-box;
  align-self: center; display: inline-flex; align-items: center; justify-content: center;
  height: 40px; margin: 16px auto 0; padding: 0 16px; border: 0; border-radius: 999px; cursor: pointer;
  background: var(--nmo-selected, color-mix(in srgb, var(--accent) 16%, transparent));
  color: var(--nmo-on-selected, var(--text));
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking); white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}
.nmo-deck-toTraders:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .nmo-deck-toTraders:hover { background: color-mix(in srgb, var(--text) 8%, var(--nmo-selected, var(--accent-dim))); }
}
/* A pass that came back says when it was (#456): one quiet line under the card's sentence, nothing louder. */
.nmo-deck-passedon {
  margin: 8px 0 0; color: var(--text-faint);
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
}

/* ═══ THE GESTURE CARD, SHOWN ONCE ═════════════════════════════════════════
   Over the deck and inside the tab — NOT on the body. It explains the tab it is
   drawn on, so it belongs to that tab: putting it on the body would leave it up
   over the Feed if a person switched tabs while it was showing. */
.nmo-deck-hint {
  position: absolute; inset: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  border-radius: 22px;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(2px);
}
.nmo-deck { position: relative; }
.nmo-deck-hintbox {
  display: flex; flex-direction: column; gap: 10px; align-items: center;
  padding: 20px 18px; margin: 0 18px;
  border-radius: 18px; background: var(--bg-elev-2);
  border: 1px solid var(--border);
  text-align: center;
}
.nmo-deck-hintline {
  margin: 0; color: var(--text-dim);
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}

/* ═══ THE ENTRANCE, IN THE CANVAS NOTE'S ORDER ═════════════════════════════
   The wash fades up under the stroke while the stroke draws itself (that half is
   a clip in the chart's own units that grows in code, #289 — never a dash, whose
   pattern a browser lays out in screen pixels against a length measured in chart
   units); the end dot lands after it; the coins pop one at a time from their own
   delay; the copiers' line fades in last. Every delay is a custom property set in
   code from one set of constants, so the order lives in one file and not two.
   Each one fills BACKWARDS only: hidden until its moment, and once it has played
   the mark is back on its own resting style, so a finished card is exactly the
   card reduced motion paints and not a copy still held by a finished animation. */
@keyframes nmo-deck-fadeup { from { opacity: 0; } to { opacity: 1; } }
@keyframes nmo-deck-land { from { opacity: 0; transform: scale(.2); } to { opacity: 1; transform: scale(1); } }
.nmo-deck-card.is-entering .nmo-deck-front .nmo-balance-hero .nmo-tv-wash { animation: nmo-deck-fadeup 1500ms ease-out backwards; }
.nmo-deck-card.is-entering .nmo-deck-front .nmo-balance-hero .nmo-tv-dot { animation: nmo-deck-land 220ms ease-out 1500ms backwards; transform-origin: center; }

/* ═══ AND NONE OF IT UNDER REDUCED MOTION ══════════════════════════════════
   The final state immediately, which is what that setting asks for. Every rule
   above is an addition to a card that is already correct without it, so turning
   them all off leaves the card complete rather than half-drawn. The gestures are
   untouched: a swipe still copies, passes and opens his profile — it just does not move. */
@media (prefers-reduced-motion: reduce) {
  .nmo-deck-card, .nmo-deck-card.is-gone { transition: none; }
  .nmo-deck-card.is-entering .nmo-deck-front .nmo-balance-hero .nmo-tv-wash,
  .nmo-deck-card.is-entering .nmo-deck-front .nmo-balance-hero .nmo-tv-dot { animation: none; }
  .nmo-deck-front .nmo-balance-hero .nmo-tv-stroke { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; clip-path: none !important; transition: none !important; }
}

/* ═══ THE TRADER PAGE'S FOUR QUESTIONS ════════════════════════════════════════
   (#188's page, #191's screen. Material 3 as the rest of this plugin already is:
   one elevated card per block, one big figure in each, and no colour that is not
   carrying a meaning — green is good, amber is worth a look, red is bad.)

   EVERY WIDTH HERE IS A SHARE OR A MINIMUM, never a fixed pixel column. The page
   is drawn at 390 and the rule the owner set is that nothing crosses an edge; a
   column measured in pixels is a column that fits until a translation is longer
   than the English. */

/* The app's own caution colour, beside the green and the red that already have
   names here. `--nmo-late` is the same colour under a different meaning (a late
   reading), so it is not borrowed: one class, one meaning. */
.nmo-warn { color: var(--warn); }

/* The range's change, beside the balance rather than under it. Baseline-aligned to
   the figure and a size that cannot compete with it: the figure is the headline and
   this is what it did over the span on the pills. */
.nmo-tv-range {
  margin-left: 10px; font-family: var(--font-ui); font-variant-numeric: tabular-nums;
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
  white-space: nowrap;
}


/* ── the verdict, in a card of its own ────────────────────────────────────── */
/* ═══ A CARD HAS TO BE VISIBLE AGAINST THE SHEET IT IS ON ═══
   The sheet's own background IS `--bg-elev-1`, so every block drawn in that colour
   was a card nobody could see — measured in the browser at 390 on 11 Sep 2026, and
   the pictures show it: three headline figures and a chart with no boundary between
   them. `--bg-elev-2` is the next step up and is defined in both themes. */
.nmo-verdict { background: var(--bg-elev-2); border-radius: var(--radius-md); padding: 14px; margin: 12px 0 4px; }
.nmo-verdict-head { margin: 0 0 6px; font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking); }
.nmo-verdict-says { margin: 0; font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking); }
/* Grey for every state that is not a judgement, so nothing here can be read as
   one by its colour: still working, refused, or nothing to judge on. */
.nmo-verdict-says.is-quiet { color: var(--text-faint); }

/* ── the four compact copier questions, which scroll sideways at 390 ──────── */
.nmo-dd-tabs {
  flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
  /* The row is dragged, so the names keep their full length instead of being
     squeezed to fit — five squeezed names are five names nobody can read. */
  gap: 16px;
  scroll-padding-inline: 12px;
}
.nmo-dd-tabs::-webkit-scrollbar { display: none; }
.nmo-dd-tabs .nmo-coin-tab { flex: none; white-space: nowrap; }
.nmo-profile-tabs.nmo-dd-tabs {
  gap: 4px; padding: 4px 0 0; margin-bottom: 4px; border-bottom: 0;
}
.nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab {
  position: relative; isolation: isolate; display: inline-grid; place-items: center;
  min-width: 48px; min-height: 48px; height: 48px; padding: 0 12px; margin: 0;
  border: 0; background: none; color: var(--text-faint);
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
.nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab::before {
  content: ""; position: absolute; z-index: -1; inset: 8px 0;
  border-radius: var(--radius-lg); background: transparent;
}
.nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on { color: var(--text); border-bottom: 0; }
.nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on::before { background: var(--bg-elev-3); }
.nmo-dd-panel { padding-top: 8px; }

/* ── one block: a heading, a picture, one sentence ────────────────────────── */
.nmo-dd { display: grid; gap: 8px; }
.nmo-dd-card { background: var(--bg-elev-2); border-radius: var(--radius-md); padding: 12px; }
.nmo-dd-head { margin: 0 0 6px; font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking); }
.nmo-dd-figure {
  font: var(--m3-title-large); letter-spacing: var(--m3-title-large-tracking);
  font-variant-numeric: tabular-nums;
  /* A long figure wraps inside the card rather than widening it. */
  overflow-wrap: anywhere;
}
.nmo-dd-figure-note { color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); padding: 0 0 4px; }
.nmo-dd-says { margin: 6px 0 0; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-absent { margin: 4px 0 0; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-caveat { margin: 6px 0 0; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }

/* ── the compact record overview ─────────────────────────────────────────── */
/* Three equal measures belong together: each explains the evidence under the first number,
   and keeping them on one surface makes the comparison card the next thing a reader sees. */
.nmo-dd-overview {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
  background: var(--bg-elev-2); border-radius: var(--radius-md); padding: 10px;
}
.nmo-dd-overview-metric { min-width: 0; display: grid; align-content: start; gap: 2px; }
.nmo-dd-overview .nmo-dd-figure {
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
  white-space: nowrap; overflow-wrap: normal;
}
.nmo-dd-overview-label {
  color: var(--text-faint); font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  overflow-wrap: anywhere;
}
/* The artifact's first record group runs almost edge-to-edge on a phone: 356 by 68 at 390px.
   The profile keeps its ordinary 16px inner rhythm elsewhere; this one measured comparison row
   deliberately reaches across that second inset without widening past a narrower reader's screen. */
@media (max-width: 640px) {
  .nmo-dd-overview {
    box-sizing: border-box;
    width: min(356px, calc(100vw - 34px)); height: 68px;
    margin-inline: -15px;
  }
}

/* ── the month calendar (#303) ──────────────────────────────────────────────── */
/* The month and its calendar button on the left, that month's realised total on the right, then
   a Monday-to-Sunday grid of rounded days carrying their own amounts. */
.nmo-dd-calendar-card { padding: 12px; }
.nmo-dd-cal-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.nmo-dd-cal-title { margin: 0; min-width: 0; }
/* A 48px press target whose painted part is only the icon and the month: the negative margins
   give the press its height without pushing the grid down. */
.nmo-dd-cal-step {
  display: inline-flex; align-items: center; gap: 6px; min-height: 48px; margin: -12px 0 -12px -12px; padding: 0 12px;
  border: 0; border-radius: 999px; background: transparent; color: var(--text);
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking); text-transform: none; cursor: pointer;
}
.nmo-dd-cal-step:disabled { cursor: default; color: var(--text); }
.nmo-dd-cal-icon { flex: none; color: var(--text-dim); }
.nmo-dd-cal-total { flex: none; overflow-wrap: anywhere; font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking); }
.nmo-dd-cal-total:not(.nmo-good):not(.nmo-bad) { color: var(--text-faint); }
/* Seven equal columns of whatever is left after the card's padding. `minmax(0, 1fr)` rather than
   `1fr`: a column may never grow to fit its text, so a long amount cannot widen one day. */
.nmo-dd-cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; padding: 4px 0 0; }
.nmo-dd-cal-day { text-align: center; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
/* Square, by its own width, with the amount centred inside it. */
.nmo-dd-cell {
  display: flex; align-items: center; justify-content: center; aspect-ratio: 1; min-width: 0; overflow: hidden;
  border-radius: 8px; background: var(--nmo-card-bg-2);
}
/* The days of the week before the 1st and after the last day: no date, nothing drawn. */
.nmo-dd-cell.is-pad { background: transparent; }
/* A day nobody can speak for, or one still to come: an empty outlined cell, so it can never be
   mistaken for the filled grey of a day he closed nothing. */
.nmo-dd-cell:is(.is-not-covered, .is-future) { background: transparent; box-shadow: inset 0 0 0 1px var(--nmo-card-bg-2); }
.nmo-dd-cell-amount { font: 500 10px/12px var(--font-ui); letter-spacing: -0.1px; white-space: nowrap; }
.nmo-dd-cell.is-up { background: color-mix(in srgb, var(--nmo-up) 16%, transparent); color: var(--nmo-up); }
.nmo-dd-cell.is-down { background: color-mix(in srgb, var(--nmo-down) 16%, transparent); color: var(--nmo-down); }
.nmo-dd-cell.is-lucky { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
.nmo-dd-cell.is-flat { color: var(--text-dim); }
.nmo-dd-calendar-card .nmo-dd-says { margin-top: 8px; color: var(--text-dim); }
.nmo-dd-cal-note { margin: 4px 0 0; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }

/* ── a bar of bands, and the rows that name them ──────────────────────────── */
.nmo-dd-bar { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--nmo-card-bg-2); margin: 6px 0 2px; }
.nmo-dd-bar-part.is-up { background: var(--nmo-up); }
.nmo-dd-bar-part.is-down { background: var(--nmo-down); }
.nmo-dd-bar-part.is-neutral { background: color-mix(in srgb, var(--text) 45%, transparent); }
/* Each band a shade lighter than the one before it, so five slices in two colours
   are still five slices. */
.nmo-dd-bar-part + .nmo-dd-bar-part { opacity: 0.78; }
.nmo-dd-bar-part + .nmo-dd-bar-part + .nmo-dd-bar-part { opacity: 0.56; }
.nmo-dd-rows { display: grid; gap: 4px; padding-top: 4px; }
/* The name takes what is left, the two figures take what they need: a fixed
   column for a name is a name cut off in the other language. */
.nmo-dd-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; align-items: baseline; font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-row-name { min-width: 0; overflow-wrap: anywhere; }
.nmo-dd-row-count, .nmo-dd-row-share { font-variant-numeric: tabular-nums; color: var(--text-faint); }

/* ── the four test cards ──────────────────────────────────────────────────── */
.nmo-dd-tests {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  grid-auto-rows: 1fr;
  gap: 6px;
}
.nmo-dd-test {
  min-width: 0;
  min-height: 72px;
  box-sizing: border-box;
  background: var(--bg-elev-2);
  border-radius: var(--radius-md);
  padding: 8px 10px;
}
.nmo-dd-test-title { display: flex; gap: 4px; align-items: baseline; min-width: 0; }
.nmo-dd-test-name { min-width: 0; font: var(--m3-label-medium); letter-spacing: var(--m3-label-medium-tracking); }
.nmo-dd-test-mark { flex: none; font: var(--m3-label-medium); line-height: 1; }
.nmo-dd-test.is-pass .nmo-dd-test-mark { color: var(--nmo-up); }
.nmo-dd-test.is-fail .nmo-dd-test-mark { color: var(--nmo-down); }
.nmo-dd-test.is-grey .nmo-dd-test-mark { color: var(--text-faint); }
.nmo-dd-test-why {
  margin: 4px 0 0;
  overflow-wrap: anywhere;
  color: var(--text-faint);
  font: var(--m3-body-small);
  letter-spacing: var(--m3-body-small-tracking);
}

/* ── the warning signs ────────────────────────────────────────────────────── */
.nmo-dd-warns { display: grid; gap: 6px; }
.nmo-dd-warn { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; align-items: center; font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
.nmo-dd-dot.is-good { background: var(--nmo-up); }
.nmo-dd-dot.is-warn { background: var(--warn); }
.nmo-dd-dot.is-bad { background: var(--nmo-down); }
.nmo-dd-dot.is-grey { background: var(--text-faint); }
.nmo-dd-warn-name { min-width: 0; overflow-wrap: anywhere; }
.nmo-dd-warn-count { font-variant-numeric: tabular-nums; color: var(--text-faint); }

/* ── the three sliders ────────────────────────────────────────────────────── */
.nmo-dd-slider { display: grid; grid-template-columns: minmax(0, 5rem) minmax(0, 1fr) auto; gap: 8px; align-items: center; padding: 4px 0; font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-slider-name { min-width: 0; color: var(--text-faint); }
.nmo-dd-rail { position: relative; height: 4px; border-radius: 999px; background: var(--nmo-card-bg-2); }
/* The knob is centred on its place along the rail, so a trader at the very end
   of the scale has half a knob over the end rather than a knob past it. */
.nmo-dd-knob { position: absolute; top: 50%; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 50%; background: var(--text); }
.nmo-dd-slider-value { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── all the numbers ──────────────────────────────────────────────────────── */
.nmo-dd-num { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: baseline; padding: 5px 0; border-top: 1px solid var(--nmo-line); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-num:first-of-type { border-top: 0; }
.nmo-dd-num-name { min-width: 0; overflow-wrap: anywhere; }
.nmo-dd-num-value { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The reason a row has no figure, under the two of them and across both columns,
   because "not provided by the service" is a sentence and not a value. */
.nmo-dd-num-why { grid-column: 1 / -1; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }

/* ── him against you ──────────────────────────────────────────────────────── */
.nmo-dd-bars { display: grid; gap: 5px; padding: 4px 0 0; }
.nmo-dd-bar-row { display: grid; grid-template-columns: minmax(0, 3rem) minmax(0, 1fr) auto; gap: 8px; align-items: center; font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-bar-name { min-width: 0; color: var(--text-faint); overflow-wrap: anywhere; }
.nmo-dd-bar-rail { height: 10px; border-radius: 999px; background: var(--nmo-card-bg-2); overflow: hidden; }
.nmo-dd-bar-fill { height: 100%; border-radius: 999px; }
.nmo-dd-bar-fill.is-up { background: var(--nmo-up); }
.nmo-dd-bar-fill.is-down { background: var(--nmo-down); }
/* His bar is the quieter of the two: the one a person is reading this card for is
   their own. */
.nmo-dd-bar-fill.is-his { opacity: 0.6; }
.nmo-dd-bar-value { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── the ring ─────────────────────────────────────────────────────────────── */
.nmo-dd-catch { display: flex; align-items: center; gap: 12px; }
.nmo-dd-catch .nmo-dd-says { margin: 0; flex: 1 1 auto; }
.nmo-dd-ring { display: block; flex: none; width: 80px; height: 80px; margin: 0; }
.nmo-dd-ring-back { stroke: var(--nmo-card-bg-2); }
.nmo-dd-ring-fill { stroke: var(--nmo-up); stroke-linecap: round; }

/* ── the three meters, on one scale ───────────────────────────────────────── */
.nmo-dd-meter { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 8px; align-items: center; padding: 5px 0; }
.nmo-dd-meter-name { min-width: 0; font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); overflow-wrap: anywhere; }
/* One scale under all three, so the three readings are comparable at a glance —
   that is the whole point of drawing them together. */
.nmo-dd-scale { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; height: 5px; }
.nmo-dd-zone { border-radius: 999px; }
.nmo-dd-zone.is-good { background: color-mix(in srgb, var(--nmo-up) 55%, transparent); }
.nmo-dd-zone.is-warn { background: color-mix(in srgb, var(--warn) 55%, transparent); }
.nmo-dd-zone.is-bad { background: color-mix(in srgb, var(--nmo-down) 55%, transparent); }
.nmo-dd-meter-value { font-variant-numeric: tabular-nums; text-align: right; font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-dd-meter-value.is-grey { color: var(--text-faint); }

/* ── the six-sided chart's corner figures ─────────────────────────────────── */
/* The size is set on the element, in the box's own units — see `LABEL_SIZE` in
   `traderReport.ts` for why a pixel size here would drift from the shape. */
.nmo-report-corner { fill: var(--text-faint); }
.nmo-report-corner.nmo-good { fill: var(--nmo-up); }
.nmo-report-corner.nmo-bad { fill: var(--nmo-down); }
.nmo-report-corner.is-hollow { fill: var(--text-faint); opacity: 0.55; }

/* ── the sparkle in the top bar, and the word beside the sliders ──────────── */
/* The touch target is the button, 48 square, exactly as the bell and the share
   beside it — the drawing inside stays 24. */
.nmo-profile-sparkle .nmo-profile-act-ico { fill: currentColor; stroke: none; }
.nmo-profile-gear-word { margin-left: 6px; font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking); }
/* With a word in it the button is no longer a circle. It still may not grow at
   the expense of Unfollow, which is the wide one. */
.nmo-profile-gear:has(.nmo-profile-gear-word) { flex: none; padding: 0 14px; min-height: 48px; border-radius: 24px; }

/* ═══ THE THREE LATER QUESTIONS, TO DESIGN V14'S OWN VALUES ═══ (#292, owner 13 Sep 2026)

   "Should I copy?", "How does he trade?" and "What would I make?" were drawn by eye
   from pictures, with the app's general Material 3 sizes. The owner's direction is
   that the design wins on this screen, so every size and colour below is a value
   from design-v14-source.html's style block, and the rule it came from is named
   beside it. Scoped to `.nmo-dd-v14`, which `traderQuestions.ts` puts on these
   three tabs only, so "Is he good?" keeps #291's rules.

   NOT HERE, ON PURPOSE: the page and card colours, the question pills and the
   Follow button are #291's. Where a rule below needs one of the design's shared
   colours it reads #291's own variables (`--nmo-v14-page`, `--nmo-v14-card`,
   `--nmo-v14-tag`, `--nmo-v14-outline`, `--nmo-v14-red-rank`, `--nmo-v14-primary`),
   each falling back to the design's value or today's token, so the two branches
   agree on one colour the moment they fold. */

/* The design's own inks for these tabs. The design is dark only; in the light theme they
   take the app's light green, caution and loss colours, as #291's variables do. */
.nmo-dd-v14, .nmo-profile-dock {
  --nmo-v14-green: #1ee07f; --nmo-v14-amber: rgba(255, 255, 255, .66); --nmo-v14-dot-red: #ff4f64; --nmo-v14-him: #a15cff;
}
:root[data-theme="light"] :is(.nmo-dd-v14, .nmo-profile-dock) {
  --nmo-v14-green: var(--ok); --nmo-v14-amber: var(--warn); --nmo-v14-dot-red: var(--err);
  --nmo-v14-him: color-mix(in srgb, var(--ok) 70%, var(--bg));
}

/* ── the pinned questions, with his name and the verdict over them (frames 2-4, .top) ── */
.nmo-dd-pin-sentinel { height: 0; }
/* The question row sticks 32px down, and the name-and-verdict line fills those 32px
   above it: .top's 12px over a 20px line. The rest of .top's 8px and .body's 2px are
   already in the question row, whose 48px buttons paint their pill a few pixels in. */
.nmo-dd-sticky {
  position: sticky; top: 32px; z-index: 3;
  margin-inline: calc(-1 * var(--nmo-sheet-pad-x, 16px)); padding-inline: var(--nmo-sheet-pad-x, 16px);
  background: var(--nmo-v14-page, var(--bg-elev-1, var(--bg)));
}
.nmo-dd-pinned {
  position: absolute; left: 0; right: 0; bottom: 100%; height: 32px; box-sizing: border-box;
  display: flex; align-items: center; gap: 10px; padding: 12px var(--nmo-sheet-pad-x, 16px) 0;
  background: inherit; visibility: hidden; opacity: 0; pointer-events: none;
}
.nmo-dd-sticky.is-stuck .nmo-dd-pinned { visibility: visible; opacity: 1; }
/* The name is never the part that gives way; the verdict line is cut instead. */
.nmo-dd-pinned-name {
  flex: none; max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 500 16px/20px var(--font-ui); letter-spacing: normal; color: var(--text);
}
/* .src: the verdict's own sentence, cut to one line rather than reworded. */
.nmo-dd-pinned-verdict {
  flex: 1 1 0; min-width: 0; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 11px/14px var(--font-ui); letter-spacing: normal; color: var(--text-faint);
}

/* ── every card on these tabs (.body gap 12, .card, .card h3, .one, .row, .src) ── */
.nmo-dd-v14 { gap: 12px; }
.nmo-dd-v14 .nmo-dd-card { display: flex; flex-direction: column; gap: 10px; padding: 14px 16px; border-radius: 16px; }
.nmo-dd-v14 .nmo-dd-head {
  margin: 0; font: 500 11px/16px var(--font-ui); letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-faint);
}
.nmo-dd-v14 .nmo-dd-says { margin: 0; font: 400 13px/18px var(--font-ui); letter-spacing: normal; color: var(--text-dim); }
.nmo-dd-v14 .nmo-dd-absent { margin: 0; font: 400 13px/18px var(--font-ui); letter-spacing: normal; color: var(--text-faint); }
.nmo-dd-card-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.nmo-dd-card-top > :first-child { flex: 1 1 auto; min-width: 0; }
.nmo-dd-card-top > :not(:first-child) { flex: none; }
.nmo-dd-v14 .nmo-dd-card-aside { margin: 0; text-align: right; font: 400 11px/14px var(--font-ui); letter-spacing: normal; color: var(--text-faint); }

/* ── should I copy? ── */
/* .tests, .t, .t .k, .t .v, and the marks' colours from .t.ok/.t.no */
.nmo-dd-v14 .nmo-dd-tests { gap: 8px; grid-template-rows: none; grid-auto-rows: auto; }
.nmo-dd-v14 .nmo-dd-test {
  min-height: 0; display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px; border-radius: 12px; background: var(--nmo-v14-tag, var(--bg-elev-2));
}
.nmo-dd-v14 .nmo-dd-test-title { gap: 4px; }
.nmo-dd-v14 .nmo-dd-test-name, .nmo-dd-v14 .nmo-dd-test-mark { font: 500 14px/20px var(--font-ui); letter-spacing: normal; color: var(--text); }
.nmo-dd-v14 .nmo-dd-test.is-pass .nmo-dd-test-mark { color: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-test.is-fail .nmo-dd-test-mark { color: var(--nmo-v14-red-rank, #ff4f64); }
.nmo-dd-v14 .nmo-dd-test.is-grey .nmo-dd-test-mark { color: var(--text-faint); }
.nmo-dd-v14 .nmo-dd-test-why { margin: 0; font: 400 12px/16px var(--font-ui); letter-spacing: normal; color: var(--text-dim); }
/* .say at 15px */
.nmo-dd-v14 .nmo-dd-advice { font: 400 15px/23px var(--font-ui); color: var(--text); text-wrap: balance; }
/* the warnings card: gap 2, its label row 6px above the rows; .warn, .warn .dot, .warn .c */
.nmo-dd-v14 .nmo-dd-warnings-card { gap: 2px; }
.nmo-dd-v14 .nmo-dd-warnings-card > .nmo-dd-card-top { margin-bottom: 6px; }
.nmo-dd-v14 .nmo-dd-warns { display: block; }
.nmo-dd-v14 .nmo-dd-warn {
  grid-template-columns: 12px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  padding: 7px 0; border-top: 1px solid var(--nmo-v14-rule, #2c2d2f); font: 400 13px/20px var(--font-ui); letter-spacing: normal;
}
.nmo-dd-v14 .nmo-dd-warn:first-child { border-top: 0; }
.nmo-dd-v14 .nmo-dd-dot { width: 10px; height: 10px; }
.nmo-dd-v14 .nmo-dd-dot.is-good { background: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-dot.is-warn { background: var(--nmo-v14-amber); }
.nmo-dd-v14 .nmo-dd-dot.is-bad { background: var(--nmo-v14-dot-red); }
.nmo-dd-v14 .nmo-dd-warn-name { color: var(--text); }
.nmo-dd-v14 .nmo-dd-warn-count { text-align: right; font-weight: 500; color: var(--text); }
.nmo-dd-v14 .nmo-dd-warn-count.is-warn { color: var(--nmo-v14-amber); }
.nmo-dd-v14 .nmo-dd-warn-count.is-bad { color: var(--nmo-v14-red-rank, #ff4f64); }
.nmo-dd-v14 .nmo-dd-warn-count.is-grey { color: var(--text-faint); }
.nmo-dd-v14 .nmo-dd-warn-missing { margin: 8px 0 0; font: 400 13px/18px var(--font-ui); color: var(--text-dim); }

/* ── how does he trade? ── */
/* the style card: gap 14, its 18px .head, and three .scale rows */
.nmo-dd-v14 .nmo-dd-style-card { gap: 14px; }
.nmo-dd-v14 .nmo-dd-figure { font: 500 22px/28px var(--font-ui); letter-spacing: normal; color: var(--text); }
.nmo-dd-v14 .nmo-dd-style-card > .nmo-dd-figure,
.nmo-dd-v14 .nmo-dd-size-card .nmo-dd-figure { font-size: 18px; line-height: 24px; }
.nmo-dd-v14 .nmo-dd-figure.nmo-good, .nmo-dd-v14 .nmo-dd-figure .nmo-good { color: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-figure.nmo-bad, .nmo-dd-v14 .nmo-dd-figure .nmo-bad { color: var(--nmo-v14-red-rank, #ff4f64); }
.nmo-dd-v14 .nmo-dd-slider {
  grid-template-columns: 86px minmax(0, 1fr); gap: 10px; align-items: center; padding: 0;
  font: 400 13px/18px var(--font-ui); letter-spacing: normal;
}
.nmo-dd-v14 .nmo-dd-slider-name { font: 500 13px/18px var(--font-ui); color: var(--text); }
.nmo-dd-slider-scale { min-width: 0; }
/* .track and .track i: the knob is ringed in the card's own colour so the rail stops short of it */
.nmo-dd-v14 .nmo-dd-rail { height: 6px; border-radius: 3px; background: var(--nmo-v14-tag, var(--bg-elev-2)); }
.nmo-dd-v14 .nmo-dd-knob {
  top: -5px; width: 16px; height: 16px; margin: 0 0 0 -8px; box-sizing: border-box;
  border: 3px solid var(--nmo-v14-card, var(--bg-elev-2)); background: var(--nmo-v14-green);
}
/* .ends */
.nmo-dd-ends {
  display: flex; justify-content: space-between; gap: 8px; margin-top: 4px;
  font: 400 10px/12px var(--font-ui); letter-spacing: normal; color: var(--text-faint);
}
.nmo-dd-ends .nmo-dd-slider-value { font: inherit; white-space: nowrap; }
.nmo-dd-end { min-width: 0; }
.nmo-dd-end.is-high { text-align: right; }
/* the coin-size card: gap 8, .bar, .dist, .d */
.nmo-dd-v14 .nmo-dd-size-card { gap: 8px; }
.nmo-dd-v14 .nmo-dd-bar { height: 10px; border-radius: 5px; margin: 0; background: var(--nmo-v14-tag, var(--bg-elev-2)); }
.nmo-dd-v14 .nmo-dd-bar-part + .nmo-dd-bar-part { opacity: 1; }
.nmo-dd-v14 .nmo-dd-bar-part.is-small, .nmo-dd-row-dot.is-small { background: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-bar-part.is-middle, .nmo-dd-row-dot.is-middle { background: var(--nmo-v14-primary, #a15cff); }
.nmo-dd-v14 .nmo-dd-bar-part.is-large, .nmo-dd-row-dot.is-large { background: #c4c7c5; }
.nmo-dd-v14 .nmo-dd-rows { gap: 5px; padding: 0; }
.nmo-dd-v14 .nmo-dd-row {
  grid-template-columns: 12px minmax(0, 1fr) auto auto; column-gap: 10px; align-items: center;
  font: 400 13px/20px var(--font-ui); letter-spacing: normal; color: var(--text-dim);
}
.nmo-dd-row-dot { width: 10px; height: 10px; border-radius: 50%; }
.nmo-dd-v14 .nmo-dd-row-count { text-align: right; font-weight: 500; color: var(--text); }
.nmo-dd-v14 .nmo-dd-row-share { margin-left: -6px; font-size: smaller; color: var(--text-faint); }
/* all the numbers: gap 6 under the label, .kv's 7px rows */
.nmo-dd-v14 .nmo-dd-numbers-card { gap: 7px; }
.nmo-dd-v14 .nmo-dd-numbers-card > .nmo-dd-head { margin-bottom: -1px; }
.nmo-dd-v14 .nmo-dd-num { column-gap: 12px; padding: 0; border-top: 0; font: 400 13px/20px var(--font-ui); letter-spacing: normal; }
.nmo-dd-v14 .nmo-dd-num-name { color: var(--text-dim); }
.nmo-dd-v14 .nmo-dd-num-value { text-align: right; font-weight: 500; color: var(--text); }
.nmo-dd-v14 .nmo-dd-num-value.is-none { font-weight: 400; color: var(--text-faint); }
.nmo-dd-v14 .nmo-dd-num-value.nmo-good { color: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-num-value.nmo-bad { color: var(--nmo-v14-red-rank, #ff4f64); }

/* ── what would I make? ── */
/* the replay card: gap 8; the figure and its percentage on one .row */
.nmo-dd-v14 .nmo-dd-replay-card { gap: 8px; }
.nmo-dd-v14 .nmo-dd-figure-note { padding: 0; font: 500 14px/20px var(--font-ui); letter-spacing: normal; color: var(--text-faint); }
.nmo-dd-v14 .nmo-dd-figure-note.nmo-good { color: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-figure-note.nmo-bad { color: var(--nmo-v14-red-rank, #ff4f64); }
/* frame 4's bars: a 340-wide drawing at the card's 324px, so names in a 50px column and
   14px rails with corner 7 that stop 8px short of the edge; figures inside in #0d1f14 */
.nmo-dd-v14 .nmo-dd-bars { gap: 16px; padding: 0; }
.nmo-dd-v14 .nmo-dd-bar-row { grid-template-columns: 50px minmax(0, 1fr); gap: 0; font: 400 11px/14px var(--font-ui); letter-spacing: normal; }
.nmo-dd-v14 .nmo-dd-bar-name { color: var(--text-faint); }
.nmo-dd-v14 .nmo-dd-bar-rail { position: relative; height: 14px; margin-right: 8px; border-radius: 7px; background: var(--nmo-v14-tag, var(--bg-elev-2)); overflow: visible; }
.nmo-dd-v14 .nmo-dd-bar-fill { position: relative; height: 100%; border-radius: 7px; opacity: 1; }
.nmo-dd-v14 .nmo-dd-bar-fill.is-his.is-up { background: var(--nmo-v14-him); }
.nmo-dd-v14 .nmo-dd-bar-fill.is-yours.is-up { background: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-bar-fill.is-down { background: var(--nmo-v14-red-rank, #ff4f64); }
.nmo-dd-v14 .nmo-dd-bar-value { position: absolute; top: 0; right: 8px; font: 400 11px/14px var(--font-ui); white-space: nowrap; color: #0d1f14; }
.nmo-dd-v14 .nmo-dd-bar-rail > .nmo-dd-bar-value { color: var(--text-dim); }
/* .donut: an 80px ring with a 10px track, the arc in the colour of how bad the share is */
.nmo-dd-v14 .nmo-dd-catch { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px; align-items: center; }
.nmo-dd-v14 .nmo-dd-ring { width: 80px; height: 80px; }
.nmo-dd-v14 .nmo-dd-ring-back { stroke: var(--nmo-v14-tag, var(--bg-elev-2)); }
.nmo-dd-v14 .nmo-dd-ring-fill.is-good { stroke: var(--nmo-v14-green); }
.nmo-dd-v14 .nmo-dd-ring-fill.is-warn { stroke: var(--nmo-v14-amber); }
.nmo-dd-v14 .nmo-dd-ring-fill.is-bad { stroke: var(--nmo-v14-red-rank, #ff4f64); }
.nmo-dd-ring-figure { font: 500 15px var(--font-ui); fill: var(--text); }
.nmo-dd-catch-words { min-width: 0; }
.nmo-dd-catch-lead { font: 500 16px/22px var(--font-ui); letter-spacing: normal; color: var(--text); }
/* things to watch: .meter, one .zone with its stops, and the 2 by 14px marker */
.nmo-dd-v14 .nmo-dd-meter { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 10px; padding: 0; }
.nmo-dd-v14 .nmo-dd-meter-name { font: 500 13px/18px var(--font-ui); letter-spacing: normal; color: var(--text); }
.nmo-dd-v14 .nmo-dd-meter-value { font: 500 13px/18px var(--font-ui); letter-spacing: normal; text-align: right; }
.nmo-dd-v14 .nmo-dd-meter-value.is-fine { color: var(--text); }
.nmo-dd-v14 .nmo-dd-meter-value.nmo-warn { color: var(--nmo-v14-amber); }
.nmo-dd-v14 .nmo-dd-meter-value.nmo-bad { color: var(--nmo-v14-red-rank, #ff4f64); }
.nmo-dd-v14 .nmo-dd-scale {
  position: relative; display: block; height: 6px; border-radius: 3px;
  background: linear-gradient(90deg, #3f8a5b 0 60%, #8a7a3a 60% 80%, #8a4a4a 80% 100%);
}
.nmo-dd-scale-mark { position: absolute; top: -4px; width: 2px; height: 14px; margin-left: -1px; background: #fff; }
.nmo-dd-v14 .nmo-dd-footnote { font: 400 11px/14px var(--font-ui); color: var(--text-faint); }

/* ── already following: Unfollow wide and Tune beside it (.cta .un, .cta .following) ──
   Scoped to the trader profile's sheet. The only Unfollow rule for this sheet: #291's
   shallower copy was merged into this one (#293). */
.nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-dock .nmo-profile-follow.is-on {
  flex: 1 1 auto; box-sizing: border-box; height: 52px; min-height: 0; padding: 15px;
  border: 1px solid var(--nmo-v14-outline, rgba(255, 255, 255, .28)); border-radius: 999px; box-shadow: none;
  background: var(--nmo-v14-card, var(--bg-elev-2)); color: var(--nmo-v14-red-rank, #ff4f64);
  font: 500 16px/20px var(--font-ui); letter-spacing: normal;
}
.nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-dock .nmo-profile-follow.is-on:not(:disabled):hover { background: color-mix(in srgb, var(--nmo-v14-card, var(--bg-elev-2)) 92%, var(--text)); }
.nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-dock .nmo-profile-follow.is-on:not(:disabled):active { background: color-mix(in srgb, var(--nmo-v14-card, var(--bg-elev-2)) 84%, var(--text)); }
/* #63's tuning drawing stays; the pill and its padding are the design's */
.nmo-profile-dock .nmo-profile-gear {
  flex: none; box-sizing: border-box; width: auto; height: 50px; min-height: 0; margin: 0; padding: 14px 20px; gap: 8px;
  border: 1px solid var(--nmo-v14-outline, rgba(255, 255, 255, .28)); border-radius: 999px;
  background: var(--nmo-v14-card, var(--bg-elev-2)); color: var(--text);
  font: 500 16px/20px var(--font-ui); letter-spacing: normal;
}
.nmo-profile-dock .nmo-profile-gear .nmo-profile-gear-ico { flex: none; width: 17px; height: 17px; }
.nmo-profile-dock .nmo-profile-gear-word { margin-left: 0; font: inherit; }

/* ═══ WHAT GENIE MAKES OF A TRADER, AS A CARD ══════════════════════════════
   (#192) The sparkle leaves the deep dive beside a question; when the person
   sends it, Genie's six blocks are drawn here instead of as a paragraph.

   IT SITS INSIDE AN ANSWER BUBBLE, so it carries no background and no border of
   its own — the bubble is already the card. Adding a second surface inside the
   first is the thing that makes a chat look like a form. */
.nmo-ask-card { display: flex; flex-direction: column; gap: 12px; }
.nmo-ask-block { display: flex; flex-direction: column; gap: 2px; }
.nmo-ask-head {
  margin: 0; font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
}
.nmo-ask-says {
  margin: 0; font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}
/* The one line that is advice rather than a re-telling, set apart so it does not
   read as a seventh block. */
.nmo-ask-do {
  margin: 0; font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
  padding-top: 10px; border-top: 1px solid var(--border);
}
/* The sentence that says where to check every figure above it. Quiet, because it
   is not one of the six things the person came to read — and never hidden, because
   it is the whole reason they can trust the six. */
.nmo-ask-footer { margin: 0; font-size: 12px; line-height: 1.45; color: var(--text-faint); }

/* ═══ THE VERDICT CARD'S RULES ARE #191'S, AND THERE IS ONE COPY ═══ (the #219 fold)

   #192 carried its own copy of `.nmo-verdict`, `.nmo-verdict-head` and
   `.nmo-verdict-says` with a note saying to delete them when the two folded,
   because the answer card reuses the trader page's verdict card — the same three
   class names, so a person meets the same shape twice and reads the second as the
   first re-told. Deleted here, as that note asked. #191's copy is the one above in
   the trader page's own block: three rules byte-for-byte identical, plus a fourth
   #192 never had — a quieter colour for a verdict line with little behind it. */

/* ═══ THE TRADER PROFILE IS DRAWN TO DESIGN V14 (#291) ═══════════════════════════════════════

   The owner opened Unipcs's profile and it looked very different from the approved design.
   His direction (via friday, 13 Sep 2026 19:55): on this screen the design's own values win over
   the app's general Material sizes and over earlier restyles (#59, #272). The sizes below are
   the v14 source's own. ITS SURFACES ARE NOT, SINCE #349 (owner, 15 Sep 2026 02:2x: "the trader
   profile modale back ground color needs to be updated, its really hard to see as it's blends
   with background black"). The design's ground was #131314, the app's own ground, so the sheet
   measured 1.01 against the dimmed screen behind it and its edge vanished. Every surface now sits
   one step up the app's own dark scale, as #334 did for the For You deck's cards:
     ground #1e1f20 = --bg-elev (the For You card's own face)   card #2a2b2d = --bg-elev-2
     tag #333537 = --bg-elev-3      chosen pill #444746 = the design's own outline colour
     soft words #c4c7c5 = --text-dim
   The block is scoped to a sheet or page that holds the trader hero, so our genie's profile,
   the coin sheet and the chat's answer card (which reuses the verdict classes) do not move.
   It is measured, not asserted, by nmo-ui-profile-v14-look-390-test.ts. */
.nmo-sheet:has(.nmo-balance-hero-profile),
.nmo-profile:has(.nmo-balance-hero-profile),
.nmo-deck-front:has(.nmo-balance-hero-profile) {
  --nmo-v14-page: var(--bg-elev);
  --nmo-v14-card: var(--bg-elev-2);
  --nmo-v14-tag: var(--bg-elev-3);
  --nmo-v14-chosen: #202025;
  /* Hairlines drawn ON a card (a block's row rules, the hero's guide line, the comparison's web): the
     design's #2c2d2f was one step above its #1e1f20 card and vanishes on #2a2b2d, so they take the
     app's own border colour, the same step above the new card (#349). */
  --nmo-v14-rule: var(--border);
  --nmo-v14-soft: var(--text-dim);
  --nmo-v14-radius: 16px;
  /* The design's primary: the Vivid violet since #424. */
  --nmo-v14-primary: #a15cff;
  --nmo-v14-on-primary: #0b0016;
  --nmo-v14-green-chosen: rgba(161, 92, 255, .26);
  --nmo-v14-red-rank: #ff4f64;
  --nmo-v14-outline: rgba(255, 255, 255, .28);
  /* ═══ THE DESIGN'S THREE INKS, WHICH THIS SURFACE DID NOT HAVE ═══ (#381)
     Measured, not guessed: the badge pills drew properly on the For You card and drew as bare text
     on his page, and the cause is that green, amber and the dot red were declared on `.nmo-deck`
     and on the question-tab block but never here. The card inherited them through the deck; his
     page had nowhere to get them. Worse than a missing colour, a missing one POISONS the rule that
     uses it: `color-mix` with an undefined ink is an invalid declaration, so the pill lost its
     background as well as its colour and fell back to plain text on the page only. The red rank
     below was already here, which is exactly why the warning pill kept its colour and the two money
     pills did not — the half-working state that made this hard to see.
     Same values as `.nmo-deck` declares, so nothing changes colour anywhere. */
  --nmo-v14-green: #1ee07f;
  --nmo-v14-amber: rgba(255, 255, 255, .66);
  --nmo-v14-dot-red: #ff4f64;
  /* The badge families needed a purple and the fit chip a teal, and the design set carried neither;
     both are in the same Material family as the blue above. Declared HERE rather than at the
     document root because every selector in this stylesheet is an .nmo- one. */
  --nmo-v14-purple: #f1e6ff;
  --nmo-v14-teal: #f1e6ff;
}
/* The design is dark only. In the light theme --bg and --bg-elev are both white, so the cards
   take white on the light elevation instead of vanishing, and the primary is its light pair. */
:root[data-theme="light"] .nmo-sheet:has(.nmo-balance-hero-profile),
:root[data-theme="light"] .nmo-profile:has(.nmo-balance-hero-profile),
:root[data-theme="light"] .nmo-deck-front:has(.nmo-balance-hero-profile) {
  --nmo-v14-page: var(--bg-elev-1);
  --nmo-v14-card: var(--bg);
  /* The light theme's rails, chosen pill and hairlines keep what they had before #349 moved the dark ones. */
  --nmo-v14-tag: var(--bg-elev-2);
  --nmo-v14-chosen: var(--bg-elev-3);
  --nmo-v14-rule: #2c2d2f;
  --nmo-v14-primary: #0b57d0;
  --nmo-v14-on-primary: #ffffff;
  --nmo-v14-green-chosen: color-mix(in srgb, var(--ok) 14%, var(--bg));
  --nmo-v14-red-rank: var(--err);
  --nmo-v14-outline: var(--border);
  --nmo-v14-green: var(--ok);
  --nmo-v14-amber: var(--warn);
  --nmo-v14-dot-red: var(--err);
  --nmo-v14-purple: #6750a4;
  --nmo-v14-teal: #00695f;
}
/* THE SHEET READS AS A SHEET (#349): its own surface above, a hairline around its rounded top and a
   small shadow of its own — the For You deck's answer from #334 ("darkening does not separate two
   dark surfaces"), with the shadow cast upwards because this sheet rises from the bottom. The
   shared `.nmo-sheet:has(.nmo-profile-dock)` rule takes the shadow off every docked profile; this
   later rule of the same weight gives the trader's back. THE HAIRLINE IS AN OUTLINE laid over the
   sheet's own edge, not a border: a border's pixel would push everything inside down by one, and the
   face's 14px under the top edge is measured (nmo-ui-profile-v14-look-390-test.ts). Its foot lies on
   the screen's own bottom edge, under the Follow bar. */
.nmo-sheet:has(.nmo-balance-hero-profile) {
  background: var(--nmo-v14-page);
  outline: 1px solid rgba(255, 255, 255, .16); outline-offset: -1px;
  box-shadow: none;
}
:root[data-theme="light"] .nmo-sheet:has(.nmo-balance-hero-profile) {
  outline-color: var(--border); box-shadow: 0 -4px 10px rgba(0, 0, 0, .08);
}

/* Twelve pixels between cards, as the design's column spaces them. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd { gap: 12px; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-panel { padding-top: 6px; }

/* Every card: one fill, a 16px corner, 14/16 padding, and no extra side margin, so all of them
   share the column's two edges. The figures card's old 356px width and -15px margins were
   written for a sheet with a second inset and pushed it 16px left of every other card. */
.nmo-profile:has(.nmo-balance-hero-profile) :is(.nmo-verdict, .nmo-dd-overview, .nmo-dd-card),
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict {
  box-sizing: border-box; width: auto; height: auto; margin: 0;
  background: var(--nmo-v14-card); border-radius: var(--nmo-v14-radius); padding: 14px 16px;
}

/* VERDICT: a small grey capital label over one large sentence. The For You card's front is this same
   box under this same hero (#373), so every verdict rule here names the card's front beside the page. */
:is(.nmo-profile, .nmo-deck-front):has(.nmo-balance-hero-profile) .nmo-verdict { margin-top: 12px; }
:is(.nmo-profile, .nmo-deck-front):has(.nmo-balance-hero-profile) .nmo-verdict-head,
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-title {
  margin: 0; color: var(--text-faint);
  font: 500 11px/16px var(--font-ui); letter-spacing: 0.5px; text-transform: uppercase;
}
:is(.nmo-profile, .nmo-deck-front):has(.nmo-balance-hero-profile) .nmo-verdict-head { margin-bottom: 8px; }
/* The sentence is SUPPORTING TEXT under the pills, not a headline (owner, 16 Sep 01:2x): the evidence
   rows' own label type and colour, 13px in the soft grey. It was 16px body text until then. */
:is(.nmo-profile, .nmo-deck-front):has(.nmo-balance-hero-profile) .nmo-verdict-says {
  color: var(--nmo-v14-soft, var(--text-dim)); font: 400 13px/18px var(--font-ui); letter-spacing: normal; text-wrap: balance;
}
:is(.nmo-profile, .nmo-deck-front):has(.nmo-balance-hero-profile) .nmo-verdict-says.is-quiet { color: var(--text-faint); }
/* #393's cut with an ellipsis is gone (#439): the card's sentence picks the half that fits two lines (`verdictCard`). */
/* The kind of trader, at the front of the verdict, at the sentence's size but heavier: it is the part
   a reader takes away, and the evidence behind it reads as ordinary text (#381). */
.nmo-verdict-kind { font-weight: 600; }
.nmo-dd-record-age { margin: 0; color: var(--text-faint); font: 400 13px/18px var(--font-ui); letter-spacing: normal; }

/* THE QUESTIONS: filled pills, the chosen one a step lighter. The button stays a 48px target;
   the painted pill inside it is the design's 36px. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs { gap: 6px; padding: 6px 0 0; margin: 0; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab {
  padding: 0 14px; color: var(--nmo-v14-soft); font: 500 14px/20px var(--font-ui); letter-spacing: normal;
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab::before {
  inset: 6px 0; border-radius: 999px; background: var(--nmo-v14-card);
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on { color: var(--text); }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on::before {
  background: var(--nmo-v14-chosen);
}

/* THE FIGURES CARD: three columns, an 18px figure over an 11px caption. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-overview { gap: 8px; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-overview .nmo-dd-figure { font: 500 18px/24px var(--font-ui); letter-spacing: normal; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-overview-label { font: 400 11px/14px var(--font-ui); letter-spacing: normal; }


/* THE CALENDAR CARD: label and an 18px total on one line, the design's squares and colours. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-calendar-card { display: flex; flex-direction: column; gap: 8px; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-top { margin: 0; align-items: center; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-total { font: 500 18px/24px var(--font-ui); letter-spacing: normal; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal { padding: 0; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-day { font: 400 10px/12px var(--font-ui); color: var(--text-faint); }
/* The month is the card's title in the owner's reference: sentence case, not the small capitals
   of the other card labels. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-title { text-transform: none; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-step { font: 500 16px/24px var(--font-ui); letter-spacing: normal; color: var(--text); }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-icon { color: var(--nmo-v14-soft); }
.nmo-profile:has(.nmo-balance-hero-profile) :is(.nmo-dd-cell.is-no-closes, .nmo-dd-cell.is-flat) { background: var(--nmo-v14-tag); }
.nmo-profile:has(.nmo-balance-hero-profile) :is(.nmo-dd-cell.is-not-covered, .nmo-dd-cell.is-future) {
  background: transparent; box-shadow: inset 0 0 0 1px var(--nmo-v14-tag);
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cell.is-flat { color: var(--nmo-v14-soft); }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cal-note {
  margin: 0; color: var(--text-faint); font: 400 12px/16px var(--font-ui); letter-spacing: normal;
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-calendar-card .nmo-dd-says {
  margin: 0; color: var(--nmo-v14-soft); font: 400 13px/18px var(--font-ui); letter-spacing: normal;
}

/* FOLLOW: the design's primary, 50px tall, on a bar that fades out of the page over the list. */
.nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-dock {
  position: relative; z-index: 1; margin-top: -28px; min-height: 0;
  padding: 28px var(--nmo-sheet-pad-x) calc(16px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(180deg, color-mix(in srgb, var(--nmo-v14-page) 0%, transparent) 0%, var(--nmo-v14-page) 45%);
}
.nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-follow {
  min-height: 50px; border-radius: 999px;
  background: var(--nmo-v14-primary); color: var(--nmo-v14-on-primary);
  font: 500 16px/20px var(--font-ui); letter-spacing: normal;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
.nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-follow:not(:disabled):hover {
  background: color-mix(in srgb, var(--nmo-v14-primary) 92%, var(--nmo-v14-on-primary));
}
.nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-follow:not(:disabled):active {
  background: color-mix(in srgb, var(--nmo-v14-primary) 84%, var(--nmo-v14-on-primary));
}
/* Unfollow, the design's outlined card-coloured button with the loss colour for its word, is one
   rule, with Tune beside it, in the three later questions' section above (#292, merged #293). */

/* WHO HE IS: a 44px face, a 16px name with one small grey tag per chain, and the address and
   trading age in one 11px line. The sparkle is the design's blue on a card-coloured circle;
   the share arrow has no circle. Both stay 48px touch targets around a 36px drawing. */
/* The card's front draws this row too (#373), and so does the panel's pinned bar (#374), so all
   three are named wherever the row's look is declared. */
:is(.nmo-profile, .nmo-deck-front) .nmo-balance-hero-profile { padding: 6px 0 0; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-identity { gap: 10px; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-av { width: 44px; height: 44px; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-name { font: 500 16px/22px var(--font-ui); letter-spacing: normal; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-details { margin-top: 0; font: 400 11px/14px var(--font-ui); letter-spacing: normal; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-actions { gap: 0; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-action::before { width: 36px; height: 36px; background: transparent; }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-action.is-genie { color: var(--nmo-v14-primary, var(--accent)); }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-action.is-genie::before { background: var(--nmo-v14-card, var(--bg-elev)); }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-action.is-share { color: var(--nmo-v14-soft, var(--text-dim)); }
:is(.nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-balance-hero-action.is-share .nmo-balance-hero-action-icon { width: 18px; height: 18px; }

/* THE BALANCE CARD: one card-coloured surface holding the figure, the chart, the spans and the
   copiers, 6px apart as the design stacks them. */
.nmo-balance-hero-profile .nmo-balance-hero-panel {
  gap: 6px; min-height: 0; margin-top: 12px; padding: 14px 16px;
  background: var(--nmo-v14-card, var(--bg-elev)); border-radius: var(--nmo-v14-radius, 16px);
}
.nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable { padding: 14px 16px; }
.nmo-balance-hero-profile .nmo-balance-hero-figures { align-items: center; }
.nmo-balance-hero-profile .nmo-balance-hero-figures .nmo-tv-value { margin-top: 0; font: 500 30px/36px var(--font-ui); letter-spacing: normal; }
.nmo-balance-hero-profile .nmo-tv-range { font: 500 15px/20px var(--font-ui); letter-spacing: normal; }
/* THE BALANCE ROW (#304): the balance with its change under it as a subtitle on the left, and his
   win rate over its caption on the right, both columns centred on the row. The left column keeps
   the subtitle's line when there is no change to print, so neither a held reading's date nor a
   change arriving moves anything. The For You card keeps its change beside the figure.
   THE CARD KEEPS ITS HEIGHT (ruling on #304, 14 Sep 2026 06:30): the subtitle's 20px line is taken
   out of the empty air at the top of the drawing, which starts 50px above its top guide, so the
   chart, spans and copiers line stand exactly where they stood. The row is drawn over that air,
   never under it, so the chart cannot cover the words. ONLY WHEN A DRAWING FOLLOWS: while his
   balance is still being read, or when no line can be drawn, the next row is the spans, which have
   no air to give, and pulling up would lay the caption over them (found live on 4536, 14 Sep 07:11). */
.nmo-balance-hero-profile .nmo-balance-hero-figures { position: relative; z-index: 1; }
.nmo-balance-hero-profile .nmo-balance-hero-figures:has(+ .nmo-tv-chart) { margin-bottom: -20px; }
.nmo-balance-hero-profile .nmo-balance-hero-reading {
  display: flex; flex-direction: column; align-items: flex-start; min-width: 0; min-height: 56px;
}
.nmo-balance-hero-profile .nmo-balance-hero-reading :is(.nmo-tv-range, .nmo-balance-hero-held-when, .nmo-balance-hero-older-when) { margin-left: 0; }
/* #392: the trader service's own word on its answer (stale feed, how far "all" reaches, a partial newest reading), under the chart. */
.nmo-balance-hero-delisted { margin-top: 2px; color: var(--text-dim); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
.nmo-balance-hero-service-note { margin-top: 4px; color: var(--text-faint); font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); }
/* #404: what the figure measures ("holds now · sum of 2 chains"), and a change withheld across a cut in the quiet colour,
   so it never reads as a rise or a fall. */
.nmo-balance-hero-range-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; max-width: 100%; }
.nmo-balance-hero-quantity { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--text-faint); font: 400 11px/14px var(--font-ui); letter-spacing: normal; white-space: nowrap; }
.nmo-tv-range.is-not-comparable { color: var(--text-faint); font: 400 12px/16px var(--font-ui); letter-spacing: normal; }
/* #342: the question tab's place while the same read is out: three faint lines, no words. */
.nmo-dd-waiting { display: flex; flex-direction: column; gap: 10px; padding: 8px 0; }
.nmo-dd-waiting > span { display: block; height: 12px; border-radius: 6px; background: var(--nmo-line); }
.nmo-dd-waiting > span:nth-child(1) { width: 78%; }
.nmo-dd-waiting > span:nth-child(2) { width: 56%; }
.nmo-dd-waiting > span:nth-child(3) { width: 66%; }
.nmo-balance-hero-profile .nmo-balance-hero-figures .nmo-tv-value.is-reading { color: var(--text-faint); font: 400 14px/36px var(--font-ui); }

/* THE CHART is the design's 170px drawing across the card's content: 50px of air, three guide
   lines 50px apart with their figures just right of the plot, the dates under it. The plot
   runs from the balance figure's left edge to 35px short of the right edge; the design's starts
   20px in, and the owner moved it under the figure (#298). The line, its dot and its wash are
   the hero's on both forms (THE LINE IS THE DESIGN'S ON BOTH FORMS, above). */
.nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart {
  width: auto; height: calc(var(--nmo-hero-plot) + 70px); flex: 0 0 calc(var(--nmo-hero-plot) + 70px); align-self: stretch; margin: 0;
}
.nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart > .nmo-tv-line {
  top: 50px; right: auto; bottom: auto; left: 0; width: calc(100% - 35px); height: var(--nmo-hero-plot);
}
.nmo-balance-hero-profile .nmo-balance-hero-guides { inset: 50px 35px 20px 0; }
.nmo-balance-hero-profile .nmo-balance-hero-guide { border-top-color: var(--nmo-v14-rule, #2c2d2f); }
:root[data-theme="light"] .nmo-balance-hero-profile .nmo-balance-hero-guide { border-top-color: var(--nmo-line); }
.nmo-balance-hero-profile .nmo-balance-hero-guide-value {
  /* Ends at the content edge, just right of the plot, so a long figure grows over the plot's
     empty right end instead of past the card. */
  top: -7px; right: -35px; left: auto; padding: 0 0 0 4px; background: var(--nmo-v14-card, var(--bg-elev));
  font: 400 11px/14px var(--font-ui); letter-spacing: normal; white-space: nowrap;
}
/* The floor's figure sits 2px higher than the design so it never touches "Today" under it. */
.nmo-balance-hero-profile .nmo-balance-hero-guide:last-child .nmo-balance-hero-guide-value { top: -9px; }
.nmo-balance-hero-profile .nmo-balance-hero-dates {
  inset: auto 26px 0 0; font: 400 11px/14px var(--font-ui); letter-spacing: normal;
}

/* THE SPANS: 13px words in the soft grey, the chosen one green on its dark green pill. Each is
   still a 48px button; the painted pill is 42 by 28 and the row takes the design's 28px. */
.nmo-balance-hero-profile .nmo-balance-hero-controls {
  grid-template-columns: 192px minmax(0, 1fr); column-gap: 8px; margin: 0;
}
.nmo-balance-hero-profile .nmo-tv-pills { gap: 0; margin: -4px 0 -16px; }
.nmo-balance-hero-profile .nmo-tv-pill {
  padding: 0 0 12px; color: var(--nmo-v14-soft, var(--text-dim)); font: 500 13px/18px var(--font-ui); letter-spacing: normal;
}
/* The 48px button starts 4px above the painted pill, so the pill clears the chart's dates by the
   design's 6px; its lower 16px reaches under the copiers line, which takes no press. */
.nmo-balance-hero-profile .nmo-tv-pill::before { inset: 4px 3px 16px; border-radius: 999px; }
.nmo-balance-hero-profile .nmo-tv-pill[aria-pressed="true"] { color: var(--nmo-up); font-weight: 500; }
.nmo-balance-hero-profile .nmo-tv-pill[aria-pressed="true"]::before { background: var(--nmo-v14-green-chosen, color-mix(in srgb, var(--nmo-up) 22%, var(--bg-elev))); }

.nmo-balance-hero-profile .nmo-tv-cannot { margin-top: 0; }

/* ═══ THE VERDICT CARD'S PILLS: FIT FIRST, THEN THE BADGES (#381) ═══
   (owner 15 Sep 23:3x: no pills in the balance section, one card of badges then verdict; friday
   16 Sep 00:39: the fit is that card's first pill, not a chip beside his name)

   The card reads fit, badges, the verdict sentence, the evidence rows. On his page the badges are
   the whole rail; on his For You card, the top three. Every rule is scoped to the verdict card, which
   both screens draw, so a pill cannot look one way on the card and another on his page.

   THE FAMILY IS THE COLOUR: money he made in green, the form he is in in purple, how he behaves in
   amber, a warning in red — each its own ink on a faint wash of itself over the card, the shape the
   calendar's days already use. */
.nmo-verdict .nmo-verdict-pills { margin: 0 0 10px; }

/* THE FIT PILL: "Moonshot fit" in amber, "Grinder fit" in teal, sized like the pills it leads, and
   absent when the verdict found no fit. */
.nmo-verdict .nmo-tv-fit {
  flex: none; box-sizing: border-box; display: inline-flex; align-items: center;
  border: 1px solid transparent; border-radius: 999px; padding: 5px 10px;
  font: 500 12px/16px var(--font-ui); letter-spacing: normal; white-space: nowrap;
}
.nmo-verdict .nmo-tv-badges .nmo-tv-fit { border: 0; padding: 4px 10px; }
.nmo-verdict .nmo-tv-fit.is-moonshot {
  background: color-mix(in srgb, var(--nmo-v14-amber) 18%, var(--nmo-v14-card, var(--bg-elev)));
  color: var(--nmo-v14-amber);
}
.nmo-verdict .nmo-tv-fit.is-grinder {
  background: color-mix(in srgb, var(--nmo-v14-teal, #7fd8c8) 18%, var(--nmo-v14-card, var(--bg-elev)));
  color: var(--nmo-v14-teal, #7fd8c8);
}

/* THE CARD'S THREE. They WRAP: three pills with their real names do not fit one line on a 390 phone
   (about a hundred pixels each), and held to one line they came out "Caree... 1.33x" — the name cut,
   a number left with nothing to say what it counted. Each face scrolls inside its own card, so a
   second line makes the face taller rather than pushing anything off it. */
.nmo-verdict .nmo-tv-badges { display: flex; align-items: center; gap: 6px; padding: 0; flex-wrap: wrap; }
:is(.nmo-verdict, .nmo-deck-askpill) .nmo-tv-badge {
  flex: 0 1 auto; min-width: 0; box-sizing: border-box;
  display: inline-flex; align-items: center; gap: 4px;
  border-radius: 999px; padding: 4px 10px;
  font: 500 12px/16px var(--font-ui); letter-spacing: normal; white-space: nowrap;
  background: var(--nmo-v14-tag, var(--bg-elev-3)); color: var(--nmo-v14-soft, var(--text-dim));
}
:is(.nmo-verdict, .nmo-deck-askpill) .nmo-tv-badge-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nmo-verdict .nmo-tv-badge-figure { flex: none; font-weight: 600; }
:is(.nmo-verdict, .nmo-deck-askpill) .nmo-tv-badge.is-money {
  background: color-mix(in srgb, var(--nmo-v14-green) 16%, var(--nmo-v14-card, var(--bg-elev))); color: var(--nmo-v14-green);
}
:is(.nmo-verdict, .nmo-deck-askpill) .nmo-tv-badge.is-form {
  background: color-mix(in srgb, var(--nmo-v14-purple) 18%, var(--nmo-v14-card, var(--bg-elev))); color: var(--nmo-v14-purple);
}
:is(.nmo-verdict, .nmo-deck-askpill) .nmo-tv-badge.is-behaviour {
  background: color-mix(in srgb, var(--nmo-v14-amber) 18%, var(--nmo-v14-card, var(--bg-elev))); color: var(--nmo-v14-amber);
}
:is(.nmo-verdict, .nmo-deck-askpill) .nmo-tv-badge.is-warning {
  background: color-mix(in srgb, var(--nmo-v14-dot-red) 20%, var(--nmo-v14-card, var(--bg-elev))); color: var(--nmo-v14-red-rank);
}

/* ═══ HIS PAGE'S RAIL, AT THE TOP OF THE SAME CARD ═══

   Only badges he has a value for, each in its family's colour, then the warnings that apply, in red.
   The grey "measured, none" and dashed "cannot check" pills were taken off on 16 Sep 01:2x. */
.nmo-dd-badge-rail { display: flex; flex-wrap: wrap; gap: 6px; padding: 0; margin: 0; list-style: none; }
.nmo-dd-badge-rail-one {
  box-sizing: border-box; display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid transparent; border-radius: 999px; padding: 5px 10px;
  font: 500 12px/16px var(--font-ui); letter-spacing: normal;
  background: var(--nmo-v14-tag, var(--bg-elev-3)); color: var(--nmo-v14-soft, var(--text-dim));
}
/* A pressable rail item is a button, so a keyboard reaches the receipts too. */
button.nmo-dd-badge-rail-one { cursor: pointer; font-family: inherit; }
.nmo-dd-badge-rail-one.is-earned.is-money {
  background: color-mix(in srgb, var(--nmo-v14-green) 16%, var(--nmo-v14-card)); color: var(--nmo-v14-green);
}
.nmo-dd-badge-rail-one.is-earned.is-form {
  background: color-mix(in srgb, var(--nmo-v14-purple) 18%, var(--nmo-v14-card)); color: var(--nmo-v14-purple);
}
.nmo-dd-badge-rail-one.is-earned.is-behaviour {
  background: color-mix(in srgb, var(--nmo-v14-amber) 18%, var(--nmo-v14-card)); color: var(--nmo-v14-amber);
}
.nmo-dd-badge-rail-one.is-earned.is-warning {
  background: color-mix(in srgb, var(--nmo-v14-dot-red) 20%, var(--nmo-v14-card)); color: var(--nmo-v14-red-rank);
}
.nmo-dd-badge-rail-figure { flex: none; font-weight: 600; }
.nmo-dd-badge-rail-icon { flex: none; width: 13px; height: 13px; }


/* THE RECEIPTS, OPENED IN PLACE under the rail item that was pressed, so a reader never loses their
   place. Stars live here and nowhere else in the app: they are the roster's verdict on one reading,
   which is a thing worth showing when somebody asks for it and noise everywhere else. */
.nmo-dd-receipt {
  margin: 10px 0 2px; padding: 10px 12px; border-radius: 12px;
  background: var(--nmo-v14-page, var(--bg-elev-1));
}
.nmo-dd-receipt-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.nmo-dd-receipt-stars { flex: none; letter-spacing: 1px; color: var(--nmo-v14-amber); font: 500 13px/18px var(--font-ui); }
.nmo-dd-receipt-stars .is-empty { color: var(--nmo-v14-outline, var(--border)); }
.nmo-dd-receipt-rank { color: var(--nmo-v14-soft, var(--text-dim)); font: 400 12px/17px var(--font-ui); letter-spacing: normal; }
/* THE METER: where his figure sits against the roster's four cuts. */
.nmo-dd-badge-meter { position: relative; height: 6px; border-radius: 3px; margin: 8px 0 6px; background: var(--nmo-v14-tag, var(--bg-elev-3)); }
.nmo-dd-badge-meter-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; background: var(--nmo-v14-green); }
.nmo-dd-badge-meter-cut { position: absolute; top: -2px; bottom: -2px; width: 1px; background: var(--nmo-v14-outline, var(--border)); }
.nmo-dd-receipt-coins, .nmo-dd-receipt-cover, .nmo-dd-receipt-asof, .nmo-dd-receipt-why {
  margin: 4px 0 0; color: var(--nmo-v14-soft, var(--text-dim));
  font: 400 12px/17px var(--font-ui); letter-spacing: normal; overflow-wrap: anywhere;
}
.nmo-dd-receipt-coins { color: var(--text); }

/* THE CALENDAR'S DAYS in the Vivid gain, the loss container and the selected container (#303,
   recoloured by #424: no amber, no third colour). The light theme keeps the app's own tints, which
   the design does not cover. */
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cell.is-up {
  background: color-mix(in srgb, #1ee07f 16%, var(--nmo-v14-card)); color: #1ee07f;
}
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cell.is-down {
  background: rgba(255, 79, 100, .16); color: #ffb3bf;
}
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-cell.is-lucky {
  background: rgba(161, 92, 255, .26); color: #f1e6ff;
}


/* The dot between a bottom corner's name and its rank, in the names' grey. */
.nmo-report-between { fill: var(--text-faint); }

/* ═══ THE TRADER PROFILE IS A PANEL THAT SLIDES UP, NOT THE WHOLE SCREEN (#301, owner 13 Sep 2026) ═══

   #293 made it the whole phone screen; the owner reversed that the same night: "dont make it full
   screen, use previous slide up modale". So a trader's sheet is the plain `.nmo-sheet` again: 90%
   of the screen's height off its bottom edge, rounded top corners, and the app still showing
   above it behind the scrim. It pays no top inset (the app above it does); the Follow dock still
   pays the bottom one. `agentProfile.ts` puts `.nmo-sheet-profile` on the sheet whenever it holds a
   trader, whichever door opened it, and these are the only rules it still carries.

   THE HANDLE TAKES NO ROW. It floats over the design's 14px top padding, centred, so the
   identity row starts where the design's does and the scrolled header is not pushed down. It is
   the one thing here the design does not draw, and it stays because it is how the sheet closes.

   IT IS ONE LAYER ABOVE THE PINNED HEADER. The pinned name-and-questions block (`.nmo-dd-sticky`)
   is layer 3 inside the list, and the list comes after the handle, so at the same layer the
   header's strip painted over the handle once it pinned and took the tap meant for it (#299,
   found live on 4536). Layer 4 keeps the handle painted and pressable over it.

   A wide screen keeps the centred panel with its handle in its own row. */
@media (max-width: 640px) {
  .nmo-sheet.nmo-sheet-profile > .nmo-sheet-grab {
    position: absolute; z-index: 4; top: 0; left: 50%; transform: translateX(-50%);
    box-sizing: border-box; width: 96px; height: 14px; padding: 5px 0 0; background: none;
  }
  .nmo-sheet.nmo-sheet-profile > .nmo-sheet-body { overscroll-behavior: contain; }
  /* 12px, not the design's 14: the identity row is as tall as its 48px touch targets, which
     centre the 44px face 2px further down. The face lands at the design's 14. */
  .nmo-sheet-profile .nmo-profile .nmo-balance-hero-profile { padding-top: 0; }
}

/* ═══ HIS IDENTITY ROW IS THE PANEL'S TOP BAR (#374, owner 15 Sep 2026) ═══

   "the trader profile section should be anchored as nav bar of the modale, and rest of the content
   should be scrolling under". The row — his face, his name, his chain tags, his Genie id line, the
   Ask Genie sparkle and Share — is a child of the sheet itself, above the scrolling page, so it
   stays put while the balance, the chart, the verdict and the tabs go under it. The page's own top
   padding moves here (the design's 14px, 12px as measured on the identity row's 48px targets), and
   the sheet's inset gives it its left and right.

   IT IS OPAQUE. Nothing may show through a bar that stands over moving content, so it paints the
   sheet's own surface; and while the page is at its top there is no line under it, because then the
   row and the page are one surface, exactly as the design draws them. A hairline appears the moment
   anything has scrolled beneath it (`is-scrolled`, set in code from the page's own scroll).

   EVERY DOOR IS THE SAME DOOR. The deck briefly had a close button of its own in this row; the owner
   reversed that, so the row carries Ask Genie and Share and nothing else, on every door alike. */
.nmo-sheet-profile > .nmo-sheet-nav {
  /* No layer of its own: the page scrolls INSIDE the box below it and never reaches it, and the grab
     handle, which floats over this row's own top padding exactly as it floated over the page's, has
     to stay on top of it. */
  flex: none; box-sizing: border-box;
  padding: 12px var(--nmo-sheet-pad-x) 8px;
  background: var(--nmo-v14-page, var(--bg-elev-1, var(--bg)));
  border-bottom: 1px solid transparent;
}
.nmo-sheet-profile.is-scrolled > .nmo-sheet-nav { border-bottom-color: var(--nmo-v14-rule, var(--border)); }
.nmo-sheet-profile > .nmo-sheet-nav > .nmo-balance-hero-nav { padding: 0; }
/* His id line keeps one line whatever the row carries. */
.nmo-sheet-profile > .nmo-sheet-nav .nmo-balance-hero-details {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The line that used to appear over the question row while scrolled — his name and his verdict in
   short — is off inside the panel: the bar above it says who this is, and printing his name twice on
   one screen is what the bar was put there to stop. His page outside a panel keeps it. */
.nmo-sheet-profile .nmo-dd-pinned { display: none; }
/* ═══ AND SO THE QUESTION ROW STICKS FLUSH UNDER THE BAR ═══ (owner, 15 Sep 2026, with a picture)

   His screenshot of Wood's panel: the bar, then a strip where the scrolling figures showed through,
   then the question row. That strip was exactly 32px, and it was the room the question row holds for
   the name-and-verdict line above it — the very line the rule directly above turns off in a panel.
   The row was reserving space for something not drawn, and his page showed through the hole.

   Inside a panel the row sticks at nought, which IS the bar's bottom edge, because the page scrolls
   inside the box under the bar and nought is where that box begins. His page outside a panel is
   untouched and keeps both the line and the 32px it sits in. */
.nmo-sheet-profile .nmo-dd-sticky { top: 0; }

/* The warning card's closing explanation, design v14's `.one` with `margin-top:8px` under the rows
   (#293). The card's own 2px gap is the rest of the space. */
.nmo-dd-v14 .nmo-dd-warnings-card > .nmo-dd-warn-note { margin-top: 8px; }

/* ═══ "OF THE 2,000 COINS HE TRADED", AS DESIGN V14'S FIRST FRAME DRAWS IT (#293) ═══
   `.row h3 + .head` at 18px in green, `.bar` 10px with corner 5, and `.dist` rows of a 10px dot,
   a 13px soft name and a 500-weight count with its share small and faint beside it. Same values
   as #292's coin-size card; the bar and dot colours are the design's five. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card {
  /* Vivid gain at 55% and 78% over black, gain, loss at 60% over black, loss (#424). */
  --nmo-dd-profit-1: #107b46; --nmo-dd-profit-2: #17af63; --nmo-dd-profit-3: #1ee07f;
  --nmo-dd-profit-4: #992f3c; --nmo-dd-profit-5: #ff4f64;
  display: flex; flex-direction: column; gap: 8px;
}
:root[data-theme="light"] .nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card {
  --nmo-dd-profit-1: color-mix(in srgb, var(--ok) 70%, var(--text)); --nmo-dd-profit-2: color-mix(in srgb, var(--ok) 85%, var(--text));
  --nmo-dd-profit-3: var(--ok); --nmo-dd-profit-4: color-mix(in srgb, var(--err) 55%, var(--bg)); --nmo-dd-profit-5: var(--err);
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-head {
  margin: 0; font: 500 11px/16px var(--font-ui); letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-faint);
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-figure {
  padding: 0; font: 500 18px/24px var(--font-ui); letter-spacing: normal; color: var(--text);
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-figure.nmo-good { color: var(--nmo-dd-profit-3); }
/* With no result, the figure's slot holds a faint dash rather than a coloured one. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-figure:not(.nmo-good):not(.nmo-bad) { color: var(--text-faint); }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-bar {
  height: 10px; border-radius: 5px; margin: 0; background: var(--nmo-v14-tag, var(--bg-elev-2));
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-bar-part { opacity: 1; }
.nmo-dd-profit-card :is(.nmo-dd-bar-part, .nmo-dd-row-dot).is-over5x { background: var(--nmo-dd-profit-1); }
.nmo-dd-profit-card :is(.nmo-dd-bar-part, .nmo-dd-row-dot).is-2to5x { background: var(--nmo-dd-profit-2); }
.nmo-dd-profit-card :is(.nmo-dd-bar-part, .nmo-dd-row-dot).is-upTo2x { background: var(--nmo-dd-profit-3); }
.nmo-dd-profit-card :is(.nmo-dd-bar-part, .nmo-dd-row-dot).is-lostUpToHalf { background: var(--nmo-dd-profit-4); }
.nmo-dd-profit-card :is(.nmo-dd-bar-part, .nmo-dd-row-dot).is-lostOverHalf { background: var(--nmo-dd-profit-5); }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-rows { gap: 5px; padding: 0; }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-row {
  grid-template-columns: 12px minmax(0, 1fr) auto auto; column-gap: 10px; align-items: center;
  font: 400 13px/20px var(--font-ui); letter-spacing: normal; color: var(--nmo-v14-soft, var(--text-dim));
}
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-row-count { text-align: right; font-weight: 500; color: var(--text); }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card .nmo-dd-row-share { margin-left: -6px; font-size: smaller; color: var(--text-faint); }
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-dd-profit-card :is(.nmo-dd-absent, .nmo-dd-profit-evidence) {
  margin: 0; font: 400 13px/18px var(--font-ui); letter-spacing: normal; color: var(--text-faint);
}


/* ═══ THE PROFILE'S OWN BLOCKS ON A CARD ═══ (#308; #373)
   The card's front is his page's balance section and verdict box, and it takes his page's look: the
   page rules above name `.nmo-deck-front` beside the page, and nothing here restyles either block
   (#373 removed the deck's own sizes for the hero, the chart, the spans, the copiers and the verdict,
   the owner's "only reuse what trader profile is here to provide"). #397 removed what the back and the
   #323 front left here: the figures, the test chips and their heads, which no card draws. */
.nmo-deck-front .nmo-deck-page { gap: 0; }

/* ═══ THE V55 DECK CARD: FIVE PARTS ON ONE SURFACE ═══ (#427, owner 16 Sep 2026)
   His face, name and chains; his balance with its change under it; the chart and its spans; one plain
   sentence; the badge pills he earned. The balance section and the verdict box lose their own inner
   surfaces, so the five read as one card, as the design draws it (fomo-look-and-feel-v55.html, section 04).
   Sizes are v59's deck card: the figure 48/54 at 600, the change and the sentence Body Large. The name row is the shared identity row that #424's block below draws (16px name over a 14px
   line), and the colours are #424's Vivid ones read through its variables. */
.nmo-deck-front .nmo-balance-hero-profile { padding: 0; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-identity { gap: 12px; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-panel,
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable {
  margin-top: 12px; padding: 0; background: transparent; border-radius: 0;
}
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-figures .nmo-tv-value { font: 600 48px/54px var(--font-ui); letter-spacing: -.5px; font-variant-numeric: tabular-nums; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-tv-range { font: 500 16px/24px var(--font-ui); letter-spacing: .5px; font-variant-numeric: tabular-nums; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-tv-range.is-not-comparable { color: var(--text-dim); font: 400 16px/24px var(--font-ui); letter-spacing: .5px; }
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict { margin-top: 12px; padding: 0; background: transparent; border-radius: 0; }
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-says { color: var(--text-dim); font: 400 16px/24px var(--font-ui); letter-spacing: .5px; text-wrap: pretty; }
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-says .nmo-verdict-kind { color: var(--text); font-weight: 500; }
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-pills { margin-top: 10px; }
/* A card behind taller than the one in front is cut at the front's top line. Cut flat, its surface showed in the
   front's two rounded top corners (4536, 16 Sep 21:28), so the cut takes the front's 28px corners, drawn size. */
.nmo-deck-card.is-under {
  clip-path: inset(var(--nmo-stack-clip, 0px) 0 0 0 round calc(28px / var(--nmo-stack-scale, .95)) calc(28px / var(--nmo-stack-scale, .95)) 0 0);
}




/* ═══ THE SHARED PARTS, AS DESIGN V55 DRAWS THEM (#424) ═══════════════════════════════════════
   The owner, 16 Sep: "update the ui / ux accordingly … vivid only". Sections 02 and 03 of
   ~/.genie/design/fomo-look-and-feel-v55.html: Material 3 parts in the Vivid palette, flat (no
   shadows, hairlines for separation), and the shape scale — chips 8, toasts 16, deck card and
   sheets 28, buttons and switches fully round.

   THIS BLOCK IS THE SKIN, NOT THE SCREENS. Nothing here moves a part to a new place or changes
   what a screen holds; the charts, the deck card, the profiles, the coin sheet and the moments are
   redrawn on top of it by #425-#430. It is last in the file and every rule carries the dark-theme
   prefix, so it wins over the earlier looks of the same parts, and the light theme keeps its own.
   It supersedes the earlier looks where they disagree: the white Follow (#291), the blue Follow
   (#59), the green chosen span (#303) and the amber and teal pills (#381) are the design's past,
   not an argument against it. */

/* ── the ground: true black behind the screen, the head bar included ── */
:root:not([data-theme="light"]) .nmo-root { background: var(--bg); }
:root:not([data-theme="light"]) .nmo-head { background: var(--bg); }
/* THE ONE RULE ON AN APP ELEMENT: the chat column that holds the screen, and only while it holds it.
   Its own grey showed as a frame round the black screen and as a grey floor under a short one.
   The ground only — the ask bar inside it keeps every look of its own. */
:root:not([data-theme="light"]) .chat-col:has(.nmo-root) { background: #000000; border-color: rgba(255, 255, 255, .08); }
/* Money reads in tabular figures everywhere on these screens; addresses and times take the code face. */
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet, .nmo-copy-sheet) :is(.nmo-num, .nmo-bal, .nmo-tv-value, .nmo-balance-now, .nmo-balance-hero-value) {
  font-variant-numeric: tabular-nums;
}

/* ── the tab row: round chips, the chosen one filled lavender, the gear at the end (design v60 `.chiptabs`, #435) ──
   Each chip is 36 tall, fully round, 8px from the next, on the raised grey; no underline. The row keeps 12px
   under the chips, and THAT 12px IS THE GAP to whatever a tab draws first — the Feed's switch, the Traders
   chips, the card, the profile — so nothing under the row adds space of its own above its first row.

   THE DESIGN'S 16px CHIP SIDES DO NOT FIT, in the design either: its four chips and gear are 374px on a 352px
   row, and at 390 wide this row has 332px. So a chip starts at the design's width and gives up side space
   only as far as the row needs: it may shrink, its words stay centred, and they spill into its own side space
   rather than being cut, until that space is gone. That works for any language — the Chinese labels are
   about 14px wider than the English ones, which a fixed 12px side did not survive. Measured at 390 wide the
   sides come out 13 to 14px in English and 11 to 14px in Chinese; at 360 wide in Chinese, 6 to 11px. The gear's 32px box reaches 6px into the row's right margin,
   which puts its glyph on the same edge as the × above it.

   NOTHING CLIPS THE FOCUS RING. Core's chip row scrolls sideways, and a row that scrolls cuts anything drawn
   outside it — the old row showed only the two sides of a focused tab's ring. This row does not scroll. */
:root:not([data-theme="light"]) .nmo-tabs.m3-chiprow {
  gap: 0; padding-top: 0; padding-bottom: 12px; align-items: center;
  border-bottom: 0; overflow: visible;
}
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab.m3-chip {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 1 auto; min-width: 0; overflow: hidden; height: 36px; min-height: 36px; padding: 0 13px;
  border: 0; border-radius: 999px; background: var(--bg-elev-2); transform: none;
  color: var(--text); font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking); white-space: nowrap;
}
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab.m3-chip + .nmo-tab.m3-chip { margin-left: 6px; }
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab.m3-chip.is-on,
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab.m3-chip.is-on:hover { background: #e8def8; color: #1c1a22; }
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab.m3-chip:hover { background: var(--bg-elev-3); color: var(--text); }
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab-settings {
  flex: none; width: 32px; height: 36px; padding: 0; margin-right: -6px; justify-content: center;
  color: var(--text-dim);
}
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab-settings .nmo-ic { font-size: 20px; }
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab-settings .nmo-ic-drawn { width: 20px; height: 20px; }

/* The coin's tabs and our own profile's tabs: 48 tall, label in title small, a 3px line under the chosen one.
   (The screen's top tab row was drawn this way too, until #435 made it chips.) */
:root:not([data-theme="light"]) .nmo-coin-tabs:not(.nmo-dd-tabs) { gap: 0; border-bottom-color: var(--nmo-outline-variant); }
:root:not([data-theme="light"]) .nmo-coin-tabs:not(.nmo-dd-tabs) .nmo-coin-tab {
  position: relative; height: 48px; padding: 0 16px; margin-bottom: 0; border-bottom: 0;
  color: var(--text-dim); font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
}
:root:not([data-theme="light"]) .nmo-coin-tabs:not(.nmo-dd-tabs) .nmo-coin-tab.is-on { color: var(--nmo-violet); border-bottom: 0; }
:root:not([data-theme="light"]) .nmo-coin-tabs:not(.nmo-dd-tabs) .nmo-coin-tab.is-on::after {
  content: ""; position: absolute; left: 16px; right: 16px; bottom: 0; height: 3px;
  border-radius: 3px 3px 0 0; background: var(--nmo-violet);
}

/* ── chips: 32 tall, 8px corners, an outline; chosen = the selected container ── */
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) .m3-chip:not(.nmo-tab) {
  height: 32px; min-height: 32px; padding: 0 12px; box-sizing: border-box;
  border: 1px solid var(--nmo-outline); border-radius: 8px; background: none;
  color: var(--text-dim); font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) .m3-chip:not(.nmo-tab).is-on {
  border-color: transparent; background: var(--nmo-selected); color: var(--nmo-on-selected); font-weight: 500;
}
/* The trader profile's question tabs are filter chips. */
:root:not([data-theme="light"]) .nmo-profile-tabs.nmo-dd-tabs { gap: 8px; align-items: center; }
:root:not([data-theme="light"]) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab {
  height: 48px; min-height: 48px; padding: 0 12px; color: var(--text-dim);
  font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
:root:not([data-theme="light"]) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab::before {
  inset: 8px 0; border-radius: 8px; background: none; box-shadow: inset 0 0 0 1px var(--nmo-outline);
}
:root:not([data-theme="light"]) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on { color: var(--nmo-on-selected); }
:root:not([data-theme="light"]) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on::before {
  background: var(--nmo-selected); box-shadow: none;
}
/* Badges and pills: earned = the selected container, a warning = the loss container, the rest quiet. */
:root:not([data-theme="light"]) :is(.nmo-verdict .nmo-tv-badge, .nmo-deck-askpill .nmo-tv-badge, .nmo-dd-badge-rail-one, .nmo-verdict .nmo-tv-fit, .nmo-learned-chip) {
  min-height: 32px; box-sizing: border-box; padding: 0 12px; border: 1px solid var(--nmo-outline); border-radius: 8px;
  background: none; color: var(--text-dim); font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
:root:not([data-theme="light"]) :is(.nmo-verdict .nmo-tv-badge.is-money, .nmo-verdict .nmo-tv-badge.is-form,
  .nmo-verdict .nmo-tv-badge.is-behaviour, .nmo-deck-askpill .nmo-tv-badge:not(.is-warning), .nmo-dd-badge-rail-one.is-earned, .nmo-verdict .nmo-tv-fit) {
  border-color: transparent; background: var(--nmo-selected); color: var(--nmo-on-selected);
}
:root:not([data-theme="light"]) :is(.nmo-verdict .nmo-tv-badge.is-warning, .nmo-deck-askpill .nmo-tv-badge.is-warning, .nmo-dd-badge-rail-one.is-earned.is-warning) {
  border-color: transparent; background: var(--nmo-loss-container); color: var(--nmo-on-loss-container);
}
:root:not([data-theme="light"]) :is(.nmo-verdict .nmo-tv-badge-figure, .nmo-dd-badge-rail-figure) { font-weight: 600; font-variant-numeric: tabular-nums; }

/* ── the time-range row, Robinhood's: quiet words, the chosen one filled violet ── */
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) { gap: 4px; }
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill {
  flex: 0 0 auto; width: auto; min-width: 0; height: 48px; padding: 0 10px; color: var(--text-dim);
  font: 600 13px/28px var(--font-ui); letter-spacing: .3px; font-variant-numeric: tabular-nums;
}
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill::before {
  inset: 10px 0; border-radius: 6px; background: transparent;
}
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill[aria-pressed="true"] { color: var(--nmo-on-violet); font-weight: 600; }
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill[aria-pressed="true"]::before { background: var(--nmo-violet); }
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill:not([aria-pressed="true"]):not([aria-disabled="true"]):hover::before { background: var(--bg-elev-2); }
/* The coin chart's spans and our own balance's spans are the same row. */
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) :is(.nmo-range, .m3-rangechip) {
  height: 28px; min-height: 28px; padding: 0 10px; border: 0; border-radius: 6px; background: none; color: var(--text-dim);
  font: 600 13px/28px var(--font-ui); letter-spacing: .3px; font-variant-numeric: tabular-nums;
}
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) :is(.nmo-range, .m3-rangechip):is(.on, .is-on) {
  background: var(--nmo-violet); color: var(--nmo-on-violet);
}
:root:not([data-theme="light"]) .nmo-ranges.m3-chiprow { gap: 4px; }

/* ── the list switch: a filled track, a raised thumb, no outline ── */
:root:not([data-theme="light"]) .nmo-tv-pills.nmo-feedpills {
  gap: 0; height: 44px; box-sizing: border-box; padding: 4px; margin-left: 16px; margin-right: 16px;
  border: 0; border-radius: 999px; background: var(--bg-elev-2);
}
:root:not([data-theme="light"]) .nmo-feedpills .nmo-tv-pill {
  height: 36px; min-height: 36px; border-radius: 999px; background: transparent; color: var(--text-dim);
  font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
}
:root:not([data-theme="light"]) .nmo-feedpills .nmo-tv-pill.is-on { background: var(--bg-elev-3); color: var(--text); }
:root:not([data-theme="light"]) .nmo-feedscope-switch { gap: 0; padding: 4px; border-radius: 999px; background: var(--bg-elev-2); }
:root:not([data-theme="light"]) .nmo-feedscope-choice {
  min-height: 36px; padding: 0 16px; color: var(--text-dim); font: var(--m3-title-small); letter-spacing: var(--m3-title-small-tracking);
}
:root:not([data-theme="light"]) .nmo-feedscope-choice.is-on { background: var(--bg-elev-3); color: var(--text); }
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet, .nmo-copy-sheet) .nmo-seg { padding: 4px; background: var(--bg-elev-2); }
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet, .nmo-copy-sheet) .nmo-seg-b.is-on { background: var(--bg-elev-3); color: var(--text); }

/* ── buttons: fully round; 56 main, 40 small; filled violet, tonal, outlined ── */
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) .nmo-profile-follow,
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-follow {
  min-height: 56px; border-radius: 999px; box-shadow: none;
  background: var(--nmo-violet); color: var(--nmo-on-violet);
  font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking);
}
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) .nmo-profile-follow:not(:disabled):hover,
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-follow:not(:disabled):hover {
  background: color-mix(in srgb, var(--nmo-violet) 92%, var(--text));
}
/* Following already: the tonal button. */
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) .nmo-profile-follow.is-on,
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-balance-hero-profile) .nmo-profile-dock .nmo-profile-follow.is-on {
  height: 56px; padding: 0 24px; border: 0; box-shadow: none; background: var(--nmo-selected); color: var(--nmo-on-selected);
}
:root:not([data-theme="light"]) .nmo-follow-btn {
  height: 40px; padding: 0 16px; border-color: var(--nmo-outline); font: var(--m3-label-large); letter-spacing: var(--m3-label-large-tracking);
}
:root:not([data-theme="light"]) .nmo-follow-btn.is-on { border-color: transparent; background: var(--nmo-selected); color: var(--nmo-on-selected); font-weight: 500; }
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet, .nmo-copy-sheet) .m3-filledbtn:not(.nmo-profile-follow) {
  border-radius: 999px; box-shadow: none; background: var(--nmo-violet); color: var(--nmo-on-violet);
}
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet, .nmo-copy-sheet) .m3-tonalbtn { border-radius: 999px; background: var(--nmo-selected); color: var(--nmo-on-selected); }
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet, .nmo-copy-sheet) .m3-outlinedbtn { border-radius: 999px; border-color: var(--nmo-outline); }
/* The deck's three round buttons: outlined icon buttons on the black ground. */
:root:not([data-theme="light"]) .nmo-deck-act { border-color: var(--nmo-outline); background: var(--bg); box-shadow: none; }

/* ── sheets: 28px top corners, a visible handle, a solid surface, a hairline, no shadow ──
   The design's sheet is the highest surface, #202025 (v55 section 07), so it stands clear of the
   dimmed black screen behind it (the #349 complaint, "it blends with background black"); the cards
   inside it step down to #17171b. */
:root:not([data-theme="light"]) :is(.nmo-sheet, .nmo-copy-sheet) {
  border-radius: 28px 28px 0 0; background: var(--bg-elev-3); box-shadow: none;
  outline: 1px solid rgba(255, 255, 255, .08); outline-offset: -1px;
}
/* The copy sheet carries the violet wash at its top edge, fading out under the name, and a
   brighter handle (design v60 #cp1 and #cp2, #432). */
:root:not([data-theme="light"]) :is(.nmo-copy-sheet-ask, .nmo-copy-sheet-more) {
  background: linear-gradient(180deg, rgba(161, 92, 255, .26) 0, rgba(161, 92, 255, .08) 70px, transparent 96px) no-repeat, var(--bg-elev-3);
}
:root:not([data-theme="light"]) .nmo-copy-grab::before { background: rgba(255, 255, 255, .45); opacity: 1; border-radius: 2px; }
/* The deck's question sheet (#433, v60 .ask-sheet): the same highest surface under a wash that says who is
   speaking, violet 20% to 6% over the first 64px and gone by 110px, red 18% to 5% on the warning, and one
   hairline of light along the top edge in place of the outline. */
:root:not([data-theme="light"]) .nmo-deck-asksheet {
  border: 0; outline: 0; box-shadow: 0 -1px 0 rgba(255, 255, 255, .08);
  background: linear-gradient(180deg, rgba(161, 92, 255, .20) 0, rgba(161, 92, 255, .06) 64px, transparent 110px) no-repeat, var(--bg-elev-3);
}
:root:not([data-theme="light"]) .nmo-deck-asklayer.is-never .nmo-deck-asksheet {
  background: linear-gradient(180deg, rgba(255, 79, 100, .18) 0, rgba(255, 79, 100, .05) 64px, transparent 110px) no-repeat, var(--bg-elev-3);
}
:root:not([data-theme="light"]) .nmo-deck-askface { border-color: var(--bg-elev-3); }
:root:not([data-theme="light"]) .nmo-deck-askdot { background: #a15cff; box-shadow: 0 0 8px rgba(161, 92, 255, .9); }
:root:not([data-theme="light"]) .nmo-deck-asklayer.is-never .nmo-deck-askdot { background: #ff4f64; box-shadow: 0 0 8px rgba(255, 79, 100, .9); }
:root:not([data-theme="light"]) .nmo-deck-askbtn.is-outlined { border-color: var(--nmo-outline); color: var(--text); }
:root:not([data-theme="light"]) .nmo-deck-askbtn.is-filled { background: var(--nmo-violet); color: var(--nmo-on-violet); }
:root:not([data-theme="light"]) .nmo-deck-askbtn.is-warning { background: var(--nmo-loss-container); color: var(--nmo-on-loss-container); }
/* After the outlined rule above, which would otherwise put the app's own ink back on it (#454). */
:root:not([data-theme="light"]) .nmo-deck-askbtn.is-outlined.is-warning-words {
  color: #ff4f64; border-color: rgba(255, 79, 100, .45);
}
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-balance-hero-profile) {
  background: var(--bg-elev-3); box-shadow: none;
  --nmo-v14-page: #202025; --nmo-v14-card: #17171b; --nmo-v14-tag: #0e0e11; --nmo-v14-chosen: #202025;
}
:root:not([data-theme="light"]) .nmo-sheet-grab::before {
  width: 32px; height: 4px; border-radius: 2px; background: var(--nmo-outline); opacity: 1;
}
:root:not([data-theme="light"]) .nmo-deck-askgrab::before { background: rgba(255, 255, 255, .45); }
:root:not([data-theme="light"]) .nmo-sheet-scrim { background: rgba(0, 0, 0, .6); }
:root:not([data-theme="light"]) .nmo-deck-askscrim { background: rgba(0, 0, 0, .62); }

/* ── lists: two-line rows 72 tall, avatars 40, hairlines between ── */
:root:not([data-theme="light"]) .nmo-balance-row:not(.nmo-balance-head) {
  min-height: 72px; box-sizing: border-box; align-content: center; border-bottom-color: var(--nmo-line);
}
:root:not([data-theme="light"]) .nmo-balance-row:not(.nmo-balance-head) .nmo-balance-name { font: var(--m3-body-large); letter-spacing: var(--m3-body-large-tracking); }
:root:not([data-theme="light"]) .nmo-balance-row:not(.nmo-balance-head) .nmo-balance-facts { color: var(--text-dim); font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking); }
:root:not([data-theme="light"]) .nmo-balance-row:not(.nmo-balance-head) .nmo-balance-now { font: var(--m3-title-medium); letter-spacing: var(--m3-title-medium-tracking); font-variant-numeric: tabular-nums; }
:root:not([data-theme="light"]) .nmo-balance-row .nmo-rk { font-family: var(--font-mono); }
/* #445: the Traders list's row is the design's 64, and the "no trades" words stay small and faint. */
:root:not([data-theme="light"]) .nmo-balance-row.nmo-trader-line { min-height: 64px; }
:root:not([data-theme="light"]) .nmo-trader-line .nmo-trader-money .nmo-balance-now.nmo-pnl-say {
  font: var(--m3-body-medium); letter-spacing: .25px;
}

/* ── the identity block is never taller than its 40px avatar: name 16/22 over 14/18 ── */
:root:not([data-theme="light"]) :is(.nmo-balance-hero, .nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-av { width: 40px; height: 40px; }
:root:not([data-theme="light"]) :is(.nmo-balance-hero, .nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-top { gap: 12px; }
:root:not([data-theme="light"]) :is(.nmo-balance-hero, .nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-who {
  display: flex; flex-direction: column; justify-content: center; max-height: 40px; overflow: hidden;
}
/* Two lines and no third: a third (his intro, a delisted note, the handle under a name that already has
   the details line) would make the block taller than its picture. */
:root:not([data-theme="light"]) :is(.nmo-balance-hero, .nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-who > * { flex: none; }
/* Two lines and no third: the delisted note (#392) would make the block taller than his picture, so the row keeps his
   name and the one grey line, and the note is read on the page below. */
:root:not([data-theme="light"]) :is(.nmo-balance-hero, .nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-who > .nmo-balance-hero-delisted { display: none; }
:root:not([data-theme="light"]) .nmo-balance-hero:not(.nmo-balance-hero-profile, .nmo-balance-hero-nav) .nmo-tv-cap ~ .nmo-balance-hero-details { display: none; }
:root:not([data-theme="light"]) :is(.nmo-balance-hero, .nmo-balance-hero-profile, .nmo-sheet-nav) .nmo-tv-name {
  font: 500 16px/22px var(--font-ui); letter-spacing: .15px;
}
:root:not([data-theme="light"]) :is(.nmo-balance-hero, .nmo-balance-hero-profile, .nmo-sheet-nav) :is(.nmo-tv-cap, .nmo-balance-hero-details) {
  margin-top: 0; color: var(--text-dim); font: 400 14px/18px var(--font-ui); letter-spacing: .25px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── money: gain and loss, no third colour (the calendar and the profit bands are recoloured where they are declared) ── */
:root:not([data-theme="light"]) .nmo-dd-v14 .nmo-dd-scale {
  background: linear-gradient(90deg, #107b46 0 60%, rgba(255, 255, 255, .28) 60% 80%, #992f3c 80% 100%);
}
:root:not([data-theme="light"]) .nmo-dd-v14 :is(.nmo-dd-bar-part.is-large, .nmo-dd-row-dot.is-large) { background: rgba(255, 255, 255, .66); }
:root:not([data-theme="light"]) .nmo-dd-v14 .nmo-dd-bar-value { color: var(--nmo-on-violet); }
:root:not([data-theme="light"]) :is(.nmo-chart-pill text, .nmo-mark-letter) { fill: #001a0c; }

/* ── snackbars: the app's shared one is the design's M3 snackbar while nmo is open: 16px corners,
   56 tall, the inverse surface (light on the black screen) ── */
body:has(.nmo-root) .genie-toast {
  border-radius: 16px; min-height: 56px; box-sizing: border-box; box-shadow: none;
  font: var(--m3-body-medium); letter-spacing: var(--m3-body-medium-tracking);
}
:root:not([data-theme="light"]) body:has(.nmo-root) .genie-toast {
  background: #e6e1ee; color: #312f38; font-family: "Google Sans Flex", "Google Sans Text", Roboto, system-ui, sans-serif;
}
:root:not([data-theme="light"]) body:has(.nmo-root) .genie-toast.is-error { background: #e6e1ee; color: #b3261e; }

/* ═══ A COIN'S PRICE IS A ROBINHOOD LINE ON AN OKX DOT GROUND (#423, the fomo look, section 08) ═══
   Coin and asset prices only (a trader's money is the LED board). One 2px stroke, green when the drawn span
   closed up and red when it closed down, with a soft glow along it; under it a fine dot matrix in the line's
   colour fading to nothing; no grid; the current price as a dotted hairline in the faint white. Vivid only. */
:root:not([data-theme="light"]) .nmo-coin-line.is-falling { --nmo-chart-green: var(--nmo-down); }
:root:not([data-theme="light"]) .nmo-coin-line .nmo-chart-ground { visibility: hidden; }
:root:not([data-theme="light"]) .nmo-coin-line .nmo-chart-dotcell { fill: var(--nmo-chart-green); }
:root:not([data-theme="light"]) .nmo-coin-line .nmo-chart-fill { fill: var(--nmo-price-dots); mask: var(--nmo-price-dots-mask); }

/* ═══ THE SMOOTH LINE, ITS LIT GROUND, AND ENTRY/EXIT MARKS (#426) ═══
   The coin line draws two strokes over the same readings: `.nmo-chart-path`, the straight-segment record the
   checks read, and `.nmo-chart-curve`, the smooth line a person sees. Only the curve is painted; the fill is cut
   from the curve's own path, so the lit ground meets the line at every pixel. The ground is a faint wash with
   the dots on top, faded over the fill's own height. Marks from `placeMarks` are discs centred on the line. */
.nmo-coin-line .nmo-chart-path { stroke: none; }
.nmo-chart-curve { stroke: var(--nmo-chart-green); stroke-width: 2; fill: none; }
.nmo-chart-dotwash { fill: none; }
:root:not([data-theme="light"]) .nmo-coin-line .nmo-chart-dotwash { fill: var(--nmo-chart-green); fill-opacity: 0.2; }
:root:not([data-theme="light"]) .nmo-coin-line .nmo-chart-curve {
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--nmo-chart-green) 90%, transparent));
}
:root:not([data-theme="light"]) .nmo-coin-line.is-falling .nmo-chart-pill text { fill: #3a0810; }
.nmo-price-mark-disc { stroke: rgba(8, 8, 11, 0.85); stroke-width: 2; }
.nmo-price-mark-sign { stroke-width: 2; stroke-linecap: round; }
.nmo-price-mark-entry .nmo-price-mark-disc { fill: var(--nmo-up, var(--nmo-chart-green)); }
.nmo-price-mark-entry .nmo-price-mark-sign { stroke: #062b18; }
.nmo-price-mark-exit .nmo-price-mark-disc { fill: #ff7a3d; }
.nmo-price-mark-exit .nmo-price-mark-sign { stroke: #3a1200; }
:root:not([data-theme="light"]) .nmo-coin-line .nmo-chart-now {
  stroke: rgba(255, 255, 255, 0.35); stroke-dasharray: 1 3; opacity: 1;
}

/* ═══ THE SPAN BUTTONS KEEP THEIR WHOLE WORD (#426) ═══
   Measured on 4536 in Chinese at 390 wide: the five span buttons shrank to fit the row and each word's last
   character broke onto a second line the 28px button hides, so "15 分钟 / 1 小时 / 1 天 / 全部" read "15 分 / 1 小
   / 1 / 全". A button never shrinks and never breaks its word (the design's span row is nowrap). The whole Chinese
   words are wider, so they take 6px each side instead of 10px: all five and the gear then fit 390 (gear ends at
   342 of 374) and stay on screen at 360. English fits both widths at 10px and is unchanged. */
.nmo-ranges > .nmo-range { flex: none; white-space: nowrap; }
:root:lang(zh) .nmo-ranges > .nmo-range { padding-inline: 6px; }

/* ═══ THE COIN POSITION SHEET (#429, design v59 section 04 and the owner's rulings of 16 Sep) ═══
   Vivid only. The header — grab handle, picture, name, Following — takes a violet wash from about 30% at the
   sheet's top edge to about 5% at the header's foot over the sheet surface, closed by a faint violet hairline
   (v56). The colours are the violet #a15cff mixed over the sheet surface #17171b, written as hexes. The coin row
   and everything under it stay on the plain surface. */
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-top) .nmo-sheet-grab { background: linear-gradient(180deg, #402c5f 0%, #3a2955 100%); }
:root:not([data-theme="light"]) .nmo-sheet .nmo-coin-top {
  background: linear-gradient(180deg, #3a2955 0%, #1e1a26 100%);
  border-bottom: 1px solid #3e2a5b;
}

/* The four tiles: no title, two columns; the position's dollars sit in the label; entry and exit a marked pair. */
.nmo-cs-pos { border: 1px solid var(--nmo-outline-variant, rgba(255, 255, 255, .12)); border-radius: 16px; padding: 12px 14px; margin-top: 12px; }
.nmo-cs-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.nmo-cs-tile { display: flex; flex-direction: column; min-width: 0; }
.nmo-cs-figure { display: block; font-size: 22px; line-height: 28px; font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nmo-cs-figure small { font-size: 12px; font-weight: 500; letter-spacing: .5px; color: var(--text-dim); margin-left: 4px; }
/* The coins held: the whole number never shrinks or cuts; the coin's name after it gives way first. A long amount
   steps down in size (nmo.ts `coinSheetTiles`): 9 characters or more at 18px, 12 or more at 15px. */
.nmo-cs-figure.nmo-cs-coins { display: flex; align-items: baseline; text-overflow: clip; }
.nmo-cs-coins > [data-nmo-num] { flex: none; }
.nmo-cs-coins > small { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nmo-cs-figure.nmo-cs-long { font-size: 18px; }
.nmo-cs-figure.nmo-cs-longer { font-size: 15px; }
.nmo-cs-label { font-size: 11px; line-height: 16px; font-weight: 500; letter-spacing: .5px; color: var(--text-faint); }
.nmo-cs-label .nmo-cs-usd { font-size: 14px; font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* #500, seen in its pictures: since #492 the caption reads "his position · $1,994,241", and at 390 wide a seven-figure
   position broke INSIDE the number ("$1,994,24" over "1") — the live tick writes it in pieces, and a line may break
   between them. The dollars now wrap as one piece, under "his position ·" when they do not fit beside it. */
.nmo-cs-label .nmo-cs-pct { font-weight: 600; }
.nmo-cs-note { font-size: 11px; line-height: 15px; color: var(--text-faint); }
.nmo-cs-said { margin: 0; font-size: 13px; line-height: 18px; color: var(--text-dim); }
/* #510: the one sentence under his own position when it is over a tenth of its pool, in the sheet's plain ink. */
.nmo-cs-poolroom { margin-top: 10px; }
.nmo-cs-level { border-top: 1px solid var(--nmo-outline-variant, rgba(255, 255, 255, .12)); padding-top: 10px; }
.nmo-cs-lv { display: flex; align-items: center; gap: 7px; font-weight: 600; }
.nmo-cs-dot { width: 12px; height: 12px; border-radius: 50%; flex: none; }
.nmo-cs-dot-in { background: #1ee07f; box-shadow: 0 0 8px rgba(30, 224, 127, .6); }
.nmo-cs-dot-out { background: #ff7a3d; box-shadow: 0 0 8px rgba(255, 122, 61, .6); }
/* #492 (owner 17 Sep 23:30): a figure that does not exist yet — the exit before any sale — is a dash in the figure's
   place, at the figure's size so the four cells keep one line, in the caption's quiet colour and with no dot: the
   dot is the chart's reference line, and there is no exit line to mark. */
.nmo-cs-figure.nmo-cs-dash { color: var(--text-faint); font-weight: 400; }
.nmo-cs-age { margin-top: 8px; }

/* Everyone's orders down the chart's left edge (v48/v49): the code face, buys green and sells red, each arrow a
   triangle in its order's own colour, an order of $800 or more in bold with a glow of its own colour, and a
   brief white flash as each new order arrives. Rows moved into a rebuilt chart have lost their flash class. */
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade {
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace); letter-spacing: 0;
}
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade-fresh { animation: nmo-trade-fresh 1.1s ease-out both; }
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade-big { font-weight: 600; text-shadow: 0 0 10px currentColor; }
/* `.nmo-trade.` in front, or these reach the trade bar's Buy and Sell buttons, which carry the same two class
   names: on 16 Sep the Buy button's words went green on its own green fill and could not be read. */
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade.nmo-trade-buy { color: #1ee07f; }
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade.nmo-trade-sell { color: #ff4f64; }
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade-arrow { font-size: 0; display: inline-block; width: 8px; height: 6px; position: relative; flex: none; }
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade-arrow::before {
  content: ""; position: absolute; left: 0; top: 0; width: 0; height: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
}
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade.nmo-trade-buy .nmo-trade-arrow::before { border-bottom: 6px solid currentColor; }
:root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade.nmo-trade-sell .nmo-trade-arrow::before { border-top: 6px solid currentColor; }
@keyframes nmo-trade-fresh {
  0% { color: #ffffff; text-shadow: 0 0 12px #ffffff, 0 0 22px rgba(255, 255, 255, .7); opacity: 1; }
  55% { color: #ffffff; text-shadow: 0 0 6px rgba(255, 255, 255, .6); }
  100% { opacity: .9; }
}
@media (prefers-reduced-motion: reduce) {
  :root:not([data-theme="light"]) .nmo-sheet:has(.nmo-coin-chart) .nmo-trade-fresh { animation: none; }
}

/* ═══ A TRADER'S POSITION FIGURES FLASH THEIR TEXT GREEN OR RED ON EACH REFRESH (owner, designs v57 and v58) ═══
   The position's dollars, the profit and its percent, on the feed card and on this sheet: green when the value
   went up, red when it went down, with a soft glow held about half of 0.9s, then back to the figure's own
   colour. No background and no zoom; the digits still roll. Off under reduced motion. */
:root:not([data-theme="light"]) [data-nmo-live^="trader"]:is(.nmo-flash-up, .nmo-flash-down) { color: inherit; }
:root:not([data-theme="light"]) [data-nmo-live^="trader"].nmo-up:is(.nmo-flash-up, .nmo-flash-down) { color: var(--nmo-up); }
:root:not([data-theme="light"]) [data-nmo-live^="trader"].nmo-down:is(.nmo-flash-up, .nmo-flash-down) { color: var(--nmo-down); }
:root:not([data-theme="light"]) [data-nmo-live^="trader"].nmo-flash-up { animation: nmo-figure-up .9s ease-out; }
:root:not([data-theme="light"]) [data-nmo-live^="trader"].nmo-flash-down { animation: nmo-figure-down .9s ease-out; }
@keyframes nmo-figure-up {
  0%, 50% { color: #1ee07f; text-shadow: 0 0 10px rgba(30, 224, 127, .55); }
  100% { text-shadow: 0 0 0 rgba(30, 224, 127, 0); }
}
@keyframes nmo-figure-down {
  0%, 50% { color: #ff4f64; text-shadow: 0 0 10px rgba(255, 79, 100, .5); }
  100% { text-shadow: 0 0 0 rgba(255, 79, 100, 0); }
}
@media (prefers-reduced-motion: reduce) {
  :root:not([data-theme="light"]) [data-nmo-live^="trader"]:is(.nmo-flash-up, .nmo-flash-down) { animation: none; }
}

/* ═══ THE FEED'S POSITION CARD: A SMALL SYMBOL AT THE ICON'S TOP, THE SHORT ADDRESS UNDER IT (owner, design v59) ═══
   The coin symbol is smaller (medium weight, about 12px) and sits against the top edge of the coin's icon, with
   the shortened contract address in the code face and the quiet colour on the line under it. The card has no age
   line (v57); the money columns on the right are unchanged. */
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader .nmo-card-names .nmo-sym { font: 500 12px/15px var(--font-ui); letter-spacing: .4px; }
/* The short address is always nine characters; it is drawn whole rather than cut to "Hg5J…pu…" when the
   money columns leave the name column a few pixels short (measured at 390 wide on 16 Sep, Chinese). */
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader .nmo-card-names .nmo-coin-name.nmo-mint {
  font: 400 10px/14px var(--font-mono); letter-spacing: 0; color: var(--text-faint);
  overflow: visible; text-overflow: clip;
}
/* ═══ TWO LINES THAT SHARE THEIR BASELINES (owner, 16 Sep 22:30 and 23:10, design v60 .pos-live) ═══
   "contract address should be bottom aligned with position / profit numbers". The card is one grid of two rows: the
   symbol sits on the labels' line (Position / Profit), and the short address sits on the figures' line, so its foot
   lines up with the numbers. The picture stays centred across both rows. The names block and the two money blocks
   give their children to the card's grid (`display: contents`), exactly as the design does; this replaces v59's
   "symbol at the icon's top edge". */
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader {
  grid-template-rows: auto auto; row-gap: 2px; align-items: baseline;
}
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-tok { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-card-names { display: contents; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-card-names > * { grid-column: 2; min-width: 0; align-self: baseline; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-card-names > :first-child { grid-row: 1; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-card-names > :nth-child(n + 2) { grid-row: 2; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-kv { display: contents; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-card-names + .nmo-kv > * { grid-column: 3; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-kv + .nmo-kv > * { grid-column: 4; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-kv > .nmo-k { grid-row: 1; justify-self: end; text-align: right; }
:root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-kv > :not(.nmo-k) { grid-row: 2; justify-self: end; text-align: right; }
/* ═══ THE SYMBOL'S LETTERS START AT THE ICON'S TOP EDGE (owner, 17 Sep 05:05, #452) ═══
   "symbol text needs to be top aligned to the icon". This replaces v60's "symbol on the labels' baseline" with the
   icon centred across the two rows (#429's 23:10 order): centred, the icon met the letters only when the two rows
   happened to add up right, and on his phone they did not.
   Now the icon sits at the top of the rows, and every text box on them is trimmed to its letters, from the capitals'
   top to the baseline, so the top row starts where "ELON" starts. The symbol and the Position / Profit labels still
   share a baseline, and so do the short address and the figures. The top row takes whatever the icon's height leaves,
   which puts the bottom row's baseline on the icon's bottom edge whenever both rows fit beside it; a cell that wraps
   makes the rows taller than the icon, and then the bottom row simply follows, 16px under the labels' baseline.
   The card keeps its height and the icon its place: it sat 15px inside the card's top and bottom edges while
   centred, and that is now the card's own padding.
   The symbol ends a long name in an ellipsis, so it clips what it paints. Its 4px of padding, taken back by a 4px
   negative margin, keep descenders, accents and Chinese characters inside that clip without moving the letters.
   A browser that cannot trim a text box keeps #429's centred rows. */
@supports (text-box: trim-both cap alphabetic) {
  :root:not([data-theme="light"]) .nmo-card.nmo-card-leader {
    grid-template-rows: 1fr auto; row-gap: 16px; padding-top: 15px; padding-bottom: 15px;
  }
  :root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-tok { align-self: start; }
  :root:not([data-theme="light"]) .nmo-card.nmo-card-leader > :is(.nmo-card-names, .nmo-kv) > * {
    text-box: trim-both cap alphabetic; margin-top: 0; margin-bottom: 0;
  }
  :root:not([data-theme="light"]) .nmo-card.nmo-card-leader > .nmo-card-names > .nmo-sym {
    padding-top: 4px; padding-bottom: 4px; margin-top: -4px; margin-bottom: -4px;
  }
}

/* ═══ MOMENTS: THE TAKEOVER (#430, design v55 section 05) ═══
   One block, so it re-applies whole after #424. It hangs off the page's body, so it inherits
   nothing from `.nmo-root`: every colour carries #424's value as its fallback. The small moments
   are the shared snackbar, which #424 already styles on these screens.
   Three speeds and one curve, as the design says: 280ms into place, 900ms for the celebration. */
.nmo-moment {
  position: fixed; inset: 0; z-index: 30000;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 0 28px 40px; box-sizing: border-box; text-align: center;
  background: rgba(0, 0, 0, .9);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  color: #ffffff;
  font-family: "Google Sans Flex", "Google Sans Text", "Roboto Flex", Roboto, system-ui, sans-serif;
  opacity: 0; transition: opacity .22s cubic-bezier(.2, .8, .2, 1);
}
.nmo-moment.is-on { opacity: 1; }
.nmo-moment-badge { position: relative; width: 188px; height: 188px; margin-bottom: 26px; }
.nmo-moment-rays {
  position: absolute; inset: -56px; border-radius: 50%;
  background: repeating-conic-gradient(from 0deg, rgba(161, 92, 255, .32) 0deg 7deg, transparent 7deg 22deg);
  -webkit-mask: radial-gradient(closest-side, #000 30%, transparent 100%);
  mask: radial-gradient(closest-side, #000 30%, transparent 100%);
  opacity: 0; transform: scale(.4);
}
.nmo-moment-glow {
  position: absolute; inset: -24px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(208, 92, 255, .55), transparent 75%);
  opacity: 0;
}
/* The scalloped badge: twelve points, the design's own outline. */
.nmo-moment-shape {
  position: absolute; inset: 0; display: grid; place-items: center;
  clip-path: polygon(50% 0%, 61.4% 7.5%, 75% 6.7%, 81.1% 18.9%, 93.3% 25%, 92.5% 38.6%, 100% 50%, 92.5% 61.4%, 93.3% 75%, 81.1% 81.1%, 75% 93.3%, 61.4% 92.5%, 50% 100%, 38.6% 92.5%, 25% 93.3%, 18.9% 81.1%, 6.7% 75%, 7.5% 61.4%, 0% 50%, 7.5% 38.6%, 6.7% 25%, 18.9% 18.9%, 25% 6.7%, 38.6% 7.5%);
  background: linear-gradient(145deg, var(--nmo-orchid, #d05cff), var(--nmo-violet, #a15cff) 70%);
  transform: scale(0) rotate(-40deg);
}
.nmo-moment-shape svg {
  width: 88px; height: 88px; fill: none; stroke: #ffffff; stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round; filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .25));
}
.nmo-moment-kicker {
  font-weight: 500; font-size: 16px; line-height: 24px; letter-spacing: .15px; color: #f1e6ff;
  opacity: 0; transform: translateY(12px);
}
.nmo-moment-big {
  font-weight: 600; font-size: 57px; line-height: 64px; letter-spacing: -.25px;
  color: #1ee07f; text-shadow: 0 0 28px rgba(30, 224, 127, .4); font-variant-numeric: tabular-nums;
  max-width: 100%; overflow-wrap: anywhere;
  opacity: 0; transform: translateY(12px);
}
.nmo-moment-big.is-plain { color: #ffffff; text-shadow: 0 0 28px rgba(161, 92, 255, .5); font-size: 45px; line-height: 52px; }
.nmo-moment-sentence {
  font-weight: 400; font-size: 16px; line-height: 24px; letter-spacing: .5px; color: rgba(255, 255, 255, .66);
  margin-top: 6px; max-width: 340px; opacity: 0; transform: translateY(12px);
}
.nmo-moment-actions { display: flex; gap: 10px; margin-top: 28px; opacity: 0; transform: translateY(12px); }
.nmo-moment-btn {
  min-width: 120px; height: 40px; padding: 0 24px; border-radius: 999px; cursor: pointer;
  font-weight: 500; font-size: 14px; line-height: 20px; letter-spacing: .1px;
  transition: border-radius .12s cubic-bezier(.2, .8, .2, 1);
}
.nmo-moment-btn:active { border-radius: 16px; }
.nmo-moment-btn.is-primary { background: var(--nmo-violet, #a15cff); color: var(--nmo-on-violet, #0b0016); border: 0; }
.nmo-moment-btn.is-ghost { background: transparent; color: #ffffff; border: 1px solid rgba(255, 255, 255, .28); }
.nmo-moment-btn:focus-visible { outline: 2px solid #f1e6ff; outline-offset: 2px; }
.nmo-moment.is-on .nmo-moment-shape { animation: nmo-moment-pop .7s cubic-bezier(.2, 1.6, .35, 1) .05s forwards; }
.nmo-moment.is-on .nmo-moment-rays { animation: nmo-moment-rays-in .6s ease-out .2s forwards, nmo-moment-spin 9s linear .2s infinite; }
.nmo-moment.is-on .nmo-moment-glow { animation: nmo-moment-glow 1.8s ease-in-out .3s infinite alternate; }
.nmo-moment.is-on .nmo-moment-kicker { animation: nmo-moment-up .28s cubic-bezier(.2, .8, .2, 1) .45s forwards; }
.nmo-moment.is-on .nmo-moment-big { animation: nmo-moment-up .28s cubic-bezier(.2, .8, .2, 1) .55s forwards; }
.nmo-moment.is-on .nmo-moment-sentence { animation: nmo-moment-up .28s cubic-bezier(.2, .8, .2, 1) .65s forwards; }
.nmo-moment.is-on .nmo-moment-actions { animation: nmo-moment-up .28s cubic-bezier(.2, .8, .2, 1) .8s forwards; }
@keyframes nmo-moment-pop { 0% { transform: scale(0) rotate(-40deg); } 100% { transform: scale(1) rotate(0); } }
@keyframes nmo-moment-rays-in { to { opacity: 1; transform: scale(1); } }
@keyframes nmo-moment-spin { from { rotate: 0deg; } to { rotate: 360deg; } }
@keyframes nmo-moment-glow { from { opacity: .55; transform: scale(.92); } to { opacity: 1; transform: scale(1.06); } }
@keyframes nmo-moment-up { to { opacity: 1; transform: none; } }
/* Reduced motion keeps the moment: everything is in its final place, nothing turns or pops. */
@media (prefers-reduced-motion: reduce) {
  .nmo-moment { transition: none; }
  .nmo-moment.is-on :is(.nmo-moment-shape, .nmo-moment-rays, .nmo-moment-kicker, .nmo-moment-big, .nmo-moment-sentence, .nmo-moment-actions) {
    animation: none; opacity: 1; transform: none;
  }
  .nmo-moment.is-on .nmo-moment-glow { animation: none; opacity: .8; }
}
/* A Chinese sentence breaks at its comma, not inside a word: on 4536 at 390 wide the copy-start sentence
   broke 模拟 in two. `anywhere` still breaks a long sentence with no comma rather than let it overflow. */
:root:lang(zh) .nmo-moment-sentence { word-break: keep-all; overflow-wrap: anywhere; }
/* The snackbar's one action (View, Undo) in the design's violet. #424 made the snackbar the light inverse
   surface but the action kept the app's pale accent, which read as blank on it (4536, 16 Sep 23:0x). The
   snackbar hangs off the page's body, where the type variables are not defined, so the values are written out. */
:root:not([data-theme="light"]) body:has(.nmo-root) .genie-toast-action {
  color: #5b4bc4; font: 500 14px/20px "Google Sans Flex", "Google Sans Text", Roboto, system-ui, sans-serif; letter-spacing: .1px;
}
:root:not([data-theme="light"]) body:has(.nmo-root) .genie-toast.is-error .genie-toast-action { color: #b3261e; }
/* ═══ THE SNACKBAR AFTER AN ANSWER ON THE DECK, WITH UNDO (#433, v60 #ak3 .ask-snack) ═══
   The whole width less 16px each side, 56 tall, 16px corners, the words on the left and Undo on the right,
   standing clear above the deck's three round buttons: 26px above their top edge, as v60 has it, which on a
   390 by 844 phone with a 34px home bar is 101px over the bar. Dark: white with black words and a violet
   Undo, as the design draws it. Written out, because the snackbar hangs off the body. */
body:has(.nmo-root) .genie-toast[data-variant="nmo-undo"] {
  left: 16px; right: 16px; width: auto; max-width: 448px; margin: 0 auto;
  bottom: calc(101px + env(safe-area-inset-bottom, 0px));
  justify-content: space-between; gap: 12px; min-height: 56px; padding: 0 8px 0 20px; border-radius: 16px;
  font: 400 14px/20px "Google Sans Flex", "Google Sans Text", Roboto, system-ui, sans-serif; letter-spacing: .25px;
}
body:has(.nmo-root) .genie-toast[data-variant="nmo-undo"] .genie-toast-action {
  flex: none; height: 40px; min-height: 40px; margin: 0; padding: 0 12px; border-radius: 999px;
  font: 500 14px/20px "Google Sans Flex", "Google Sans Text", Roboto, system-ui, sans-serif; letter-spacing: .1px;
}
/* In Chinese the sentence breaks only after the badge's closing quote, where the dictionary marks it; it broke 交易者 in two. */
:root:lang(zh) body:has(.nmo-root) .genie-toast[data-variant="nmo-undo"] > span { word-break: keep-all; overflow-wrap: anywhere; }
:root:not([data-theme="light"]) body:has(.nmo-root) .genie-toast[data-variant="nmo-undo"] { background: #ffffff; color: #000000; }
:root:not([data-theme="light"]) body:has(.nmo-root) .genie-toast[data-variant="nmo-undo"] .genie-toast-action { color: #5b1fbf; }

/* ═══ #428: THE TRADER SHEET AND YOUR OWN PROFILE, TO DESIGN V56 ═══════════════════════════════════
   Dark (Vivid) only, like #424's block above it; the light theme keeps what it had. */

/* ── the sheet's header: a violet wash behind the handle and his identity row (owner 16 Sep ~19:10,
   the same header as the coin position sheet, #429). Accent violet at 30% on the top edge, 12% most of
   the way down, 5% at the row's foot, over the sheet's own solid surface; a faint violet hairline under
   it at all times, and the plain surface below. The handle floats over the row's top padding on a
   phone, so the wash is behind it too. ── */
:root:not([data-theme="light"]) .nmo-sheet-profile > .nmo-sheet-nav {
  background:
    linear-gradient(180deg, rgba(161, 92, 255, .30) 0, rgba(161, 92, 255, .12) 77%, rgba(161, 92, 255, .05) 100%),
    var(--nmo-v14-page, #202025);
  border-bottom-color: rgba(161, 92, 255, .28);
}
:root:not([data-theme="light"]) .nmo-sheet-profile.is-scrolled > .nmo-sheet-nav { border-bottom-color: rgba(161, 92, 255, .28); }
:root:not([data-theme="light"]) .nmo-sheet-profile > .nmo-sheet-grab::before { width: 36px; background: rgba(255, 255, 255, .45); }
/* A wide screen gives the handle its own row: that row carries the wash's top colour, so the two meet
   with no seam. */
:root:not([data-theme="light"]) .nmo-sheet-profile > .nmo-sheet-grab {
  background: linear-gradient(rgba(161, 92, 255, .30), rgba(161, 92, 255, .30)), var(--nmo-v14-page, #202025);
}
/* On a phone the handle floats over the row, which paints the wash itself. */
@media (max-width: 640px) {
  :root:not([data-theme="light"]) .nmo-sheet-profile > .nmo-sheet-grab { background: none; }
}

/* ── the question tabs: filled pills with no outline; the chosen one is the light lavender pill ── */
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab {
  color: var(--text); font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab::before {
  box-shadow: none; background: var(--nmo-v14-card, #17171b);
}
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on { color: #1c1a22; }
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs .nmo-coin-tab.is-on::before { background: #e8def8; }

/* ── the chain drop-down: design v56's `.p-chainsel`, a 36px pill with a chevron. A tap opens the page's
   own menu under it (#517); the select inside is hidden and only holds the choices ── */
.nmo-chainsel {
  position: relative; align-self: flex-start; display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  -webkit-tap-highlight-color: transparent; user-select: none; -webkit-user-select: none;
  box-sizing: border-box; height: 36px; margin: 2px 0 6px; padding: 0 12px 0 14px; border-radius: 999px;
  background: var(--bg-elev-2); color: var(--text); font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
:root:not([data-theme="light"]) .nmo-chainsel { background: #202025; }
:root:not([data-theme="light"]) .nmo-sheet .nmo-chainsel { background: #17171b; }
.nmo-chainsel b { font-weight: 500; color: var(--text-dim); }
.nmo-chainsel-chevron { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.nmo-chainsel-select[hidden] { display: none; }
.nmo-chainsel:has(.nmo-chainsel-select:disabled) { cursor: default; }
.nmo-chainsel:has(.nmo-chainsel-select:disabled) .nmo-chainsel-chevron { display: none; }
.nmo-chainsel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-chainsel-chevron { transition: transform 160ms cubic-bezier(.2, 0, 0, 1); }
.nmo-chainsel[aria-expanded="true"] .nmo-chainsel-chevron { transform: rotate(180deg); }
/* The open menu (#517). It hangs off the page's body, never the app column, whose transform would place a
   fixed box against the column (#394), so it names its own colours. The design draws the pill but no open
   menu; the menu takes the pill's surface (the design's surface-container-highest #202025), 48px rows at the
   pill's Label Large, and marks the chosen chain as the design marks a chosen range: the selected tint and a
   check. As wide as its longest name, never narrower than the pill, never past the screen's 8px margins. */
.nmo-chainmenu {
  position: fixed; z-index: 1000; box-sizing: border-box; overflow-y: auto; overscroll-behavior: contain;
  width: max-content; padding: 8px 0; border-radius: 16px;
  background: #202025; color: #ffffff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 2px 6px 2px rgba(0, 0, 0, .15), inset 0 0 0 1px rgba(255, 255, 255, .08);
  font: 500 14px/20px var(--font-ui, "Google Sans Flex", "Google Sans Text", Roboto, system-ui, sans-serif); letter-spacing: .1px;
}
:root[data-theme="light"] .nmo-chainmenu {
  background: var(--bg-elev-2, #e9eef6); color: var(--text, #1f1f1f);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 2px 6px 2px rgba(0, 0, 0, .15);
}
.nmo-chainmenu-item {
  display: flex; align-items: center; gap: 12px; box-sizing: border-box; min-height: 48px; padding: 0 16px;
  cursor: pointer; white-space: nowrap; outline: none; -webkit-tap-highlight-color: transparent;
}
.nmo-chainmenu-words { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nmo-chainmenu-item:hover, .nmo-chainmenu-item:focus-visible { background: rgba(255, 255, 255, .08); }
:root[data-theme="light"] .nmo-chainmenu-item:hover, :root[data-theme="light"] .nmo-chainmenu-item:focus-visible { background: rgba(0, 0, 0, .06); }
.nmo-chainmenu-item[aria-selected="true"] { background: rgba(161, 92, 255, .26); color: #f1e6ff; }
:root[data-theme="light"] .nmo-chainmenu-item[aria-selected="true"] { background: rgba(161, 92, 255, .16); color: inherit; }
.nmo-chainmenu-check { flex: none; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.nmo-portfolio-coins { display: flex; flex-direction: column; }

/* ── a coin row is the design's coin card (`.p-coin`): a 44px picture, the name at title-medium over how
   much is held, and on the right its value at title-medium over its move ──
   The three type rules are the trader sheet's Portfolio tab only (#459). Unscoped, they also reached a genie's
   page, where the rows sit under the "Positions" heading and came out at its size and weight; that page keeps
   the rows' own roles (Body Large, Label Large, Body Small), as the base rules above say. */
:root:not([data-theme="light"]) .nmo-profile-pos { grid-template-columns: 44px minmax(0, 1fr) auto; gap: 12px; min-height: 60px; padding: 8px 0; }
:root:not([data-theme="light"]) .nmo-profile-pos-tok { width: 44px; height: 44px; }
:root:not([data-theme="light"]) .nmo-portfolio .nmo-profile-pos-name { font: 500 16px/24px var(--font-ui); letter-spacing: .15px; }
:root:not([data-theme="light"]) .nmo-profile-pos-amount {
  color: var(--text-dim); font: 400 14px/20px var(--font-ui); letter-spacing: .25px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
:root:not([data-theme="light"]) .nmo-portfolio .nmo-profile-pos-value { font: 500 16px/24px var(--font-ui); letter-spacing: .15px; }
:root:not([data-theme="light"]) .nmo-portfolio :is(.nmo-profile-pos-pct, .nmo-profile-pos-unpriced, .nmo-profile-pos-said) {
  margin-top: 0; font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
:root:not([data-theme="light"]) .nmo-profile-pos-right { gap: 0; }
/* The Portfolio tab's total leads as the design's figure: 36/44 over its caption. */
:root:not([data-theme="light"]) .nmo-portfolio-total { color: var(--text-dim); font: 400 14px/20px var(--font-ui); letter-spacing: .25px; }
:root:not([data-theme="light"]) .nmo-portfolio-total-usd { display: block; font: 500 36px/44px var(--font-display); letter-spacing: 0; color: var(--text); }
/* His row stands on the wash itself, not on a card of its own; Ask Genie and Share are the design's
   outlined round icon buttons. */
:root:not([data-theme="light"]) .nmo-sheet-profile > .nmo-sheet-nav > .nmo-balance-hero-nav { background: transparent; border-radius: 0; }
:root:not([data-theme="light"]) .nmo-sheet-nav .nmo-balance-hero-action::before,
:root:not([data-theme="light"]) .nmo-sheet-nav .nmo-balance-hero-action.is-genie::before {
  width: 40px; height: 40px; background: transparent; box-shadow: inset 0 0 0 1px var(--nmo-outline);
}
:root:not([data-theme="light"]) .nmo-sheet-nav .nmo-balance-hero-action.is-share { color: var(--text); }
/* The page inside the sheet re-declared the older, darker surfaces for itself, so the pinned question
   row painted a black strip across the sheet. Inside a sheet the page takes the sheet's own: the
   surface #202025 and the cards a step down at #17171b, as #424 set on the sheet. */
:root:not([data-theme="light"]) .nmo-sheet .nmo-profile:has(.nmo-balance-hero-profile) {
  --nmo-v14-page: #202025; --nmo-v14-card: #17171b; --nmo-v14-tag: #0e0e11; --nmo-v14-chosen: #202025;
}
/* The drop-down is as wide as its words, in a grid column or a flex one. */
.nmo-chainsel { justify-self: start; width: max-content; max-width: 100%; }

/* ── your own profile (design v60 #up1/#up2, #442 over #428's v56): the social row, the figure, the board, the spans,
   Portfolio | Activities. Measured on the design at 390 wide: the row 6px over and 12px under a 44px picture, then 12px
   to the figure, 6px to its line, 18px to a 150px board, 24px to the spans, 24px to the switch, 24px to the drop-down
   (or 22px to the first activity) and 18px to the first wallet row, with 12px between rows. ── */
/* The tab row's 12px above is the gap to this row (#435); the 6px is the design's own padding above the picture. */
[data-nmo-scope="profile"] .nmo-profile-social {
  display: flex; align-items: center; gap: 12px; margin: 0 16px; padding: 6px 0 12px; min-height: 44px;
}
[data-nmo-scope="profile"] .nmo-profile-social > .nmo-own-avatar {
  width: 44px; height: 44px; flex: none; border-radius: 999px; display: grid; place-items: center; overflow: hidden;
  background: linear-gradient(135deg, var(--nmo-tint-a, #5b2bb8), var(--nmo-tint-b, #d05cff));
  color: #fff; font: 500 16px/24px var(--font-ui); letter-spacing: .15px;
}
[data-nmo-scope="profile"] .nmo-profile-social-who { flex: 1 1 auto; min-width: 0; }
[data-nmo-scope="profile"] .nmo-profile-social-name {
  font: 500 16px/22px var(--font-ui); letter-spacing: .15px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
[data-nmo-scope="profile"] .nmo-profile-social-sub {
  font: 400 14px/18px var(--font-ui); letter-spacing: .25px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
[data-nmo-scope="profile"] .nmo-profile-stat {
  flex: none; width: 64px; min-height: 42px; margin: 0; padding: 0; border: 0; background: none; gap: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--text); text-align: center;
  font: 400 16px/24px var(--font-ui);
}
[data-nmo-scope="profile"] button.nmo-profile-stat { cursor: pointer; }
/* The count a 22px figure, its word an 11px label under it, as the design draws both. */
[data-nmo-scope="profile"] .nmo-profile-stat > :first-child:not(.nmo-profile-stat-k) {
  font: 600 22px/26px var(--font-display); letter-spacing: 0; font-variant-numeric: tabular-nums;
}
[data-nmo-scope="profile"] .nmo-profile-stat-k { font: 500 11px/16px var(--font-ui); letter-spacing: .5px; color: var(--text-faint); }
[data-nmo-scope="profile"] .nmo-profile-stat:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }

/* One figure and one line, on the page and not on a card. */
[data-nmo-scope="profile"] .nmo-own-hero { margin: 12px 16px 0; }
[data-nmo-scope="profile"] .nmo-own-figure {
  color: var(--text); font: 600 45px/52px var(--font-display); letter-spacing: 0; white-space: nowrap; font-variant-numeric: tabular-nums;
}
[data-nmo-scope="profile"] .nmo-own-delta {
  margin-top: 6px; color: var(--text-dim); font: 500 16px/24px var(--font-ui); letter-spacing: .5px; font-variant-numeric: tabular-nums;
}
[data-nmo-scope="profile"] .nmo-own-delta.nmo-up { color: var(--nmo-up); }
[data-nmo-scope="profile"] .nmo-own-delta.nmo-down { color: var(--nmo-down); }
[data-nmo-scope="profile"] .nmo-own-delta .nmo-num { font: inherit; color: inherit; }
/* #505: "· 48 of 60 coins priced" rides the line in its plain ink, never the span's green or red. */
[data-nmo-scope="profile"] .nmo-own-delta .nmo-own-covered { color: var(--text-dim); }
/* The board: a trader page's picture and LED layer, 150px tall and as wide as the page's content. */
[data-nmo-scope="profile"] .nmo-own-board { position: relative; height: 150px; margin: 18px 16px 0; }
[data-nmo-scope="profile"] .nmo-own-board > .nmo-tv-line {
  position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; overflow: visible;
}
/* 1D 7D 30D All, left-aligned under the board. They are the app's range chips (the Vivid look sizes them, 28px, the
   chosen one filled violet); the design writes the ones not chosen in violet too. */
[data-nmo-scope="profile"] .nmo-own-spans { display: flex; flex-wrap: nowrap; justify-content: flex-start; gap: 4px; margin: 24px 16px 0; padding: 0; }
[data-nmo-scope="profile"] .nmo-own-spans > .nmo-own-span { flex: 0 0 auto; margin: 0; cursor: pointer; }
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-own-spans > .nmo-range.nmo-own-span:not(.is-on) { color: var(--nmo-violet); }
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-own-spans > .nmo-range.nmo-own-span.is-on { font-weight: 500; }

/* Portfolio | Activities: the design's segmented switch, a 44px track with the chosen half raised. Named
   at #424's weight for the coin tab bar so its underline and violet do not reach this one. */
:root [data-nmo-scope="profile"] .nmo-coin-tabs.nmo-profile-tabs.nmo-profile-seg:not(.nmo-dd-tabs) {
  display: flex; gap: 0; height: 44px; box-sizing: border-box; margin: 24px 16px 0; padding: 4px;
  border: 0; border-radius: 999px; background: #17171b; overflow: hidden;
}
:root [data-nmo-scope="profile"] .nmo-coin-tabs.nmo-profile-seg:not(.nmo-dd-tabs) .nmo-coin-tab.nmo-profile-tab {
  flex: 1 1 0; box-sizing: border-box; height: 36px; min-height: 0; margin: 0; padding: 0 12px; border: 0; border-radius: 999px;
  background: transparent; color: var(--text-dim); font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
:root [data-nmo-scope="profile"] .nmo-coin-tabs.nmo-profile-seg:not(.nmo-dd-tabs) .nmo-coin-tab.nmo-profile-tab::before,
:root [data-nmo-scope="profile"] .nmo-coin-tabs.nmo-profile-seg:not(.nmo-dd-tabs) .nmo-coin-tab.nmo-profile-tab::after { content: none; display: none; }
:root [data-nmo-scope="profile"] .nmo-coin-tabs.nmo-profile-seg:not(.nmo-dd-tabs) .nmo-coin-tab.nmo-profile-tab.is-on {
  background: #202025; color: var(--text); border-bottom: 0;
}
:root[data-theme="light"] [data-nmo-scope="profile"] .nmo-coin-tabs.nmo-profile-tabs.nmo-profile-seg:not(.nmo-dd-tabs) { background: var(--bg-elev-2); }
:root[data-theme="light"] [data-nmo-scope="profile"] .nmo-coin-tabs.nmo-profile-seg:not(.nmo-dd-tabs) .nmo-coin-tab.nmo-profile-tab.is-on { background: var(--bg); }

/* Portfolio: the drop-down, then wallet rows 12px apart, Cash last. */
[data-nmo-scope="profile"] .nmo-profile-portfolio {
  display: flex; flex-direction: column; gap: 12px; margin: 0 16px; padding: 0;
}
[data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-chainsel { margin: 24px 0 6px; }
[data-nmo-scope="profile"] .nmo-own-row {
  display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; column-gap: 12px; align-items: center;
  min-height: 60px; margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
}
[data-nmo-scope="profile"] .nmo-own-row-ico {
  width: 44px; height: 44px; border-radius: 50%; overflow: hidden; display: grid; place-items: center;
  background: linear-gradient(135deg, #3a1c7a, #8b3dff); color: #fff; font: 600 13px/1 var(--font-ui);
}
[data-nmo-scope="profile"] .nmo-own-row-ico > .nmo-tok { width: 44px; height: 44px; border-radius: 50%; margin: 0; }
[data-nmo-scope="profile"] .nmo-own-row-ico > .nmo-tok-letters {
  display: grid; place-items: center; background: transparent; color: #fff; font: 600 13px/1 var(--font-ui);
}
[data-nmo-scope="profile"] .nmo-own-row-mid { min-width: 0; }
[data-nmo-scope="profile"] .nmo-own-row-name {
  color: var(--text); font: 500 16px/24px var(--font-ui); letter-spacing: .15px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
[data-nmo-scope="profile"] .nmo-own-row-sub { color: var(--text-dim); font: 400 14px/20px var(--font-ui); letter-spacing: .25px; overflow-wrap: anywhere; }
[data-nmo-scope="profile"] .nmo-own-row-sub .nmo-num { font: inherit; color: inherit; }
[data-nmo-scope="profile"] .nmo-own-row-right { display: flex; flex-direction: column; align-items: flex-end; text-align: right; }
[data-nmo-scope="profile"] .nmo-own-row-value {
  color: var(--text); font: 500 16px/24px var(--font-ui); letter-spacing: .15px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
[data-nmo-scope="profile"] .nmo-own-row-pct {
  margin-top: 2px; color: var(--text-dim); font: 500 14px/20px var(--font-ui); letter-spacing: .1px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
[data-nmo-scope="profile"] .nmo-own-row-pct.nmo-up { color: var(--nmo-up); }
[data-nmo-scope="profile"] .nmo-own-row-pct.nmo-down { color: var(--nmo-down); }
[data-nmo-scope="profile"] .nmo-own-row-unpriced { color: var(--text-faint); font: 400 14px/20px var(--font-ui); white-space: nowrap; }
[data-nmo-scope="profile"] .nmo-own-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
[data-nmo-scope="profile"] .nmo-own-cash { cursor: default; }
/* Activities: one line each, a hairline over every row, 12px apart — the design's own list, not the bell's. */
[data-nmo-scope="profile"] .nmo-profile-activities { display: flex; flex-direction: column; gap: 12px; margin: 22px 16px 0; padding-top: 0; }
[data-nmo-scope="profile"] .nmo-own-act {
  display: grid; grid-template-columns: 36px minmax(0, 1fr) auto; column-gap: 12px; align-items: center;
  min-height: 56px; border-top: 1px solid rgba(255, 255, 255, .12);
}
:root[data-theme="light"] [data-nmo-scope="profile"] .nmo-own-act { border-top-color: var(--border); }
[data-nmo-scope="profile"] .nmo-own-act-ic {
  width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
  background: var(--nmo-selected, var(--bg-elev-2)); color: var(--nmo-on-selected, var(--text)); font: 400 16px/1 var(--font-ui);
}
[data-nmo-scope="profile"] .nmo-own-act-mid { min-width: 0; }
[data-nmo-scope="profile"] .nmo-own-act-line { color: var(--text); font: 400 14px/20px var(--font-ui); letter-spacing: .25px; overflow-wrap: anywhere; }
[data-nmo-scope="profile"] .nmo-own-act-line b { font-weight: 500; }
[data-nmo-scope="profile"] .nmo-own-act-when {
  display: block; color: var(--text-faint); font: 400 12px/16px var(--font-ui); letter-spacing: .4px;
}
[data-nmo-scope="profile"] .nmo-own-act-fig {
  color: var(--text); font: 500 14px/20px var(--font-ui); letter-spacing: .1px; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right;
}
[data-nmo-scope="profile"] .nmo-own-act-fig .nmo-num { font: inherit; color: inherit; }
[data-nmo-scope="profile"] .nmo-own-act-fig .nmo-num.nmo-up { color: var(--nmo-up); }
[data-nmo-scope="profile"] .nmo-own-act-note { margin: 0; color: var(--text-dim); font: 400 14px/20px var(--font-ui); letter-spacing: .25px; }
/* The bottom of the list (#450): an empty marker that asks for older lines as it comes within reach, "Loading…"
   while they are on the way, or why they could not be read with Try again. */
[data-nmo-scope="profile"] .nmo-own-act-more { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 1px; }

/* Each holding is the design's coin row: a 44px picture, the coin's name over its address, and on the
   right what it is worth over its profit in the money colour. The two headings stay for a screen
   reader and are not drawn. */
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card.nmo-card-profile {
  display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; grid-template-rows: auto auto;
  column-gap: 12px; row-gap: 0; align-items: center; min-height: 60px; margin: 0; padding: 8px 0;
  background: none; border: 0; border-radius: 0; box-shadow: none;
}
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile > :first-child {
  grid-column: 1; grid-row: 1 / span 2; width: 44px; height: 44px; border-radius: 50%; overflow: hidden;
}
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile > .nmo-card-names { grid-column: 2; grid-row: 1 / span 2; min-width: 0; }
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile .nmo-sym { font: 500 16px/24px var(--font-ui); letter-spacing: .15px; }
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile .nmo-full {
  font: 400 14px/20px var(--font-ui); letter-spacing: .25px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile > .nmo-kv {
  grid-column: 3; margin: 0; padding: 0; text-align: right; display: block;
}
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile > .nmo-kv[data-nmo-card-fact="trade-amount"] { grid-row: 1; align-self: end; }
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile > .nmo-kv[data-nmo-card-fact="close-pnl"] { grid-row: 2; align-self: start; }
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile .nmo-k {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile [data-nmo-card-fact="trade-amount"] .nmo-v {
  font: 500 16px/24px var(--font-ui); letter-spacing: .15px; font-variant-numeric: tabular-nums;
}
:root:not([data-theme="light"]) [data-nmo-scope="profile"] .nmo-profile-portfolio .nmo-card-profile [data-nmo-card-fact="close-pnl"] .nmo-v {
  font: 500 14px/20px var(--font-ui); letter-spacing: .1px; font-variant-numeric: tabular-nums;
}

/* The Following list's top line: a back arrow and the list's name. */
[data-nmo-scope="profile"] .nmo-profile-following-head { display: flex; align-items: center; gap: 4px; margin: 8px 8px 0; }
[data-nmo-scope="profile"] .nmo-profile-back {
  width: 48px; height: 48px; display: grid; place-items: center; border: 0; background: none; color: var(--text); cursor: pointer; border-radius: 999px;
}
[data-nmo-scope="profile"] .nmo-profile-following-title { margin: 0; font: 400 22px/28px var(--font-ui); color: var(--text); }

/* The board breathes (#430): a soft band of light crosses the money board about every ten seconds
   while the page is at rest; `is-flare` is the same band, bright, for a trader who really banked a
   profit in the last day. It lies over the chart and changes nothing under it. Off under reduced
   motion (the timer does not run either). */
/* The chart box is already `position: relative` (the line and its guides hang off it). */
.nmo-board-light::after {
  content: ""; position: absolute; inset: 0 0 16px; z-index: 2; pointer-events: none;
  background: linear-gradient(100deg, transparent 0%, transparent 35%, rgba(161, 92, 255, .16) 50%, transparent 65%, transparent 100%);
  background-size: 250% 100%; background-position: 100% 0; opacity: 0;
}
.nmo-board-light.is-breath::after { animation: nmo-board-sweep .9s cubic-bezier(.2, .8, .2, 1) forwards; }
.nmo-board-light.is-flare::after {
  background-image: linear-gradient(100deg, transparent 0%, transparent 30%, rgba(208, 92, 255, .42) 50%, transparent 70%, transparent 100%);
  mix-blend-mode: screen;
  animation: nmo-board-sweep .9s cubic-bezier(.2, .8, .2, 1) forwards;
}
@keyframes nmo-board-sweep {
  0% { opacity: 1; background-position: 100% 0; }
  85% { opacity: 1; }
  100% { opacity: 0; background-position: 0% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .nmo-board-light.is-breath::after, .nmo-board-light.is-flare::after { animation: none; opacity: 0; }
}

/* ═══ A TRADER'S MONEY IS AN LED BOARD (#423, the fomo look, section 08) ═══
   `lightLedBoard` lays a canvas over the balance picture and lights cells from its readings. It shows in the
   Vivid look only; there the picture's own stroke, wash and end dot stand down, and its hole bands, their
   dashed edges and a held reading's guide stay on top of the board. The held dot takes the board's colour. In the
   light theme the canvas stays hidden and the line is drawn as before. */
.nmo-tv-chart > .nmo-led { display: none; }
:root:not([data-theme="light"]) .nmo-tv-chart.has-led > .nmo-led {
  display: block; position: absolute; z-index: 0; pointer-events: none;
}
/* Lit green when the span went up and red when it went down (the owner's rule on #425); a level span in the soft
   white. The canvas reads this as its colour; a held reading's dot takes the same. */
:root:not([data-theme="light"]) .nmo-tv-chart.has-led { --nmo-led-rgb: 30, 224, 127; }
:root:not([data-theme="light"]) .nmo-tv-chart.has-led:has(> .nmo-tv-line.is-down) { --nmo-led-rgb: 255, 79, 100; }
:root:not([data-theme="light"]) .nmo-tv-chart.has-led:has(> .nmo-tv-line.is-level) { --nmo-led-rgb: 200, 200, 210; }
:root:not([data-theme="light"]) .nmo-tv-chart.has-led > .nmo-tv-line { color: rgb(var(--nmo-led-rgb)); }
:root:not([data-theme="light"]) .nmo-tv-chart.has-led > .nmo-tv-line :is(.nmo-tv-stroke, .nmo-tv-wash, .nmo-tv-dot) { visibility: hidden; }
:root:not([data-theme="light"]) .nmo-tv-chart.has-led .nmo-balance-hero-guide { border-top-color: transparent; }

/* ═══ THE TRADERS LIST'S SMALL MONEY BOARD (#425; owner 16 Sep 2026 ~19:10, design v56; in #445's row) ═══
   About 64 by 26, LEFT of the row's figure in the row's last cell, 12px apart, and the figure right-aligned in a
   fixed width, so the boards line up row to row whatever the figure's length. The cell keeps the board's width on
   a row with no stored week. Green up, red down, the soft white when level, as on his page. The Vivid look only:
   in the light theme the board is hidden. */
.nmo-trader-money > .nmo-balance-led { display: none; }
:root:not([data-theme="light"]) .nmo-trader-money { min-width: 142px; }
:root:not([data-theme="light"]) .nmo-trader-money > .nmo-balance-led {
  display: block; flex: none; width: 64px; height: 26px;
  --nmo-led-rgb: 30, 224, 127;
}
:root:not([data-theme="light"]) .nmo-trader-money > .nmo-balance-led.is-down { --nmo-led-rgb: 255, 79, 100; }
:root:not([data-theme="light"]) .nmo-trader-money > .nmo-balance-led.is-level { --nmo-led-rgb: 200, 200, 210; }
:root:not([data-theme="light"]) .nmo-trader-money > .nmo-balance-led > .nmo-led { display: block; }

/* ═══ THE COIN POSITION SHEET, TO DESIGN v60 #cs1 (#429, owner 16 Sep 22:25: "follow same ui and message") ═══
   Everything here hangs off `.nmo-cs-sheet`, which `nmo.ts` puts on the sheet only when it is a trader's coin
   position (`isCoinPositionSheet`). Sizes are read off the design's own page at 390 wide. Vivid only. */
/* The header: a 40px picture, the name at 16px over "Solana · 91 days" at 14px, and an outlined Follow button. */
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-top { gap: 12px; padding-top: 8px; padding-bottom: 10px; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-top > .nmo-av { width: 40px; height: 40px; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-who-box { display: flex; flex-direction: column; min-width: 0; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-top .nmo-who { font: 500 16px/24px var(--font-ui); letter-spacing: .15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-who-sub { font: 400 14px/18px var(--font-ui); letter-spacing: .25px; color: #a8a8ab; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-top .nmo-follow-btn,
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-top .nmo-follow-btn.is-on {
  height: 36px; min-height: 36px; padding: 0 16px; border-radius: 999px; border: 1px solid #4d4d51;
  background: transparent; color: #ffffff; font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
/* The coin row: a 44px rounded-square picture, the symbol with its OPEN / CLOSED tag over the short address, and on the
   right the market cap at 22px over today's move at 12px. The address's copy control stays, drawn quietly. */
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-head { gap: 12px; margin-top: 12px; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-head .nmo-sym { font: 500 16px/24px var(--font-ui); letter-spacing: .15px; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-sym-line { align-items: center; gap: 8px; }
.nmo-cs-tag {
  display: inline-grid; place-items: center; height: 20px; padding: 0 6px; box-sizing: border-box;
  border: 1px solid #4d4d51; border-radius: 4px; color: #a8a8ab;
  font: 500 11px/16px var(--font-ui); letter-spacing: .5px; white-space: nowrap;
}
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-names .nmo-mintrow .nmo-mint { font: 400 12px/16px var(--font-mono); color: #6f6f73; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-names .nmo-copy { opacity: .45; transform: scale(.8); }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-facts { text-align: right; }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-mc { font: 500 22px/28px var(--font-ui); color: #ffffff; }
/* #495 (owner 17 Sep 23:50): where a price is too small to read, the market cap stands in and says so — one small grey
   word after the figure, on its line, never wrapped under it. The coin's page draws its market cap bare. */
.nmo-coin-mc.nmo-coin-cap { white-space: nowrap; }
/* #498: the same for an average entry or exit too small to read, drawn as the market cap at that price. */
.nmo-cs-figure.nmo-cs-cap { white-space: nowrap; }
/* #502: every other place a price too small to read is drawn as the market cap at that price — the money cards, a
   Feed post's price — with the same quiet word; and on the chart's scale and pill, as a smaller grey word after the
   figure, since an SVG word cannot take the row's 12px span. */
.nmo-price-cap { white-space: nowrap; }
.nmo-chart-cap-word { font-size: 9px; fill: var(--text-faint); letter-spacing: .3px; }
/* In the pill the word keeps the pill's own dark ink, a little lighter, so it reads on green and on red alike. */
.nmo-chart-pill .nmo-chart-cap-word { fill: inherit; opacity: .7; font-weight: 500; }
.nmo-coin-cap-word { font: 500 12px/16px var(--font-ui); letter-spacing: .4px; color: var(--text-faint); }
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-cap-word { color: #a8a8ab; }
.nmo-cs-today { font: 500 12px/16px var(--font-ui); letter-spacing: .5px; white-space: nowrap; }
:root:not([data-theme="light"]) .nmo-cs-today.nmo-up { color: #1ee07f; }
:root:not([data-theme="light"]) .nmo-cs-today.nmo-down { color: #ff4f64; }
/* "3 of the top 30 hold it · @ether_monk, @derek518" */
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-cs-holders { font: 400 12px/16px var(--font-ui); letter-spacing: .4px; color: #6f6f73; margin-top: 10px; }
/* The spans: the design's five, violet words, the chosen one filled. */
:root:not([data-theme="light"]) .nmo-cs-sheet .nmo-coin-chart .nmo-range:not(.is-on) { color: #a15cff; }
/* The one button and the quiet line under it. */
:root:not([data-theme="light"]) .nmo-sheet .nmo-trade-bar-mirror { gap: 0; padding-top: 6px; }
:root:not([data-theme="light"]) .nmo-trade-bar .nmo-trade-mirror {
  width: 100%; height: 56px; border: 0; border-radius: 999px; cursor: pointer;
  background: #a15cff; color: #0b0016; font: 500 16px/24px var(--font-ui); letter-spacing: .15px;
}
.nmo-cs-ask {
  display: block; width: 100%; padding: 8px 0 0; border: 0; background: none; cursor: pointer;
  font: 400 12px/16px var(--font-ui); letter-spacing: .4px; color: #6f6f73; text-align: center;
}

/* ═══ THE TOP OF A TRADER'S PAGE, TO DESIGN v60 #pf0 (#443, owner 17 Sep ~10:20: "this looks very diff from the design") ═══
   Measured off the design page at 390 wide, from the foot of his name row: the figure 12px under it, 45/52; its one grey
   line 6px under that, 16/24; the board 18px under the line, 150 tall with a 16px strip over it for the "now" label,
   across the card's whole width, with no price scale, no dates and no dashed edges; the spans 24px under the board;
   the chips 18px under the spans, 32 tall in two rows 6px apart; the sentence 10px under them, 16/24 with the kind in
   white; the question chips 12px under the sentence. Nothing is drawn on a raised box: the top stands on the sheet's own
   surface, as the design draws it. The board's green-up and red-down is #425's, not the design page's violet. */
.nmo-balance-hero-profile .nmo-balance-hero-panel,
.nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable {
  margin-top: 0; padding: 0; gap: 0; background: transparent; border-radius: 0; min-height: 0;
}
.nmo-balance-hero-profile .nmo-balance-hero-figures:has(+ .nmo-tv-chart) { margin-bottom: 0; }
.nmo-balance-hero-profile .nmo-balance-hero-reading { min-height: 0; }
.nmo-balance-hero-profile .nmo-balance-hero-figures .nmo-tv-value {
  margin-top: 12px; color: var(--text); font: 600 45px/52px var(--font-display); letter-spacing: 0;
}
/* The one line under it: the change in its colour, then the caption, on one line that never wraps. */
.nmo-balance-hero-profile .nmo-balance-hero-range-line {
  display: flex; align-items: baseline; gap: 0; margin-top: 6px; min-width: 0; white-space: nowrap; overflow: hidden;
}
.nmo-balance-hero-profile :is(.nmo-tv-range, .nmo-balance-hero-quantity, .nmo-balance-hero-range-sep) {
  font: 500 16px/24px var(--font-ui); letter-spacing: .5px; font-variant-numeric: tabular-nums;
}
.nmo-balance-hero-profile :is(.nmo-balance-hero-quantity, .nmo-balance-hero-range-sep) { color: var(--text-dim); }
/* A held reading's moment stands in that same line's place, in the same box: 6px under the figure, 16/24. It replaces
   the line while a finger is down, so any other box would move the board 10px under the finger that is holding it. */
.nmo-balance-hero-profile .nmo-balance-hero-held-when {
  display: block; margin: 6px 0 0; font: 500 16px/24px var(--font-ui); letter-spacing: .5px;
}
/* The separator keeps its spaces: laid out as a flex item, an ordinary space at either end of it collapses away. */
.nmo-balance-hero-profile .nmo-balance-hero-range-sep { white-space: pre; }
.nmo-balance-hero-profile .nmo-balance-hero-quantity { overflow: hidden; text-overflow: ellipsis; }
/* #497 (the owner, 17 Sep 23:58): the line says what the figure leaves out and when — "holds now · 125 of 153 coins
   priced · Sep 17 11:50 PM" — and at 16px that does not fit the 358px it has. Cut with "…", the moment was the part lost,
   and the moment is what lets a reader put this figure beside his Portfolio tab's. So on his page the line runs on to a
   second row as ordinary words, where it needs one. A held reading keeps the line's height (`wireHeldReading`), so the
   board still does not move under the finger. This replaces #443's "one line that never wraps" for his page. */
.nmo-balance-hero-profile .nmo-balance-hero-range-line { display: block; white-space: normal; overflow: visible; }
.nmo-balance-hero-profile .nmo-balance-hero-quantity { white-space: normal; overflow: visible; text-overflow: clip; word-break: keep-all; }
/* keep-all: Chinese may otherwise break between any two characters, and "9月17日 23:50" split as "9月17" / "日 23:50".
   The row now breaks only at the line's own spaces; the moment's are no-break spaces (`readMoment`). */
/* The board: the picture 150 tall under a 16px strip for its "now" label, across the whole width. */
.nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart {
  width: auto; height: 166px; flex: 0 0 166px; align-self: stretch; margin: 2px 0 0;
}
.nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart > .nmo-tv-line {
  top: 16px; right: auto; bottom: auto; left: 0; width: 100%; height: 150px;
}
/* No price scale, no dates, no dashed hole edges: a hole is the board's own dark columns (#439's card board). */
.nmo-balance-hero .nmo-tv-chart :is(.nmo-balance-hero-guides, .nmo-balance-hero-dates, .nmo-tv-gap-edge) { display: none; }
.nmo-balance-hero .nmo-tv-gap:not(.is-held) { fill-opacity: 0; }
/* The spans 24px under the board, and the words of the ones not chosen violet, as the card draws them. */
.nmo-balance-hero-profile .nmo-balance-hero-controls { display: block; margin: 0; }
.nmo-balance-hero-profile .nmo-tv-pills { margin: 14px 0 -10px; }
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill:not([aria-pressed="true"]) { color: var(--nmo-violet); }
:root:not([data-theme="light"]) .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill[aria-pressed="true"] { font-weight: 500; }
/* The chips and the sentence stand on the page, not on a card. */
.nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict {
  margin: 18px 0 0; padding: 0; background: transparent; border-radius: 0;
}
.nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict .nmo-verdict-pills { margin: 0; }
.nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict .nmo-dd-badge-rail { gap: 6px; }
.nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict .nmo-verdict-says {
  margin: 10px 0 0; color: var(--text-dim); font: 400 16px/24px var(--font-ui); letter-spacing: .5px; text-wrap: wrap;
}
.nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict .nmo-verdict-says .nmo-verdict-kind { color: var(--text); font-weight: 500; }
.nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict .nmo-verdict-says.is-quiet { color: var(--text-faint); }
/* A chip is the design's: 32 tall, 8px corners, its figure heavier. Earned violet, a warning red, and the "+N" that
   opens the rest outlined and quiet. */
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict :is(.nmo-dd-badge-rail-one, .nmo-tv-fit) {
  min-height: 32px; height: 32px; padding: 0 12px; gap: 6px; border: 1px solid transparent; border-radius: 8px;
  font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict :is(.nmo-dd-badge-rail-one.is-earned, .nmo-tv-fit) {
  background: rgba(161, 92, 255, .26); color: #f1e6ff;
}
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict .nmo-dd-badge-rail-one.is-earned.is-warning {
  background: rgba(255, 79, 100, .16); color: #ffb3bf;
}
:root:not([data-theme="light"]) .nmo-profile:has(.nmo-balance-hero-profile) > .nmo-verdict .nmo-dd-badge-more {
  background: transparent; border-color: rgba(255, 255, 255, .28); color: var(--text-dim); cursor: pointer;
}
.nmo-dd-badge-rail-one[hidden] { display: none; }
/* The question chips 12px under the sentence: their row keeps the design's 36px pill inside a 48px target. */
.nmo-profile:has(.nmo-balance-hero-profile) .nmo-profile-tabs.nmo-dd-tabs { padding: 6px 0 0; margin-top: 6px; }

/* ═══ THE FOR YOU CARD, TO DESIGN v60 #dk1 AND #dk2 (#439, owner 17 Sep: "does not look like from design") ═══
   Measured off the design page at 390 wide: the card 352 wide with 18px inside it. His face 40, his name 16/22 over one grey line 14/18, and "12 copying" on the right.
   The figure, then the span's change 4px under it; the board 10px under that, 178 tall with a 16px strip on top for
   its "now" label, across the whole inner width, with no price scale, no dates and no dashed edges (a cut is the
   board's own dark columns); the spans 10px under it; the sentence 14px under them, two lines of Body Large; the three
   facts 14px under it, each over a hairline; the chips 10px under those, 28 tall, on one row. The board's green-up and
   red-down colour is #425's, not the design page's violet (friday's decision, 16 Sep 23:20). The card is as tall as
   its content, with the pile's edges under it (#334; the owner, 17 Sep ~05:50, over the design's full-height card). */
.nmo-deck { padding: 0 6px; }
.nmo-deck-front .nmo-deck-scroll { padding: 18px 18px 16px; }
:root:not([data-theme="light"]) .nmo-deck-card:not(.is-under) .nmo-deck-front {
  border-color: rgba(255, 255, 255, .12);
  background: radial-gradient(90% 60% at 50% -10%, rgba(161, 92, 255, .22), transparent 60%), #0e0e11;
}
/* The name row: his face, his name over the grey line, and the copying pill centred on the face. */
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-identity { align-items: center; padding: 0; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-tv-who { flex: 1 1 auto; min-width: 0; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-tv-av .nmo-av-letters {
  background: linear-gradient(135deg, #3a1c7a, #a15cff); color: #ffffff;
  font: 500 16px/24px var(--font-ui); letter-spacing: .5px;
}
.nmo-balance-hero-copying {
  flex: none; margin-left: auto; padding: 4px 10px; border-radius: 999px; white-space: nowrap;
  background: rgba(236, 230, 245, .06); color: var(--text-dim);
  font: 500 12px/16px var(--font-ui); letter-spacing: .5px; font-variant-numeric: tabular-nums;
}
:root:not([data-theme="light"]) .nmo-balance-hero-copying { color: rgba(255, 255, 255, .66); }
/* The figure 14px under the name row, and its change (or the cut's line) 4px under the figure, both 500 16/24. The
   card's figure carries no gap of its own above it: the page's 12px (#443) is its own, and the card's row gives it 14. */
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-figures .nmo-tv-value { margin-top: 0; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-panel,
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable { margin-top: 14px; gap: 0; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-figures:has(+ .nmo-tv-chart) { margin-bottom: 0; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-figures .nmo-tv-range { margin-top: 4px; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-tv-range.is-not-comparable { font: 500 16px/24px var(--font-ui); letter-spacing: .5px; }
:root:not([data-theme="light"]) .nmo-deck-front .nmo-balance-hero-profile .nmo-tv-range.nmo-up { color: #1ee07f; }
:root:not([data-theme="light"]) .nmo-deck-front .nmo-balance-hero-profile .nmo-tv-range.nmo-down { color: #ff4f64; }
:root:not([data-theme="light"]) .nmo-deck-front .nmo-balance-hero-profile .nmo-tv-range.is-not-comparable { color: rgba(255, 255, 255, .66); }
/* The board: 178 tall, its drawing 162 under a 16px strip for the painted "now" label (#442's painter), across the whole inner
   width. On a screen too short for the whole card the deck's fit takes the drawing down to 80 (`--nmo-deck-board`, #538). */
.nmo-deck-front .nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart {
  height: calc(var(--nmo-deck-board, 162px) + 16px); flex: 0 0 auto; margin: 10px 0 0; width: auto;
}
.nmo-deck-front .nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart > .nmo-tv-line {
  top: 16px; left: 0; width: 100%; height: var(--nmo-deck-board, 162px);
}
.nmo-deck-front .nmo-balance-hero .nmo-tv-chart :is(.nmo-balance-hero-guides, .nmo-balance-hero-dates, .nmo-tv-gap-edge) { display: none; }
.nmo-deck-front .nmo-balance-hero .nmo-tv-gap:not(.is-held) { fill-opacity: 0; }
/* The spans: the painted 28px pill 10px under the board, the unchosen words violet as in the design. */
.nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-controls { margin: 0; display: block; }
.nmo-deck-front .nmo-balance-hero-profile .nmo-tv-pills { margin: 0; }
:root:not([data-theme="light"]) .nmo-deck-front .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill:not([aria-pressed="true"]) { color: #a15cff; }
:root:not([data-theme="light"]) .nmo-deck-front .nmo-balance-hero .nmo-tv-pills:not(.nmo-feedpills) .nmo-tv-pill[aria-pressed="true"] { font-weight: 500; }
/* The sentence 14px under the painted spans: the kind in white, the rest in the soft grey, two lines at most. */
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict { margin-top: 4px; }
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-says { text-wrap: wrap; }
:root:not([data-theme="light"]) .nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-says:not(.is-quiet) { color: rgba(255, 255, 255, .66); }
:root:not([data-theme="light"]) .nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-says .nmo-verdict-kind { color: #ffffff; }
/* The three facts. */
.nmo-verdict-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 14px; }
.nmo-verdict-fact { min-width: 0; padding-top: 10px; border-top: 1px solid var(--border); }
:root:not([data-theme="light"]) .nmo-verdict-fact { border-top-color: rgba(255, 255, 255, .12); }
.nmo-verdict-fact-figure {
  display: block; color: var(--text); font: 500 16px/24px var(--font-ui); letter-spacing: .15px; font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden;
}
.nmo-verdict-fact-figure.nmo-none { color: var(--text-faint); }
.nmo-verdict-fact-figure.is-reading { width: 40px; height: 16px; margin: 4px 0; border-radius: 8px; background: var(--nmo-line); }
/* The label wraps rather than cuts a word (#476). It was one line, cut at the column's edge, and at 390 the middle
   column is 99px while "coins made money" needs 104: the card read "coins made mone". The design's own row (v75 #dk1,
   `.dk-stats span`) sets no such rule, so a label too long for its third takes a second line. */
.nmo-verdict-fact-label {
  display: block; color: var(--text-faint); font: 500 11px/16px var(--font-ui); letter-spacing: .5px;
}
:root:not([data-theme="light"]) .nmo-verdict-fact-label { color: rgba(255, 255, 255, .42); }
/* His tags, first in the card's bottom half, over the verdict (#480, owner 17 Sep 17:25; it was between the verdict and
   the facts under #476): up to three of his page's own chips, 28 tall, earned in violet and warnings in red. No gap
   above them, so the row starts exactly where the sentence starts on a card with no chips, and 10px under them before
   the sentence. They may take a second row — three chips carrying figures ("Win size vs loss size 148×") do not fit
   390 wide on one — and each may shrink and cut its own name rather than push the row wider than the card. */
.nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-pills { margin: 0 0 10px; }
.nmo-deck-front .nmo-verdict .nmo-tv-badges { flex-wrap: wrap; gap: 6px; overflow: hidden; }
:root:not([data-theme="light"]) .nmo-deck-front :is(.nmo-verdict .nmo-tv-badge, .nmo-verdict .nmo-tv-fit) {
  flex: 0 1 auto; min-width: 0; min-height: 28px; height: 28px; padding: 0 12px; border-radius: 8px; white-space: nowrap;
  font: 500 12px/16px var(--font-ui); letter-spacing: .5px;
}
:root:not([data-theme="light"]) .nmo-deck-front .nmo-verdict .nmo-tv-fit { flex: none; background: rgba(161, 92, 255, .26); color: #f1e6ff; }
:root:not([data-theme="light"]) .nmo-deck-front .nmo-verdict .nmo-tv-badge.is-warning { background: rgba(255, 79, 100, .16); color: #ffb3bf; }
/* ═══ A PHONE TOO SHORT FOR THE WHOLE CARD (#538, owner 18 Sep ~15:50) ═══
   After the chart is at its floor, the deck's fit (forYouDeck.ts, `fitDeck`) sets chips aside (`hidden`) and then asks
   for the tight card: every gap between the blocks a little smaller, nothing taken off and nothing resized. A card that
   fits is never tight, so it looks exactly as before. */
.nmo-deck-front .nmo-verdict .nmo-tv-badges > [hidden] { display: none; }
/* The bottom padding stays: the cards behind step 14px under the one in front, inside their own 16px (#334). */
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-deck-scroll { padding: 8px 18px 16px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-panel,
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-panel.is-unavailable { margin-top: 4px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-balance-hero-profile .nmo-balance-hero-figures .nmo-tv-range { margin-top: 2px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-balance-hero.nmo-balance-hero-profile .nmo-tv-chart,
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-balance-hero .nmo-hb-slot { margin-top: 4px; }
/* The spans keep their 48px buttons; only the empty band above and below each painted pill is drawn over. */
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-balance-hero-profile .nmo-tv-pills { margin: -6px 0 -6px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front:has(.nmo-balance-hero-profile) .nmo-verdict-pills { margin-bottom: 4px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-verdict { margin-top: 0; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-verdict-facts { margin-top: 6px; column-gap: 4px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-verdict-fact { padding-top: 4px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-front .nmo-deck-passedon { margin-top: 2px; }
.nmo-deck[data-nmo-deck-tight] .nmo-deck-actions { margin-top: 4px; }

/* ═══ THE HEARTBEAT IN THE MIDDLE OF WHAT IS LOADING ═══ (#455, owner 17 Sep 2026; design v69 option C, #gc #gd #ld2 #ld3)
   One strip of the board's violet cells with a flat, dim line, and one quiet line of words under it. Only the trace
   moves (ledHeartbeat.ts draws it); nothing here animates while it beats. It shows once a wait is 400 ms old. */
.nmo-hb {
  --nmo-hb-rgb: 161, 92, 255;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  flex: 1 1 auto; min-height: 0; width: 100%; box-sizing: border-box;
}
.nmo-hb.is-early { visibility: hidden; }
.nmo-hb-strip { display: block; flex: none; width: 100%; height: 180px; }
.nmo-hb-strip.is-fill { height: 100%; }
.nmo-hb-words { margin: 0; font: var(--m3-body-medium); letter-spacing: .25px; color: var(--text-dim); text-align: center; }
/* Try again: one text button in the interface violet. */
.nmo-hb-retry {
  appearance: none; background: none; border: 0; margin: 0; cursor: pointer;
  padding: 6px 0 4px; font: var(--m3-label-large); letter-spacing: .1px; color: var(--nmo-violet, #a15cff);
}
.nmo-hb-retry:disabled { opacity: .5; cursor: default; }
/* Where the figure goes, when his balance could not be read: words, never $0 and never a dash. */
.nmo-hb-failed { display: flex; flex-direction: column; align-items: flex-start; }
.nmo-hb-failed-words { margin: 0; padding: 10px 0 2px; font: 500 22px/28px var(--font-ui); color: var(--text-dim); }
/* The strip as it stood when the answer landed, fading out over the arriving card or page. */
.nmo-hb.is-leaving { position: absolute; inset: 0; z-index: 3; pointer-events: none; animation: nmo-hb-out 280ms ease-out forwards; }
@keyframes nmo-hb-out { to { opacity: 0; } }
@keyframes nmo-hb-in { from { opacity: 0; } }
@keyframes nmo-hb-power { from { clip-path: inset(0 100% 0 0); } }

/* The For You card while it waits: the strip across the card's full inner width, in its middle. */
.nmo-deck-face.is-waiting .nmo-deck-scroll { padding: 16px 0; overflow: hidden; }
.nmo-deck-face.is-waiting .nmo-deck-page { height: 100%; }
.nmo-deck-face > .nmo-hb.is-leaving { padding: 16px 0; }
/* …and as it arrives: the card fades in, the chips come last. The board powers on in forYouDeck.ts (#425). */
.nmo-deck-card.is-arriving .nmo-deck-front .nmo-deck-page { animation: nmo-hb-in 280ms ease-out backwards; animation-delay: var(--nmo-arrive-at, 0ms); }
.nmo-deck-card.is-arriving .nmo-deck-front [data-nmo-badge-row] {
  animation: nmo-hb-in 200ms ease-out backwards; animation-delay: calc(var(--nmo-arrive-at, 0ms) + 500ms);
}

/* A trader's page while it waits: the header stays in the panel's top bar, the strip is in the middle of the rest. */
.nmo-sheet-profile .nmo-sheet-body { position: relative; }
.nmo-sheet-body.is-waiting { display: flex; flex-direction: column; }
.nmo-profile.is-waiting { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 100%; }
.nmo-profile.is-waiting > .nmo-hb.is-page { padding-bottom: 40px; }
.nmo-sheet-body > .nmo-hb.is-leaving {
  left: var(--nmo-sheet-pad-x, 16px); right: var(--nmo-sheet-pad-x, 16px); bottom: auto; height: calc(100% - 24px); padding-bottom: 40px;
}
.nmo-profile.is-arriving { animation: nmo-hb-in 280ms ease-out backwards; animation-delay: var(--nmo-arrive-at, 0ms); }
.nmo-profile.is-arriving canvas.nmo-led {
  animation: nmo-hb-power 400ms ease-out backwards; animation-delay: calc(var(--nmo-arrive-at, 0ms) + 100ms);
}
.nmo-profile.is-arriving [data-nmo-badge-row] {
  animation: nmo-hb-in 200ms ease-out backwards; animation-delay: calc(var(--nmo-arrive-at, 0ms) + 500ms);
}

/* The board's place in the balance section while a pressed span is read, or after its read failed. */
.nmo-balance-hero .nmo-hb-slot { display: flex; align-self: stretch; flex: none; height: calc(var(--nmo-hero-plot, 110px) + 70px); }
.nmo-deck-front .nmo-balance-hero .nmo-hb-slot { height: calc(var(--nmo-deck-board, 162px) + 16px); margin: 10px 0 0; }

/* ═══ AND EVERY OTHER AREA OF THESE SCREENS THAT WAITS ═══ (#474, owner 17 Sep 2026: "show loading anim")
   The same strip #455 built, in the middle of the area that is loading rather than a line of grey type at its top.
   Each of these areas is EMPTY while it waits — that is the whole point of the state — so the strip is given the
   rest of the window to sit in the middle of, which is where the eye already is. */
.nmo-hb.is-feed, .nmo-hb.is-traders, .nmo-hb.is-profile, .nmo-hb.is-activity, .nmo-hb.is-rules, .nmo-hb.is-deck {
  min-height: min(58svh, 440px);
}
/* ═══ AND THE PLACES #474 MISSED ═══ (#500, owner 18 Sep 00:35: "show loading anim when loading")
   The chart's place on the three coin screens (the strip is the chart's own height, set in nmo.ts, so nothing under it
   moves when the line lands), the balance line's place on your genie's page (its 80px), a trader's Portfolio and
   question tabs, your Portfolio tab, the Following list, the search results and the copy sheet's rows: the one strip,
   no words. */
.nmo-hb.is-chart, .nmo-hb.is-worth { gap: 0; }
.nmo-hb.is-portfolio, .nmo-hb.is-positions, .nmo-hb.is-questions, .nmo-hb.is-following, .nmo-hb.is-search,
.nmo-hb.is-copyrules { padding: 12px 0; }
/* The tab's area is a column, so the strip's `flex: 1 1 auto` centres in what is left under the tabs. */
.nmo-screen:has(> .nmo-hb), .nmo-screen:has(> .nmo-traders-list > .nmo-hb) { display: flex; flex-direction: column; }
.nmo-traders-list:has(> .nmo-hb) { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
/* The coin sheet has a height of its own already, so the strip takes the body the coin was going to fill rather
   than a box its own size at the top of it. The strip is not a direct child of the body — the sheet puts a screen
   of its own in between — so both of them have to be told to pass the height down. */
.nmo-sheet-body:has(.nmo-hb.is-coin) { display: flex; flex-direction: column; }
.nmo-sheet-body > .nmo-screen:has(> .nmo-hb.is-coin) {
  display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;
}
.nmo-hb.is-coin { min-height: 0; padding-bottom: 32px; }
/* A list's next page, and the comments under a post: the short strip, no words, no room taken from the rows. */
.nmo-hb.is-foot { min-height: 0; gap: 0; padding: 4px 0; }

@media (prefers-reduced-motion: reduce) {
  .nmo-hb.is-leaving { display: none; }
  .nmo-deck-card.is-arriving .nmo-deck-front .nmo-deck-page,
  .nmo-deck-card.is-arriving .nmo-deck-front [data-nmo-badge-row],
  .nmo-profile.is-arriving,
  .nmo-profile.is-arriving canvas.nmo-led,
  .nmo-profile.is-arriving [data-nmo-badge-row] { animation: none; }
}

/* ═══ THE THREE COIN SCREENS, TO DESIGN v86 SECTION 09 (#478, owner 17 Sep 2026) ══════════
   Measured off the design page at 390 wide, scale 1. The two position screens keep the look
   #429 built for a trader's coin position (`.nmo-cs-sheet`, further up this file) and your own
   position now wears it too. Everything below is either the coin's PAGE, which is the new one,
   or a part all three share.

   THE PALETTE RULE THIS SECTION IS UNDER: green and red belong to money that has moved — a
   profit, a day's change, an order in the marquee. A control never wears them. The interface
   colour is the one violet, in its two tones. */

/* ── 1 · the coin's page: the bar IS the coin ──────────────────────────────────────────── */
/* Back arrow, picture, symbol over its address, and the market cap hard against the right.
   Same row height and same padding as the person bar on the two position screens, so moving
   between them does not move the chart under it. */
.nmo-coin-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 16px 10px;
}
.nmo-coin-bar .nmo-coin-names { flex: 1 1 auto; min-width: 0; }
.nmo-coin-bar .nmo-sym-line { align-items: center; gap: 8px; }
.nmo-coin-bar .nmo-sym { font: 500 16px/24px var(--font-ui); letter-spacing: .15px; }
.nmo-coin-bar .nmo-mintrow .nmo-mint { font: 400 12px/16px var(--font-mono); color: var(--text-faint); }
.nmo-coin-bar .nmo-copy { opacity: .45; transform: scale(.8); }
.nmo-coin-bar .nmo-coin-facts { text-align: right; flex: none; }
.nmo-coin-bar .nmo-coin-mc { font: 500 20px/26px var(--font-ui); color: var(--text); }
/* The tabs on the coin's page sit under the spans, full width, the count riding the first one. */
.nmo-coin-page .nmo-coin-tabs { padding: 0 16px; }
/* #487: the room kept here for Genie in the corner (88px under the last row) is gone. The coin's page has Buy and
   Sell directly under its list now, and Genie steps aside while they are up, as under every sheet (v86 #apc9). */

/* ═══ #487 — THE COIN'S PAGE AGAINST DESIGN v86 #apc9 TO #apc11 (owner, 17 Sep) ═══ */
/* 1 · The raised violet bar: the coin's page is a sheet now, and its bar wears the same violet as the person bar on
   the two position screens — the wash from the handle down, closed by a violet hairline. It pulls out to the sheet's
   own edges and pays the padding back, so the wash reaches both sides. */
.nmo-coin-page .nmo-coin-bar {
  margin-inline: calc(-1 * var(--nmo-sheet-pad-x));
  padding: 8px var(--nmo-sheet-pad-x) 10px;
}
:root:not([data-theme="light"]) .nmo-sheet.nmo-coin-page .nmo-sheet-grab { background: linear-gradient(180deg, #402c5f 0%, #3a2955 100%); }
:root:not([data-theme="light"]) .nmo-sheet.nmo-coin-page .nmo-coin-bar {
  background: linear-gradient(180deg, #3a2955 0%, #1e1a26 100%);
  border-bottom: 1px solid #3e2a5b;
}
/* 2 · "3 of the top 30 hold it · @ether_monk, @derek518": one quiet line under the bar (the design's body-small in
   the third ink, 12px under the bar). */
.nmo-coin-top30 { margin: 12px 0 0; font: 400 12px/16px var(--font-ui); letter-spacing: .4px; color: var(--text-faint); }
/* 3 · The chart is the design's 196px, its orders running down to 22px from the foot: nothing under it but the spans. */
.nmo-coin-page .nmo-coin-line { height: 196px; }
.nmo-coin-page .nmo-coin-trades { bottom: 22px; }
/* 9 · Buy and Sell sit directly under the list, on the sheet's own ground, and the sheet ends there — the position
   sheets' rule (#483), so a short list does not leave a tall empty gap above pinned buttons. */
.nmo-sheet.nmo-coin-page { height: auto; max-height: min(90vh, 860px); }
.nmo-coin-page .nmo-trade-bar:not(.nmo-trade-sheet) { background: transparent; }
.nmo-coin-page .nmo-sheet-body { flex: 0 1 auto; padding-bottom: 12px; }
/* 7 · Holders as people, in the design's one outlined box (v86 #apc9 `.ap-rows`): the box lines up with the chart
   and the spans, a hairline between rows, 36px pictures, the name in title-small and the share with its value under
   it in the label face. The tabs line up with the same edge. */
.nmo-coin-page .nmo-coin-tabs { padding: 0 0 10px; }
.nmo-coin-page .nmo-holder-list {
  border: 1px solid var(--nmo-outline-variant, rgba(255, 255, 255, .12)); border-radius: 16px; padding: 2px 14px;
}
.nmo-coin-page .nmo-holder-row { gap: 10px; padding: 10px 0; }
.nmo-coin-page .nmo-holder-row + .nmo-holder-row { border-top: 1px solid var(--nmo-outline-variant, rgba(255, 255, 255, .12)); }
.nmo-coin-page .nmo-holder-face, .nmo-coin-page .nmo-holder-row > .nmo-av { width: 36px; height: 36px; flex: none; }
.nmo-coin-page .nmo-holder-name { font: 500 14px/20px var(--font-ui); letter-spacing: .1px; }
.nmo-coin-page .nmo-holder-figures { font: 500 11px/16px var(--font-ui); letter-spacing: .5px; color: var(--text-faint); }
.nmo-coin-page .nmo-holder-others { padding: 10px 2px 0; }

/* ── 2 · Holders: picture, name, share with its value, Follow ──────────────────────────── */
.nmo-holder-list { display: flex; flex-direction: column; }
.nmo-holder-row { display: flex; align-items: center; gap: 12px; padding: 10px 16px; }
.nmo-holder-who { flex: 1 1 auto; min-width: 0; }
.nmo-holder-name {
  font: var(--m3-body-large); letter-spacing: var(--m3-body-large-tracking); color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nmo-holder-figures {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking); color: var(--text-dim);
}
/* The share is the reason the row is worth reading, so it is the one part in the text colour. */
.nmo-holder-share { color: var(--text); font-weight: 600; }
.nmo-holder-row .nmo-holder-follow { flex: none; }
/* The wallets we cannot name, once, at the foot of the list. */
.nmo-holder-others {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-faint); padding: 10px 16px 0;
}
/* #540: how many we know and who they are, once, over the rows; a stale reading's age, fainter than its figures;
   a row that opens his position screen answers the finger. */
.nmo-holder-summary {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-dim); margin: 0; padding: 0 2px 8px;
}
.nmo-holder-read { color: var(--text-faint); }
.nmo-holder-opens { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.nmo-holder-opens:active { opacity: .7; }

/* ── 3 · About: one plain row list, and one line under it when there is one ────────────── */
.nmo-about-list { padding-top: 4px; }
/* The safety check, when it passed and a sell test was among what ran: an EARNED badge, so it
   has to look earned. Muted green because it is a fact about the coin and not a control. */
.nmo-ct-badge {
  display: inline-block; border-radius: 999px; padding: 2px 9px;
  font: var(--m3-label-small); letter-spacing: var(--m3-label-small-tracking);
  background: color-mix(in srgb, var(--nmo-up) 18%, transparent); color: var(--nmo-up);
}
.nmo-about-desc {
  font: var(--m3-body-small); letter-spacing: var(--m3-body-small-tracking);
  color: var(--text-dim); padding: 10px 16px 0; margin: 0;
}

/* ── 4 · buying and selling: one sheet, raised over the coin (#483, owner 17 Sep, design v87 §09b) ──
   Replaces #478's buy panel (a bare figure with a slider across both edges, and the fee on its own) and the
   sell strip (three pills beside a violet "Choose how much to sell"). Measured off #aptb and #apts at 390 wide,
   scale 1: the sheet 560 tall with 8/16/16 padding and 12 between its parts, a 36×4 handle, 28px corners; the
   head row 24 tall; the figure 40/44 over a 14/20 line; 34px chips 8 apart; the slide-or-type row 104; three
   42px rows in a box with a 16px corner; a 56px button with Cancel 2 under it on a 32px line. */
.nmo-trade-panel:empty { display: none; }
/* At rest the card is no box at all, so the bar is the strip on the sheet's floor it has always been. */
.nmo-trade-card { display: contents; }
.nmo-trade-grab { display: none; }
/* Open, the bar is the dimmed layer over the whole coin — a tap on it closes the sheet — and the card is the
   raised sheet at its foot. */
.nmo-trade-bar.nmo-trade-sheet {
  position: absolute; inset: 0; z-index: 30; padding: 0; gap: 0; justify-content: flex-end;
  background: rgba(0, 0, 0, .55); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.nmo-trade-sheet > .nmo-trade-card {
  display: flex; flex-direction: column; gap: 12px; max-height: calc(100% - 24px); overflow-y: auto;
  padding: 8px 16px calc(16px + env(safe-area-inset-bottom, 0px)); border-radius: 28px 28px 0 0;
  background: var(--bg-elev-1, var(--bg)); box-shadow: 0 -1px 0 rgba(255, 255, 255, .06);
}
.nmo-trade-sheet .nmo-trade-grab {
  display: block; flex: none; width: 36px; height: 4px; margin: 2px auto 4px; border-radius: 2px;
  background: rgba(255, 255, 255, .22); cursor: pointer;
}
/* The mirror's quiet "Ask Genie" line belongs to the bar at rest, not to the sheet. */
.nmo-trade-sheet .nmo-cs-ask { display: none; }
.nmo-ts { display: flex; flex-direction: column; gap: 12px; }
.nmo-ts .nmo-num { font-family: inherit; }
/* "Buy UBI" on the left, what there is to spend or what is held on the right. */
.nmo-ts-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; min-width: 0; }
.nmo-ts-title { font: 500 16px/24px var(--font-ui); letter-spacing: .15px; color: var(--text); white-space: nowrap; }
.nmo-ts-have {
  font: 500 12px/16px var(--font-ui); letter-spacing: .5px; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* The amount as the figure, centred, and what it means under it. */
.nmo-ts-amt { text-align: center; padding-top: 2px; }
.nmo-ts-big {
  font: 500 40px/44px var(--font-ui); letter-spacing: -.5px; font-variant-numeric: tabular-nums; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nmo-ts-unit { font: 500 16px/20px var(--font-ui); letter-spacing: 0; color: var(--text-dim); margin-left: 4px; }
.nmo-ts-means { font: 400 14px/20px var(--font-ui); letter-spacing: .25px; color: var(--text-dim); padding-top: 2px; min-height: 20px; }
/* #506: an amount that would leave more of the coin than exists says so here, in the refusal's own colour. */
.nmo-ts-means.nmo-ts-refused { color: var(--err); padding-inline: 16px; }
/* The quick chips, at their own widths, the chosen one filled violet. */
.nmo-trade-chips { display: flex; justify-content: center; gap: 8px; }
.nmo-trade-chips .nmo-trade-chip {
  padding: 7px 14px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, .12); background: none;
  color: var(--text-dim); font: 500 14px/18px var(--font-ui); letter-spacing: .1px; cursor: pointer; white-space: nowrap;
}
.nmo-trade-chips .nmo-trade-chip.nmo-trade-chip-on {
  background: var(--nmo-violet, #a15cff); border-color: transparent; color: #fff; font-weight: 600;
  text-shadow: 0 1px 0 rgba(0, 0, 0, .25);
}
.nmo-trade-chips .nmo-trade-chip:disabled { opacity: .38; cursor: not-allowed; }
/* The slide-or-type row is the copy sheets' own (#453), with its name and its typed value over the track, as the
   design draws it. It is not the first row of a list here, so it carries no rule above it. */
.nmo-ts .nmo-copy-row { border-top: 0; }
.nmo-ts .nmo-copy-ends { min-height: 16px; }
/* The same three rows in both directions. */
.nmo-ts-rows { border: 1px solid rgba(255, 255, 255, .12); border-radius: 16px; padding: 2px 14px; }
.nmo-ts-rows:empty { display: none; }
.nmo-ts-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 0; border-top: 1px solid rgba(255, 255, 255, .12);
}
.nmo-ts-row:first-child { border-top: 0; }
.nmo-ts-k { font: 400 14px/20px var(--font-ui); letter-spacing: .25px; color: var(--text-dim); }
.nmo-ts-v { font: 500 14px/20px var(--font-ui); letter-spacing: .1px; font-variant-numeric: tabular-nums; color: var(--text); }
/* One full-width violet button naming the whole act, and Cancel a quiet line 2px under it. */
.nmo-trade-sheet .nmo-trade-row { flex-wrap: nowrap; }
.nmo-trade-sheet .nmo-trade-confirm {
  width: 100%; height: 56px; min-height: 56px; padding: 0 24px; border-radius: 999px;
  background: var(--nmo-violet, #a15cff); color: #0b0016; font: 500 16px/24px var(--font-ui); letter-spacing: .15px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nmo-trade-sheet > .nmo-trade-card > .nmo-trade-say { margin-top: -10px; }
/* The way out of the sheet: a quiet line under the one button, not a second button beside it. */
.nmo-trade-back {
  display: block; width: 100%; padding: 10px 0 2px; border: 0; background: none; cursor: pointer;
  font: 400 14px/20px var(--font-ui); letter-spacing: .25px; color: var(--text-faint); text-align: center;
}
.nmo-trade-back:disabled { opacity: .5; cursor: default; }

/* ── 4b · your own coin position: the bar is you (#483, design v87 §09b "The bar is you") ──
   Your picture — the Profile's own initials on violet — and your name, where Genie's face and "Genie" were. */
.nmo-coin-top > .nmo-own-avatar {
  width: 40px; height: 40px; border-radius: 999px; display: grid; place-items: center; overflow: hidden;
  background: linear-gradient(135deg, var(--nmo-tint-a, #5b2bb8), var(--nmo-tint-b, #d05cff));
  color: #fff; font: 500 16px/24px var(--font-ui); letter-spacing: .15px; cursor: pointer;
}

/* ── 5 · a span we have no prices for ─────────────────────────────────────────────────── */
/* Dimmed and unpressable, in place of the two sentences that used to apologise for the gap.
   `pointer-events: none` as well as `disabled`, so a tap on a phone does nothing at all
   rather than flashing the pressed state of a control that will not act. */
.nmo-range.is-thin {
  opacity: .32; pointer-events: none; cursor: default;
}
/* #495 (owner 17 Sep 23:52): "Prices since" is gone from every screen, and its rule with it. */

/* ═══ THE SWEEP AGAINST DESIGN V86 (#423, 17 Sep 2026) ═══════════════════════════════════════════════
   Every fomo screen on 4536 was pictured beside its design phone (proof/423/DIFFS.md). These are the
   differences that were one or two lines of styling; the bigger ones are tasks of their own. Dark theme
   only, like the rest of the Vivid layer. */

/* ── 1 · no heading in capitals (owner, 17 Sep; design v86 #pf2 #pf3 #pf4) ──
   "Warning signs", "Things to watch", "Size of coin when he buys": title small, 500 14/20, in the ink.
   The same for fomo's settings ("Chains", "Copy limits", "Genie's rules"). */
:root:not([data-theme="light"]) :is(.nmo-root, .nmo-sheet) :is(.m3-sectionlabel, .nmo-dd-head) {
  text-transform: none; font: 500 14px/20px var(--font-ui); letter-spacing: .1px; color: var(--text);
}

/* ── 2 · the three-band scale in the design's green, amber and red (design v86 #pf4 `.p-tri`) ──
   The grey middle band sat under a word that said "amber". The bands keep their 60/80 splits, which is
   where `scaleAt` puts the thresholds. */
:root:not([data-theme="light"]) .nmo-dd-v14 .nmo-dd-scale {
  background: linear-gradient(90deg, #1ee07f 0 60%, #ffb400 60% 80%, #ff4f64 80% 100%);
}

/* ── 3 · Sell is outlined in the ink, not in violet (design v86 #apc8 #apc9: `.btn.ghost`) ──
   Buy stays the one violet control. Both words in label large at 16/24, as the design sets them. */
:root:not([data-theme="light"]) .nmo-trade-bar :is(.nmo-trade-buy, .nmo-trade-sell) { font: 500 16px/24px var(--font-ui); }
:root:not([data-theme="light"]) .nmo-trade-bar .nmo-trade-sell { color: #fff; border-color: rgba(255, 255, 255, .28); }

/* ── 4 · the coin's page tabs are pills, like his page's question tabs (design v86 #apc9–#apc11) ──
   Holders, Feed and About: 32 tall, round, the chosen one the light lavender pill. */
:root:not([data-theme="light"]) .nmo-coin-page .nmo-coin-tabs:not(.nmo-dd-tabs) { gap: 8px; border-bottom: 0; }
:root:not([data-theme="light"]) .nmo-coin-page .nmo-coin-tabs:not(.nmo-dd-tabs) .nmo-coin-tab {
  height: 32px; margin: 8px 0; padding: 0 14px; border-radius: 999px;
  background: var(--nmo-v14-card, #17171b); color: #fff; font: 500 14px/20px var(--font-ui); letter-spacing: .1px;
}
:root:not([data-theme="light"]) .nmo-coin-page .nmo-coin-tabs:not(.nmo-dd-tabs) .nmo-coin-tab.is-on { background: #e8def8; color: #1c1a22; }
:root:not([data-theme="light"]) .nmo-coin-page .nmo-coin-tabs:not(.nmo-dd-tabs) .nmo-coin-tab.is-on::after { content: none; }

/* ── 5 · a card whose balance could not be read has no span to choose (design v86 #ld3) ──
   The spans are dimmed and take no press; "Try again" is the one way on. */
.nmo-deck-front:has(.nmo-hb-failed) .nmo-tv-pill { opacity: .38; pointer-events: none; }

/* ═══ #483 ROUND THREE — THE TWO POSITION SCREENS AGAINST DESIGN v87 #apc6 AND #apc8 (owner, 17 Sep 21:05) ═══ */
/* "Ask Genie about this coin": a 50px outlined button the width of the card, 24px under the four cells, in the
   dim ink — not a filled pill, and not a quiet line under the Mirror button. */
.nmo-sheet .nmo-coin-ask.nmo-coin-ask-card {
  display: block; width: 100%; height: 50px; min-height: 50px; margin: 24px 0 0; padding: 0 16px; box-sizing: border-box;
  border: 1px solid rgba(255, 255, 255, .12); border-radius: 16px; background: transparent; box-shadow: none;
  color: var(--text-dim); font: 400 16px/24px var(--font-ui); letter-spacing: .5px; text-align: center;
}
/* Buy and Sell (his Mirror) sit directly under that button, 16px in from each side, on the sheet's own ground: no
   darker bar behind them, and the sheet ends there rather than pinning them to the bottom of a taller screen with
   a gap above. A position longer than the screen still scrolls, with the buttons on the sheet's floor. */
.nmo-sheet.nmo-cs-sheet { height: auto; max-height: min(90vh, 860px); }
.nmo-cs-sheet .nmo-trade-bar:not(.nmo-trade-sheet) { background: transparent; }
.nmo-cs-sheet .nmo-sheet-body { flex: 0 1 auto; padding-bottom: 12px; }

/* ═══ THE ROUND SEARCH BUTTON FIRST ON THE TAB ROW, AND THE FIELD IT STRETCHES INTO (#537, design v88 section 04b) ═══
   Ported from the design's `.chiptabs > span.sb`, `.sh-goo`, `.sh-ui` rules. The button is a 36px circle in the quiet
   raised colour — never purple — inside a 48px touch target. Each chip's sides went from 16 to 13 and the gap from 8
   to 6 (the rules above), and the gear moved up beside the bell, so the button and four chips fit a 390 phone: the
   design measured 346 of its row's 352 pixels. This replaces the #435 row's 16px sides, 8px gaps and the gear at the
   row's end. */
.nmo-tabs { position: relative; }
.nmo-tabs .nmo-tab-search {
  position: relative; flex: none; width: 36px; height: 36px; min-height: 36px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: var(--bg-elev-2); color: var(--text-dim); cursor: pointer;
}
.nmo-tabs .nmo-tab-search::before { content: ""; position: absolute; inset: -6px; border-radius: 50%; }
.nmo-tabs .nmo-tab-search .nmo-ic { font-size: 20px; }
.nmo-tabs .nmo-tab-search:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nmo-tabs .nmo-tab-search + .nmo-tab { margin-left: 6px; }
:root:not([data-theme="light"]) .nmo-tabs .nmo-tab-search { background: var(--bg-elev-2); }
/* The gear in the top bar: the bell's box and glyph size, and the design's 6px pull between two icons. */
.nmo-top .nmo-tab-settings .nmo-ic { font-size: var(--m3-toprow-glyph); }
.nmo-top .nmo-tab-settings .nmo-ic-drawn { width: var(--m3-toprow-glyph); height: var(--m3-toprow-glyph); }

/* While the field moves or is open the blobs draw every ground, so the button and chips give theirs up, and
   nothing may clip the field's 44px or the filter's blur. */
/* Three layers, as the design stacks them: the blobs at the bottom, the button's magnifier and the chips' words
   above them, and the open field's input and ✕ on top, so a tap anywhere on the open field lands on the field. */
.nmo-tabs.is-searching { overflow: visible; isolation: isolate; }
.nmo-tabs.is-searching > .nmo-tab-search, .nmo-tabs.is-searching > .nmo-tab { position: relative; z-index: 1; }
/* Only the button gives its ground up: the field's blob is the circle. Each chip keeps its own ground and hands
   it over smoothly as the field's edge reaches it (nmo.ts, renderSearch, v88.1). */
.nmo-tabs.is-searching > .nmo-tab-search,
:root:not([data-theme="light"]) .nmo-tabs.is-searching > .nmo-tab-search { background: transparent; }
/* The field covers the row's content box, 44 tall on the chips' centre line. */
.nmo-sh { position: absolute; left: 16px; right: 16px; top: -4px; height: 44px; pointer-events: none; }
.nmo-sh-defs { position: absolute; width: 0; height: 0; }
.nmo-sh-goo { position: absolute; inset: -12px; z-index: 0; filter: url(#nmo-sgoo); }
.nmo-sh-goo i { position: absolute; display: block; height: 36px; border-radius: 999px; top: 16px; transform-origin: left center; }
.nmo-sh-ui {
  position: absolute; left: 0; right: 0; top: 0; height: 44px; display: flex; align-items: center; gap: 10px;
  padding: 0 6px 0 46px; opacity: 0; z-index: 2;
}
.nmo-sh.is-open .nmo-sh-ui { pointer-events: auto; }
.nmo-sh-input {
  flex: 1; min-width: 0; height: 44px; padding: 0; border: 0; outline: 0; background: none;
  color: var(--text); caret-color: var(--accent); font: var(--m3-body-large);
  letter-spacing: var(--m3-body-large-tracking, 0.5px); text-overflow: ellipsis;
}
.nmo-sh-input::placeholder { color: var(--text-faint); opacity: 1; }
.nmo-sh-x {
  flex: none; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50%; background: none;
  display: grid; place-items: center; color: var(--text-dim); cursor: pointer; position: relative;
}
.nmo-sh-x::before { content: ""; position: absolute; inset: -8px; border-radius: 50%; }
.nmo-sh-x .nmo-ic { font-size: 18px; }
/* Reduced motion: no liquid, a plain cross-fade of the row and the field. */
.nmo-sh.is-still .nmo-sh-goo { filter: none; }
.nmo-sh.is-still .nmo-sh-goo, .nmo-sh.is-still .nmo-sh-ui, .nmo-tabs.is-searching:has(.nmo-sh.is-still) > .nmo-tab { transition: opacity 200ms linear; }
/* What the search screen (#535) draws into: under the row, over the page, for as long as the field is open. */
.nmo-search-panel {
  position: absolute; left: 0; right: 0; top: 100%;
  height: calc(100dvh - 100%); overflow-y: auto; overscroll-behavior: contain; background: var(--bg-elev);
}
:root:not([data-theme="light"]) .nmo-search-panel { background: var(--bg); }
.nmo-search-panel[hidden] { display: none; }

/* ═══ THE COIN'S BAR IS THE SHEET'S PINNED TOP (#541, owner 18 Sep ~16:40) ═══
   "on coin page, the nav bar coin profile should anchor to the top of the modale, so when user scrolls, the content
   goes under the nav bar." On all three coin screens `nmo.ts` stands the bar — the coin's on its page, the person's on
   the two position screens — in `.nmo-sheet-head`, a child of the sheet between the handle and the list. The list
   scrolls in its own box below it and is clipped at the bar's foot; Buy and Sell stay on the sheet's floor.

   OUT OF THE LIST'S GUTTER, SO NO PULL TO ITS EDGES. Both bars pulled out to the sheet's edges with a negative margin
   because they sat inside the list's side padding. The head has none, so the margin goes and the padding stays.

   THE HAIRLINE ONLY ONCE SOMETHING IS UNDER IT. This replaces #487's always-on violet hairline at the bar's foot
   (design v86 #apc9): at rest the handle, the bar and the page read as one surface, and the line comes in with
   `is-scrolled`, set from the list's own scroll, as on a trader's page (#374). The 1px is always there, transparent
   at rest, so nothing moves when it appears. */
.nmo-sheet-head { flex: none; position: relative; z-index: 1; }
.nmo-sheet-head:empty { display: none; }
/* The wash is laid from the border's edge, not the padding's: laid from the padding it repeats into the transparent
   1px below and draws its own first violet there, a hairline at rest by another road. The dark rule below says it
   again after its `background:`, because that shorthand puts the start back to the padding's edge. */
.nmo-sheet > .nmo-sheet-head > :is(.nmo-coin-bar, .nmo-coin-top) { margin-inline: 0; border-bottom: 1px solid transparent; background-origin: border-box; }
/* The violet wash now falls all the way to the sheet's own surface: it used to stop at #1e1a26, a shade darker than
   the page below, and that step drew an edge of its own at rest. */
:root:not([data-theme="light"]) .nmo-sheet > .nmo-sheet-head > :is(.nmo-coin-bar, .nmo-coin-top) {
  border-bottom-color: transparent;
  background: linear-gradient(180deg, #3a2955 0%, var(--bg-elev-3) 100%);
  background-origin: border-box;
}
.nmo-sheet.is-scrolled > .nmo-sheet-head > :is(.nmo-coin-bar, .nmo-coin-top) { border-bottom-color: var(--nmo-v14-rule, var(--border)); }
:root:not([data-theme="light"]) .nmo-sheet.is-scrolled > .nmo-sheet-head > :is(.nmo-coin-bar, .nmo-coin-top) { border-bottom-color: #3e2a5b; }

/* ═══ GENIE IN THE CORNER ═══════════════════════════════════════════════════
   (#468 — design v73 §10 phone #gxa; the owner's ruling on this task: nothing
   drawn around it. No ring, no pixel frame, no plate — just the mascot.)

   With the ask bar folded away on all four tabs (#323, #468) this is the way to
   ask, so it has to be reachable and it has to stay put: FIXED to the viewport,
   not to the list, because the owner's phone has it sitting still while the feed
   runs past underneath.

   THE LAYER IS A ZERO-HEIGHT STRIP, not a box in the corner, so that on a screen
   wider than a phone Genie sits at the right edge of the COLUMN the app is drawn
   in rather than at the right edge of the monitor. It takes no taps of its own
   (`pointer-events: none`); only Genie and its bubble do. */
.nmo-gx {
  position: fixed; left: 0; right: 0; bottom: 0; height: 0;
  max-width: 480px; margin: 0 auto;
  z-index: 30; pointer-events: none;
}
.nmo-gx[hidden] { display: none; }
.nmo-gx > * { pointer-events: auto; }
/* 64px, 14 in from the right, 26 up from the bottom — ON TOP OF the home bar's
   own inset, so it never sits under the bar on a phone that has one. */
.nmo-gx-face {
  position: absolute; right: 14px; bottom: calc(26px + env(safe-area-inset-bottom, 0px));
  width: 64px; height: 64px; padding: 0; border: 0; background: none; border-radius: 0;
  display: grid; place-items: center; cursor: pointer; -webkit-tap-highlight-color: transparent;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .6));
}
.nmo-gx-face:focus-visible { outline: 2px solid var(--nmo-up); outline-offset: 4px; border-radius: 50%; }
.nmo-gx-mascot { grid-area: 1 / 1; width: 64px; height: 64px; display: block; }
/* How many notes are still unread, at Genie's TOP-LEFT — the side away from the
   screen's edge, where a 20px pill has room to sit whole. */
.nmo-gx-badge {
  position: absolute; top: -2px; left: -2px; min-width: 20px; height: 20px; border-radius: 10px;
  padding: 0 5px; box-sizing: border-box; text-align: center;
  background: var(--nmo-up); color: #06140d;
  font: 700 12px/20px var(--font-ui); box-shadow: 0 0 0 2px var(--bg);
}
.nmo-gx-badge[hidden] { display: none; }
/* What Genie is saying, to its left, with the tail pointing at it. */
.nmo-gx-say {
  position: absolute; right: 86px; bottom: calc(34px + env(safe-area-inset-bottom, 0px));
  max-width: 220px; box-sizing: border-box; padding: 10px 12px;
  background: var(--nmo-card-bg); color: var(--text);
  border: 1px solid var(--nmo-line); border-radius: 18px 18px 4px 18px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
}
.nmo-gx-say[hidden] { display: none; }
.nmo-gx-say-line { font: 500 14px/1.35 var(--font-ui); }
.nmo-gx-figure { font-weight: 700; }
.nmo-gx-figure.nmo-up { color: var(--nmo-up); }
.nmo-gx-figure.nmo-down { color: var(--nmo-down); }
.nmo-gx-say-actions {
  display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 8px;
}
.nmo-gx-more {
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--nmo-up); font: 600 13px var(--font-ui);
}
/* The count is there to be glanced at, never read: quiet, and gone on the wink. */
.nmo-gx-count { color: var(--text-faint); font: 600 13px var(--font-ui); }
/* ON FOR YOU the corner is where the Copy button's row ends: at 390 wide Copy's right edge and
   Genie's left edge were 1px apart, so a thumb aiming for Copy could land on Genie. Genie and its
   bubble sit ABOVE that row instead — 14 below the row, 62 for its buttons, 8 of air — over the
   card's empty lower-right corner. */
.nmo-gx[data-nmo-gx-screen="forYou"] .nmo-gx-face { bottom: calc(84px + env(safe-area-inset-bottom, 0px)); }
.nmo-gx[data-nmo-gx-screen="forYou"] .nmo-gx-say { bottom: calc(92px + env(safe-area-inset-bottom, 0px)); }
/* ON THE THREE LISTS the last row can scroll up clear of Genie: 26 + 64 + 8 of air. */
.nmo-root[data-nmo-gx-room] > .nmo-body > .nmo-screen { padding-bottom: calc(98px + env(safe-area-inset-bottom, 0px)); }

/* ═══ GENIE'S HALF SHEET ═══════════════════════════════════════════════════
   (#470 — design v89 "Genie on call", phone #gxb.) Every size, weight and colour
   below is the design's; its names map onto ours as: sc-highest → --bg-elev-3,
   raised-2 → --bg-elev-2, line-2 → rgba(255,255,255,.16), outline → --nmo-outline,
   outline-variant → --nmo-outline-variant, lime and led → --nmo-violet (#a15cff),
   ink-2 → --text-dim, ink-3 → --text-faint, page → --bg, --font → --font-display,
   --text → --font-ui.

   Fixed to the column the app is drawn in, like Genie in the corner, and above it
   (30) but under the screen's own sheets (99): a coin or a copy sheet takes the
   half sheet off the screen rather than sitting on it. */
.nmo-gxs {
  position: fixed; inset: 0; max-width: 480px; margin: 0 auto; z-index: 60;
}
.nmo-gxs[hidden] { display: none; }
.nmo-gxs-scrim { position: absolute; inset: 0; background: rgba(5, 5, 8, .62); }
.nmo-gxs-sheet {
  position: absolute; left: 0; right: 0; bottom: 0; max-height: calc(100% - 48px); overflow-y: auto;
  box-sizing: border-box; background: var(--bg-elev-3); color: var(--text);
  border-radius: 28px 28px 0 0; border-top: 1px solid rgba(255, 255, 255, .16);
  padding: 8px 18px calc(22px + env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; gap: 10px;
  /* #548: a finger scrolls the sheet up and down (pan-x left it unscrollable by touch, and its bottom out of reach on a
     short phone); the handle and the header row keep the drag down that closes it. */
  touch-action: pan-y; overscroll-behavior: contain;
}
.nmo-gxs-grab {
  all: unset; display: block; width: 36px; height: 4px; border-radius: 2px; background: var(--nmo-outline);
  margin: 0 auto 4px; cursor: pointer;
  /* A 4px line is too thin a thing to tap: the press reaches 12px above and below it. */
  box-shadow: 0 0 0 0 transparent; position: relative;
}
.nmo-gxs-grab::after { content: ""; position: absolute; inset: -12px -24px; }
/* Nothing in a sheet taller than the screen is squeezed: it scrolls instead (#548). */
.nmo-gxs-sheet > * { flex-shrink: 0; }
/* After the handle's own `all: unset`, which would wipe it. */
.nmo-gxs-grab, .nmo-gxs-top { touch-action: none; }
.nmo-gxs-top { display: flex; align-items: center; gap: 12px; }
.nmo-gxs-m { position: relative; display: inline-grid; place-items: center; flex: none; width: 48px; height: 48px; }
.nmo-gxs-mascot { grid-area: 1 / 1; width: 48px; height: 48px; display: block; }
.nmo-gxs-who { flex: 1; display: flex; flex-direction: column; }
.nmo-gxs-who b { font: 600 18px var(--font-display); }
.nmo-gxs-level { font: 500 13px var(--font-ui); color: var(--nmo-violet); }
.nmo-gxs-level[hidden] { display: none; }
.nmo-gxs-goal {
  background: rgba(161, 92, 255, .08); border: 1px solid rgba(161, 92, 255, .22); border-radius: 16px;
  padding: 10px 12px; display: flex; flex-direction: column; gap: 7px;
}
.nmo-gxs-goal-t { font: 500 14.5px/1.35 var(--font-ui); }
.nmo-gxs-goal-t b { font-weight: 700; }
.nmo-gxs-goal-s { font: 400 12.5px var(--font-ui); color: var(--text-dim); }
/* The lit cells: the design's canvas, as elements. Gap 40% of the height (at least 2px), corners at most 2px. */
.nmo-gxs-cells { display: flex; }
.nmo-gxs-cells i { flex: 1; border-radius: 2px; background: rgba(161, 92, 255, .16); }
.nmo-gxs-cells i.is-lit { background: rgba(161, 92, 255, .95); }
.nmo-gxs-goal-cells { height: 10px; gap: 4px; }
.nmo-gxs-xp-cells { width: 54px; height: 6px; gap: 2.4px; flex: none; }
.nmo-gxs-notes, .nmo-gxs-asks { display: flex; flex-direction: column; gap: 10px; }
.nmo-gxs-notes[hidden], .nmo-gxs-asks[hidden], .nmo-gxs-goal[hidden] { display: none; }
.nmo-gxs-label { font: 500 12px var(--font-ui); letter-spacing: .4px; color: var(--text-faint); margin-top: 2px; }
.nmo-gxs-note { display: flex; gap: 10px; align-items: flex-start; font: 400 14px/1.4 var(--font-ui); color: var(--text); }
.nmo-gxs-dot {
  width: 8px; height: 8px; border-radius: 2px; background: var(--nmo-violet); margin-top: 6px; flex: none;
  box-shadow: 0 0 6px rgba(161, 92, 255, .7);
}
.nmo-gxs-name { all: unset; font-weight: 700; cursor: pointer; }
.nmo-gxs-name:focus-visible { outline: 2px solid var(--nmo-violet); outline-offset: 2px; border-radius: 4px; }
.nmo-gxs-fig { font-weight: 700; }
.nmo-gxs-fig.nmo-up { color: var(--nmo-up); }
.nmo-gxs-fig.nmo-down { color: var(--nmo-down); }
.nmo-gxs-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.nmo-gxs-chip {
  font: 500 13.5px var(--font-ui); padding: 8px 12px; border-radius: 12px; background: none;
  border: 1px solid var(--nmo-outline-variant); color: var(--text); cursor: pointer; text-align: left;
}
.nmo-gxs-input {
  display: flex; align-items: center; gap: 12px; height: 52px; border-radius: 26px; background: var(--bg-elev-2);
  padding: 0 16px; color: var(--text-dim); margin: 2px 0 0; box-sizing: border-box;
}
.nmo-gxs-plus { font: 400 22px var(--font-display); }
.nmo-gxs-field {
  flex: 1; min-width: 0; border: 0; outline: 0; background: none; padding: 0;
  font: 400 16px var(--font-ui); color: var(--text);
}
.nmo-gxs-field::placeholder { color: var(--text-dim); }
.nmo-gxs-go {
  all: unset; display: grid; place-items: center; width: 32px; height: 32px; border-radius: 50%;
  color: var(--text-dim); cursor: pointer; flex: none;
}
.nmo-gxs-go[hidden] { display: none; }
.nmo-gxs-go .material-symbols-rounded { font-size: 22px; }
.nmo-gxs-go.is-send { background: var(--text); color: var(--bg); }
.nmo-gxs-strip { display: flex; align-items: center; gap: 10px; font: 500 12px var(--font-ui); color: var(--text-dim); }
.nmo-gxs-strip[hidden] { display: none; }
.nmo-gxs-chill {
  all: unset; background: var(--bg-elev-2); padding: 4px 10px; border-radius: 999px; white-space: nowrap; flex: none;
  cursor: pointer; font: 500 12px var(--font-ui); color: var(--text-dim);
}
.nmo-gxs-xp { flex: 1; display: flex; align-items: center; gap: 8px; white-space: nowrap; min-width: 0; }
.nmo-gxs-xp[hidden] { display: flex; visibility: hidden; }
.nmo-gxs-cr { white-space: nowrap; flex: none; }
.nmo-gxs-top-up {
  all: unset; background: var(--text); color: var(--bg); padding: 4px 10px; border-radius: 999px;
  font: 600 12px var(--font-ui); white-space: nowrap; flex: none; cursor: pointer;
}
