/* =============================================================
   Second Brain — Design Tokens
   colors_and_type.css

   Import this file at the top of any UI you build for this brand.
   All values are intentional. Don't override them inline.
   ============================================================= */

/* ----- Webfonts -----------------------------------------------
   Estedad — Persian + Latin sans (primary), SELF-HOSTED.
   PERF: was an @import of Google Fonts CSS. @import serializes the
   network waterfall (tokens.css → fonts CSS → woff2, two extra
   cross-origin round-trips before text can render in Estedad) and on
   the offline Android build it never resolved at all — the WebView
   stalled on a dead request and fell back to Tahoma. These are the
   same variable woff2 subsets Google Fonts serves (arabic 56 KB +
   latin 27 KB, weights 100–900), now loaded same-origin/from the APK.
   unicode-range keeps the latin file from downloading until a latin
   glyph is actually used.
   ----------------------------------------------------------- */
@font-face {
  font-family: 'Estedad';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/Estedad-arabic.woff2') format('woff2');
  unicode-range:
    U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F,
    U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF,
    U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B,
    U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D,
    U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89,
    U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}
@font-face {
  font-family: 'Estedad';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/Estedad-latin.woff2') format('woff2');
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
    U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* ===== Color — paper + ink ===== */
  --paper: #f8f9fa; /* app background */
  --paper-2: #f3f4f5; /* secondary surface — section dividers */
  --surface: #ffffff; /* lifted card surface */
  --surface-2: #edeeef; /* nested card / inset row */
  --surface-glass: rgba(255, 255, 255, 0.74);

  --ink: #191c1d; /* primary text, near-black */
  --ink-2: #3d4947; /* secondary text, metadata */
  --ink-3: #6d7a77; /* placeholder, disabled, hairlines */
  --ink-4: #bcc9c6; /* faintest — dividers on paper */

  /* ===== Brand ===== */
  --firoozeh: #2a9d8f; /* teal — primary brand */
  --firoozeh-deep: #006a60; /* pressed, link hover */
  --firoozeh-soft: #ddf3f0; /* tinted backgrounds, focus halos */

  /* ===== Secondary accents ===== */
  --saffron: #ffac1f; /* amber — warm accent */
  --saffron-soft: #fff3d8;
  --reward-gold: #d99b27; /* temporary milestone material */
  --reward-gold-soft: #fff3d8;
  --garnet: #b23a48; /* destructive only */
  --garnet-soft: #f1d6d9;
  --moss: #5a7a3a; /* success / completion */
  --moss-soft: #dee6cc;

  /* ===== Semantic shortcuts ===== */
  --fg: var(--ink);
  --fg-2: var(--ink-2);
  --fg-3: var(--ink-3);
  --bg: var(--paper);
  --bg-elev: var(--surface);
  --accent: var(--firoozeh);
  --danger: var(--garnet);
  --success: var(--moss);
  --warn: var(--saffron);

  /* ===== Productivity accent family + completion (amber in the noir theme) =====
     Only productivity actions (add task, the voice FAB, the done check, the Home
     pulse score, the active tab pill) route through these — so a single accent
     can survive an otherwise monochrome theme. Default = brand teal/moss. */
  --accent-deep: var(--firoozeh-deep);
  --accent-soft: var(--firoozeh-soft);
  --accent-ring: rgba(42, 157, 143, 0.3);
  --done: var(--moss); /* completion-check fill */
  --check-ring: var(--c-border-md); /* undone check-circle ring (amber in noir to invite completion) */

  /* ===== Borders ===== */
  --hairline: 1px solid rgba(25, 28, 29, 0.08);
  --hairline-strong: 1px solid rgba(25, 28, 29, 0.16);
  --focus-ring: 0 0 0 2px var(--paper), 0 0 0 4px var(--firoozeh);

  /* ===== Adaptive border/surface colors (safe for inline styles) ===== */
  --c-border-xs: rgba(25, 28, 29, 0.06);
  --c-border-sm: rgba(25, 28, 29, 0.1);
  --c-border-md: rgba(25, 28, 29, 0.16);
  --c-tint-xs: rgba(25, 28, 29, 0.04);
  --c-tint-sm: rgba(25, 28, 29, 0.08);
  --c-inactive: rgba(25, 28, 29, 0.18);

  /* ===== Radii ===== */
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-pill: 999px;

  /* ===== Shadows ===== */
  --shadow-lift: 0 1px 3px rgba(0, 0, 0, 0.04), 0 6px 15px rgba(0, 0, 0, 0.04);
  --shadow-float: 0 4px 20px rgba(42, 157, 143, 0.12), 0 16px 32px rgba(0, 0, 0, 0.08);
  --shadow-ink: 0 18px 48px rgba(25, 28, 29, 0.13);
  --shadow-press: inset 0 1px 0 rgba(255, 255, 255, 0.5), inset 0 -1px 0 rgba(25, 28, 29, 0.06);

  /* ===== Normalized app surfaces ===== */
  --surface-card: var(--surface-glass);
  --surface-card-solid: var(--surface);
  --r-control: 12px;
  --r-card: 18px;
  --r-sheet: 22px;
  --shadow-card: var(--shadow-lift), var(--shadow-press);
  --shadow-card-hover: var(--shadow-float), var(--shadow-press);
  --shadow-sheet: 0 -18px 54px rgba(25, 28, 29, 0.2);
  --screen-pad-x: 16px;
  --screen-pad-x-desktop: 24px;

  /* ===== Inverted / hero surface — always dark in both themes ===== */
  --surface-invert: var(--ink); /* dark in light mode */
  --fg-on-invert: var(--paper); /* light text on dark hero */
  --label-on-invert: var(--firoozeh-soft); /* subtle label on dark hero */

  /* ===== Spacing ramp ===== */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;

  /* ===== Layering ===== */
  --z-bottom-nav: 100;
  --z-reward: 760;
  --z-celebrate: 880;
  --z-voice-fab: 950;
  --z-modal: 2000;
  --z-system-modal: 5000;
  --z-confirm: 8000;

  /* ===== Motion ===== */
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-io: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-micro: 120ms;
  --dur-base: 240ms;
  --dur-route: 400ms;
  --dur-soft: 520ms;
  --dur-slow: 900ms;

  /* ===== Type — families ===== */
  --font-sans: 'Estedad', sans-serif;
  --font-mono: 'Estedad', sans-serif;

  /* ===== Type — scale (each variable carries size/line-height) ===== */
  --type-display: 600 40px/48px var(--font-sans);
  --type-h1: 600 28px/34px var(--font-sans);
  --type-h2: 600 22px/28px var(--font-sans);
  --type-h3: 500 18px/24px var(--font-sans);
  --type-body: 400 16px/24px var(--font-sans);
  --type-body-strong: 500 16px/24px var(--font-sans);
  --type-small: 400 14px/20px var(--font-sans);
  --type-micro: 500 12px/16px var(--font-sans);
  --type-mono: 400 14px/20px var(--font-mono);
}

/* Dark mode — opt in with [data-theme="dark"] on <html> or any ancestor.
   A deep forest-green palette with a warm amber action accent (inspired by the
   product mock): green paper/surfaces, cream ink, amber on the productivity
   elements (score, add-task, done rings, active tab). */
[data-theme='dark'] {
  --paper: #13231c;
  --paper-2: #182c23;
  --surface: #1e3228;
  --surface-2: #284035;
  --surface-glass: rgba(28, 47, 38, 0.76);

  --ink: #edf3ee;
  --ink-2: #c3d1c8;
  --ink-3: #93a69b;
  --ink-4: #5b6c63;

  --firoozeh: #4fc2a8;
  --firoozeh-deep: #8fdcc9;
  --firoozeh-soft: #173a30;

  /* Inverted surface stays a deep green in dark mode too */
  --surface-invert: #1a3127;
  --fg-on-invert: var(--ink);
  --label-on-invert: var(--firoozeh);

  --saffron-soft: #3a2c10;
  --garnet-soft: #3a1f22;
  --moss-soft: #1f3018;

  --hairline: 1px solid rgba(237, 243, 238, 0.1);
  --hairline-strong: 1px solid rgba(237, 243, 238, 0.2);
  --focus-ring: 0 0 0 2px var(--paper), 0 0 0 4px var(--accent);

  --c-border-xs: rgba(237, 243, 238, 0.08);
  --c-border-sm: rgba(237, 243, 238, 0.14);
  --c-border-md: rgba(237, 243, 238, 0.22);
  --c-tint-xs: rgba(237, 243, 238, 0.05);
  --c-tint-sm: rgba(237, 243, 238, 0.1);
  --c-inactive: rgba(237, 243, 238, 0.22);

  --shadow-lift: 0 1px 2px rgba(0, 18, 11, 0.34), 0 4px 12px rgba(0, 18, 11, 0.44);
  --shadow-float: 0 4px 12px rgba(0, 18, 11, 0.44), 0 16px 32px rgba(0, 18, 11, 0.5);
  --shadow-ink: 0 18px 48px rgba(0, 18, 11, 0.42);
  --shadow-press: inset 0 1px 0 rgba(255, 255, 255, 0.07), inset 0 -1px 0 rgba(0, 0, 0, 0.28);
  --shadow-sheet: 0 -18px 54px rgba(0, 18, 11, 0.58);

  /* Warm amber action accent — the hero colour on the forest-green base */
  --accent: #f5a623;
  --accent-deep: #d98c12;
  --accent-soft: #3a2c10;
  --accent-ring: rgba(245, 166, 35, 0.3);
  --check-ring: var(--accent); /* amber undone-check ring invites completion */
}

/* Noir / Focus mode — opt in with [data-theme="noir"] on <html>. A minimal,
   distraction-free palette on a deep forest-green-black base (sharing the dark
   theme's green family), with EVERY brand/semantic colour neutralized to a
   green-gray and ONE amber accent reserved for productivity actions (routed
   only through --accent* / --done). Distinct from dark, which keeps the brand
   colours; here everything but the amber recedes. */
[data-theme='noir'] {
  --paper: #0b1611;
  --paper-2: #0f1c16;
  --surface: #12231b;
  --surface-2: #18301f;
  --surface-glass: rgba(16, 28, 22, 0.78);

  --ink: #eef3ef;
  --ink-2: #c6d2cb;
  --ink-3: #8a988f;
  --ink-4: #4a564f;

  /* Neutralize all brand/semantic colour → green-gray, so only --accent/--done
     carry hue. --firoozeh is a MID green-gray, not near-white: dozens of filled
     buttons/bubbles hardcode white text on it, so it must keep white legible
     (AA) while still reading as a highlight on the dark-green surfaces. */
  --firoozeh: #5d6862;
  --firoozeh-deep: #eef3ef;
  --firoozeh-soft: #1a2620;
  --saffron: #c9cfca;
  --saffron-soft: #1a2620;
  --garnet: #b2b8b3;
  --garnet-soft: #1a2620;
  --moss: #b6bdb7;
  --moss-soft: #1a2620;

  --surface-invert: #18301f;
  --fg-on-invert: var(--ink);
  --label-on-invert: var(--ink-3);

  --hairline: 1px solid rgba(238, 243, 239, 0.1);
  --hairline-strong: 1px solid rgba(238, 243, 239, 0.2);
  --focus-ring: 0 0 0 2px var(--paper), 0 0 0 4px var(--accent);

  --c-border-xs: rgba(238, 243, 239, 0.07);
  --c-border-sm: rgba(238, 243, 239, 0.13);
  --c-border-md: rgba(238, 243, 239, 0.2);
  --c-tint-xs: rgba(238, 243, 239, 0.04);
  --c-tint-sm: rgba(238, 243, 239, 0.08);
  --c-inactive: rgba(238, 243, 239, 0.2);

  --shadow-lift: 0 1px 2px rgba(0, 14, 8, 0.5), 0 4px 14px rgba(0, 14, 8, 0.55);
  --shadow-float: 0 4px 14px rgba(0, 14, 8, 0.55), 0 16px 34px rgba(0, 14, 8, 0.6);
  --shadow-ink: 0 18px 48px rgba(0, 14, 8, 0.6);
  --shadow-press: inset 0 1px 0 rgba(255, 255, 255, 0.06), inset 0 -1px 0 rgba(0, 0, 0, 0.4);
  --shadow-sheet: 0 -18px 54px rgba(0, 14, 8, 0.7);

  /* The single accent — amber — only on productivity actions */
  --accent: #f5a623;
  --accent-deep: #d98c12;
  --accent-soft: #2a2114;
  --accent-ring: rgba(245, 166, 35, 0.32);
  --done: #f5a623;
  --check-ring: var(--accent); /* amber ring on undone tasks → invites completion */
}

/* =============================================================
   Element-level defaults

   Apply via:  <body class="sb">  or   <div class="sb">
   ============================================================= */
/* Hide scrollbars app-wide — keep scroll functionality, look native */
* {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge */
}
*::-webkit-scrollbar {
  display: none; /* Chrome/Safari/WebKit */
}

.sb,
.sb * {
  box-sizing: border-box;
}

.sb {
  font: var(--type-body);
  color: var(--fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

html,
body,
button,
input,
textarea,
select {
  font-family: var(--font-sans);
}

button,
input,
textarea,
select {
  letter-spacing: 0;
}

button {
  -webkit-tap-highlight-color: transparent;
}

/* Persian-first: any element inside .sb[dir="rtl"] flows RTL by default. */
.sb[dir='rtl'],
.sb [dir='rtl'] {
  direction: rtl;
}

/* Headings */
.sb h1,
.sb .h1 {
  font: var(--type-h1);
  margin: 0;
  color: var(--fg);
  letter-spacing: 0;
}
.sb h2,
.sb .h2 {
  font: var(--type-h2);
  margin: 0;
  color: var(--fg);
  letter-spacing: 0;
}
.sb h3,
.sb .h3 {
  font: var(--type-h3);
  margin: 0;
  color: var(--fg);
}
.sb .display {
  font: var(--type-display);
  margin: 0;
  color: var(--fg);
  letter-spacing: 0;
}

/* Body */
.sb p,
.sb .body {
  font: var(--type-body);
  margin: 0;
  color: var(--fg);
}
.sb .body-strong {
  font: var(--type-body-strong);
  color: var(--fg);
}
.sb .small {
  font: var(--type-small);
  color: var(--fg-2);
}
.sb .micro {
  font: var(--type-micro);
  color: var(--fg-2);
  letter-spacing: 0;
}
.sb code,
.sb .mono {
  font: var(--type-mono);
  color: var(--fg);
}

/* Links */
.sb a {
  color: var(--firoozeh);
  text-decoration: none;
  border-bottom: 1px solid rgba(42, 157, 143, 0.32);
  transition:
    color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out);
}
.sb a:hover {
  color: var(--firoozeh-deep);
  border-bottom-color: var(--firoozeh-deep);
}

/* Focus — keyboard only */
.sb :focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
}

/* Hairline divider */
.sb hr {
  border: 0;
  border-top: var(--hairline);
  margin: var(--s-5) 0;
}

/* Selection */
.sb ::selection {
  background: var(--firoozeh-soft);
  color: var(--firoozeh-deep);
}

/* =============================================================
   Reusable primitives — small, opinionated, ready to drop into UI
   ============================================================= */

/* Card */
.sb-card {
  background: var(--surface-card-solid);
  border-radius: var(--r-card);
  border: 1px solid var(--c-border-sm);
  box-shadow: var(--shadow-card);
  padding: var(--s-5);
}

/* Buttons */
.sb-btn {
  font: var(--type-body-strong);
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 10px 16px;
  border-radius: var(--r-control);
  border: 1px solid transparent;
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  transition:
    background var(--dur-micro) var(--ease-out),
    transform var(--dur-micro) var(--ease-out);
}
.sb-btn:hover {
  background: #2a2823;
}
.sb-btn:active {
  transform: scale(0.98);
}

.sb-btn--primary {
  background: var(--firoozeh);
  color: #fff;
}
.sb-btn--primary:hover {
  background: var(--firoozeh-deep);
}

.sb-btn--ghost {
  background: transparent;
  color: var(--fg);
  border-color: var(--c-border-md);
}
.sb-btn--ghost:hover {
  background: var(--c-tint-xs);
}

.sb-btn--danger {
  background: var(--garnet);
  color: #fff;
}

/* Inputs */
.sb-input {
  font: var(--type-body);
  width: 100%;
  padding: 12px 14px;
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--c-border-md);
  border-radius: var(--r-control);
  transition:
    border-color var(--dur-micro) var(--ease-out),
    box-shadow var(--dur-micro) var(--ease-out);
}
.sb-input::placeholder {
  color: var(--ink-3);
}
.sb-input:focus {
  outline: none;
  border-color: var(--firoozeh);
  box-shadow: 0 0 0 3px var(--firoozeh-soft);
}

/* Chip / tag */
.sb-chip {
  font: var(--type-micro);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--paper-2);
  color: var(--ink-2);
  border: 1px solid var(--c-border-xs);
}
.sb-chip--accent {
  background: var(--firoozeh-soft);
  color: var(--firoozeh-deep);
  border-color: transparent;
}
.sb-chip--warn {
  background: var(--saffron-soft);
  color: var(--saffron);
  border-color: transparent;
}
.sb-chip--success {
  background: var(--moss-soft);
  color: var(--moss);
  border-color: transparent;
}
.sb-chip--danger {
  background: var(--garnet-soft);
  color: var(--garnet);
  border-color: transparent;
}

/* =============================================================
   App atmosphere + motion
   ============================================================= */
.sb-shell {
  isolation: isolate;
  background-color: var(--paper);
}

.sb-mobile-shell {
  --sb-compact-ui-scale: 1;
  --sb-compact-ui-scale-inv: 1;
}

.sb-mobile-scale {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  transform: scale(var(--sb-compact-ui-scale));
  transform-origin: top right;
  will-change: auto;
}

@media (max-width: 380px), (max-height: 700px) {
  .sb-mobile-shell {
    --sb-compact-ui-scale: 0.95;
    --sb-compact-ui-scale-inv: 1.05264;
  }

  .sb-mobile-scale {
    flex: 0 0 calc(100% * var(--sb-compact-ui-scale-inv));
    flex-basis: calc(100% * var(--sb-compact-ui-scale-inv));
    width: calc(100% * var(--sb-compact-ui-scale-inv));
    height: calc(100% * var(--sb-compact-ui-scale-inv));
  }
}

@media (max-width: 350px), (max-height: 640px) {
  .sb-mobile-shell {
    --sb-compact-ui-scale: 0.92;
    --sb-compact-ui-scale-inv: 1.08696;
  }
}

@media (max-width: 320px), (max-height: 580px) {
  .sb-mobile-shell {
    --sb-compact-ui-scale: 0.89;
    --sb-compact-ui-scale-inv: 1.1236;
  }
}

/* ── App-wide ambient background ────────────────────────────────────────────
   A gentle, theme-aware gradient that breathes behind every page. Three soft
   radial blobs (warm amber + brand teal + a hint of cool teal) drift slowly,
   matched to the active theme so it never fights the cards' --surface fill:
     • light : pale saffron + pale teal on white paper — keeps white cards
       visually separated without shouting.
     • dark  : warm amber + deep forest glow on the green-paper base — gives
       the dashboard depth without breaking the pulse/fabric/finance cards.
     • noir  : off — focus mode keeps every hue neutralised and the amber
       accent must stay single-sourced (productivity actions only).
   prefers-reduced-motion is already handled globally in tokens.css
   (animation-duration: 1ms !important), so the drift silently freezes.
   The blobs use the same hues that Hello (`lp-hero`/`lw-aurora`) uses, so
   Welcome and Home share a recognisable brand atmosphere.             ── */
.sb-shell::before {
  content: '';
  position: fixed;
  inset: -10%;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(40% 32% at 18% 14%, rgba(255, 172, 31, 0.18) 0%, transparent 62%),
    radial-gradient(46% 36% at 88% 86%, rgba(42, 157, 143, 0.22) 0%, transparent 64%),
    radial-gradient(34% 28% at 78% 18%, rgba(83, 58, 253, 0.1) 0%, transparent 68%),
    radial-gradient(30% 26% at 12% 90%, rgba(255, 172, 31, 0.1) 0%, transparent 70%);
  /* FINITE drift (was infinite): an always-visible fixed layer looping forever
     forces the WebView to re-composite a 120%-viewport gradient every frame
     behind every scroller — the project's own no-infinite-loop rule. Four
     alternate passes (~2min of life after launch) keep the breathing entrance,
     then the backdrop settles for free. */
  animation: sbAmbientDrift 32s ease-in-out 4 alternate;
}

/* Dark mode — swap the paper-bright blobs for deeper, dimmer glows on the
   forest-green base. Same shape positions so the breathing reads continuous
   across the theme flip; opacity dialled down so dark cards don't drown. */
[data-theme='dark'] .sb-shell::before {
  background:
    radial-gradient(40% 32% at 18% 14%, rgba(245, 166, 35, 0.14) 0%, transparent 62%),
    radial-gradient(46% 36% at 88% 86%, rgba(79, 194, 168, 0.16) 0%, transparent 64%),
    radial-gradient(34% 28% at 78% 18%, rgba(122, 90, 240, 0.1) 0%, transparent 68%),
    radial-gradient(30% 26% at 12% 90%, rgba(245, 166, 35, 0.08) 0%, transparent 70%);
}

/* ── Noir: kill the tinted grain AND the ambient glow. Focus mode wants a
   near-flat, monochrome canvas so the single amber accent (productivity
   actions, pulse score, active tab) reads as the only colour on screen. */
[data-theme='noir'] .sb-shell::before {
  background: none;
  animation: none;
}

@keyframes sbAmbientDrift {
  0% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  50% {
    transform: translate3d(-3%, 2%, 0) scale(1.04);
  }
  100% {
    transform: translate3d(2%, -2%, 0) scale(1.02);
  }
}
[data-theme='noir'] .sb-fin-cat,
[data-theme='noir'] .sb-finance-screen,
[data-theme='noir'] .sb-noir-decor,
[data-theme='noir'] .sb-gamify-card,
[data-theme='noir'] .sb-empty-ico,
[data-theme='noir'] .lp-root,
[data-theme='noir'] .lw-root,
[data-theme='noir'] .lt-root {
  filter: grayscale(1);
}
[data-theme='noir'] .sb-noir-deemph {
  filter: grayscale(1);
  opacity: 0.72;
}

/* The mini orb owns the FAB material in noir too; the generic button shell
   stays transparent so the monochrome orb palette remains visible. */
[data-theme='noir'] .sb-fab-btn {
  background: transparent !important;
  color: var(--surface) !important;
  box-shadow: none !important;
}
[data-theme='noir'] .sb-fab-orbit {
  border-color: rgba(255, 255, 255, 0.3) !important;
}
[data-theme='noir'] .sb-fab-aura {
  background: radial-gradient(circle, rgba(255, 255, 255, 0.12), transparent 70%) !important;
}
[data-theme='noir'] .sb-fab-cap {
  color: var(--ink-2);
}

/* The task-completion circle always invites with the amber accent in noir —
   priority variants (urgent→saffron) neutralize to gray, so force the ring amber
   so "tap to finish" reads as the one warm action on the screen. */
[data-theme='noir'] .sb-task-check {
  border-color: var(--accent) !important;
}

/* (.sb-next-dot and its sbNextPulse ring were retired with the upcoming-
   reminders timeline — Home's «لاگ‌های شما» card replaced that section.) */

.sb-surface {
  background: var(--surface-glass);
  border: 1px solid var(--c-border-xs);
  box-shadow: var(--shadow-lift), var(--shadow-press);
}

/* «حالت متمرکز» reveal — a one-shot cinematic overlay played when the user
   enters focus mode, masking the theme flip with a dark veil + big amber title.
   transform/opacity only; self-removes on animationend (the veil drives it). */
.sb-focus-intro {
  position: fixed;
  inset: 0;
  z-index: var(--z-confirm);
  display: grid;
  place-items: center;
  text-align: center;
  direction: rtl;
  pointer-events: none;
  background: radial-gradient(120% 120% at 50% 42%, #1b1b1f 0%, #0d0d0e 72%);
  animation: sbFocusVeil 1.7s ease-in-out forwards;
}
.sb-focus-intro-ring {
  position: absolute;
  width: 230px;
  height: 230px;
  border-radius: 50%;
  border: 2px solid rgba(245, 166, 35, 0.5);
  animation: sbFocusRing 1.7s ease-out forwards;
}
.sb-focus-intro-title {
  font:
    900 34px/1.2 'Estedad',
    sans-serif;
  color: #f5a623;
  letter-spacing: 0.5px;
  text-shadow: 0 6px 34px rgba(245, 166, 35, 0.35);
  animation: sbFocusTitle 1.7s ease-out forwards;
}
.sb-focus-intro-sub {
  margin-top: 10px;
  font:
    600 14px/1.6 'Estedad',
    sans-serif;
  color: rgba(255, 255, 255, 0.6);
  animation: sbFocusSub 1.7s ease-out forwards;
}
@keyframes sbFocusVeil {
  0% {
    opacity: 0;
  }
  14% {
    opacity: 1;
  }
  78% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@keyframes sbFocusTitle {
  0% {
    opacity: 0;
    transform: translateY(10px) scale(0.92);
  }
  24% {
    opacity: 1;
    transform: none;
  }
  72% {
    opacity: 1;
    transform: none;
  }
  100% {
    opacity: 0;
    transform: scale(1.05);
  }
}
@keyframes sbFocusSub {
  0%,
  18% {
    opacity: 0;
    transform: translateY(8px);
  }
  38% {
    opacity: 1;
    transform: none;
  }
  72% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@keyframes sbFocusRing {
  0% {
    opacity: 0;
    transform: scale(0.4);
  }
  30% {
    opacity: 0.55;
  }
  100% {
    opacity: 0;
    transform: scale(1.6);
  }
}

.sb-reveal {
  animation: sbRouteIn var(--dur-route) var(--ease-out) both;
}

.sb-route {
  height: 100%;
  min-height: 0;
  animation: sbRouteIn var(--dur-route) var(--ease-out) both;
}

/* ── Dialog / sheet entrance ("fade through black" + "slide wipe" motifs) ──
   Every sheet/modal/confirm in the app conditionally-mounts (`if (!open)
   return null`), so a plain one-shot `animation` on mount is enough — no
   `.is-visible` scroll-toggling needed like Welcome.jsx's landing reveals.
   `backwards` (not `both`): once it finishes, the element reverts to its
   un-animated opacity:1/transform:none — which is the resting state anyway,
   since nothing else sets those properties — so it stops winning the
   cascade instead of leaving a stale animation-owned value behind.
     .sb-overlay-in  — backdrop fade, on the fixed inset:0 scrim.
     .sb-sheet-in    — bottom sheets (the vast majority: Settings, add/edit/
                       confirm/upload/share sheets) decelerate up from the
                       edge they're anchored to. No overshoot — a physical
                       sheet coming to rest doesn't bounce past its stop.
     .sb-panel-in    — top-anchored panels (GlobalSearch) ease down instead. */
.sb-overlay-in {
  animation: sbOverlayIn 220ms var(--ease-out) backwards;
}
.sb-sheet-in {
  animation: sbSheetUp 340ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
}
.sb-panel-in {
  animation: sbPanelDown 260ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
}
@keyframes sbOverlayIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes sbSheetUp {
  from {
    opacity: 0;
    transform: translateY(28px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@keyframes sbPanelDown {
  from {
    opacity: 0;
    transform: translateY(-14px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.sb-flow > * {
  animation: sbItemRise var(--dur-soft) var(--ease-out) both;
}

.sb-flow > *:nth-child(1) {
  animation-delay: 20ms;
}
.sb-flow > *:nth-child(2) {
  animation-delay: 60ms;
}
.sb-flow > *:nth-child(3) {
  animation-delay: 100ms;
}
.sb-flow > *:nth-child(4) {
  animation-delay: 140ms;
}
.sb-flow > *:nth-child(5) {
  animation-delay: 180ms;
}
.sb-flow > *:nth-child(6) {
  animation-delay: 220ms;
}
.sb-flow > *:nth-child(7) {
  animation-delay: 260ms;
}
.sb-flow > *:nth-child(8) {
  animation-delay: 300ms;
}
.sb-flow > *:nth-child(9) {
  animation-delay: 340ms;
}
.sb-flow > *:nth-child(10) {
  animation-delay: 380ms;
}
.sb-flow > *:nth-child(n + 11) {
  animation-delay: 420ms;
}

/* ── Animated-list stagger ("animated list" motif) ─────────────────────────
   Fast row-level re-entrance for a container whose contents change with a
   filter / day / segment switch. One-shot, transform+opacity only, and much
   snappier than .sb-flow (26ms steps, ~300ms total) so it reads as rhythm,
   not as a loading state. Replays only when elements (re)mount — put a
   changing `key` on the container to force a replay after a filter change. */
.sb-stagger > * {
  /* backwards (not both): once the entrance finishes it must stop winning the
     cascade, so rows keep their own inline transforms (e.g. the done-slide)
     and no lingering stacking context traps overlays. */
  animation: sbItemRise 280ms var(--ease-out) backwards;
}
.sb-stagger > *:nth-child(1) {
  animation-delay: 0ms;
}
.sb-stagger > *:nth-child(2) {
  animation-delay: 26ms;
}
.sb-stagger > *:nth-child(3) {
  animation-delay: 52ms;
}
.sb-stagger > *:nth-child(4) {
  animation-delay: 78ms;
}
.sb-stagger > *:nth-child(5) {
  animation-delay: 104ms;
}
.sb-stagger > *:nth-child(6) {
  animation-delay: 130ms;
}
.sb-stagger > *:nth-child(7) {
  animation-delay: 156ms;
}
.sb-stagger > *:nth-child(8) {
  animation-delay: 182ms;
}
.sb-stagger > *:nth-child(n + 9) {
  animation-delay: 208ms;
}

/* ── Chip / quick-action pop-in ("notification pop" motif) ──────────────────
   Springy one-shot entrance for small tappable chips (suggestion cards,
   quick-capture buttons). The overshoot lives in the bezier, so it stays
   transform/opacity-only. */
.sb-chip-pop > * {
  animation: sbChipPop 340ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
}
.sb-chip-pop > *:nth-child(1) {
  animation-delay: 30ms;
}
.sb-chip-pop > *:nth-child(2) {
  animation-delay: 90ms;
}
.sb-chip-pop > *:nth-child(3) {
  animation-delay: 150ms;
}
.sb-chip-pop > *:nth-child(n + 4) {
  animation-delay: 210ms;
}

/* ── SVG chart bars ("bar chart" motif) ─────────────────────────────────────
   Bars grow out of the axis with a per-bar delay (set --bar-delay inline).
   -rise grows upward (income / positive net), -drop grows downward from the
   midline (expense / negative net). transform-box makes the origin the rect
   itself, not the whole SVG viewBox. */
.sb-bar-rise,
.sb-bar-drop {
  transform-box: fill-box;
  animation: sbBarGrow 480ms var(--ease-out) backwards;
  animation-delay: var(--bar-delay, 0ms);
}
.sb-bar-rise {
  transform-origin: 50% 100%;
}
.sb-bar-drop {
  transform-origin: 50% 0%;
}

/* .sb-road-pulse is a genuinely infinite loop (a permanent halo on the "next"
   treasure-map milestone) — the one animation on this screen the project's
   no-infinite-loop rule actually rules out. .sb-lvl-sheen belongs to
   LevelAvatar/NameAvatar, which nothing currently renders (dead code); its
   disable is inert either way. Everything else that used to be blanket-killed
   here (.sb-gamify-card's pop+sheen, .sb-week-bar's grow, the badge
   wake/rise/medal/ribbon animations, and the plain .sb-flow stagger on the
   four top-level sections) is the exact same treatment Home/Tasks already
   run for these identical classes — Profile was the only screen missing it. */
.sb-profile-screen .sb-lvl-sheen,
.sb-profile-screen .sb-road-pulse {
  display: none;
}

.sb-animated-card {
  transition:
    transform var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out);
}

.sb-animated-card:active {
  transform: translateY(1px) scale(0.995);
}

.sb-screen,
.sb-tasks-screen,
.sb-finance-screen,
.sb-knowledge-screen,
.sb-profile-screen,
.sb-assistant-screen {
  width: 100%;
  max-width: 760px;
  margin-inline: auto;
}

.sb-assistant-screen {
  max-width: 860px;
}

/* =============================================================
   Desktop workspace
   -------------------------------------------------------------
   Below the breakpoint these wrappers are `display: contents`, so
   they are invisible to layout and the mobile flow is untouched.
   Above it, Home becomes a real two-column dashboard instead of one
   tall column: the working surface (orb, week strip, today's list)
   stays in the reading column, and the context that mobile is forced
   to bury below the fold (اهداف، لاگ‌ها، نگاه مالی) moves into a
   sticky rail that stays on screen while the main column scrolls.
   ============================================================= */
.sb-home-main,
.sb-home-rail {
  display: contents;
}

@media (min-width: 1180px) {
  .sb-home-screen {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 348px;
    align-content: start;
    column-gap: 26px;
  }

  .sb-home-main,
  .sb-home-rail {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  /* The rail follows the reader instead of scrolling away. Its own
     overflow keeps a long rail usable on short screens. */
  .sb-home-rail {
    position: sticky;
    top: 0;
    align-self: start;
    max-height: 100%;
    overflow: auto;
    scrollbar-width: none;
    padding-bottom: 24px;
  }

  .sb-home-rail::-webkit-scrollbar {
    display: none;
  }

  /* Rail cards are secondary — quieter type, tighter padding, so the
     column reads as context rather than a second main column. */
  .sb-home-rail .sb-home-card {
    padding: 15px 16px;
    margin-bottom: 14px;
  }
}

@media (min-width: 1180px) {
  /* Tasks: date groups run two-up. Each column lands around 560px, which is
     what removes the dead space inside a row — the title and its time end up
     next to each other again — and puts today AND tomorrow on screen at once
     instead of one group per viewport. `align-items: start` keeps a short
     group from stretching to match a tall neighbour. */
  .sb-task-groups {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: start;
    column-gap: 22px;
  }
}

/* Row-based lists (tasks, the financial log) stretched to the full
   1200px put the title on one edge and its time on the other with a
   half-metre of nothing between — the clearest tell of a blown-up
   phone layout. Cap the row's measure and keep it in the reading
   column; the freed space is margin, not dead space inside the card. */
@media (min-width: 1180px) {
  /* RTL: the row starts at the right edge, so capping its measure pulls the
     trailing meta (the time) back toward the title instead of parking it a
     screen away. */
  .sb-row-measure {
    max-width: 620px;
  }
}

@media (min-width: 768px) {
  .sb-screen,
  .sb-tasks-screen,
  .sb-finance-screen,
  .sb-knowledge-screen,
  .sb-profile-screen,
  .sb-assistant-screen {
    max-width: 100%;
  }

  /* Tasks & finance screens: wider on desktop */
  .sb-tasks-screen,
  .sb-finance-screen {
    padding-inline: 8px;
  }
}

/* Chat and profile have the opposite problem to a list: stretched to the full
   width, a line of Persian runs ~140 characters, which is unreadable. Cap the
   measure and centre the column — the space around it is margin, and margin is
   what makes long-form text legible.
   NOTE: this must stay AFTER the 768px block above, which sets
   `max-width: 100%` on the same selectors at the same specificity. */
@media (min-width: 1180px) {
  .sb-assistant-screen,
  .sb-profile-screen {
    max-width: 880px;
    margin-inline: auto;
  }
}

.sb-section-label {
  margin: 18px 2px 10px;
  color: var(--ink-2);
  font: 700 13px/1.45 var(--font);
  letter-spacing: 0;
}

.sb-sheet-surface {
  background: var(--paper);
  border: 1px solid var(--c-border-sm);
  border-bottom: 0;
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  box-shadow: var(--shadow-sheet);
}

.sb-normalized-card {
  background: var(--surface-card);
  border: 1px solid var(--c-border-sm);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
}

.sb-normalized-row {
  border-radius: var(--r-control);
  border: 1px solid var(--c-border-xs);
  background: var(--surface-card-solid);
}

.sb-live-session {
  isolation: isolate;
}

.sb-home-screen {
  container-type: inline-size;
}

.sb-home-install,
.sb-home-card {
  isolation: isolate;
}

/* Flat home surfaces — opaque, hairline-bordered, shadowless, in sync with
   the orb hero (also removes blur from the scroll path — see Performance). */
.sb-home-card:hover {
  border-color: var(--c-border-sm);
}

.sb-home-finance-stats {
  display: grid !important;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px !important;
}

.sb-home-finance-stats > div {
  min-width: 0;
  padding: 10px 11px;
  border-radius: var(--r-md);
  background: var(--paper-2);
  border: 1px solid var(--c-border-xs);
}

@container (min-width: 560px) {
  .sb-home-finance-stats {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.sb-gamify-card {
  position: relative;
  overflow: hidden;
  animation: sbSoftPop 560ms var(--ease-out) both;
}

.sb-gamify-card::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(115deg, transparent 0%, rgba(255, 255, 255, 0.18) 42%, transparent 62%);
  transform: translateX(120%);
  animation: sbSheen 1800ms var(--ease-out) 220ms both;
}

.sb-xp-orb {
  position: relative;
  animation: sbXpBreathe 2800ms var(--ease-io) 3;
}

.sb-xp-orb::after {
  content: '';
  position: absolute;
  inset: -5px;
  border-radius: inherit;
  border: 1px solid currentColor;
  opacity: 0;
  animation: sbXpRing 2800ms var(--ease-out) 3;
}

.sb-progress-track {
  position: relative;
  overflow: hidden;
}

.sb-progress-bar {
  position: relative;
  overflow: hidden;
  transform-origin: right center;
  animation: sbProgressGrow 700ms var(--ease-out) both;
}

.sb-progress-bar::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.42), transparent);
  transform: translateX(120%);
  animation: sbProgressSheen 1600ms var(--ease-out) 420ms both;
}

.sb-stat-card {
  animation: sbScaleIn 420ms var(--ease-out) both;
}

/* Chat bubbles land with a slight overshoot (sbSoftPop) — springier and a
   touch faster than the old plain rise, so a streaming reply keeps pace. */
.sb-message-motion {
  animation: sbSoftPop 320ms var(--ease-out) both;
}

.sb-week-bar {
  transform-origin: bottom center;
  animation: sbBarGrow 620ms var(--ease-out) both;
}

.sb-badge-unlocked {
  animation: sbBadgeWake 620ms var(--ease-out) both;
}

.sb-badge-locked {
  animation: sbItemRise 460ms var(--ease-out) both;
}

.sb-badge-medal {
  animation: sbBadgeMedalWake 680ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

.sb-badge-ribbon-shine {
  overflow: hidden;
}

.sb-badge-ribbon-shine::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.34), transparent);
  transform: translateX(120%);
  animation: sbBadgeRibbonShine 1300ms var(--ease-out) 260ms both;
}

.sb-streak-chip {
  animation: sbWarmPulse 2200ms var(--ease-io) 4;
}

.sb-reward-burst {
  position: fixed;
  top: 86px;
  left: 50%;
  z-index: 760;
  direction: rtl;
  pointer-events: none;
  transform: translateX(-50%);
  animation: sbRewardFloat 1650ms var(--ease-out) both;
}

.sb-reward-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 132px;
  justify-content: center;
  padding: 11px 16px;
  border-radius: 999px;
  background: var(--surface);
  color: var(--firoozeh-deep);
  border: 1px solid var(--c-border-sm);
  box-shadow: var(--shadow-float), var(--shadow-press);
  font: 800 15px var(--font-sans);
}

.sb-reward-sub {
  font: 600 11px var(--font-sans);
  color: var(--ink-3);
}

.sb-reward-spark {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--saffron);
  left: 50%;
  top: 50%;
  opacity: 0;
  animation: sbRewardSpark 900ms var(--ease-out) both;
  animation-delay: var(--spark-delay, 0ms);
}

.sb-icon-button:hover {
  background: var(--firoozeh-soft) !important;
  color: var(--firoozeh-deep) !important;
  transform: translateY(-1px);
}

.sb-icon-button:active {
  transform: translateY(0) scale(0.98);
}

.sb-tab-button:hover {
  color: #fff !important;
  transform: translateY(-1px);
}

.sb-tab-button:active {
  transform: translateY(0) scale(0.98);
}

/* TabBar liveliness: icon pop on activation + badge entrance */
@keyframes sbTabPop {
  0% {
    transform: scale(1);
  }
  35% {
    transform: scale(1.3) translateY(-2px) rotate(-4deg);
  }
  68% {
    transform: scale(0.92);
  }
  100% {
    transform: scale(1);
  }
}
.sb-tab-icon-pop {
  animation: sbTabPop 420ms var(--ease-out);
}

@keyframes sbBadgeIn {
  0% {
    transform: scale(0);
    opacity: 0;
  }
  60% {
    transform: scale(1.3);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}
.sb-tab-badge-in {
  animation: sbBadgeIn 320ms var(--ease-out) both;
}

.sb-lift-button:hover {
  transform: translateY(-1px);
}

.sb-lift-button:active {
  transform: translateY(1px) scale(0.99);
}

@media (hover: hover) {
  .sb-animated-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-float), var(--shadow-press);
    border-color: var(--c-border-sm);
  }
}

@keyframes sbRouteIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes sbItemRise {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes sbScaleIn {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes sbChipPop {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.9);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes sbSoftPop {
  0% {
    opacity: 0;
    transform: translateY(12px) scale(0.985);
  }
  70% {
    opacity: 1;
    transform: translateY(-1px) scale(1.006);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes sbSheen {
  0% {
    transform: translateX(120%);
    opacity: 0;
  }
  28% {
    opacity: 1;
  }
  100% {
    transform: translateX(-120%);
    opacity: 0;
  }
}

@keyframes sbXpBreathe {
  0%,
  100% {
    transform: translateY(0) scale(1);
  }
  50% {
    transform: translateY(-1px) scale(1.035);
  }
}

@keyframes sbXpRing {
  0%,
  58%,
  100% {
    opacity: 0;
    transform: scale(0.86);
  }
  16% {
    opacity: 0.26;
    transform: scale(1);
  }
}

@keyframes sbProgressGrow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

@keyframes sbProgressSheen {
  from {
    transform: translateX(120%);
  }
  to {
    transform: translateX(-120%);
  }
}

@keyframes sbBarGrow {
  from {
    transform: scaleY(0.18);
    opacity: 0.45;
  }
  to {
    transform: scaleY(1);
    opacity: 1;
  }
}

@keyframes sbBadgeWake {
  0% {
    opacity: 0;
    transform: translateY(12px) scale(0.92);
  }
  62% {
    opacity: 1;
    transform: translateY(-2px) scale(1.03);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes sbBadgeMedalWake {
  0% {
    opacity: 0;
    transform: translateY(14px) scale(0.72) rotate(-7deg);
  }
  58% {
    opacity: 1;
    transform: translateY(-3px) scale(1.06) rotate(3deg);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1) rotate(0deg);
  }
}

@keyframes sbBadgeRibbonShine {
  from {
    transform: translateX(120%);
  }
  to {
    transform: translateX(-120%);
  }
}

@keyframes sbWarmPulse {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-1px);
  }
}

@keyframes sbRewardFloat {
  0% {
    opacity: 0;
    transform: translate(-50%, 12px) scale(0.94);
  }
  18% {
    opacity: 1;
    transform: translate(-50%, 0) scale(1.02);
  }
  72% {
    opacity: 1;
    transform: translate(-50%, -4px) scale(1);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -18px) scale(0.98);
  }
}

@keyframes sbRewardSpark {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.3);
  }
  18% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--spark-x)), calc(-50% + var(--spark-y))) scale(1);
  }
}

/* Full-screen task-completion celebration (confetti + level-up badge) */
.sb-celebrate {
  position: fixed;
  inset: 0;
  z-index: 880;
  pointer-events: none;
  overflow: hidden;
}
.sb-celebrate-origin {
  position: absolute;
  left: 50%;
  top: 42%;
  width: 0;
  height: 0;
}
.sb-confetti {
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  animation: sbConfettiFly 1150ms var(--ease-out) both;
  animation-delay: var(--cdelay, 0ms);
}
@keyframes sbConfettiFly {
  0% {
    transform: translate(-50%, -50%) scale(0.3) rotate(0deg);
    opacity: 0;
  }
  14% {
    opacity: 1;
  }
  62% {
    opacity: 1;
  }
  100% {
    transform: translate(calc(-50% + var(--cx)), calc(-50% + var(--cy))) scale(1) rotate(var(--cr));
    opacity: 0;
  }
}
.sb-celebrate-check {
  position: absolute;
  left: 50%;
  top: 42%;
  width: 76px;
  height: 76px;
  margin: -38px 0 0 -38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #fff;
  background: linear-gradient(135deg, var(--moss), var(--firoozeh));
  box-shadow: 0 12px 36px rgba(42, 157, 143, 0.42);
  animation: sbCelebrateCheck 1100ms var(--ease-out) both;
}
@keyframes sbCelebrateCheck {
  0% {
    transform: scale(0) rotate(-18deg);
    opacity: 0;
  }
  26% {
    transform: scale(1.22) rotate(4deg);
    opacity: 1;
  }
  44% {
    transform: scale(1) rotate(0deg);
  }
  76% {
    opacity: 1;
  }
  100% {
    transform: scale(1) translateY(-14px);
    opacity: 0;
  }
}
.sb-celebrate-flash {
  position: absolute;
  left: 50%;
  top: 42%;
  width: 280px;
  height: 280px;
  margin: -140px 0 0 -140px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 172, 31, 0.55), transparent 70%);
  opacity: 0;
  animation: sbCelebrateFlash 1000ms var(--ease-out) both;
}
@keyframes sbCelebrateFlash {
  0% {
    transform: scale(0.3);
    opacity: 0;
  }
  30% {
    opacity: 0.85;
  }
  100% {
    transform: scale(2.3);
    opacity: 0;
  }
}
.sb-celebrate-badge {
  position: absolute;
  left: 50%;
  top: 42%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  animation: sbCelebrateBadge 1550ms var(--ease-out) both;
}
.sb-celebrate-badge-ring {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #1a1100;
  background: linear-gradient(135deg, #ffd66b, var(--saffron));
  box-shadow: 0 14px 40px rgba(255, 172, 31, 0.5);
}
.sb-celebrate-badge-text {
  font: 900 19px var(--font-sans);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--c-border-sm);
  border-radius: 999px;
  padding: 5px 16px;
  box-shadow: var(--shadow-float);
}
@keyframes sbCelebrateBadge {
  0% {
    transform: translate(-50%, -50%) scale(0) rotate(-12deg);
    opacity: 0;
  }
  18% {
    transform: translate(-50%, -50%) scale(1.2) rotate(5deg);
    opacity: 1;
  }
  34% {
    transform: translate(-50%, -50%) scale(1) rotate(0deg);
  }
  80% {
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -58%) scale(1);
    opacity: 0;
  }
}

/* Level roadmap — pulsing halo on the next reward milestone */
@keyframes sbRoadPulse {
  0% {
    transform: scale(0.82);
    opacity: 0.55;
  }
  70% {
    transform: scale(1.55);
    opacity: 0;
  }
  100% {
    transform: scale(1.55);
    opacity: 0;
  }
}
.sb-road-pulse {
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid var(--saffron);
  pointer-events: none;
  animation: sbRoadPulse 1900ms var(--ease-out) infinite;
}

.sb-treasure-map {
  isolation: isolate;
}
.sb-treasure-map::before {
  content: '';
  position: absolute;
  inset: 10px;
  border-radius: 20px;
  border: 1px solid rgba(255, 172, 31, 0.16);
  pointer-events: none;
}
.sb-treasure-scroll::before,
.sb-treasure-scroll::after {
  content: '';
  position: absolute;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(42, 157, 143, 0.16), transparent 64%);
  pointer-events: none;
}
.sb-treasure-scroll::before {
  right: -42px;
  top: -44px;
}
.sb-treasure-scroll::after {
  left: -48px;
  bottom: -52px;
  background: radial-gradient(circle, rgba(255, 172, 31, 0.18), transparent 64%);
}
.sb-treasure-route-shadow,
.sb-treasure-route {
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sb-treasure-route-shadow {
  stroke: rgba(82, 61, 31, 0.14);
  stroke-width: 8;
}
.sb-treasure-route {
  stroke: rgba(199, 127, 0, 0.42);
  stroke-width: 3;
  stroke-dasharray: 6 8;
}
/* The traveled portion of the path — solid gold, drawn on mount over the
   dashed track so progress reads at a glance up to the last reached stop. */
.sb-treasure-route-progress {
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke: var(--saffron, #e9960c);
  stroke-width: 4;
  filter: drop-shadow(0 1px 2px rgba(199, 127, 0, 0.45));
  animation: sbTreasureRouteDraw 1100ms var(--ease-out) both;
}
/* Dark mode: the treasure card's parchment/white washes are literals in the
   component's inline styles, so these overrides need !important to win. */
[data-theme='dark'] .sb-treasure-map {
  background:
    radial-gradient(circle at 12% 14%, rgba(255, 214, 107, 0.1), transparent 26%),
    radial-gradient(circle at 86% 10%, rgba(42, 157, 143, 0.14), transparent 28%),
    linear-gradient(155deg, var(--surface), var(--paper-2)) !important;
  border-color: rgba(255, 172, 31, 0.22) !important;
  box-shadow: 0 22px 52px rgba(0, 0, 0, 0.45) !important;
}
[data-theme='dark'] .sb-treasure-scroll {
  background: linear-gradient(180deg, rgba(255, 214, 107, 0.08), rgba(255, 255, 255, 0.03)) !important;
  border-color: rgba(255, 172, 31, 0.16) !important;
}
[data-theme='dark'] .sb-treasure-route-shadow {
  stroke: rgba(0, 0, 0, 0.4);
}
[data-theme='dark'] .sb-treasure-route {
  stroke: rgba(233, 196, 106, 0.4);
}
.sb-treasure-x {
  animation: sbTreasureFloat 2600ms var(--ease-io) 3;
}
.sb-treasure-compass {
  animation: sbTreasureFloat 2400ms var(--ease-io) 2;
}
@keyframes sbTreasureRouteDraw {
  from {
    stroke-dashoffset: 80;
    opacity: 0.35;
  }
  to {
    stroke-dashoffset: 0;
    opacity: 1;
  }
}
@keyframes sbTreasureFloat {
  0%,
  100% {
    transform: translateY(0) rotate(0deg);
  }
  50% {
    transform: translateY(-2px) rotate(-2deg);
  }
}

/* Level avatar — finite sheen sweep on mount (pairs with sb-xp-orb breathing) */
@keyframes sbLvlSheen {
  0% {
    transform: translateX(-160%) skewX(-12deg);
  }
  55%,
  100% {
    transform: translateX(320%) skewX(-12deg);
  }
}
.sb-lvl-sheen {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.5), transparent);
  pointer-events: none;
  animation: sbLvlSheen 2600ms var(--ease-out) 2;
}

/* Top-bar connection node — finite reconnect pulse (no infinite header anim) */
@keyframes sbConnPulse {
  0% {
    transform: scale(0.55);
    opacity: 0.6;
  }
  100% {
    transform: scale(2.7);
    opacity: 0;
  }
}
.sb-conn-pulse {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  border: 2px solid var(--moss);
  pointer-events: none;
  animation: sbConnPulse 1400ms var(--ease-out) 3;
}

/* Task completion — satisfying check payoff at the point of interaction.
   perf rule: never animate box-shadow/filter in keyframes — an expanding
   box-shadow ring forces a repaint every frame. The halo is now a ::after
   ring animating transform+opacity only (fully GPU-composited); the checkbox
   itself only ever animates transform too. Same bounce timing, same look. */
@keyframes sbCheckPop {
  0% {
    transform: scale(1);
  }
  10% {
    transform: scale(0.72);
  }
  24% {
    transform: scale(1.28);
  }
  44% {
    transform: scale(0.96);
  }
  66% {
    transform: scale(1.08);
  }
  100% {
    transform: scale(1);
  }
}
.sb-check-pop {
  animation: sbCheckPop 2000ms var(--ease-out);
}
@keyframes sbCheckRing {
  0% {
    transform: scale(0.55);
    opacity: 0.9;
  }
  24% {
    transform: scale(1.4);
    opacity: 0.7;
  }
  100% {
    transform: scale(1.4);
    opacity: 0;
  }
}
.sb-check-pop::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--accent-ring);
  pointer-events: none;
  animation: sbCheckRing 620ms var(--ease-out);
}

@keyframes sbCheckDraw {
  from {
    stroke-dashoffset: 17;
  }
  to {
    stroke-dashoffset: 0;
  }
}
.sb-check-draw {
  stroke-dasharray: 17;
  animation: sbCheckDraw 360ms 120ms var(--ease-out) both;
}

.sb-done-burst {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 6;
  isolation: isolate;
}
.sb-done-sprite-viewport {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 72px;
  height: 72px;
  margin: -36px 0 0 -36px;
  overflow: hidden;
  pointer-events: none;
  z-index: 4;
  filter: drop-shadow(0 8px 18px rgba(42, 157, 143, 0.28)) drop-shadow(0 0 18px var(--accent-ring));
  animation: sbDoneSpriteLift 2000ms var(--ease-out) both;
}
.sb-done-sprite-sheet {
  display: block;
  width: 1152px;
  height: 72px;
  transform: translate3d(0, 0, 0);
  animation: sbDoneSprite 2000ms steps(15) both;
}
@keyframes sbDoneSprite {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-1080px, 0, 0);
  }
}
@keyframes sbDoneSpriteLift {
  0% {
    opacity: 0;
    transform: scale(0.62) rotate(-10deg);
  }
  14% {
    opacity: 1;
    transform: scale(1.08) rotate(3deg);
  }
  72% {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }
  100% {
    opacity: 0;
    transform: scale(0.92) translateY(-8px);
  }
}
.sb-done-flash {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 94px;
  height: 94px;
  margin: -47px 0 0 -47px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
  background: radial-gradient(circle, rgba(255, 214, 107, 0.85) 0%, rgba(42, 157, 143, 0.2) 44%, transparent 72%);
  animation: sbDoneFlash 2000ms var(--ease-out) both;
}
@keyframes sbDoneFlash {
  0% {
    opacity: 0;
    transform: scale(0.22);
  }
  18% {
    opacity: 0.95;
    transform: scale(0.92);
  }
  100% {
    opacity: 0;
    transform: scale(2.15);
  }
}
.sb-done-glow-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 32px;
  height: 32px;
  margin: -16px 0 0 -16px;
  border-radius: 50%;
  border: 2px solid var(--saffron);
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.28),
    0 0 28px var(--accent-ring);
  pointer-events: none;
  z-index: 2;
  animation: sbDoneGlowRing 2000ms var(--ease-out) both;
}
@keyframes sbDoneGlowRing {
  0% {
    transform: scale(0.45);
    opacity: 0;
  }
  18% {
    opacity: 0.9;
  }
  100% {
    transform: scale(3.2);
    opacity: 0;
  }
}
.sb-done-spark {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7px;
  height: 4px;
  margin: -2px 0 0 -3.5px;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  z-index: 3;
  box-shadow: 0 0 10px currentColor;
  animation: sbDoneSpark 1840ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes sbDoneSpark {
  0% {
    transform: translate(0, 0) scale(0.1) rotate(0deg);
    opacity: 0;
  }
  14% {
    opacity: 1;
  }
  62% {
    transform: translate(var(--dx), var(--dy)) scale(1) rotate(var(--rot));
    opacity: 1;
  }
  100% {
    transform: translate(calc(var(--dx) * 1.22), calc(var(--dy) * 1.22 + 12px)) scale(0.18)
      rotate(calc(var(--rot) + 120deg));
    opacity: 0;
  }
}
.sb-done-orb {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  z-index: 5;
  box-shadow: 0 0 16px currentColor;
  animation: sbDoneOrb 840ms var(--ease-out) both;
}
@keyframes sbDoneOrb {
  0% {
    transform: translate(0, 0) scale(0.25);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  66% {
    transform: translate(var(--dx), var(--dy)) scale(1);
    opacity: 1;
  }
  100% {
    transform: translate(calc(var(--dx) * 0.5), calc(var(--dy) * 0.5 - 8px)) scale(0.2);
    opacity: 0;
  }
}

/* =============================================================
   Pulse orb — «نبض» (Orb.jsx: LogiOrb + HomeOrbHero) and the
   voice listening stage built on it (VoiceComposer).

   Motion rules honored here: transform/opacity-only keyframes, no
   filter/box-shadow animation, no CSS blur (gradients are pre-soft),
   and Orb.jsx pauses everything offscreen via .sb-orb-paused.
   ============================================================= */
.sb-orb {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  /* glass + chrome of the sphere/dish (theme blocks override) */
  --orb-glass-1: rgba(255, 255, 255, 0.95);
  --orb-glass-2: rgba(255, 255, 255, 0.6);
  --orb-glass-3: rgba(203, 218, 215, 0.72);
  --orb-rim: rgba(255, 255, 255, 0.85);
  --orb-fresnel: rgba(255, 255, 255, 0.68);
  --orb-ribbon: rgba(255, 252, 246, 0.92);
  --orb-glassband: rgba(255, 255, 255, 0.55);
  --orb-dish: rgba(25, 28, 29, 0.05);
  --orb-ringline: rgba(25, 28, 29, 0.09);
  --orb-seat: rgba(25, 28, 29, 0.24);
  --orb-edge: rgba(25, 28, 29, 0.07);
  --orb-sat-1: #ffffff;
  --orb-sat-2: #e2e9e7;
  --orb-progress: var(--firoozeh);
  --orb-copy: rgba(10, 67, 57, 0.98);
  --orb-copy-soft: rgba(10, 67, 57, 0.84);
  --orb-copy-well: rgba(255, 255, 255, 0.52);
  --orb-copy-shadow: rgba(255, 255, 255, 0.58);
  --orb-gold-1: var(--reward-gold-soft);
  --orb-gold-2: var(--reward-gold);
  --orb-gyro-x: 0%;
  --orb-gyro-y: 0%;
}

/* mood palettes — which color family leads the fluid. Each mood carries a
   deeper saturated core (the marble's heart) plus a --orb-deep vein tone one
   step darker, so the satin folds have real depth, not just a tinted glow. */
.sb-orb-calm {
  --orb-a: rgba(42, 157, 143, 0.75);
  --orb-b: rgba(116, 212, 189, 0.6);
  --orb-c: rgba(255, 172, 31, 0.26);
  --orb-core: rgba(11, 121, 106, 0.68);
  --orb-deep: rgba(6, 96, 83, 0.7);
}
.sb-orb-warm {
  --orb-a: rgba(255, 172, 31, 0.7);
  --orb-b: rgba(255, 138, 101, 0.6);
  --orb-c: rgba(42, 157, 143, 0.28);
  --orb-core: rgba(222, 132, 8, 0.62);
  --orb-deep: rgba(186, 102, 4, 0.68);
}
.sb-orb-low {
  --orb-a: rgba(233, 86, 110, 0.7);
  --orb-b: rgba(255, 143, 131, 0.6);
  --orb-c: rgba(255, 183, 77, 0.3);
  --orb-core: rgba(203, 43, 84, 0.66);
  --orb-deep: rgba(176, 22, 63, 0.7);
}

.sb-orb-sphere {
  position: relative;
  isolation: isolate;
  transform-origin: 50% 56%;
  border-radius: 50%;
  overflow: hidden;
  background: radial-gradient(
    105% 105% at 32% 26%,
    var(--orb-glass-1) 0%,
    var(--orb-glass-2) 46%,
    var(--orb-glass-3) 100%
  );
  /* static form shading (never animated) — gives the flat page its one 3D
     object; the outer hairline defines the silhouette on light paper */
  box-shadow:
    0 0 0 1px var(--orb-edge),
    0 16px 32px -16px rgba(25, 28, 29, 0.2),
    inset 0 -18px 30px rgba(25, 28, 29, 0.11),
    inset 0 10px 20px rgba(255, 255, 255, 0.55);
  /* alive: a very slow, very small breath */
  animation: sbOrbBreathe 7.5s var(--ease-io) infinite;
  will-change: transform;
  z-index: 2;
}

.sb-orb-sphere i {
  position: absolute;
  border-radius: 50%;
  inset: 0;
}

/* The sensor moves only this inner carrier. The glass and dish stay fixed, so
   the few-percent offset reads as liquid sliding inside a physical shell. */
.sb-orb-liquid {
  position: absolute;
  inset: -4%;
  border-radius: 50%;
  z-index: 0;
  transform: translate3d(var(--orb-gyro-x), var(--orb-gyro-y), 0);
  transition: transform 100ms linear;
  will-change: transform;
}

/* the liquid-marble stack — folded satin, not tinted vapor. Every band is an
   ELONGATED gradient (a ribbon of color, never a dot) riding a layer that
   rolls a full 0→360 turn per cycle (first/last keyframes match, so the roll
   never reverses — silky continuous flow, like paint folding in a sphere).
   Layers run on co-prime periods over a slow carrier, so the composite never
   visibly repeats. */
.sb-orb-fluid {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  animation: sbOrbSpin 34s linear infinite;
  will-change: transform;
}
.sb-orb-blob {
  inset: -12%;
  will-change: transform;
}
.sb-orb-b1 {
  /* the leading satin mass — a deep-hearted main band + a plumper echo fold
     beside it (bands stay ≥24% tall so they read as folds, not scratches) */
  background:
    radial-gradient(54% 34% at 41% 37%, var(--orb-deep) 0%, var(--orb-a) 44%, rgba(255, 255, 255, 0) 74%),
    radial-gradient(44% 24% at 62% 26%, var(--orb-a) 0%, rgba(255, 255, 255, 0) 70%);
  /* negative delays de-phase the layers so no frame ever shows every band
     lying horizontal (the synchronized-start "sediment" look) */
  animation: sbOrbDrift1 11s linear -3.2s infinite;
}
.sb-orb-b2 {
  /* counter-rolling secondary fold */
  background:
    radial-gradient(50% 28% at 62% 58%, var(--orb-b) 0%, rgba(255, 255, 255, 0) 72%),
    radial-gradient(40% 20% at 40% 66%, var(--orb-b) 0%, rgba(255, 255, 255, 0) 68%);
  animation: sbOrbDrift2 15s linear -7.4s infinite;
}
.sb-orb-b3 {
  /* wide low blush — keeps the bottom of the sphere lighter than the mass */
  background: radial-gradient(58% 30% at 50% 72%, var(--orb-c) 0%, rgba(255, 255, 255, 0) 74%);
  animation: sbOrbDrift3 19s linear -6.1s infinite;
}
/* the deep saturated heart of the marble, with a darker streak folded in */
.sb-orb-core {
  inset: 2%;
  background:
    radial-gradient(42% 34% at 45% 42%, var(--orb-core) 0%, rgba(255, 255, 255, 0) 68%),
    radial-gradient(50% 16% at 52% 33%, var(--orb-deep) 0%, rgba(255, 255, 255, 0) 64%);
  animation: sbOrbCore 8.5s var(--ease-io) infinite;
  will-change: transform;
}
/* marble veins — soft darker creases along the fold edges. Deliberately
   medium-thin and low-alpha: skinnier bands read as straight scratches */
.sb-orb-vein {
  inset: -10%;
  background:
    radial-gradient(38% 9% at 46% 38%, var(--orb-deep) 0%, rgba(255, 255, 255, 0) 74%),
    radial-gradient(30% 7% at 58% 53%, var(--orb-deep) 0%, rgba(255, 255, 255, 0) 70%);
  opacity: 0.55;
  animation: sbOrbVein 12.5s linear -5.2s infinite;
  will-change: transform;
}
/* the cream ribbon that folds through the color — counter-rotating against
   the fluid carrier; this interruption is what makes it read as marble */
.sb-orb-ribbon {
  inset: -14%;
  background:
    radial-gradient(50% 21% at 50% 37%, var(--orb-ribbon) 0%, rgba(255, 255, 255, 0) 70%),
    radial-gradient(38% 15% at 42% 57%, var(--orb-ribbon) 0%, rgba(255, 255, 255, 0) 66%);
  animation: sbOrbRibbon 18s linear -9.3s infinite;
  will-change: transform;
}
/* a soft cream sheen band sweeping against the flow — the satin highlight
   that makes the folds look lit rather than printed */
.sb-orb-sheet {
  inset: -8%;
  background: radial-gradient(60% 26% at 50% 30%, var(--orb-ribbon) 0%, rgba(255, 255, 255, 0) 72%);
  opacity: 0.55;
  animation: sbOrbSpin 24s linear infinite reverse;
  will-change: transform;
}
.sb-orb-swirl {
  inset: -14%;
  background: conic-gradient(
    from 20deg,
    rgba(255, 255, 255, 0) 0deg,
    var(--orb-a) 82deg,
    rgba(255, 255, 255, 0) 168deg,
    var(--orb-b) 262deg,
    rgba(255, 255, 255, 0) 338deg
  );
  opacity: 0.28;
  animation: sbOrbSpin 26s linear infinite;
  will-change: transform;
}
/* A low, curved tide makes the lower mass read as liquid rather than a
   revolving gradient. It moves only with transform/opacity, so the extra
   material stays cheap on mobile. */
.sb-orb-tide {
  inset: 37% -24% -27%;
  border-radius: 48% 52% 44% 56%;
  background:
    radial-gradient(96% 38% at 50% 7%, rgba(255, 255, 255, 0.62) 0%, rgba(255, 255, 255, 0) 72%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.05), var(--orb-b));
  opacity: 0.3;
  transform-origin: 50% 62%;
  animation: sbOrbTide 9.8s var(--ease-io) infinite;
  will-change: transform, opacity;
}
/* static glasswork (never animated) */
.sb-orb-glass {
  /* glass thickness — a milky band just inside the silhouette, painted OVER
     the liquid so the fluid reads as sitting inside a wall of glass rather
     than inked to the edge */
  z-index: 1;
  background: radial-gradient(
    closest-side,
    rgba(255, 255, 255, 0) 72%,
    var(--orb-glassband) 88%,
    rgba(255, 255, 255, 0.02) 98%
  );
}
.sb-orb-fresnel,
.sb-orb-shine,
.sb-orb-spec,
.sb-orb-rim {
  z-index: 2;
  pointer-events: none;
}
.sb-orb-fresnel {
  /* edge light where the glass curves away */
  background: radial-gradient(
    closest-side,
    rgba(255, 255, 255, 0) 66%,
    var(--orb-fresnel) 88%,
    rgba(255, 255, 255, 0.05) 99%
  );
  opacity: 0.9;
}
.sb-orb-shine {
  background: radial-gradient(50% 42% at 33% 22%, rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0) 100%);
}
.sb-orb-spec {
  /* the crisp specular dot that sells the glossy surface, plus a faint
     counter-glint low on the opposite shoulder */
  background:
    radial-gradient(
      13% 9% at 29% 16%,
      rgba(255, 255, 255, 0.98) 0%,
      rgba(255, 255, 255, 0.5) 46%,
      rgba(255, 255, 255, 0) 100%
    ),
    radial-gradient(8% 5.5% at 69% 80%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 100%);
}
.sb-orb-rim {
  box-shadow:
    inset 0 0 0 1.25px var(--orb-rim),
    inset 0 3px 6px rgba(255, 255, 255, 0.65),
    inset 0 -4px 10px rgba(25, 28, 29, 0.06);
  opacity: 0.7;
}

/* the dish — soft disc + faint concentric rings the sphere rests in */
.sb-orb-dish {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    var(--orb-dish) 55%,
    rgba(25, 28, 29, 0.018) 76%,
    rgba(255, 255, 255, 0) 100%
  );
}
.sb-orb-ring {
  position: absolute;
  border-radius: 50%;
  border: 1px solid var(--orb-ringline);
  pointer-events: none;
}
.sb-orb-ring-0 {
  inset: 0;
  opacity: 0.45;
}
.sb-orb-ring-1 {
  inset: 5.5%;
}
.sb-orb-ring-2 {
  inset: 13%;
  opacity: 0.6;
}
/* two interleaved ripples (phase-offset via a negative delay) keep the dish
   quietly alive without ever pulsing hard */
.sb-orb-ripple {
  position: absolute;
  inset: 9%;
  border-radius: 50%;
  border: 1.5px solid var(--orb-a);
  opacity: 0;
  transform: scale(0.66);
  animation: sbOrbRipple 6.2s var(--ease-out) infinite;
  will-change: transform, opacity;
  pointer-events: none;
}
.sb-orb-ripple-2 {
  inset: 6.5%;
  animation-delay: -3.1s;
}
/* contact shadow — seats the sphere in the dish (static gradient, half of it
   hides behind the sphere so only the grounding crescent shows) */
.sb-orb-seat {
  position: absolute;
  left: 50%;
  bottom: 12.2%;
  width: 56%;
  height: 8.5%;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, var(--orb-seat) 0%, rgba(0, 0, 0, 0) 72%);
  z-index: 1;
  pointer-events: none;
}

/* satellite beads riding the rings — o1 crosses in FRONT of the sphere, o2
   passes BEHIND it (occlusion), both on slightly tilted (elliptical) orbits,
   and each bead swells/shrinks in sync with its orbit (near/far). */
.sb-orb-sat-orbit {
  position: absolute;
  inset: 4%;
  border-radius: 50%;
  animation: sbOrbOrbitCw 26s linear infinite;
  will-change: transform;
  pointer-events: none;
  z-index: 3;
}
.sb-orb-sat-o2 {
  inset: 13%;
  animation: sbOrbOrbitCcw 40s linear infinite;
  z-index: 1;
}
.sb-orb-sat {
  /* --orb-sat-size blends a fixed floor with the orb's scale (--orb-unit,
     set inline by Orb.jsx) so hero beads read like the pearls in the dish
     while the mini orb keeps a visible dot */
  --orb-sat-size: calc(6px + 10px * var(--orb-unit, 1));
  position: absolute;
  top: calc(var(--orb-sat-size) / -2.2);
  left: 50%;
  width: var(--orb-sat-size);
  height: var(--orb-sat-size);
  margin-left: calc(var(--orb-sat-size) / -2);
  border-radius: 50%;
  background: radial-gradient(120% 120% at 32% 26%, #ffffff 0%, var(--orb-sat-1) 55%, var(--orb-sat-2) 100%);
  box-shadow:
    inset 0 -2px 3px rgba(25, 28, 29, 0.16),
    inset 0 1px 1.5px rgba(255, 255, 255, 0.95),
    0 3px 6px -2px rgba(25, 28, 29, 0.28),
    0 0 0 1px rgba(25, 28, 29, 0.05);
  animation: sbOrbSatNear 26s var(--ease-io) infinite;
  will-change: transform;
}
.sb-orb-sat-o2 .sb-orb-sat {
  animation-duration: 40s;
}
.sb-orb-sat-sm {
  --orb-sat-size: calc(4px + 7px * var(--orb-unit, 1));
}

.sb-orb-center {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  z-index: 1;
  pointer-events: none;
}

/* Interaction copy lives between the moving liquid and the glass highlights.
   The soft radial wash is refraction, not a chip: there is no border, solid
   fill or pill silhouette sitting on top of the sphere. */
.sb-orb-inside-prompt {
  position: relative;
  width: min(70%, 108px);
  height: 64px;
  display: grid;
  place-items: center;
  direction: rtl;
  color: var(--orb-copy);
  text-align: center;
  text-shadow: 0 1px 0 var(--orb-copy-shadow);
  -webkit-font-smoothing: antialiased;
}
.sb-orb-inside-prompt::before,
.sb-orb-inside-thinking::before {
  content: '';
  position: absolute;
  inset: -13px -10px;
  border-radius: 50%;
  background: radial-gradient(ellipse at center, var(--orb-copy-well) 0%, rgba(255, 255, 255, 0) 72%);
  pointer-events: none;
}
.sb-orb-inside-line {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  white-space: nowrap;
  opacity: 0;
  animation: sbOrbInsidePrompt 8s var(--ease-io) infinite;
  will-change: transform, opacity;
}
.sb-orb-inside-hold {
  animation-delay: -4s;
}
.sb-orb-inside-line strong {
  font: 850 16.5px/1.2 var(--font-sans);
}
.sb-orb-inside-line small {
  color: var(--orb-copy-soft);
  font: 700 11px/1.35 var(--font-sans);
}
.sb-orb-inside-thinking {
  position: relative;
  width: min(72%, 112px);
  min-height: 54px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  direction: rtl;
  color: var(--orb-copy);
  font: 750 10.5px/1.5 var(--font-sans);
  white-space: nowrap;
  text-shadow: 0 1px 0 var(--orb-copy-shadow);
  -webkit-font-smoothing: antialiased;
}
.sb-orb-inside-thinking > * {
  position: relative;
  z-index: 1;
}

/* listening — the fluid wakes up: everything circulates faster AND wetter.
   The whole liquid body sloshes (sbOrbLiveSlosh takes over the gyro carrier
   while recording), the sphere squashes/stretches like a held drop, and the
   blobs swell so the colors fold into each other instead of orbiting apart. */
.sb-orb-live .sb-orb-fluid {
  animation-duration: 20s;
}
.sb-orb-live .sb-orb-blob {
  inset: -18%;
}
.sb-orb-live .sb-orb-b1 {
  animation-duration: 5.2s;
}
.sb-orb-live .sb-orb-b2 {
  animation-duration: 6.6s;
}
.sb-orb-live .sb-orb-b3 {
  animation-duration: 8.2s;
}
.sb-orb-live .sb-orb-core {
  animation-duration: 4.4s;
}
.sb-orb-live .sb-orb-vein {
  animation-duration: 6s;
}
.sb-orb-live .sb-orb-ribbon {
  animation-duration: 9s;
}
.sb-orb-live .sb-orb-sheet {
  animation-duration: 11s;
}
.sb-orb-live .sb-orb-swirl {
  animation-duration: 10s;
  opacity: 0.38;
}
.sb-orb-live .sb-orb-ripple {
  animation-duration: 3.2s;
}
.sb-orb-live .sb-orb-ripple-2 {
  animation-delay: -1.6s;
}
.sb-orb-live .sb-orb-liquid {
  animation: sbOrbLiveSlosh 4.8s var(--ease-io) infinite;
}
.sb-orb-live .sb-orb-sphere {
  animation-name: sbOrbLiveWobble;
  animation-duration: 2.7s;
}

/* AI thinking: reuse the same layers with a denser ribbon and quicker internal
   circulation. Nothing new loops; this is a visual state of the existing orb. */
.sb-orb-thinking .sb-orb-fluid {
  animation-duration: 9s;
}
.sb-orb-thinking .sb-orb-b1 {
  animation-duration: 4.8s;
}
.sb-orb-thinking .sb-orb-b2 {
  animation-duration: 6.2s;
}
.sb-orb-thinking .sb-orb-b3 {
  animation-duration: 7.4s;
}
.sb-orb-thinking .sb-orb-core {
  animation-duration: 3.8s;
}
.sb-orb-thinking .sb-orb-vein {
  animation-duration: 5.5s;
}
.sb-orb-thinking .sb-orb-sheet {
  animation-duration: 9s;
}
.sb-orb-thinking .sb-orb-ribbon {
  animation-name: sbOrbThinkRibbon;
  animation-duration: 5.4s;
}
.sb-orb-thinking .sb-orb-swirl {
  animation-duration: 6.5s;
  opacity: 0.4;
}
.sb-orb-thinking .sb-orb-ripple {
  animation-duration: 2.7s;
}
.sb-orb-thinking .sb-orb-ripple-2 {
  animation-delay: -1.35s;
}
.sb-orb-thinking .sb-orb-sphere {
  animation-name: sbOrbThinkingPulse;
  animation-duration: 2.8s;
}

/* Night sleep is deliberately quieter, not frozen. Static opacity plus longer
   periods keep the object present while lowering its visual energy. */
.sb-orb-sleeping .sb-orb-sphere {
  opacity: 0.76;
  animation-duration: 14s;
}
.sb-orb-sleeping .sb-orb-liquid {
  opacity: 0.74;
}
.sb-orb-sleeping .sb-orb-fluid {
  animation-duration: 82s;
}
.sb-orb-sleeping .sb-orb-b1 {
  animation-duration: 24s;
}
.sb-orb-sleeping .sb-orb-b2 {
  animation-duration: 31s;
}
.sb-orb-sleeping .sb-orb-b3 {
  animation-duration: 39s;
}
.sb-orb-sleeping .sb-orb-core {
  animation-duration: 20s;
}
.sb-orb-sleeping .sb-orb-vein {
  animation-duration: 30s;
}
.sb-orb-sleeping .sb-orb-ribbon {
  animation-duration: 48s;
}
.sb-orb-sleeping .sb-orb-sheet {
  animation-duration: 56s;
}
.sb-orb-sleeping .sb-orb-swirl {
  animation-duration: 58s;
  opacity: 0.16;
}
.sb-orb-sleeping .sb-orb-ripple {
  animation-duration: 12s;
}
.sb-orb-sleeping .sb-orb-sat-o1 {
  animation-duration: 62s;
  opacity: 0.68;
}
.sb-orb-sleeping .sb-orb-sat-o2 {
  animation-duration: 88s;
  opacity: 0.58;
}

/* Completion joy is finite: two outward rings, one liquid flash and exactly
   one fast satellite revolution. The event key remounts the finite pieces. */
.sb-orb-joy {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}
.sb-orb-joy-ring {
  position: absolute;
  inset: 18%;
  border-radius: 50%;
  border: 2px solid var(--firoozeh);
  opacity: 0;
  animation: sbOrbJoyRing 900ms var(--ease-out) both;
  will-change: transform, opacity;
}
.sb-orb-joy-ring-2 {
  inset: 25%;
  border-color: var(--saffron);
  animation-delay: 90ms;
}
.sb-orb-joy-ring-3 {
  inset: 30%;
  border-width: 2px;
  animation-delay: 180ms;
}
.sb-orb-joy-flash {
  background: radial-gradient(circle at 48% 52%, var(--reward-gold-soft) 0%, var(--firoozeh) 42%, transparent 72%);
  opacity: 0;
  animation: sbOrbJoyFlash 720ms var(--ease-out) both;
  will-change: transform, opacity;
}
.sb-orb-celebrating .sb-orb-sat-o1,
.sb-orb-celebrating .sb-orb-sat-o2 {
  animation-duration: 760ms;
  animation-iteration-count: 1;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}
.sb-orb-celebrating .sb-orb-sat {
  animation-duration: 760ms;
  animation-iteration-count: 1;
}
.sb-orb-celebrating .sb-orb-sphere {
  animation: sbOrbJoySphere 820ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.sb-orb-streak-milestone .sb-orb-sat {
  background: radial-gradient(120% 120% at 32% 26%, var(--surface) 0%, var(--orb-gold-1) 48%, var(--orb-gold-2) 100%);
}

/* ── Transient personality gestures (window.logiOrbGesture) ─────────────────
   FINITE one-shot animations, transform-only, class removed after ~1.4s —
   fully inside the orb motion exception rules (no filter/box-shadow, no
   infinite loops; reduced-motion kills them below). */
@keyframes sbOrbGestureExcite {
  0% { transform: scale(1); }
  30% { transform: scale(1.08, 0.94); }
  55% { transform: scale(0.96, 1.05); }
  78% { transform: scale(1.03, 0.98); }
  100% { transform: scale(1); }
}
.sb-orb-g-excite .sb-orb-sphere {
  animation: sbOrbGestureExcite 700ms cubic-bezier(0.34, 1.56, 0.64, 1) 1;
}
@keyframes sbOrbGestureCoin {
  0%, 100% { transform: rotate(0deg) scale(1); }
  25% { transform: rotate(7deg) scale(1.04); }
  55% { transform: rotate(-6deg) scale(1.02); }
  80% { transform: rotate(3deg) scale(1.01); }
}
.sb-orb-g-coin .sb-orb-sphere {
  animation: sbOrbGestureCoin 800ms ease-in-out 1;
}
@keyframes sbOrbGestureAha {
  0%, 100% { transform: scale(1); }
  30% { transform: scale(1.06); }
  52% { transform: scale(1); }
  72% { transform: scale(1.045); }
}
.sb-orb-g-aha .sb-orb-sphere {
  animation: sbOrbGestureAha 900ms ease-in-out 1;
}
@keyframes sbOrbGestureCelebrate {
  0% { transform: translateY(0) scale(1); }
  25% { transform: translateY(-6%) scale(1.05); }
  45% { transform: translateY(0) scale(0.97, 1.02); }
  64% { transform: translateY(-3%) scale(1.03); }
  100% { transform: translateY(0) scale(1); }
}
.sb-orb-g-celebrate .sb-orb-sphere {
  animation: sbOrbGestureCelebrate 1100ms cubic-bezier(0.34, 1.56, 0.64, 1) 1;
}
@media (prefers-reduced-motion: reduce) {
  .sb-orb-g-excite .sb-orb-sphere,
  .sb-orb-g-coin .sb-orb-sphere,
  .sb-orb-g-aha .sb-orb-sphere,
  .sb-orb-g-celebrate .sb-orb-sphere {
    animation: none !important;
  }
}

/* offscreen → all orb motion suspended (Orb.jsx IntersectionObserver) */
.sb-orb-paused * {
  animation-play-state: paused !important;
}

/* ── dark & noir chrome ── */
[data-theme='dark'] .sb-orb {
  --orb-glass-1: rgba(88, 122, 107, 0.85);
  --orb-glass-2: rgba(40, 66, 55, 0.88);
  --orb-glass-3: rgba(22, 42, 34, 0.95);
  --orb-rim: rgba(255, 255, 255, 0.26);
  --orb-fresnel: rgba(255, 255, 255, 0.2);
  --orb-ribbon: rgba(255, 255, 255, 0.28);
  --orb-glassband: rgba(255, 255, 255, 0.09);
  --orb-dish: rgba(237, 243, 238, 0.05);
  --orb-ringline: rgba(237, 243, 238, 0.1);
  --orb-seat: rgba(0, 0, 0, 0.6);
  --orb-edge: rgba(0, 0, 0, 0.3);
  --orb-sat-1: #cfe0d6;
  --orb-sat-2: #86a092;
  --orb-copy: rgba(244, 250, 247, 0.98);
  --orb-copy-soft: rgba(229, 240, 234, 0.88);
  --orb-copy-well: rgba(4, 22, 16, 0.38);
  --orb-copy-shadow: rgba(0, 0, 0, 0.34);
}
[data-theme='dark'] .sb-orb-spec {
  opacity: 0.72;
}
[data-theme='dark'] .sb-orb-sphere {
  box-shadow:
    inset 0 -16px 26px rgba(0, 0, 0, 0.38),
    inset 0 10px 20px rgba(255, 255, 255, 0.09);
}
[data-theme='dark'] .sb-orb-shine {
  opacity: 0.34;
}
[data-theme='dark'] .sb-orb-calm {
  --orb-a: rgba(79, 194, 168, 0.8);
  --orb-b: rgba(143, 220, 201, 0.58);
  --orb-c: rgba(255, 172, 31, 0.22);
  --orb-core: rgba(34, 196, 164, 0.66);
  --orb-deep: rgba(19, 158, 130, 0.74);
}
[data-theme='dark'] .sb-orb-warm {
  --orb-a: rgba(255, 178, 66, 0.72);
  --orb-b: rgba(255, 150, 115, 0.58);
  --orb-c: rgba(79, 194, 168, 0.26);
  --orb-core: rgba(255, 158, 34, 0.62);
  --orb-deep: rgba(232, 128, 12, 0.68);
}
[data-theme='dark'] .sb-orb-low {
  --orb-a: rgba(240, 108, 128, 0.72);
  --orb-b: rgba(255, 152, 140, 0.58);
  --orb-c: rgba(255, 190, 92, 0.24);
  --orb-core: rgba(255, 92, 124, 0.64);
  --orb-deep: rgba(230, 58, 96, 0.7);
}
/* noir: the fluid goes monochrome (accent stays reserved for productivity) */
[data-theme='noir'] .sb-orb {
  --orb-glass-1: rgba(64, 84, 74, 0.8);
  --orb-glass-2: rgba(28, 46, 38, 0.88);
  --orb-glass-3: rgba(14, 28, 22, 0.95);
  --orb-rim: rgba(255, 255, 255, 0.22);
  --orb-fresnel: rgba(255, 255, 255, 0.16);
  --orb-ribbon: rgba(255, 255, 255, 0.22);
  --orb-glassband: rgba(255, 255, 255, 0.07);
  --orb-dish: rgba(238, 243, 239, 0.05);
  --orb-ringline: rgba(238, 243, 239, 0.1);
  --orb-seat: rgba(0, 0, 0, 0.6);
  --orb-edge: rgba(0, 0, 0, 0.32);
  --orb-sat-1: #d8ddd9;
  --orb-sat-2: #9aa39d;
  --orb-a: rgba(238, 243, 239, 0.4);
  --orb-b: rgba(198, 210, 203, 0.34);
  --orb-c: rgba(138, 152, 143, 0.3);
  --orb-core: rgba(238, 243, 239, 0.32);
  --orb-deep: rgba(238, 243, 239, 0.34);
  --orb-copy: rgba(242, 247, 243, 0.98);
  --orb-copy-soft: rgba(209, 220, 213, 0.88);
  --orb-copy-well: rgba(3, 18, 12, 0.38);
  --orb-copy-shadow: rgba(0, 0, 0, 0.36);
}
[data-theme='noir'] .sb-orb-sphere {
  box-shadow:
    inset 0 -16px 26px rgba(0, 0, 0, 0.4),
    inset 0 10px 20px rgba(255, 255, 255, 0.08);
}

/* ── HomeOrbHero layout ── */
.sb-orb-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  direction: rtl;
  margin: 2px 0 20px;
}
.sb-orb-stage {
  position: relative;
  width: 100%;
  max-width: 440px;
  display: grid;
  place-items: center;
  padding: 2px 0;
}
.sb-orb-press {
  position: relative;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  /* Keep the Home scroll path available when a gesture starts on the dish;
     horizontal/circular drags remain ours, vertical pans remain the page's. */
  touch-action: pan-y pinch-zoom;
  -webkit-tap-highlight-color: transparent;
  /* springy release — the orb answers the touch */
  transition: transform 0.26s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.sb-orb-press:active {
  transform: scale(0.94);
  transition-duration: 0.1s;
}
/* A swipe that begins on the liquid core belongs to the control (photo/cycle).
   The surrounding dish keeps pan-y, so users can still start a page scroll on
   most of the hero instead of getting trapped by the large control. */
.sb-orb-press .sb-orb-sphere {
  touch-action: none;
}
.sb-orb-press:focus-visible,
.sb-mini-orb-button:focus-visible {
  outline: 2px solid var(--firoozeh);
  outline-offset: 4px;
}
.sb-orb-hero-thinking .sb-orb-stat {
  opacity: 0.42 !important;
}
.sb-orb-hero-thinking .sb-orb-hint {
  color: var(--firoozeh-deep);
  font-weight: 700;
}
.sb-orb-hero-sleeping .sb-orb-stat {
  opacity: 0.72;
}
.sb-orb-hero-thinking.sb-orb-hero-sleeping .sb-orb-stat {
  opacity: 0.42 !important;
}

/* One compact primitive is reused by the mobile voice FAB and desktop top bar. */
.sb-mini-orb-button {
  position: relative;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #ffffff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform 150ms var(--ease-out),
    opacity 150ms var(--ease-out);
}
.sb-mini-orb-button:active {
  transform: scale(0.94);
}
.sb-mini-orb-button:disabled {
  cursor: not-allowed;
}
.sb-mini-orb-button .sb-orb-sat {
  top: -2px;
  width: 4px;
  height: 4px;
  margin-left: -2px;
  box-shadow:
    inset 0 -1px 1px rgba(25, 28, 29, 0.12),
    0 0 0 1px rgba(25, 28, 29, 0.05);
}
.sb-mini-orb-button .sb-orb-sat-sm {
  top: -1.5px;
  width: 3px;
  height: 3px;
  margin-left: -1.5px;
}
.sb-mini-orb-icon {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 48%;
  height: 48%;
  min-width: 16px;
  min-height: 16px;
  max-width: 27px;
  max-height: 27px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(10, 24, 20, 0.32);
  color: #ffffff;
  z-index: 8;
  pointer-events: none;
}
.sb-topbar-assistant-orb {
  display: none;
}
@media (min-width: 768px) {
  .sb-topbar-assistant-orb {
    display: grid;
  }
}
.sb-orb-stat {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-width: 24%;
  animation: sbOrbStatIn 640ms var(--ease-out) both;
  pointer-events: none;
}
.sb-orb-stat-start {
  inset-inline-start: 2px;
  top: 21%;
  align-items: flex-start;
  text-align: start;
}
.sb-orb-stat-end {
  inset-inline-end: 2px;
  top: 47%;
  align-items: flex-end;
  text-align: end;
  animation-delay: 110ms;
}
.sb-orb-stat-low {
  inset-inline-start: 6px;
  bottom: 9%;
  align-items: flex-start;
  text-align: start;
  animation-delay: 200ms;
}
.sb-orb-stat-val {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 800 15.5px/1 var(--font-sans);
  white-space: nowrap;
}
.sb-orb-stat-ico {
  display: inline-grid;
  place-items: center;
  opacity: 0.9;
}
.sb-orb-stat-lbl {
  font: 400 10.5px/1.4 var(--font-sans);
  color: var(--ink-3);
}
.sb-orb-hint {
  min-height: 20px;
  margin-top: 6px;
  padding: 0 20px;
  text-align: center;
  font: 500 11.5px/1.7 var(--font-sans);
  color: var(--ink-3);
  max-width: 430px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  animation: sbOrbStatIn 640ms var(--ease-out) both;
}

/* ── Assistant reply at the orb ──
   A capture that settles while the user is on Home surfaces the assistant's
   answer right under the orb, in the hint line's slot (the bottom CaptureBar
   only covers the other tabs). No card, no border: the words float on the
   paper exactly like the hint line does, so the answer reads as the orb
   speaking — actions are the same quiet pills as the orb's mic chip. The
   entry animation is one-shot transform/opacity. */
.sb-orb-reply {
  width: calc(100% - 24px);
  max-width: 430px;
  margin: 6px auto 0;
  padding: 0 6px;
  background: none;
  border: none;
  direction: rtl;
  text-align: center;
  animation: sbOrbStatIn 420ms var(--ease-out) both;
}
.sb-orb-reply-saved {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: 700 12px/1.6 var(--font-sans);
  color: var(--firoozeh-deep);
}
.sb-orb-reply-text {
  font: 500 13px/2 var(--font-sans);
  color: var(--ink-2);
  text-align: center;
  white-space: pre-wrap;
  max-height: 150px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0 10px;
}
.sb-orb-reply-saved + .sb-orb-reply-text {
  margin-top: 4px;
}
.sb-orb-reply-err {
  font: 600 12.5px/1.9 var(--font-sans);
  color: var(--ink);
  text-align: center;
}
.sb-orb-reply-actions {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
}
.sb-orb-reply-btn {
  flex: 0 0 auto;
  padding: 8px 18px;
  border-radius: 999px;
  border: 1px solid var(--c-border-sm);
  background: var(--surface);
  color: var(--ink-2);
  font: 600 12px/1 var(--font-sans);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sb-orb-reply-btn:active {
  opacity: 0.75;
}
.sb-orb-reply-btn-primary {
  border-color: transparent;
  background: var(--firoozeh);
  color: #ffffff;
}
.sb-orb-reply-btn-upgrade {
  border-color: transparent;
  background: var(--saffron);
  color: #3a2600;
}

@keyframes sbOrbStatIn {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}
@keyframes sbOrbThinkRibbon {
  0% {
    transform: rotate(0deg) scale(1.06, 0.72);
  }
  50% {
    transform: rotate(-180deg) scale(1.28, 0.58);
  }
  100% {
    transform: rotate(-360deg) scale(1.06, 0.72);
  }
}
@keyframes sbOrbInsidePrompt {
  0%,
  40% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  48%,
  91% {
    opacity: 0;
    transform: translateY(-4px) scale(0.97);
  }
  92% {
    opacity: 0;
    transform: translateY(4px) scale(0.97);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}
@keyframes sbOrbThinkingPulse {
  0%,
  100% {
    transform: translateZ(0) scale(1);
  }
  50% {
    transform: translateZ(0) scale(1.025, 0.985);
  }
}
@keyframes sbOrbJoyRing {
  0% {
    opacity: 0.86;
    transform: scale(0.62);
  }
  100% {
    opacity: 0;
    transform: scale(1.58);
  }
}
@keyframes sbOrbJoyFlash {
  0% {
    opacity: 0;
    transform: scale(0.72);
  }
  24% {
    opacity: 0.94;
    transform: scale(1.05);
  }
  100% {
    opacity: 0;
    transform: scale(1.28);
  }
}
@keyframes sbOrbJoySphere {
  0%,
  100% {
    transform: translateZ(0) scale(1);
  }
  28% {
    transform: translateZ(0) scale(1.09, 0.94) rotate(2deg);
  }
  58% {
    transform: translateZ(0) scale(0.96, 1.07) rotate(-1.5deg);
  }
  78% {
    transform: translateZ(0) scale(1.025, 0.985);
  }
}
@keyframes sbOrbSpin {
  to {
    transform: rotate(360deg);
  }
}
@keyframes sbOrbRipple {
  0% {
    opacity: 0;
    transform: scale(0.66);
  }
  16% {
    opacity: 0.32;
  }
  70% {
    opacity: 0;
    transform: scale(1.03);
  }
  100% {
    opacity: 0;
    transform: scale(1.03);
  }
}
/* fluid paths — every band rolls a FULL turn per cycle (0% and 100% frames
   are identical mod 360°, so the roll never reverses direction — silky
   continuous flow), while translate/scale stops fold the band as it goes.
   Because rotate is applied first, the constant-length translate orbits with
   the band, which is what makes it read as paint rolling inside a sphere. */
@keyframes sbOrbDrift1 {
  0% {
    transform: rotate(0deg) translate(-9%, 5%) scale(1.05, 0.95);
  }
  24% {
    transform: rotate(88deg) translate(5%, -9%) scale(1.24, 1.06);
  }
  52% {
    transform: rotate(182deg) translate(10%, 3%) scale(0.93, 1.1);
  }
  76% {
    transform: rotate(268deg) translate(-4%, -10%) scale(1.15, 0.98);
  }
  100% {
    transform: rotate(360deg) translate(-9%, 5%) scale(1.05, 0.95);
  }
}
@keyframes sbOrbDrift2 {
  0% {
    transform: rotate(0deg) translate(8%, -6%) scale(0.96, 1.08);
  }
  28% {
    transform: rotate(-96deg) translate(-5%, 9%) scale(1.16, 0.94);
  }
  55% {
    transform: rotate(-186deg) translate(-10%, -3%) scale(0.9, 1.06);
  }
  80% {
    transform: rotate(-274deg) translate(6%, 10%) scale(1.1, 1);
  }
  100% {
    transform: rotate(-360deg) translate(8%, -6%) scale(0.96, 1.08);
  }
}
@keyframes sbOrbDrift3 {
  0% {
    transform: rotate(0deg) translate(-5%, -8%) scale(1.1, 1);
  }
  33% {
    transform: rotate(120deg) translate(9%, 4%) scale(0.92, 1.05);
  }
  66% {
    transform: rotate(240deg) translate(-2%, 9%) scale(1.14, 0.96);
  }
  100% {
    transform: rotate(360deg) translate(-5%, -8%) scale(1.1, 1);
  }
}
/* the marble heart — swells and wanders slightly off-center, seamless loop */
@keyframes sbOrbCore {
  0%,
  100% {
    transform: rotate(-6deg) translate(-4%, 2%) scale(0.94);
  }
  30% {
    transform: rotate(7deg) translate(5%, -3%) scale(1.12);
  }
  62% {
    transform: rotate(-3deg) translate(3%, 5%) scale(1.02);
  }
}
/* the thin veins chase the main mass on a nearby period, slightly offset */
@keyframes sbOrbVein {
  0% {
    transform: rotate(14deg) translate(-6%, 2%) scale(1, 1);
  }
  26% {
    transform: rotate(104deg) translate(4%, -6%) scale(1.12, 0.92);
  }
  54% {
    transform: rotate(196deg) translate(7%, 2%) scale(0.92, 1.06);
  }
  78% {
    transform: rotate(283deg) translate(-3%, -7%) scale(1.06, 0.96);
  }
  100% {
    transform: rotate(374deg) translate(-6%, 2%) scale(1, 1);
  }
}
/* the cream ribbon — counter-rotates against the fluid carrier while folding */
@keyframes sbOrbRibbon {
  0% {
    transform: rotate(0deg) translate(0%, 0%) scale(1, 1);
  }
  26% {
    transform: rotate(-92deg) translate(4%, -5%) scale(1.16, 1.02);
  }
  52% {
    transform: rotate(-184deg) translate(-5%, 3%) scale(0.95, 1.1);
  }
  78% {
    transform: rotate(-272deg) translate(3%, 5%) scale(1.1, 0.96);
  }
  100% {
    transform: rotate(-360deg) translate(0%, 0%) scale(1, 1);
  }
}
@keyframes sbOrbTide {
  0%,
  100% {
    opacity: 0.3;
    transform: translate3d(-7%, 2%, 0) rotate(-4deg) scale(1.03, 0.96);
  }
  48% {
    opacity: 0.46;
    transform: translate3d(7%, -3%, 0) rotate(3deg) scale(0.96, 1.04);
  }
  76% {
    opacity: 0.36;
    transform: translate3d(2%, 3%, 0) rotate(1deg) scale(1.04, 0.98);
  }
}
/* the sphere's liquid breath — asymmetric squash & stretch with a hint of
   roll, so the ball reads as a drop of liquid, not a rigid circle (still
   transform-only; the ellipse deviation is ~1.5%, form not geometry) */
@keyframes sbOrbBreathe {
  0% {
    transform: translateZ(0) scale(1, 1) rotate(0deg);
  }
  22% {
    transform: translateZ(0) scale(1.03, 0.974) rotate(0.9deg);
  }
  47% {
    transform: translateZ(0) scale(0.976, 1.028) rotate(-0.8deg);
  }
  74% {
    transform: translateZ(0) scale(1.017, 0.99) rotate(0.5deg);
  }
  100% {
    transform: translateZ(0) scale(1, 1) rotate(0deg);
  }
}
/* listening — a drop being spoken into: pronounced squash & stretch with roll
   (transform-only; ~4.5% deviation reads as liquid without breaking the glass) */
@keyframes sbOrbLiveWobble {
  0% {
    transform: translateZ(0) scale(1, 1) rotate(0deg);
  }
  26% {
    transform: translateZ(0) scale(1.048, 0.956) rotate(1.4deg);
  }
  52% {
    transform: translateZ(0) scale(0.955, 1.046) rotate(-1.2deg);
  }
  78% {
    transform: translateZ(0) scale(1.032, 0.972) rotate(0.9deg);
  }
  100% {
    transform: translateZ(0) scale(1, 1) rotate(0deg);
  }
}
/* listening — the whole liquid body sloshes inside the fixed glass (this
   carrier is otherwise gyro-driven; while recording the slosh owns it) */
@keyframes sbOrbLiveSlosh {
  0% {
    transform: translate(0%, 0%) rotate(0deg);
  }
  28% {
    transform: translate(3.6%, -2.8%) rotate(2.4deg);
  }
  55% {
    transform: translate(-3.2%, 2.6%) rotate(-2deg);
  }
  80% {
    transform: translate(2.2%, 3%) rotate(1.4deg);
  }
  100% {
    transform: translate(0%, 0%) rotate(0deg);
  }
}
/* tilted satellite orbits (the constant scaleY squeezes them into ellipses) */
@keyframes sbOrbOrbitCw {
  from {
    transform: scaleY(0.965) rotate(0deg);
  }
  to {
    transform: scaleY(0.965) rotate(360deg);
  }
}
@keyframes sbOrbOrbitCcw {
  from {
    transform: scaleY(0.94) rotate(360deg);
  }
  to {
    transform: scaleY(0.94) rotate(0deg);
  }
}
/* near/far bead swell, synced to one orbit revolution */
@keyframes sbOrbSatNear {
  0%,
  100% {
    transform: scale(0.84);
  }
  50% {
    transform: scale(1.12);
  }
}

/* ── voice listening stage (VoiceComposer): the orb is the recorder ── */
.sb-vc-stage {
  position: absolute;
  inset: 0;
  z-index: var(--z-voice-fab);
  direction: rtl;
  display: flex;
  flex-direction: column;
  /* solid, theme-resolved paper — a clean flat room for the orb (the fade-in
     supplies the softness; no live blending over scrolled content) */
  background: var(--paper);
  animation: sbVcStageIn 0.28s var(--ease-out) both;
}
.sb-vc-orbpane {
  position: relative;
  display: grid;
  place-items: center;
  margin: 6px auto 0;
  flex: none;
}
.sb-vc-halo {
  position: absolute;
  width: 116%;
  height: 116%;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-ring) 0%, rgba(0, 0, 0, 0) 62%);
  opacity: 0.3;
  will-change: opacity;
  pointer-events: none;
}
.sb-vc-livering {
  position: absolute;
  width: 168px;
  height: 168px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  opacity: 0.12;
  will-change: transform, opacity;
  pointer-events: none;
}
.sb-vc-orbwrap {
  position: relative;
  display: grid;
  place-items: center;
  will-change: transform;
}
.sb-vc-mic {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  z-index: 6;
  pointer-events: none;
}
.sb-vc-mic > span {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(10, 24, 20, 0.26);
  color: #ffffff;
}
@keyframes sbVcStageIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* in-place listening on Home — the stage portals INTO the hero (no page
   takeover): the same orb the user pressed becomes the recorder where it sits */
.sb-orb-dock {
  width: 100%;
}
.sb-vc-dock {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  direction: rtl;
  animation: sbVcStageIn 0.24s var(--ease-out) both;
}

/* saved-capture flight — chips fly from the orb into the tab that now owns
   them (WAAPI drives the transforms; this is just the chip look) */
.sb-flight-layer {
  position: absolute;
  inset: 0;
  z-index: 940;
  pointer-events: none;
  overflow: hidden;
}
.sb-flight-chip {
  position: absolute;
  top: 0;
  left: 0;
  max-width: 46%;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--firoozeh);
  color: #ffffff;
  font: 700 12px/1.4 var(--font-sans);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  direction: rtl;
  will-change: transform, opacity;
}

/* ═══ Micro-interactions ═════════════════════════════════════════════════════
   Optimistic-settle, undo snackbar with countdown ring, streak-milestone
   confetti, and the chat long-press radial menu. Transform/opacity-only,
   pointer-events scoped, all finite (self-unmount). */

/* Optimistic settle: a just-committed card/chip lands slightly big + faint,
   then settles to rest. One-shot, layout-neutral (scale only). */
@keyframes sbSettle {
  0% { transform: scale(1.045); opacity: 0.35; }
  55% { transform: scale(0.995); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
.sb-settle {
  animation: sbSettle 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
}

/* Undo snackbar */
.sb-undobar {
  position: fixed;
  bottom: 96px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 640;
  max-width: 360px;
  width: calc(100% - 32px);
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--ink);
  color: var(--paper);
  border-radius: 14px;
  padding: 8px 8px 8px 16px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
  direction: rtl;
  animation: sbUndoIn 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes sbUndoIn {
  from { transform: translate(-50%, 14px); opacity: 0; }
  to { transform: translate(-50%, 0); opacity: 1; }
}
.sb-undobar-msg {
  flex: 1;
  min-width: 0;
  font: 500 13.5px var(--font-sans);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sb-undobar-action {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: none;
  background: rgba(255, 255, 255, 0.1);
  color: var(--saffron);
  border-radius: 999px;
  padding: 6px 12px 6px 8px;
  font: 700 13px var(--font-sans);
  cursor: pointer;
}
.sb-undobar-ring {
  position: relative;
  width: 28px;
  height: 28px;
  display: inline-grid;
  place-items: center;
}
.sb-undobar-secs {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font: 700 10px var(--font-sans);
  color: var(--paper);
}

/* Streak-milestone confetti — light gold burst from top-center */
.sb-streak-confetti {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 655;
  overflow: hidden;
}
.sb-streak-origin {
  position: absolute;
  top: 96px;
  left: 50%;
}
.sb-streak-piece {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  animation: sbStreakPiece 1100ms cubic-bezier(0.15, 0.7, 0.3, 1) var(--cd) 1 forwards;
}
@keyframes sbStreakPiece {
  0% { transform: translate(0, 0) rotate(0deg) scale(0.6); opacity: 0; }
  14% { opacity: 1; }
  100% { transform: translate(var(--cx), var(--cy)) rotate(var(--cr)) scale(1); opacity: 0; }
}

/* Chat long-press radial menu */
.sb-radial {
  position: fixed;
  z-index: 660;
  transform: translate(-50%, -50%);
}
.sb-radial-hub {
  position: absolute;
  left: -6px;
  top: -6px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--firoozeh);
  opacity: 0.5;
  animation: sbRadialHub 0.3s ease both;
}
@keyframes sbRadialHub {
  from { transform: scale(0); opacity: 0; }
  to { transform: scale(1); opacity: 0.5; }
}
.sb-radial-item {
  position: absolute;
  left: 0;
  top: 0;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  display: grid;
  place-items: center;
  gap: 1px;
  border: 1px solid var(--ink-4);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
  animation: sbRadialItem 0.34s cubic-bezier(0.34, 1.56, 0.64, 1) var(--rd) both;
}
@keyframes sbRadialItem {
  from { transform: translate(0, 0) scale(0.4); opacity: 0; }
  to { transform: translate(var(--dx), var(--dy)) scale(1); opacity: 1; }
}
.sb-radial-danger {
  color: var(--garnet);
  border-color: var(--garnet-soft);
}
.sb-radial-label {
  font: 600 8px var(--font-sans);
  line-height: 1;
  color: var(--ink-3);
  max-width: 42px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sb-radial-danger .sb-radial-label {
  color: var(--garnet);
}

@media (prefers-reduced-motion: reduce) {
  .sb-settle,
  .sb-undobar,
  .sb-streak-piece,
  .sb-radial-hub,
  .sb-radial-item {
    animation: none !important;
  }
  .sb-radial-item {
    transform: translate(var(--dx), var(--dy));
  }
  .sb-streak-confetti {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 1ms !important;
  }
  .sb-orb-inside-prompt {
    height: 62px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
  }
  .sb-orb-inside-line {
    position: relative;
    inset: auto;
    min-height: 25px;
    flex: 0 0 auto;
    gap: 0;
    opacity: 1;
    transform: none;
    animation: none !important;
  }
  .sb-orb-inside-line strong {
    font-size: 12px;
  }
  .sb-orb-inside-line small {
    font-size: 9px;
  }
}
