/* ============================================================
   Batwa — design tokens
   Everything visual derives from here. Change with intent.
   ============================================================ */
:root {
  /* ---- Canvas & surfaces ---- */
  --c-bg:            #F5F4FC;
  --c-bg-deep:       #EEEBFA;
  --c-surface:       #FFFFFF;
  --c-surface-2:     #FAF9FE;
  --c-border:        rgba(23, 19, 58, 0.07);
  --c-border-strong: rgba(23, 19, 58, 0.14);

  /* ---- Ink ---- */
  --c-ink:        #17133A;
  --c-ink-soft:   #4A4668;
  --c-muted:      #837FA3;
  --c-faint:      #B4B1CC;
  --c-on-grad:    #FFFFFF;
  /* "inverse" chip/toast/tooltip surface: dark in light theme, lifted in dark */
  --c-inverse:    #17133A;
  --c-on-inverse: #FFFFFF;
  --c-on-grad-dim: rgba(255, 255, 255, 0.72);

  /* ---- Brand ---- */
  --c-violet:      #6248F5;
  --c-violet-deep: #4630C9;
  --c-violet-soft: #EEEAFE;
  --c-aqua:        #2FB6E9;
  --c-aqua-soft:   #E3F5FC;

  /* ---- Semantic ---- */
  --c-pos:        #12B77F;   /* income / positive */
  --c-pos-soft:   #E2F8EF;
  --c-warn:       #E8930C;   /* committed / caution */
  --c-warn-soft:  #FFF3DE;
  --c-neg:        #EF4667;   /* overdue / negative */
  --c-neg-soft:   #FDE9ED;

  /* ---- Recurrence system (stripe + badge, used everywhere) ---- */
  --rec-once:    #94A0B8;
  --rec-weekly:  #2FB6E9;
  --rec-monthly: #7C55F7;

  /* ---- Chart fills that aren't brand colours (weekday bars) ---- */
  --wd-bar-1: #D9D3FA;
  --wd-bar-2: #B49AFF;

  /* ---- Gradients ---- */
  --grad-hero:   linear-gradient(140deg, #4F33E8 0%, #7C55F7 46%, #9E7BFF 72%, #3FBCF8 125%);
  --grad-action: linear-gradient(135deg, #5B3DF0 0%, #8257F6 100%);
  --grad-mint:   linear-gradient(135deg, #0E9F6E 0%, #16C79A 100%);
  --grad-danger: linear-gradient(140deg, #E23A5F 0%, #F97362 100%);
  --grad-page:
    radial-gradient(60% 40% at 12% -6%, rgba(124, 85, 247, 0.14), transparent 70%),
    radial-gradient(50% 36% at 96% 2%,  rgba(63, 188, 248, 0.12), transparent 70%),
    radial-gradient(42% 30% at 70% 26%, rgba(18, 183, 127, 0.05), transparent 70%);

  /* ---- Elevation (soft, layered) ---- */
  --shadow-xs:    0 1px 2px rgba(23, 19, 58, 0.05);
  --shadow-card:  0 1px 2px rgba(23, 19, 58, 0.04), 0 10px 28px -10px rgba(23, 19, 58, 0.12);
  --shadow-lift:  0 2px 6px rgba(23, 19, 58, 0.06), 0 18px 44px -12px rgba(23, 19, 58, 0.18);
  --shadow-hero:  0 14px 36px -10px rgba(79, 51, 232, 0.48);
  --shadow-btn:   0 8px 20px -6px rgba(91, 61, 240, 0.45);
  --shadow-sheet: 0 -14px 56px rgba(23, 19, 58, 0.20);

  /* ---- Clay (light) ----
     hi = inner highlight (top-left), lo = inner shade (bottom-right).
     Interactive = full stack. Surface = quiet raise. Sunken = wells/tracks. */
  --clay-hi:  rgba(255, 255, 255, 0.85);
  --clay-lo:  rgba(98, 72, 245, 0.10);
  --clay-drop: 0 12px 28px -12px rgba(98, 72, 245, 0.22);

  /* static cards / tiles — quiet */
  --clay-surface:
    inset 0 1.5px 0 var(--clay-hi),
    inset 0 -2px 0 var(--clay-lo),
    0 1px 2px rgba(23, 19, 58, 0.04),
    var(--clay-drop);

  /* neutral pressables on a surface: ghost btn, icon-btn, chips, segmented thumb */
  --clay-soft:
    inset 2px 2px 6px rgba(255, 255, 255, 0.95),
    inset -2px -3px 6px rgba(98, 72, 245, 0.10),
    0 6px 14px -6px rgba(98, 72, 245, 0.25);
  --clay-soft-press:
    inset 2px 3px 8px rgba(98, 72, 245, 0.16),
    inset -1px -1px 3px rgba(255, 255, 255, 0.6),
    0 1px 3px -1px rgba(98, 72, 245, 0.15);

  /* coloured gradient pressables: primary / mint / danger / FAB.
     Insets are neutral white/ink alpha so one token works on any gradient;
     the coloured glow is a separate per-variant token. */
  --clay-ink-hi: rgba(255, 255, 255, 0.34);
  --clay-ink-lo: rgba(12, 4, 60, 0.26);
  --clay-btn:
    inset 3px 3px 8px var(--clay-ink-hi),
    inset -3px -4px 8px var(--clay-ink-lo);
  --clay-btn-press:
    inset 3px 4px 10px var(--clay-ink-lo),
    inset -1px -1px 3px rgba(255, 255, 255, 0.14);
  --glow-violet: 0 12px 26px -8px rgba(91, 61, 240, 0.55);
  --glow-mint:   0 12px 26px -8px rgba(18, 183, 127, 0.50);
  --glow-danger: 0 12px 26px -8px rgba(226, 58, 95, 0.50);
  --glow-press:  0 3px 8px -4px rgba(23, 19, 58, 0.30);

  /* wells: inputs, segmented track, switch track, limit track */
  --clay-sunken:
    inset 0 2px 6px rgba(23, 19, 58, 0.08),
    inset 0 -1px 0 rgba(255, 255, 255, 0.9);
  --ring-focus: 0 0 0 4px rgba(98, 72, 245, 0.16);
  --ring-error: 0 0 0 4px rgba(239, 70, 103, 0.14);

  /* pastel clay: the balance card that overlaps the canopy. Pastel fill +
     ink text is what makes clay read as clay — level 1 of max two. */
  --c-pastel:      #E9E4FF;
  --c-pastel-deep: #D9D1FF;
  --clay-pastel:
    inset 3px 3px 10px rgba(255, 255, 255, 0.95),
    inset -4px -5px 12px rgba(98, 72, 245, 0.18),
    0 18px 36px -14px rgba(98, 72, 245, 0.35);
  --clay-pastel-press:
    inset 3px 4px 12px rgba(98, 72, 245, 0.22),
    inset -1px -1px 3px rgba(255, 255, 255, 0.5),
    0 4px 10px -6px rgba(98, 72, 245, 0.25);

  /* ---- Radius ---- */
  --r-sm:   10px;
  --r-md:   16px;
  --r-lg:   22px;
  --r-xl:   28px;
  --r-pill: 999px;

  /* ---- Spacing scale ---- */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 20px; --s-6: 24px; --s-7: 32px; --s-8: 40px; --s-9: 56px;

  /* ---- Type ---- */
  --font: "Outfit", -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-hand: "Caveat", "Segoe Print", "Bradley Hand", cursive;   /* tips, facts, insights */
  --fs-hand: 1.25rem;   /* Caveat runs small; 20px reads like 15px body */
  --fs-xs:   0.75rem;   /* 12 */
  --fs-sm:   0.8125rem; /* 13 */
  --fs-base: 0.9375rem; /* 15 */
  --fs-md:   1.0625rem; /* 17 */
  --fs-lg:   1.375rem;  /* 22 */
  --fs-xl:   1.75rem;   /* 28 */
  --fs-hero: 2.625rem;  /* 42 */

  /* ---- Motion ---- */
  --t-fast: 160ms;
  --t-med:  260ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  /* ---- Layout ---- */
  --max-w: 480px;
  --max-w-wide: 1080px;
  --nav-h: 68px;
  --dock-gap: 12px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
  /* Full height the floating dock occupies, including the gap it floats above.
     Every page reserves at least this much space so content is never trapped
     behind it — on ANY breakpoint. Collapsing the dock does not change this:
     the space stays reserved for the expanded height. */
  --nav-total: calc(var(--nav-h) + var(--dock-gap) + var(--safe-b));
}

/* ============================================================
   Dark theme
   Only the colour tokens flip — spacing, type and motion are shared.
   `data-theme` is written on <html> by js/theme.js (and by the boot
   script in index.html, before first paint, so there is no flash).
   ============================================================ */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* ---- Canvas & surfaces ---- */
  --c-bg:            #0E0B22;
  --c-bg-deep:       #171331;
  --c-surface:       #1A1638;
  --c-surface-2:     #221D45;
  --c-border:        rgba(255, 255, 255, 0.09);
  --c-border-strong: rgba(255, 255, 255, 0.18);

  /* ---- Ink ---- */
  --c-ink:        #EFEDFB;
  --c-ink-soft:   #C3BFE0;
  --c-muted:      #938FB6;
  --c-faint:      #625D8A;
  --c-inverse:    #3A3270;
  --c-on-inverse: #F3F1FF;

  /* ---- Brand (lifted, so violet still reads on a dark canvas) ---- */
  --c-violet:      #9B85FF;
  --c-violet-deep: #BCACFF;
  --c-violet-soft: #2B2358;
  --c-aqua:        #56C7F2;
  --c-aqua-soft:   #133447;

  /* ---- Semantic ---- */
  --c-pos:        #3ED9A4;
  --c-pos-soft:   #103A2E;
  --c-warn:       #FFB43F;
  --c-warn-soft:  #3B2A0E;
  --c-neg:        #FF7089;
  --c-neg-soft:   #3D1420;

  /* ---- Recurrence system ---- */
  --rec-once:    #7C8AA6;
  --rec-weekly:  #56C7F2;
  --rec-monthly: #9B85FF;

  /* ---- Chart fills that aren't brand colours (weekday bars) ---- */
  --wd-bar-1: #342C63;
  --wd-bar-2: #5B4BA8;

  /* ---- Gradients ---- */
  --grad-hero:   linear-gradient(140deg, #3A22C2 0%, #5E3ADF 46%, #7A5AF0 72%, #2C93D6 125%);
  --grad-action: linear-gradient(135deg, #5B3DF0 0%, #8257F6 100%);
  --grad-mint:   linear-gradient(135deg, #0B8B60 0%, #12B08A 100%);
  --grad-danger: linear-gradient(140deg, #C42E4F 0%, #E05D50 100%);
  --grad-page:
    radial-gradient(60% 40% at 12% -6%, rgba(124, 85, 247, 0.22), transparent 70%),
    radial-gradient(50% 36% at 96% 2%,  rgba(47, 182, 233, 0.16), transparent 70%),
    radial-gradient(42% 30% at 70% 26%, rgba(18, 183, 127, 0.08), transparent 70%);

  /* ---- Elevation: shadows read as depth only if they go darker than the canvas ---- */
  --shadow-xs:    0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-card:  0 1px 2px rgba(0, 0, 0, 0.35), 0 10px 28px -10px rgba(0, 0, 0, 0.6);
  --shadow-lift:  0 2px 6px rgba(0, 0, 0, 0.4), 0 18px 44px -12px rgba(0, 0, 0, 0.7);
  --shadow-hero:  0 14px 36px -10px rgba(0, 0, 0, 0.7);
  --shadow-btn:   0 8px 20px -6px rgba(46, 25, 160, 0.7);
  --shadow-sheet: 0 -14px 56px rgba(0, 0, 0, 0.6);

  /* ---- Clay (dark) — recalibrated, not inverted ---- */
  --clay-hi:  rgba(155, 133, 255, 0.16);
  --clay-lo:  rgba(0, 0, 0, 0.30);
  --clay-drop: 0 12px 28px -12px rgba(0, 0, 0, 0.55);
  --clay-surface:
    inset 0 1px 0 var(--clay-hi),
    inset 0 -2px 0 var(--clay-lo),
    0 1px 2px rgba(0, 0, 0, 0.35),
    var(--clay-drop);
  --clay-soft:
    inset 2px 2px 6px rgba(155, 133, 255, 0.14),
    inset -2px -3px 6px rgba(0, 0, 0, 0.35),
    0 6px 14px -6px rgba(0, 0, 0, 0.5);
  --clay-soft-press:
    inset 2px 3px 8px rgba(0, 0, 0, 0.45),
    inset -1px -1px 3px rgba(155, 133, 255, 0.10),
    0 1px 3px -1px rgba(0, 0, 0, 0.4);
  --clay-ink-hi: rgba(255, 255, 255, 0.22);
  --clay-ink-lo: rgba(0, 0, 0, 0.34);
  --glow-violet: 0 12px 26px -8px rgba(46, 25, 160, 0.75);
  --glow-mint:   0 12px 26px -8px rgba(11, 139, 96, 0.6);
  --glow-danger: 0 12px 26px -8px rgba(196, 46, 79, 0.6);
  --glow-press:  0 3px 8px -4px rgba(0, 0, 0, 0.6);
  --clay-sunken:
    inset 0 2px 6px rgba(0, 0, 0, 0.45),
    inset 0 -1px 0 rgba(155, 133, 255, 0.10);
  --ring-focus: 0 0 0 4px rgba(155, 133, 255, 0.22);
  --ring-error: 0 0 0 4px rgba(255, 112, 137, 0.20);

  --c-pastel:      #2B2358;
  --c-pastel-deep: #1F1946;
  --clay-pastel:
    inset 3px 3px 10px rgba(155, 133, 255, 0.16),
    inset -4px -5px 12px rgba(0, 0, 0, 0.40),
    0 18px 36px -14px rgba(0, 0, 0, 0.6);
  --clay-pastel-press:
    inset 3px 4px 12px rgba(0, 0, 0, 0.5),
    inset -1px -1px 3px rgba(155, 133, 255, 0.12),
    0 4px 10px -6px rgba(0, 0, 0, 0.5);
}

/* ============================================================
   Performance tiers
   `data-perf` is written on <html> by the head script in index.html (before
   first paint) and kept live by js/perf.js. "high" is the default — every
   token above stays exactly as authored. Only cheap-to-recompute tokens move
   here; the backdrop-filter/background swaps live next to the components
   that use them (components.css, onboarding.css).
   ============================================================ */
html[data-perf="medium"] {
  --t-fast: 110ms;
  --t-med:  170ms;
}

html[data-perf="low"] {
  --t-fast: 90ms;
  --t-med:  90ms;

  /* big layered shadows collapse to the one cheap flat one */
  --shadow-lift:  var(--shadow-xs);
  --shadow-hero:  var(--shadow-xs);
  --shadow-sheet: var(--shadow-xs);
  --shadow-card:  var(--shadow-xs);
  --clay-surface: var(--shadow-xs);
  --clay-soft:    var(--shadow-xs);
  --clay-pastel:  var(--shadow-xs);
  --clay-drop:    none;
  --glow-violet:  var(--shadow-xs);
  --glow-mint:    var(--shadow-xs);
  --glow-danger:  var(--shadow-xs);
  --glow-press:   none;
}
/* :root[data-theme="dark"] above redefines every one of these tokens with
   higher specificity (pseudo-class + attribute) than a plain
   html[data-perf="low"] attribute selector, so it would win them back on a
   dark low-tier phone. Restate them at html[data-perf="low"][data-theme="dark"]
   (attribute + attribute), which outranks it. */
html[data-perf="low"][data-theme="dark"] {
  --shadow-lift:  var(--shadow-xs);
  --shadow-hero:  var(--shadow-xs);
  --shadow-sheet: var(--shadow-xs);
  --shadow-card:  var(--shadow-xs);
  --clay-surface: var(--shadow-xs);
  --clay-soft:    var(--shadow-xs);
  --clay-pastel:  var(--shadow-xs);
  --clay-drop:    none;
  --glow-violet:  var(--shadow-xs);
  --glow-mint:    var(--shadow-xs);
  --glow-danger:  var(--shadow-xs);
  --glow-press:   none;
}
/* ============================================================
   Batwa — base: reset, typography, layout primitives
   ============================================================ */

/* Font URLs are root-relative on purpose: /fonts/ lives in public/, and Vite
   recognises a leading slash as a public file, leaves the file unhashed and
   writes it back as "../fonts/…" next to the built CSS in assets/. That is
   the same /fonts/<name>.woff2 the HTML preloads, so each font downloads
   once, and the relative form keeps a sub-folder deploy working. */
@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/outfit-var.woff2") format("woff2");
}
@font-face {
  font-family: "Caveat";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/caveat-var.woff2") format("woff2");
}

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: 1.5;
  color: var(--c-ink);
  background: var(--c-bg);
  background-image: var(--grad-page);
  background-attachment: fixed;
  background-repeat: no-repeat;
  min-height: 100dvh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
  -webkit-tap-highlight-color: transparent;
}

h1, h2, h3, h4, p { margin: 0; }
h1 { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -0.01em; }
h2 { font-size: var(--fs-md); font-weight: 700; letter-spacing: -0.01em; }
h3 { font-size: var(--fs-base); font-weight: 600; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  touch-action: manipulation;
}

input, select, textarea { font: inherit; color: inherit; }

a { color: var(--c-violet); text-decoration: none; }

img, svg { display: block; max-width: 100%; }

ul { margin: 0; padding: 0; list-style: none; }

/* Money never wraps digits unevenly */
.num, .money {
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: -0.015em;
}

/* ---- Focus ---- */
:focus-visible {
  outline: 3px solid rgba(98, 72, 245, 0.55);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
:focus:not(:focus-visible) { outline: none; }

/* ---- Layout primitives ---- */
.app-shell {
  max-width: var(--max-w);
  margin: 0 auto;
  /* The canopy owns the top of the page (and the safe area with it), so the
     shell starts flush under it. Bottom padding clears the floating dock +
     the iOS home bar, with breathing room. */
  padding: 0 var(--s-4) calc(var(--nav-total) + var(--s-9));
  min-height: 100dvh;
}

.stack   { display: flex; flex-direction: column; gap: var(--s-4); }
.row     { display: flex; align-items: center; gap: var(--s-3); }
.spread  { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.grow    { flex: 1 1 auto; min-width: 0; }
.center  { display: grid; place-items: center; }

.muted   { color: var(--c-muted); }
.small   { font-size: var(--fs-sm); }
.xsmall  { font-size: var(--fs-xs); }
.strong  { font-weight: 600; }

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hidden { display: none !important; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- Desktop: two columns for home, capped width ---- */
@media (min-width: 900px) {
  /* Desktop keeps the same bottom nav, so it needs the same clearance. */
  .app-shell { max-width: var(--max-w-wide); padding-bottom: calc(var(--nav-total) + var(--s-8)); }
  .home-grid {
    display: grid;
    grid-template-columns: 420px 1fr;
    gap: var(--s-6);
    align-items: start;
  }
  .home-grid > .home-left {
    position: sticky;
    top: var(--s-6);
    /* An account grid can outgrow the viewport, and a sticky column taller
       than the screen parks its own bottom permanently out of reach — so cap
       it and let the column itself scroll once it gets that tall. */
    max-height: calc(100dvh - var(--s-6) * 2);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }
  .home-grid > .home-left::-webkit-scrollbar { display: none; }
}

/* ---- Reduced motion ----
   Scoped to :not([data-perf-mode="high"]) — an explicit "Full" choice in
   Settings must win over the OS preference, same as it wins over every other
   auto signal (js/perf.js, index.html's head script). `data-perf-mode` is the
   raw user choice ("auto"/"high"/"medium"/"low"); only "auto" (or "medium"/
   "low", which already zero motion of their own accord) ever reaches here. */
@media (prefers-reduced-motion: reduce) {
  html:not([data-perf-mode="high"]) *,
  html:not([data-perf-mode="high"]) *::before,
  html:not([data-perf-mode="high"]) *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* the clay shadow state still swaps instantly; only the squish is removed */
  html:not([data-perf-mode="high"]) .btn:active,
  html:not([data-perf-mode="high"]) .nav-fab:active,
  html:not([data-perf-mode="high"]) .icon-btn:active,
  html:not([data-perf-mode="high"]) .chip-btn:active,
  html:not([data-perf-mode="high"]) .key:active {
    transform: none;
  }
}

/* ---- Low performance tier ----
   Same squish removal as prefers-reduced-motion above, but keyed off the
   tier instead — a weak phone without the OS motion preference set still
   shouldn't pay for the transform transition on every tap. --t-fast/--t-med
   are shortened in tokens.css; this only drops the transform part. */
html[data-perf="low"] .btn:active,
html[data-perf="low"] .nav-fab:active,
html[data-perf="low"] .icon-btn:active,
html[data-perf="low"] .chip-btn:active,
html[data-perf="low"] .key:active {
  transform: none;
}
/* ============================================================
   Batwa — components
   ============================================================ */

/* =================== Canopy ===================
   The one saturated surface on Home and Space: a full-width violet sheet that
   starts behind the status bar and ends in 32px bottom corners. Reports,
   History, and Settings hide the canopy entirely (see body[data-view=…]). */
.canopy {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--grad-hero);
  border-radius: 0 0 32px 32px;
  padding: calc(var(--safe-t) + var(--s-4)) 0 var(--s-6);
  color: #fff;
}
.canopy.is-negative { background: var(--grad-danger); }
/* soft light blooms inside the gradient */
.canopy::before,
.canopy::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  z-index: -1;
  filter: blur(2px);
}
.canopy::before {
  width: 240px; height: 240px;
  right: -70px; top: -110px;
  background: radial-gradient(circle, rgba(255,255,255,0.22), transparent 65%);
}
.canopy::after {
  width: 200px; height: 200px;
  left: -60px; bottom: -120px;
  background: radial-gradient(circle, rgba(63,188,248,0.35), transparent 65%);
}
.canopy-inner { max-width: var(--max-w); margin: 0 auto; padding: 0 var(--s-4); }
@media (min-width: 900px) { .canopy-inner { max-width: var(--max-w-wide); } }
/* room for the balance card that overlaps the canopy's bottom edge */
body[data-view="home"] .canopy { padding-bottom: calc(var(--s-6) + 28px); }
/* desktop has no overlap (the left column is sticky and scrolls on its own),
   so the canopy does not need to reserve the extra 28px either */
@media (min-width: 900px) {
  body[data-view="home"] .canopy { padding-bottom: var(--s-6); }
}

/* Reports / History / Settings: no violet strip; shell owns the top safe area.
   Scoped to data-view so the lock/onboarding first paint keeps the canopy. */
body[data-view="reports"] .canopy,
body[data-view="history"] .canopy,
body[data-view="settings"] .canopy { display: none !important; }
body[data-view="reports"] .app-shell,
body[data-view="history"] .app-shell,
body[data-view="settings"] .app-shell {
  padding-top: calc(var(--safe-t) + var(--s-4));
}

/* =================== Header =================== */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.greeting .hello { font-size: var(--fs-sm); color: rgba(255,255,255,0.72); font-weight: 500; }
.greeting .title { font-size: var(--fs-lg); font-weight: 800; letter-spacing: -0.02em; color: #fff; }

.icon-btn {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--c-surface);
  box-shadow: var(--clay-soft);
  border: 1px solid var(--c-border);
  color: var(--c-ink-soft);
  transition: transform var(--t-fast) var(--ease-out);
}
.icon-btn:active { transform: scale(0.92); box-shadow: var(--clay-soft-press); }

/* Theme toggle — sits beside the sync pill */
.theme-toggle {
  width: 38px; height: 38px;
  flex: 0 0 auto;
}
:root[data-theme="dark"] .theme-toggle { color: var(--c-violet); }

/* Sync status pill — right of the greeting */
.sync-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  min-height: 34px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 700;
  border: 1px solid var(--c-border);
  background: var(--c-surface);
  color: var(--c-muted);
  box-shadow: var(--clay-soft);
  transition: transform var(--t-fast) var(--ease-out);
  white-space: nowrap;
}
.sync-pill:active { transform: scale(0.94); box-shadow: var(--clay-soft-press); }
.sync-pill.st-synced  { color: var(--c-pos); background: var(--c-pos-soft); border-color: transparent; }
.sync-pill.st-pending { color: var(--c-violet); background: var(--c-violet-soft); border-color: transparent; }
.sync-pill.st-syncing { color: var(--c-aqua); background: var(--c-aqua-soft); border-color: transparent; }
.sync-pill.st-offline { color: var(--c-warn); background: var(--c-warn-soft); border-color: transparent; }
.sync-pill .spin { display: inline-block; animation: pill-spin 0.9s linear infinite; }
@keyframes pill-spin { to { transform: rotate(360deg); } }

/* On the canopy both controls turn to glass — same recipe as .key. Literals on
   purpose: the canopy is a violet gradient in BOTH themes, so these must not
   follow the surface tokens. */
.canopy .sync-pill,
.canopy .theme-toggle,
.canopy .spaces-btn {
  background: rgba(255,255,255,0.16);
  color: #fff;
  border-color: rgba(255,255,255,0.22);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow:
    inset 2px 2px 6px rgba(255,255,255,0.22),
    inset -2px -3px 6px rgba(12,4,60,0.22),
    0 8px 18px -8px rgba(12,4,60,0.35);
}
:root[data-theme="dark"] .canopy .theme-toggle,
:root[data-theme="dark"] .canopy .spaces-btn { color: #fff; }
/* low tier: drop the blur, keep the same glassy look with a flatter fill */
html[data-perf="low"] .canopy .sync-pill,
html[data-perf="low"] .canopy .theme-toggle,
html[data-perf="low"] .canopy .spaces-btn {
  background: rgba(255, 255, 255, 0.22);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* state tints keep their icon colour; the text stays legible white */
.canopy .sync-pill.st-synced  { background: rgba(255,255,255,0.16); color: #fff; }
.canopy .sync-pill.st-pending { background: rgba(255,255,255,0.16); color: #fff; }
.canopy .sync-pill.st-syncing { background: rgba(255,255,255,0.16); color: #fff; }
.canopy .sync-pill.st-offline { background: rgba(255,255,255,0.16); color: #fff; }
.canopy .sync-pill.st-synced  svg.ico { color: #BFF5E2; }
.canopy .sync-pill.st-pending svg.ico { color: #E4DCFF; }
.canopy .sync-pill.st-syncing svg.ico { color: #C7EEFB; }
.canopy .sync-pill.st-offline svg.ico { color: #FFE0AE; }

/* =================== Accounts =================== */
.acc-row {
  display: flex;
  gap: var(--s-3);
  overflow-x: auto;
  padding: var(--s-4) 2px var(--s-2);
  margin: 0 -2px;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
}
.acc-row::-webkit-scrollbar { display: none; }

.acc-card {
  flex: 0 0 auto;
  scroll-snap-align: start;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 7px;
  min-width: 128px;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-lg);
  text-align: left;
  transition: transform var(--t-fast) var(--ease-out);
}
.acc-card:active { transform: scale(0.95); }
/* Content sits in its own positioned layer so the bucket fill (below) can't
   paint over it — positioned descendants stack above static ones. */
.acc-card-inner { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 7px; }
.acc-card .acc-name { font-size: var(--fs-xs); font-weight: 600; color: var(--c-muted); max-width: 110px; }
.acc-card .acc-bal { font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.02em; }
.acc-card .acc-bal.is-neg { color: var(--c-neg); }
.acc-add { border: 1.5px dashed var(--c-border-strong); background: transparent; box-shadow: none; align-items: center; justify-content: center; min-width: 84px; }

/* Desktop: the accounts sit in a grid, every balance readable at a glance,
   instead of a row you have to swipe through sideways. */
@media (min-width: 640px) {
  .acc-row {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
    overflow: visible;
    scroll-snap-type: none;
  }
  .acc-card, .acc-add { min-width: 0; }
  .acc-card .acc-name { max-width: 100%; }
}

/* Fill tone thresholds — shared by the card fills and the account-sheet
   bucket fill. P = committed/balance. <80% is violet (the app's aurora
   accent, not the account's brand color); 80–90% turns amber; 90–100%
   orange; >=100% (or balance spent out) red. Amber and red reuse the
   existing warn/neg semantic tokens; orange is new and sits between them,
   all kept soft/translucent like the violet tint. */
:root {
  --c-fill-orange: #F0650C;
}

/* "Bucket fill" — soft violet wash rising bottom-up to show committed share
   of the balance. Height is set inline/animated per card. The waterline
   reads as a liquid surface, not a border: a thin, brighter crest of the
   same hue right at the top edge, with a very short glow fading down into
   the body of the fill — tight (a few px), not a wide blurred band. */
.acc-fill {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 0;
  --fill-c: var(--c-violet);
  --fill-a: 20%;
  background: linear-gradient(to bottom,
    transparent 0,
    color-mix(in srgb, var(--fill-c) calc(var(--fill-a) + 40%), transparent) 2px,
    color-mix(in srgb, var(--fill-c) var(--fill-a), transparent) 6px);
  pointer-events: none;
}
.acc-fill.is-amber  { --fill-c: var(--c-warn);        --fill-a: 26%; }
.acc-fill.is-orange { --fill-c: var(--c-fill-orange); --fill-a: 28%; }
.acc-fill.is-red    { --fill-c: var(--c-neg);         --fill-a: 24%; }

/* Logo tiles — clean white cards so every brand mark (transparent-bg SVG)
   reads consistently. --tile-bg (inline, per-kind) is only actually used by
   the icon and fallback-glyph variants below; a loaded logo tile stays a
   plain white card. */
.acc-logo {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  color: #fff;
  font-weight: 800;
  line-height: 1;
  flex: 0 0 auto;
  user-select: none;
  background: #fff;
  border: 1px solid var(--c-border);
  box-shadow: var(--shadow-xs);
}

/* Logo box: fixed-size inner frame, the <img> fills it via object-fit:
   contain so every logo sits at the same optical size regardless of its own
   aspect ratio — no per-kind zoom hacks needed. */
.acc-logo-imgbox { display: flex; align-items: center; justify-content: center; }
.acc-logo-imgbox img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* Fallback glyph: pre-rendered but hidden — only shown (by onerror adding
   .is-fallback) when the logo file fails to load, so the letter never sits
   behind a logo that did load. */
.acc-logo-glyph {
  display: none;
  position: absolute; inset: 0;
  align-items: center; justify-content: center;
  border-radius: inherit;
}
.acc-logo.is-fallback {
  background: var(--tile-bg, var(--c-violet));
  border-color: transparent;
  box-shadow: inset 0 -2px 6px rgba(0,0,0,0.18), 0 2px 6px rgba(23,19,58,0.14);
}
.acc-logo.is-fallback .acc-logo-glyph { display: flex; }

/* Intentional icon tiles (cash, generic "other bank") — no logo file exists
   for these, they keep the colored-tile-with-icon look, matched in size /
   radius / border to sit consistently next to the white logo cards. */
.acc-logo-ico {
  background: var(--tile-bg, var(--c-muted));
  border-color: transparent;
  box-shadow: inset 0 -2px 6px rgba(0,0,0,0.18), 0 2px 6px rgba(23,19,58,0.14);
}

.acc-invite {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 100%;
  padding: var(--s-4);
  margin-top: var(--s-4);
  border-radius: var(--r-lg);
  border: 1.5px dashed rgba(98, 72, 245, 0.3);
  background: rgba(98, 72, 245, 0.04);
  transition: transform var(--t-fast) var(--ease-out);
}
.acc-invite:active { transform: scale(0.97); }

/* Account sheet = the bucket. The violet fill is painted as the .sheet
   element's own background — rising to --fill-pct, no separate vessel card,
   so it costs zero extra vertical space. Since .sheet is the scroll
   container, background-attachment's default ("scroll", i.e. fixed to the
   element's own box) keeps the fill still as content scrolls past it
   instead of dragging it along. --fill-pct is animated 0 -> P on open;
   --fill-c/--fill-a swap by committed-share tone (see accounts.js
   fillFraction / the .acc-fill rules above for the same thresholds).
   The waterline itself is a tight liquid-surface crest (a brighter line of
   the same hue, glowing a few px down into the fill) rather than a wide
   fade band or a hard border — same treatment as the card fills. */
.sheet.acc-sheet {
  --fill-pct: 0%;
  --fill-c: var(--c-violet);
  --fill-a: 20%;
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--fill-c) var(--fill-a), transparent) 0%,
      color-mix(in srgb, var(--fill-c) var(--fill-a), transparent) calc(var(--fill-pct) - 6px),
      color-mix(in srgb, var(--fill-c) calc(var(--fill-a) + 40%), transparent) calc(var(--fill-pct) - 2px),
      transparent calc(var(--fill-pct) + 1px)
    ),
    var(--c-surface);
}
.sheet.acc-sheet.is-amber  { --fill-c: var(--c-warn);        --fill-a: 26%; }
.sheet.acc-sheet.is-orange { --fill-c: var(--c-fill-orange); --fill-a: 28%; }
.sheet.acc-sheet.is-red    { --fill-c: var(--c-neg);         --fill-a: 24%; }

.acc-sheet-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
.acc-sheet-head-text { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.acc-wash-name { font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.02em; }
.acc-fill-cap { font-weight: 600; }

/* Three figures: total balance, then committed / usable side by side */
.acc-figures { margin-bottom: var(--s-5); }
.acc-figure-main { margin-bottom: var(--s-4); }
.acc-figure-row { display: flex; gap: var(--s-3); }
.acc-figure-row .acc-figure {
  flex: 1;
  padding: var(--s-3);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  border: 1px solid var(--c-border);
}

/* Pending expenses explaining what's committed */
.acc-pend-list { border-top: 1px solid var(--c-border); }
.acc-pend-row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 10px 0;
  border-bottom: 1px solid var(--c-border);
}
.acc-pend-row:last-child { border-bottom: none; }
.acc-pend-row .exp-title { font-size: var(--fs-sm); font-weight: 600; }
.acc-pend-row .exp-amount { font-size: var(--fs-sm); font-weight: 700; white-space: nowrap; }

.logo-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-2);
}
.logo-opt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 10px 4px;
  border-radius: var(--r-md);
  border: 1.5px solid transparent;
  font-weight: 600;
  color: var(--c-muted);
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast) var(--ease-out);
}
.logo-opt span:last-child { max-width: 100%; }
.logo-opt.is-active { border-color: var(--c-violet); background: var(--c-violet-soft); color: var(--c-violet-deep); }
.logo-opt:active { transform: scale(0.94); }

.logo-back {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-bottom: var(--s-2);
  padding: 6px 13px 6px 9px;
  border-radius: var(--r-pill);
  background: var(--c-surface-2);
  border: 1.5px solid var(--c-border);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--c-ink-soft);
  transition: background var(--t-fast), border-color var(--t-fast);
}
.logo-back:active { transform: scale(0.96); }
.logo-back-chev { font-size: 1.25em; line-height: 0.8; }

.acc-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px 8px 8px;
  min-height: 44px;
  border-radius: var(--r-pill);
  background: var(--c-surface-2);
  border: 1.5px solid var(--c-border);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--c-ink-soft);
  transition: all var(--t-fast);
}
.acc-chip:not(:has(.acc-logo)) { padding-left: 14px; }
.acc-chip.is-active { border-color: var(--c-violet); background: var(--c-violet-soft); color: var(--c-violet-deep); }
.acc-chip .acc-logo { border-radius: 8px; }
/* A picked transfer account collapses to one chip; the cross reopens the list. */
.acc-chip.is-picked { padding-right: 8px; }
.acc-chip .chip-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px;
  margin-left: 2px;
  border-radius: 50%;
  background: rgba(79, 51, 232, 0.12);
}

.copy-box {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  background: var(--c-surface-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  padding: 10px 10px 10px 14px;
  margin-bottom: var(--s-4);
}
.copy-box code {
  font-family: Consolas, Menlo, monospace;
  font-size: 11px;
  color: var(--c-ink-soft);
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
}
.copy-box code::-webkit-scrollbar { display: none; }

/* =================== Hero (on the canopy) =================== */
.hero { position: relative; }
.hero-label {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.72);
}
.hero-eye {
  margin-left: auto;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: rgba(255,255,255,0.14);
  color: #fff;
  transition: transform var(--t-fast) var(--ease-out), background var(--t-fast);
}
.hero-eye svg { width: 16px; height: 16px; }
.hero-eye:active { transform: scale(0.9); background: rgba(255,255,255,0.28); }

.hero-amount {
  margin-top: var(--s-2);
  font-size: var(--fs-hero);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  display: flex;
  align-items: flex-start;
  gap: 6px;
  min-height: 1.05em;
  color: #fff;
}
/* the symbol sits at the cap height of the digits, not on their baseline */
.hero-amount .cur { font-size: 0.4em; font-weight: 600; margin-top: 0.32em; opacity: 0.85; }

.hero-over-line {
  margin-top: var(--s-2);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: #fff;
  background: rgba(0,0,0,0.16);
  display: inline-block;
  padding: 4px 12px;
  border-radius: var(--r-pill);
}

/* =================== Balance card (pastel clay, overlaps the canopy) ======= */
.balance-card {
  display: block; width: 100%; text-align: left;
  margin: -28px 0 var(--s-4);
  position: relative; z-index: 2;
  padding: var(--s-5) var(--s-5) var(--s-4);
  border-radius: var(--r-xl);
  background: var(--c-pastel);
  color: var(--c-ink);
  box-shadow: var(--clay-pastel);
  transition: transform var(--t-fast) var(--ease-out);
}
.balance-card:active { transform: translateY(1px) scale(0.985); box-shadow: var(--clay-pastel-press); }
@media (min-width: 900px) { .balance-card { margin-top: 0; } }

/* sunken well with a raised two-segment fill — clay level 2, and the last one */
.money-bar {
  height: 14px;
  border-radius: var(--r-pill);
  background: var(--c-pastel-deep);
  box-shadow: var(--clay-sunken);
  display: flex;
  overflow: hidden;
  padding: 2px;
  gap: 2px;
}
.money-bar > span {
  height: 100%;
  border-radius: var(--r-pill);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.35), 0 1px 3px rgba(12,4,60,0.25);
  transition: width var(--t-med) var(--ease-out);
}
.money-bar-free { background: var(--grad-action); }
.money-bar-committed { background: linear-gradient(135deg, #F2A93B, #FFC466); }
.money-bar-over { background: var(--grad-danger); }

.balance-legend {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3);
  margin-top: var(--s-4);
  font-size: var(--fs-sm);
  color: var(--c-ink-soft);
}
.balance-legend .dot {
  width: 7px; height: 7px; border-radius: 50%;
  display: inline-block; margin-right: 6px;
}
.balance-legend b {
  display: block;
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--c-ink);
  margin-top: 2px;
}
.dot-total { background: #6248F5; }
.dot-committed { background: #F2A93B; }

.balance-foot {
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid rgba(98,72,245,0.12);
  font-size: var(--fs-sm);
  color: var(--c-muted);
  display: flex; align-items: center; gap: 6px;
}
.balance-foot b { color: var(--c-ink); margin-left: auto; }
.balance-foot svg.ico { color: var(--c-faint); }
:root[data-theme="dark"] .balance-foot { border-top-color: rgba(155,133,255,0.16); }

/* Blur state — number shape stays visible */
.blurable { transition: filter 280ms var(--ease-out); cursor: pointer; }
.is-blurred .blurable { filter: blur(11px); }
.is-blurred .hero-amount .cur { filter: blur(8px); }
.is-blurred .exp-amount.blurable { filter: blur(9px); }
/* the blur itself is the privacy feature and stays on every tier — only the
   animated reveal/hide is a performance cost worth cutting on "low" */
html[data-perf="low"] .blurable { transition: none; }

/* Exact-amount tooltip */
.tip {
  position: fixed;
  z-index: 90;
  background: var(--c-inverse);
  color: var(--c-on-inverse);
  font-size: var(--fs-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 8px 14px;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lift);
  pointer-events: none;
  white-space: nowrap;
}
.tip::after {
  content: "";
  position: absolute;
  left: 50%; bottom: -5px;
  width: 10px; height: 10px;
  background: inherit;
  transform: translateX(-50%) rotate(45deg);
  border-radius: 2px;
}

/* =================== Sections =================== */
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: var(--s-6) 0 var(--s-3);
}
.section-head h2 { font-size: var(--fs-md); font-weight: 700; }
.section-head .count {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--c-violet);
  background: var(--c-violet-soft);
  padding: 3px 10px;
  border-radius: var(--r-pill);
}

/* =================== Cards (upcoming / history) =================== */
.card {
  background: var(--c-surface);
  border-radius: var(--r-lg);
  box-shadow: var(--clay-surface);
  border: 1px solid var(--c-border);
}

.exp-card {
  position: relative;
  padding: var(--s-4) var(--s-4) var(--s-4) calc(var(--s-4) + 8px);
  overflow: hidden;
}
.exp-card::before {           /* recurrence stripe */
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 5px;
  border-radius: 0 4px 4px 0;
  background: var(--rec-once);
}
.exp-card[data-rec="weekly"]::before  { background: var(--rec-weekly); }
.exp-card[data-rec="monthly"]::before { background: var(--rec-monthly); }

.exp-card > .spread > .acc-logo { align-self: flex-start; margin-top: 1px; }
.exp-card .exp-title { font-weight: 600; font-size: var(--fs-base); }
.exp-card .exp-amount {
  font-weight: 800;
  font-size: var(--fs-md);
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.exp-card .exp-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px 10px;
  margin-top: 6px;
  font-size: var(--fs-xs);
  color: var(--c-muted);
}

.badge {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--fs-xs);
  font-weight: 700;
  padding: 2px 9px;
  border-radius: var(--r-pill);
}
.badge-once    { background: #EEF1F6; color: #64748B; }
.badge-weekly  { background: var(--c-aqua-soft); color: #0B87B8; }
.badge-monthly { background: var(--c-violet-soft); color: var(--c-violet-deep); }
.badge-cat     { background: var(--c-surface-2); color: var(--c-ink-soft); border: 1px solid var(--c-border); }
.badge-paid    { background: var(--c-pos-soft); color: var(--c-pos); }

.badge-income  { background: var(--c-pos-soft); color: var(--c-pos); }

.due-hint { font-weight: 600; }
.due-hint.is-soon    { color: var(--c-warn); }
.due-hint.is-overdue { color: var(--c-neg); }

.exp-card.is-overdue {
  border-color: rgba(239, 70, 103, 0.35);
  box-shadow:
    inset 0 1.5px 0 var(--clay-hi),
    inset 0 -2px 0 var(--clay-lo),
    0 1px 2px rgba(239,70,103,0.06),
    0 10px 28px -10px rgba(239,70,103,0.22);
}
.exp-card.is-overdue::before { background: var(--c-neg); }

.exp-card.is-income::before { background: var(--c-pos); }
.exp-card.is-income .exp-amount { color: var(--c-pos); }

/* inline one-tap action row */
.exp-actions {
  display: flex;
  gap: var(--s-2);
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px dashed var(--c-border-strong);
}
.chip-btn {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 40px;
  padding: 8px 10px;
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 700;
  box-shadow: var(--clay-soft);
  transition: transform var(--t-fast) var(--ease-out), filter var(--t-fast);
}
.chip-btn:active { transform: scale(0.95); box-shadow: var(--clay-soft-press); }
.chip-paid   { background: var(--c-pos-soft); color: var(--c-pos); }
.chip-edit   { background: var(--c-surface-2); color: var(--c-ink-soft); border: 1px solid var(--c-border); }
.chip-delete { background: var(--c-neg-soft); color: var(--c-neg); flex: 0 0 auto; width: 44px; }

/* =================== Empty states =================== */
.empty {
  text-align: center;
  padding: var(--s-8) var(--s-5);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, rgba(124,85,247,0.06), rgba(63,188,248,0.05));
  border: 1.5px dashed rgba(98, 72, 245, 0.25);
}
.empty .emoji { font-size: 40px; line-height: 1; margin-bottom: var(--s-3); }
.empty-ico {
  width: 58px; height: 58px;
  border-radius: 50%;
  background: rgba(98, 72, 245, 0.12);
  color: var(--c-violet);
  display: grid; place-items: center;
  margin: 0 auto var(--s-3);
}
.empty h3 { font-weight: 700; margin-bottom: 4px; }
.empty p { color: var(--c-muted); font-size: var(--fs-sm); max-width: 260px; margin: 0 auto; }

/* =================== Buttons =================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 52px;
  padding: 12px 22px;
  border-radius: 18px;
  font-size: var(--fs-base);
  font-weight: 700;
  letter-spacing: 0.01em;
  transition: transform var(--t-fast) var(--ease-out), filter var(--t-fast);
  user-select: none;
}
.btn:active { transform: translateY(1px) scale(0.97); }
.btn-primary { background: var(--grad-action); color: #fff; box-shadow: var(--clay-btn), var(--glow-violet); }
.btn-mint    { background: var(--grad-mint); color: #fff; box-shadow: var(--clay-btn), var(--glow-mint); }
.btn-ghost   { background: var(--c-surface); color: var(--c-ink); border: 1px solid var(--c-border-strong); box-shadow: var(--clay-soft); }
.btn-danger  { background: var(--grad-danger); color: #fff; box-shadow: var(--clay-btn), var(--glow-danger); }
.btn-soft-danger { background: var(--c-neg-soft); color: var(--c-neg); box-shadow: var(--clay-soft); }
.btn-primary:active,
.btn-mint:active,
.btn-danger:active { box-shadow: var(--clay-btn-press), var(--glow-press); }
.btn-ghost:active,
.btn-soft-danger:active { box-shadow: var(--clay-soft-press); }
.btn-sm { min-height: 40px; padding: 8px 16px; font-size: var(--fs-sm); border-radius: var(--r-sm); }
.btn[disabled] { opacity: 0.5; pointer-events: none; }
.btn-block { width: 100%; }
.btn[hidden] { display: none; }

/* Pointer devices get a lift on hover; touch keeps the squish only. */
@media (hover: hover) and (pointer: fine) {
  .btn:not([disabled]):hover, .nav-fab:hover, .icon-btn:hover, .chip-btn:hover { transform: translateY(-1px); }
  .btn:not([disabled]):active, .nav-fab:active, .icon-btn:active, .chip-btn:active { transform: translateY(1px) scale(0.97); }
}

/* Home quick actions — thumb zone */
.quick-actions {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--nav-total) + var(--s-3));
  width: min(100% - 32px, calc(var(--max-w) - 32px));
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-3);
  z-index: 40;
}
@media (min-width: 900px) {
  .quick-actions { position: static; width: auto; transform: none; margin-top: var(--s-4); }
}

.list-loader {
  display: grid;
  place-items: center;
  padding: var(--s-8) 0;
  color: var(--c-violet);
}
.list-loader svg { animation: pill-spin 0.8s linear infinite; }

/* =================== Floating dock ===================
   Detached pill, inset from both sides, glass over whatever scrolls beneath.
   Labels collapse on scroll-down (height + max-height only, never shadow). */
.bottom-nav {
  position: fixed;
  left: 50%;
  bottom: calc(var(--safe-b) + var(--dock-gap));
  transform: translateX(-50%);
  width: min(calc(100% - 32px), 420px);
  height: var(--nav-h);
  border-radius: var(--r-pill);
  z-index: 50;
  padding: 0 var(--s-2);
  background: rgba(255, 255, 255, 0.80);
  backdrop-filter: blur(20px) saturate(1.4);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  border: 1px solid rgba(255, 255, 255, 0.7);
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.95),
    inset 0 -2px 0 rgba(98, 72, 245, 0.08),
    0 10px 30px -10px rgba(23, 19, 58, 0.28);
  transition: height var(--t-med) var(--ease-out);
}
.bottom-nav.is-compact { height: 56px; }
/* medium: cheaper blur, no saturate; low: no blur at all, opaque fill instead */
html[data-perf="medium"] .bottom-nav {
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
html[data-perf="low"] .bottom-nav {
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  /* the inset+drop stack above is a literal, not a token, so it needs its
     own low-tier override here too (tokens.css only simplifies the ones that
     already went through --shadow-* and --clay-* names).
     v36 note: through v35.1 this comment was written with "--shadow-*" then
     "/--clay-*", and that star-slash closed the comment early. Browsers then
     read the rest of the comment plus the declaration below as one invalid
     declaration and dropped it, so the override never applied. Vite's CSS
     parser (postcss) refuses that text outright. The declaration stays
     commented out so the low tier looks exactly as it did; turning it on is
     a visual change for its own release.
  box-shadow: var(--shadow-xs); */
}
.bottom-nav .nav-inner {
  height: 100%;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;   /* adapts to however many tabs exist */
  align-items: center;
}
.nav-item {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px;
  height: 100%;
  min-height: 0;
  color: var(--c-muted);
  transition: color var(--t-fast);
}
.nav-item .lbl {
  font-size: 11px; font-weight: 600;
  max-height: 14px; opacity: 1; overflow: hidden;
  transition: max-height var(--t-med) var(--ease-out), opacity var(--t-fast);
}
.is-compact .nav-item .lbl { max-height: 0; opacity: 0; }
/* the pebble is a sibling of the icon, so it is centred on the icon's own box
   in both states — nothing to recompute when the labels fold away */
.nav-item .ico { position: relative; z-index: 1; width: 22px; height: 22px; }
.nav-pebble {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 40px; height: 30px;
  margin: -15px 0 0 -20px;
  border-radius: 14px;
  background: var(--c-violet-soft);
  opacity: 0;
  /* The label (14px + 2px gap) pushes the icon 8px above the item's centre,
     so the pebble rides up by the same 8px — and slides back down with the
     label, as a transform, when the dock collapses. */
  transform: translateY(-8px) scale(0.7);
  transition: opacity var(--t-fast), transform var(--t-med) var(--ease-out);
}
.is-compact .nav-pebble { transform: translateY(0) scale(0.7); }
.nav-item.is-active { color: var(--c-violet); }
.nav-item.is-active .nav-pebble { opacity: 1; transform: translateY(-8px) scale(1); }
.is-compact .nav-item.is-active .nav-pebble { transform: translateY(0) scale(1); }

/* Centre FAB — an ordinary grid cell, in flow, no ring, no absolute offset */
.nav-fab {
  width: 48px; height: 48px;
  margin: 0 auto;
  border-radius: 50%;
  background: var(--grad-action);
  color: #fff;
  display: grid; place-items: center;
  box-shadow: var(--clay-btn), var(--glow-violet);
  transition: transform var(--t-fast) var(--ease-out);
}
.nav-fab:active {
  transform: translateY(1px) scale(0.94);
  box-shadow: var(--clay-btn-press), var(--glow-press);
}
.nav-fab svg.ico { width: 22px; height: 22px; }

/* =================== Month sheet (day grid) =================== */
.ms-head {
  display: flex; align-items: center; gap: var(--s-2);
  margin-bottom: var(--s-4);
}
.ms-head .icon-btn { width: 36px; height: 36px; flex: 0 0 auto; }
.ms-head .icon-btn[disabled] { opacity: 0.3; pointer-events: none; }
.ms-head-text { flex: 1 1 auto; min-width: 0; text-align: center; }
.ms-range {
  font-size: 11px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--c-muted);
}
.ms-total { font-size: var(--fs-xl); font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; }
.ms-avg { font-family: var(--font-hand); font-size: var(--fs-hand); color: var(--c-muted); line-height: 1.1; }

.ms-dow {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px;
  margin-bottom: 6px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--c-faint); text-align: center;
}
.ms-dow .is-we { color: var(--c-violet); }

.ms-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.ms-pad { aspect-ratio: 1 / 1.05; }
.ms-cell {
  position: relative;
  aspect-ratio: 1 / 1.05;
  border-radius: 12px;
  padding: 4px 5px;
  display: flex; flex-direction: column; justify-content: space-between;
  align-items: flex-start;
  color: var(--c-ink-soft);
  background: var(--c-surface-2);
  transition: transform var(--t-fast) var(--ease-out);
}
.ms-cell:active { transform: scale(0.92); }
.ms-cell .ms-n { font-size: 12px; font-weight: 600; line-height: 1; }
.ms-cell .ms-v { font-size: 10px; font-weight: 600; line-height: 1; align-self: flex-start; }
.ms-cell.h0 { box-shadow: var(--clay-sunken); }
.ms-cell.h0.is-we { background: var(--c-aqua-soft); }
.ms-cell.h1 { background: rgba(98, 72, 245, 0.12); }
.ms-cell.h2 { background: rgba(98, 72, 245, 0.24); }
.ms-cell.h3 { background: rgba(98, 72, 245, 0.40); color: #fff; }
.ms-cell.h4 { background: rgba(98, 72, 245, 0.60); color: #fff; }
:root[data-theme="dark"] .ms-cell.h1 { background: rgba(155, 133, 255, 0.16); }
:root[data-theme="dark"] .ms-cell.h2 { background: rgba(155, 133, 255, 0.30); }
:root[data-theme="dark"] .ms-cell.h3 { background: rgba(124, 96, 240, 0.58); color: #fff; }
:root[data-theme="dark"] .ms-cell.h4 { background: rgba(124, 96, 240, 0.85); color: #fff; }
.ms-cell.is-today { outline: 2px solid var(--c-violet); outline-offset: 1px; }
.ms-cell.is-sel { outline: 2px solid var(--c-ink); outline-offset: 1px; }
.ms-cell.is-future { opacity: 0.35; pointer-events: none; }

.ms-day { margin-top: var(--s-5); }
.ms-day-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
  font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--c-border);
}
.ms-empty { font-family: var(--font-hand); font-size: var(--fs-hand); color: var(--c-muted); padding: var(--s-3) 0; }
.ms-row {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 9px 0;
}
.ms-row + .ms-row { border-top: 1px solid var(--c-border); }
.ms-row-ico {
  width: 30px; height: 30px; flex: 0 0 auto;
  border-radius: 10px;
  display: grid; place-items: center;
  background: var(--c-violet-soft); color: var(--c-violet);
}
.ms-row-amt { font-weight: 700; font-size: var(--fs-sm); white-space: nowrap; }

/* =================== Sheets / modals =================== */
.sheet-backdrop {
  position: fixed; inset: 0;
  z-index: 100;
  background: rgba(20, 16, 51, 0.45);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
html[data-perf="low"] .sheet-backdrop {
  background: rgba(20, 16, 51, 0.6);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 101;
  margin: 0 auto;
  max-width: var(--max-w);
  max-height: 92dvh;
  overflow-y: auto;
  background: var(--c-surface);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: inset 0 1.5px 0 var(--clay-hi), var(--shadow-sheet);
  padding: var(--s-3) var(--s-5) calc(var(--s-6) + var(--safe-b));
  /* `none`, not `contain`: it also kills the rubber-band at the top, so a
     downward drag there does nothing until the dismiss gesture claims it. */
  overscroll-behavior: none;
}
.sheet-grab {
  width: 42px; height: 5px;
  border-radius: 3px;
  background: var(--c-border-strong);
  margin: 0 auto var(--s-4);
  position: relative;
  touch-action: none; /* browser must never claim this touch for scrolling */
}
.sheet-grab::before {
  /* invisible enlarged hit area — the 42x5 pill is too small a drag target */
  content: "";
  position: absolute;
  inset: -16px -70px;
}
.sheet h2 {
  font-size: var(--fs-lg); font-weight: 800; letter-spacing: -0.02em; margin-bottom: var(--s-4);
  touch-action: none;
}
@media (min-width: 640px) {
  .sheet {
    top: 50%; left: 50%; right: auto; bottom: auto;
    transform: translate(-50%, -50%);
    width: min(440px, calc(100vw - 48px));
    border-radius: var(--r-xl);
    max-height: min(86dvh, 780px);
    padding: var(--s-5) var(--s-5) var(--s-6);
    /* Reserve the scrollbar so a taller or shorter quick-add tab can't change
       the sheet's inner width mid-transition and offset the pager. Mobile uses
       overlay scrollbars, which never took the space to begin with. */
    scrollbar-gutter: stable;
  }
  .sheet-grab { display: none; }
}

/* =================== Forms =================== */
.field { margin-bottom: var(--s-4); }
.field label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--c-ink-soft);
  margin-bottom: 7px;
}
.input, select.input, textarea.input {
  width: 100%;
  min-height: 54px;
  padding: 14px 18px;
  font-size: 1.0625rem;              /* ≥16px: no iOS zoom */
  font-weight: 500;
  color: var(--c-ink);
  background: var(--c-surface-2);
  border: 1.5px solid var(--c-border);
  border-radius: var(--r-md);
  box-shadow: var(--clay-sunken);
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
  appearance: none;
}
.input::placeholder { color: var(--c-faint); font-weight: 400; }
.input:focus {
  outline: none;
  background: var(--c-surface);
  border-color: var(--c-violet);
  box-shadow: var(--clay-sunken), var(--ring-focus);
}
textarea.input { min-height: 80px; resize: vertical; }

.input-amount-wrap { position: relative; }
.input-amount-wrap .cur-prefix {
  position: absolute; left: 18px; top: 50%;
  transform: translateY(-50%);
  font-weight: 700; color: var(--c-muted);
  pointer-events: none;
}
.input-amount {
  padding-left: 52px;
  font-size: 1.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

select.input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' fill='none'%3E%3Cpath d='M1 1.5 7 7.5 13 1.5' stroke='%23837FA3' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 18px center;
  padding-right: 44px;
}

/* Recent-title quick add */
.suggest-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: 9px;
  overflow-x: auto;
  padding-bottom: 2px;
  scrollbar-width: none;
}
.suggest-row::-webkit-scrollbar { display: none; }
.suggest-label {
  flex: 0 0 auto;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.suggest-chip {
  flex: 0 0 auto;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 7px 13px;
  min-height: 36px;
  border-radius: var(--r-pill);
  background: var(--c-violet-soft);
  color: var(--c-violet-deep);
  font-size: var(--fs-sm);
  font-weight: 600;
  box-shadow: var(--clay-soft);
  transition: transform var(--t-fast) var(--ease-out), filter var(--t-fast);
}
.suggest-chip:active { transform: scale(0.94); box-shadow: var(--clay-soft-press); }

.field-error {
  display: none;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--c-neg);
  margin-top: 6px;
}
/* Balance line under a form: info states what's there, warn is a heads-up you
   can act through, block explains the greyed-out button under it. */
.form-note {
  display: flex; align-items: flex-start; gap: 7px;
  font-size: var(--fs-sm); font-weight: 600;
  color: var(--c-muted);
  margin: calc(var(--s-2) * -1) 0 var(--s-4);
}
.form-note svg.ico { flex: 0 0 auto; margin-top: 1px; }
.form-note.is-warn  { color: var(--c-warn); }
.form-note.is-block { color: var(--c-neg); }

.btn:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; }
.btn:disabled:active { transform: none; }

.field.has-error .field-error { display: block; }
.field.has-error .input { border-color: var(--c-neg); box-shadow: var(--clay-sunken), var(--ring-error); }

/* Segmented control */
.segmented {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
  padding: 4px;
  background: var(--c-bg-deep);
  border-radius: var(--r-md);
  box-shadow: var(--clay-sunken);
}
.segmented button {
  min-height: 46px;
  border-radius: calc(var(--r-md) - 4px);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--c-muted);
  transition: color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast);
}
.segmented button.is-active {
  background: var(--c-surface);
  color: var(--c-violet-deep);
  box-shadow: var(--clay-soft);
}

/* Quick-add sheet: tab strip + a JS-driven pager (see quickAddSheet).
   The axes are split by owner and that split is the whole trick:
   `pan-y` leaves vertical panning to the browser, so the sheet scrolls on the
   first swipe from anywhere — including on top of the pager — while JS owns
   the horizontal axis, so one flick moves exactly one tab. */
.qa-tabs { margin-bottom: var(--s-4); }
.qa-pager {
  /* Clips both ways: sideways to hide the neighbouring pages, vertically so a
     taller tab can't add scroll under a shorter one. That makes the measured
     height load-bearing — see lockHeight() and the image-load listener in
     quickAddSheet, which keep it honest so nothing is ever clipped away. */
  overflow: hidden;
  /* Both axes stay with the browser. `pan-y` alone kept the sheet scrolling,
     but touch-action only ever narrows as it inherits down, so it also barred
     the chip rows inside the forms from panning sideways — and the tab swipe
     then ate the gesture. The tab swipe claims the horizontal axis in JS
     instead, and yields to a chip row that still has somewhere to scroll. */
  touch-action: pan-x pan-y;
  margin: 0 calc(var(--s-5) * -1);
}
.qa-pager::-webkit-scrollbar { display: none; }
.qa-track {
  display: flex;
  align-items: flex-start;   /* each page keeps its own natural height — JS sets the container's */
  will-change: transform;
}
.qa-page {
  flex: 0 0 100%;
  padding: 0 var(--s-5);
  min-width: 0;
}

/* Toggle switch */
.toggle-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  min-height: 44px;
}
.switch {
  position: relative;
  width: 52px; height: 32px;
  border-radius: var(--r-pill);
  background: var(--c-bg-deep);
  border: 1.5px solid var(--c-border);
  box-shadow: var(--clay-sunken);
  transition: background var(--t-med) var(--ease-out);
  flex: 0 0 auto;
}
.switch::after {
  content: "";
  position: absolute;
  top: 2.5px; left: 3px;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--clay-soft);
  transition: transform var(--t-med) var(--ease-out);
}
.switch[aria-checked="true"] { background: var(--grad-action); border-color: transparent; }
.switch[aria-checked="true"]::after { transform: translateX(19px); }

.form-actions { display: flex; gap: var(--s-3); margin-top: var(--s-5); }
.form-actions .btn { flex: 1; }

/* =================== Toast =================== */
.toast-root {
  position: fixed;
  left: 50%;
  bottom: calc(var(--nav-total) + 84px);
  transform: translateX(-50%);
  z-index: 200;
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s-2);
  width: min(calc(100vw - 40px), 420px);
  pointer-events: none;
}
/* Desktop: keep toasts out of the centred modal's way */
@media (min-width: 640px) {
  .toast-root {
    left: auto;
    right: var(--s-6);
    transform: none;
    bottom: calc(var(--nav-total) + var(--s-4));
    align-items: flex-end;
    width: auto;
    max-width: 380px;
  }
}

.toast {
  pointer-events: auto;
  display: flex; align-items: center; gap: var(--s-3);
  background: var(--c-inverse);
  color: var(--c-on-inverse);
  padding: 13px 18px;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lift);
  font-size: var(--fs-sm);
  font-weight: 600;
  max-width: 100%;
}
.toast .toast-ico { display: inline-flex; flex: 0 0 auto; color: #B9A8FF; }
.toast .toast-undo {
  color: #B9A8FF;
  font-weight: 800;
  padding: 6px 10px;
  margin: -6px -8px -6px 0;
  border-radius: var(--r-sm);
  flex: 0 0 auto;
}

/* =================== Boot shell =================== */
/* Paints the lock screen's own gradient behind the very first frame, before
   any JS has run — the inline head script sets data-boot="lock" only when
   js/auth.js's "batwa.hasLock" flag says a PIN exists, so first-run and
   no-PIN users never see this. var(--grad-hero) already carries both the
   dark-theme override (:root[data-theme="dark"], tokens.css) and the low-perf
   tier (which never touches this token), so one rule covers all of them.
   z-index sits one below .lock-screen's 300 so the real screen's own paint —
   whichever variant it turns out to be — simply lands on top with nothing to
   cross-fade. Removed by js/auth.js the moment the real lock screen mounts
   (or immediately after unlock when none was needed), and by an 8s failsafe
   in the head script either way. */
html[data-boot="lock"] body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 299;
  background: var(--grad-hero);
}

/* =================== Lock screen =================== */
.lock-screen {
  position: fixed; inset: 0;
  z-index: 300;
  background: var(--grad-hero);
  color: #fff;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--s-5);
  padding: var(--s-6);
  padding-bottom: calc(var(--s-6) + var(--safe-b));
  overflow: hidden;
}
.lock-screen::before {
  content: "";
  position: absolute;
  width: 420px; height: 420px;
  border-radius: 50%;
  right: -140px; top: -180px;
  background: radial-gradient(circle, rgba(255,255,255,0.16), transparent 65%);
}
.lock-logo {
  width: 76px; height: 76px;
  border-radius: 24px;
  background: rgba(255,255,255,0.16);
  backdrop-filter: blur(8px);
  display: grid; place-items: center;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.3);
}
.lock-title { font-size: var(--fs-lg); font-weight: 800; letter-spacing: -0.02em; }
.lock-sub { font-size: var(--fs-sm); color: rgba(255,255,255,0.75); font-weight: 500; text-align: center; max-width: 280px; }

.pin-dots { display: flex; gap: 14px; height: 18px; }
.pin-dot {
  width: 14px; height: 14px;
  border-radius: 50%;
  background: rgba(255,255,255,0.28);
  box-shadow: inset 0 1px 3px rgba(12,4,60,0.35);
  transition: background var(--t-fast), transform var(--t-fast);
}
.pin-dot.is-filled {
  background: #fff;
  transform: scale(1.15);
  box-shadow: 0 2px 6px rgba(12,4,60,0.35);
}

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 76px);
  gap: 14px;
  justify-content: center;
}
.key {
  width: 76px; height: 76px;
  border-radius: 50%;
  background: rgba(255,255,255,0.13);
  backdrop-filter: blur(6px);
  color: #fff;
  font-size: 1.65rem;
  font-weight: 600;
  display: grid; place-items: center;
  box-shadow:
    inset 2px 2px 6px rgba(255,255,255,0.22),
    inset -2px -3px 6px rgba(12,4,60,0.22),
    0 8px 18px -8px rgba(12,4,60,0.35);
  transition: background var(--t-fast), transform var(--t-fast) var(--ease-out);
  user-select: none;
}
.key:active {
  background: rgba(255,255,255,0.32);
  transform: scale(0.92);
  box-shadow:
    inset 2px 3px 8px rgba(12,4,60,0.30),
    inset -1px -1px 3px rgba(255,255,255,0.10);
}
.key.key-ghost { background: transparent; backdrop-filter: none; box-shadow: none; }
@media (max-height: 640px) {
  .keypad { grid-template-columns: repeat(3, 64px); gap: 10px; }
  .key { width: 64px; height: 64px; font-size: 1.4rem; }
  .lock-logo { display: none; }
}
.lock-msg { min-height: 22px; font-size: var(--fs-sm); font-weight: 600; color: #FFD48A; text-align: center; }

/* ---- fingerprint unlock ---- */
/* Both halves are rendered; only one is un-hidden, so the swap is a fade,
   not a re-mount of the whole screen. */
.lock-pin-wrap {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s-5);
  width: 100%;
}
/* an author `display` outranks the UA's [hidden] rule — say it explicitly */
.lock-logo[hidden], .lock-bio[hidden], .lock-pin-wrap[hidden], .lock-alt[hidden] { display: none; }
.lock-bio { position: relative; display: grid; place-items: center; }
.bio-btn {
  position: relative;
  width: 96px; height: 96px;
  border-radius: 50%;
  background: rgba(255,255,255,0.16);
  backdrop-filter: blur(8px);
  color: #fff;
  display: grid; place-items: center;
  box-shadow:
    inset 2px 2px 6px rgba(255,255,255,0.22),
    inset -2px -3px 6px rgba(12,4,60,0.22),
    0 8px 18px -8px rgba(12,4,60,0.35);
  transition: background var(--t-fast), transform var(--t-fast) var(--ease-out), opacity var(--t-fast);
}
.bio-btn:active {
  background: rgba(255,255,255,0.3);
  transform: scale(0.94);
  box-shadow:
    inset 2px 3px 8px rgba(12,4,60,0.30),
    inset -1px -1px 3px rgba(255,255,255,0.10);
}
.bio-btn.is-busy { opacity: 0.6; }
.bio-ring {
  position: absolute; inset: -6px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.45);
  pointer-events: none;
}
.lock-alt {
  min-height: 44px;
  padding: 0 20px;
  width: auto;
  border-radius: var(--r-pill);
  background: transparent;
  color: rgba(255,255,255,0.9);
  border: 1.5px solid rgba(255,255,255,0.32);
  font-size: var(--fs-sm);
  font-weight: 700;
}
.lock-alt:active { background: rgba(255,255,255,0.14); }
.key.key-bio { font-size: 0; }

/* ---- "add your fingerprint" chip under the keypad ---- */
.lock-offer-slot:empty { display: none; }
.lock-offer {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  gap: 10px;
  align-items: center;
  max-width: 360px; width: 100%;
  padding: 10px 12px;
  border-radius: var(--r-lg);
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.18);
  backdrop-filter: blur(8px);
}
/* low tier: the lock screen sits on the violet gradient in both themes, so
   the flat fallback fill is a literal white-alpha, same as the blurred look
   it replaces — just without the blur cost. */
html[data-perf="low"] .lock-logo,
html[data-perf="low"] .key,
html[data-perf="low"] .bio-btn,
html[data-perf="low"] .lock-offer {
  background: rgba(255, 255, 255, 0.22);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
html[data-perf="low"] .key.key-ghost { background: transparent; }
.lock-offer-ico { display: grid; place-items: center; color: #fff; }
.lock-offer-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; text-align: left; }
.lock-offer-title { font-weight: 800; font-size: var(--fs-sm); }
.lock-offer-sub { font-size: var(--fs-xs); color: rgba(255,255,255,0.72); }
.lock-offer-btn {
  background: #fff;
  /* the lock screen is violet in both themes — keep the light-theme violet */
  color: #4630C9;
  font-weight: 800;
  font-size: var(--fs-xs);
  padding: 8px 12px;
  border-radius: var(--r-pill);
  display: grid; place-items: center;
  box-shadow:
    inset 3px 3px 8px rgba(255,255,255,0.9),
    inset -3px -4px 8px rgba(70,48,201,0.18),
    0 14px 30px -12px rgba(9,5,40,0.6);
  transition: transform var(--t-fast) var(--ease-out);
}
.lock-offer-btn:active {
  transform: scale(0.97);
  box-shadow:
    inset 3px 4px 10px rgba(70,48,201,0.22),
    0 4px 10px -6px rgba(9,5,40,0.5);
}
.lock-offer-x {
  width: 32px; height: 32px;
  border-radius: 50%;
  color: rgba(255,255,255,0.7);
  background: transparent;
  display: grid; place-items: center;
}
.lock-offer-x:active { background: rgba(255,255,255,0.16); }
@media (max-height: 700px) {
  .lock-screen { gap: var(--s-4); }
  .lock-pin-wrap { gap: var(--s-4); }
}
@media (max-height: 640px) {
  .lock-screen { gap: var(--s-3); }
  .lock-pin-wrap { gap: var(--s-3); }
  .lock-offer-sub { display: none; }
  .bio-btn { width: 84px; height: 84px; }
}

/* =================== Reports =================== */
.month-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.month-nav .month-label { font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.01em; }
.month-nav-actions { display: flex; align-items: center; gap: var(--s-1); }

/* Get data sheet */
.gd-dates {
  display: flex; gap: var(--s-3);
  margin-top: var(--s-2);
}
.gd-actions { margin-top: var(--s-4); }
.gd-actions .btn { gap: 8px; }

.tiles { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.tile {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--clay-surface);
  padding: var(--s-4);
}
.tile .tile-label {
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--c-muted);
}
.tile .tile-value { font-size: var(--fs-lg); font-weight: 800; letter-spacing: -0.02em; margin-top: 4px; }
.tile .tile-delta { font-size: var(--fs-xs); font-weight: 700; margin-top: 3px; }
.tile-value.pos, .tile-delta.pos { color: var(--c-pos); }
.tile-value.neg, .tile-delta.neg { color: var(--c-neg); }

.chart-card { padding: var(--s-5); }
.chart-card h3 { margin-bottom: var(--s-4); }
/* charts keep their aspect ratio — cap the width so they don't tower on desktop */
.chart-card > svg { max-width: 520px; margin: 0 auto; display: block; }

.cat-row { display: flex; align-items: center; gap: var(--s-3); padding: 9px 0; }
.cat-row .cat-dot { width: 10px; height: 10px; border-radius: 4px; flex: 0 0 auto; }
.cat-row .cat-bar-track {
  flex: 1;
  height: 8px;
  border-radius: 4px;
  background: var(--c-bg-deep);
  overflow: hidden;
}
.cat-row .cat-bar { height: 100%; border-radius: 4px; }
.cat-row .cat-amt { font-size: var(--fs-sm); font-weight: 700; white-space: nowrap; }
.cat-row .cat-pct { font-size: var(--fs-xs); color: var(--c-muted); width: 38px; text-align: right; }

.insight {
  display: flex; gap: var(--s-3); align-items: flex-start;
  background: linear-gradient(135deg, rgba(98,72,245,0.08), rgba(63,188,248,0.07));
  border: 1px solid rgba(98,72,245,0.15);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  font-family: var(--font-hand);
  font-size: var(--fs-hand);
  line-height: 1.3;
  font-weight: 500;
  color: var(--c-violet-deep);
}
.insight .spark { flex: 0 0 auto; font-size: 18px; }

.filter-row {
  display: flex; gap: var(--s-2);
  overflow-x: auto;
  padding-bottom: 4px;
  margin: var(--s-3) 0;
  scrollbar-width: none;
}
.filter-row::-webkit-scrollbar { display: none; }
.filter-chip {
  flex: 0 0 auto;
  padding: 8px 16px;
  min-height: 38px;
  border-radius: var(--r-pill);
  background: var(--c-surface);
  border: 1.5px solid var(--c-border);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--c-muted);
  box-shadow: var(--clay-soft);
  transition: color var(--t-fast), background var(--t-fast), transform var(--t-fast);
}
.filter-chip.is-active {
  background: var(--c-inverse);
  border-color: var(--c-inverse);
  color: var(--c-on-inverse);
  box-shadow: var(--clay-btn), var(--glow-press);
}

.hist-row {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
}
.hist-row + .hist-row { border-top: 1px solid var(--c-border); }
.hist-row.is-day-first { border-top: none; }

/* Day divider inside the transaction card: date pill, day name, amount chips */
.hist-day {
  display: flex; align-items: center; gap: 10px;
  padding: 14px var(--s-4) 8px;
}
.hist-day:not(.is-first) { border-top: 1px solid var(--c-border); margin-top: 4px; }
.hist-day + .hist-row { border-top: none; }
.hist-day-date {
  width: 40px; height: 40px; flex: none;
  border-radius: 12px;
  background: var(--c-violet-soft); color: var(--c-violet);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  line-height: 1;
}
.hist-day-date b { font-size: 15px; font-weight: 800; }
.hist-day-date small { font-size: 9px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; margin-top: 3px; opacity: 0.85; }
.hist-day.is-today .hist-day-date { background: var(--grad-action); color: #fff; box-shadow: var(--clay-btn); }
.hist-day-name { flex: 1; min-width: 0; font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hist-day-name span { color: var(--c-muted); font-weight: 500; }
.hist-day-sum { display: flex; gap: 6px; flex: none; }
.hist-day-sum .sum {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: var(--fs-xs); font-weight: 700;
  padding: 5px 9px; border-radius: var(--r-pill);
  background: var(--c-bg-deep); color: var(--c-ink);
}
.hist-day-sum .sum.in { background: var(--c-pos-soft); color: var(--c-pos); }
.hist-day-sum .sum.tr { background: var(--c-aqua-soft); color: var(--c-aqua); }
.hist-ico {
  width: 42px; height: 42px;
  border-radius: 14px;
  display: grid; place-items: center;
  font-size: 17px;
  flex: 0 0 auto;
}
.hist-amt { font-weight: 800; font-size: var(--fs-base); white-space: nowrap; }
.hist-amt.in { color: var(--c-pos); }

/* ---- Spending breakdown: Category / Title ---- */
.bd-tabs { margin-bottom: var(--s-4); }

.title-item + .title-item { border-top: 1px solid var(--c-border); }
.title-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  min-height: 56px;
  padding: 10px 0;
  text-align: left;
  border-radius: var(--r-sm);
  transition: background var(--t-fast);
}
.title-row:active { background: var(--c-surface-2); }
.title-rank {
  width: 26px; height: 26px;
  border-radius: var(--r-pill);
  display: grid; place-items: center;
  font-size: var(--fs-xs); font-weight: 800;
  background: var(--c-bg-deep);
  color: var(--c-muted);
}
.title-rank.is-top { background: var(--grad-action); color: var(--c-on-grad); }

.title-main { min-width: 0; }
.title-name { display: block; font-size: var(--fs-sm); font-weight: 700; }
.title-count {
  margin-left: 6px;
  padding: 1px 7px;
  background: var(--c-violet-soft);
  color: var(--c-violet-deep);
}
.title-meta { display: block; margin-top: 2px; }
.title-main .cat-bar-track {
  display: block;
  height: 6px;
  margin-top: 7px;
  border-radius: 3px;
  background: var(--c-bg-deep);
  overflow: hidden;
}
.title-main .cat-bar { display: block; height: 100%; border-radius: 3px; }

.title-right { text-align: right; }
.title-amt { display: block; font-size: var(--fs-sm); font-weight: 800; white-space: nowrap; }
.title-pct { display: block; font-size: var(--fs-xs); color: var(--c-muted); }

.title-detail {
  margin: 0 0 var(--s-3) 34px;
  padding-left: var(--s-3);
  border-left: 2px solid var(--c-border-strong);
}
.title-detail-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  min-height: 44px;
  padding: 4px 6px;
  border-radius: var(--r-sm);
  text-align: left;
  transition: background var(--t-fast);
}
.title-detail-row:active { background: var(--c-surface-2); }

.title-more { width: 100%; min-height: 44px; margin-top: var(--s-3); }

/* ---- Spending patterns ---- */
.pattern-block > svg { max-width: 520px; margin: 0 auto; display: block; }
.pattern-block + .pattern-block {
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--c-border);
}
.pattern-cap { color: var(--c-violet-deep); margin-top: var(--s-2); font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.3; }

.stat-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-2);
  margin-top: var(--s-4);
}
.stat {
  background: var(--c-surface-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  padding: 10px 8px;
  text-align: center;
  min-width: 0;
}
.stat-v {
  /* must not overflow a third of a 320px screen */
  font-size: clamp(0.95rem, 4.2vw, var(--fs-md));
  font-weight: 800;
  letter-spacing: -0.02em;
}
.stat-l {
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--c-muted);
  line-height: 1.25;
}
.stat-s { font-size: var(--fs-xs); color: var(--c-muted); }

.pace {
  display: flex; align-items: center; gap: var(--s-2);
  margin-top: var(--s-4);
  color: var(--c-ink-soft);
  font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.3;
}
.pace.is-over { color: var(--c-warn); }
.pace svg.ico { flex: 0 0 auto; }

/* ---- Worth watching ---- */
.watch-card {
  margin-top: var(--s-4);
  padding: var(--s-5);
  border: 1px solid rgba(232, 147, 12, 0.28);
  background: linear-gradient(135deg, rgba(232, 147, 12, 0.08), rgba(239, 70, 103, 0.05));
}
.watch-head {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-sm); font-weight: 700;
  color: var(--c-warn);
}
.watch-line {
  display: flex; gap: var(--s-3);
  padding: 8px 0;
  font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.3;
  color: var(--c-ink-soft);
}
.watch-line::before {
  content: "";
  flex: 0 0 auto;
  width: 6px; height: 6px;
  margin-top: 0.55em;
  border-radius: var(--r-pill);
  background: var(--c-warn);
}
.watch-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }

/* ---- Reports layout: one stack on phones, two columns on desktop ---- */
.reports-grid { display: grid; grid-template-columns: minmax(0, 1fr); }
.rg-col { min-width: 0; }
@media (min-width: 900px) {
  .reports-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s-6);
    align-items: start;
  }
  /* the right column starts level with the tiles, not one section-head lower */
  .rg-col > .section-head:first-child { margin-top: 0; }
}

/* =================== Settings =================== */
.set-group { margin-bottom: var(--s-6); }
.set-group > h2 { margin-bottom: var(--s-3); font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: 0.07em; color: var(--c-muted); }
.set-card { overflow: hidden; }
.set-row {
  display: flex; align-items: center; gap: var(--s-3);
  width: 100%;
  min-height: 56px;
  padding: var(--s-3) var(--s-4);
  text-align: left;
  font-size: var(--fs-base);
  font-weight: 500;
  transition: background var(--t-fast);
}
.set-row + .set-row { border-top: 1px solid var(--c-border); }
.set-row:active { background: var(--c-surface-2); }
.set-row[disabled] { opacity: 0.55; }
.set-row[disabled]:active { background: transparent; }
.set-row .set-ico {
  width: 38px; height: 38px;
  border-radius: 12px;
  display: grid; place-items: center;
  flex: 0 0 auto;
}
.set-row .chev { margin-left: auto; color: var(--c-faint); flex: 0 0 auto; }
.set-row .set-val { margin-left: auto; color: var(--c-muted); font-size: var(--fs-sm); }

/* ---- Settings > About footer ---- */
.set-about {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s-2);
  padding: var(--s-7) 0 var(--s-4);
  text-align: center;
}
/* The avatar image is a transparent cut-out, so the lavender plate is CSS and
   follows the theme. .set-avatar-fb is the initials chip when it fails to load. */
.set-avatar {
  width: 72px; height: 72px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--c-violet-soft);
  border: 3px solid var(--c-surface);
  box-shadow: var(--shadow-card);
}
.set-avatar-fb {
  display: grid; place-items: center;
  font-size: var(--fs-lg); font-weight: 700; letter-spacing: 0.04em;
  color: var(--c-violet);
}
.set-about p { margin: 0; }
.set-about .set-author {
  font-size: var(--fs-base); font-weight: 600;
  color: var(--c-violet); text-decoration: none;
}
.set-about .row { gap: var(--s-2); margin-top: var(--s-2); }
.set-about .btn { text-decoration: none; }

/* Settings on a wide shell: two columns, the About footer across both. */
.set-grid { display: block; }
@media (min-width: 900px) {
  /* Multi-column rather than grid: the two columns balance by height, so a
     short Security group never leaves a hole beside a tall Backup card. */
  .set-grid { columns: 2; column-gap: var(--s-6); }
  .set-grid > .set-group { break-inside: avoid; }
  .set-about { column-span: all; }
}

/* Manage-accounts drag-to-reorder list */
.acc-reorder-list { gap: var(--s-2); }
.acc-reorder-row {
  border-radius: 16px;
  padding: 0;
  gap: 0;
  touch-action: pan-y;
  position: relative;
}
.acc-reorder-row.is-dragging {
  z-index: 5;
  box-shadow: var(--shadow-lift);
  background: var(--c-surface);
}
.acc-reorder-tap {
  flex: 1;
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 56px;
  padding: var(--s-3) var(--s-4) var(--s-3) 4px;
  text-align: left;
}
.acc-reorder-tap:active { background: var(--c-surface-2); }
.drag-handle {
  flex: 0 0 auto;
  width: 40px; height: 56px;
  display: grid; place-items: center;
  color: var(--c-faint);
  touch-action: none;
  cursor: grab;
}
.drag-handle:active { cursor: grabbing; color: var(--c-muted); }

.warn-card {
  background: var(--c-warn-soft);
  border: 1px solid rgba(232,147,12,0.3);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  font-size: var(--fs-sm);
  color: #8A5A08;
  font-weight: 500;
}

.cat-pill {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--c-surface);
  border: 1.5px solid var(--c-border);
  border-radius: var(--r-pill);
  padding: 8px 10px 8px 16px;
  font-size: var(--fs-sm);
  font-weight: 600;
}
.cat-pill .cat-x {
  width: 24px; height: 24px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: var(--c-neg-soft);
  color: var(--c-neg);
  font-size: 12px;
}

/* =================== Install banner =================== */
.install-banner {
  display: flex; align-items: center; gap: var(--s-3);
  background: var(--c-inverse);
  color: var(--c-on-inverse);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  margin-bottom: var(--s-4);
  box-shadow: var(--shadow-lift);
}
.install-banner .ib-ico {
  width: 44px; height: 44px;
  border-radius: 14px;
  background: var(--grad-action);
  display: grid; place-items: center;
  flex: 0 0 auto;
}
.install-banner p { font-size: var(--fs-sm); color: rgba(255,255,255,0.75); }
.install-banner strong { display: block; font-size: var(--fs-base); color: #fff; }
/* Always dark text on the white chip — never theme ink (light in dark mode). */
.install-banner .ib-action {
  flex: 0 0 auto;
  background: #fff;
  color: #17133A;
}
.install-banner .ib-action:active { color: #17133A; }
.install-banner .ib-dismiss {
  flex: 0 0 auto;
  width: 36px; height: 36px;
  background: transparent;
  border: none;
  box-shadow: none;
  color: rgba(255,255,255,0.6);
}

/* =================== Misc =================== */
.divider { height: 1px; background: var(--c-border); margin: var(--s-4) 0; }

/* inline SVG icons: baseline-align inside running text, flex-align elsewhere */
svg.ico { display: inline-block; vertical-align: -0.16em; flex: 0 0 auto; }
.btn svg.ico, .badge svg.ico, .chip-btn svg.ico, .sync-pill svg.ico,
.acc-chip svg.ico, .set-ico svg.ico, .empty-ico svg.ico, .hist-ico svg.ico,
.acc-logo svg.ico, .toast-ico svg.ico { vertical-align: middle; }
.sync-pill .spin { display: inline-flex; }
.insight .spark { color: var(--c-violet); display: inline-flex; }
.insight span:last-child svg.ico { color: var(--c-violet); }
.warn-card svg.ico { vertical-align: -0.18em; margin-right: 2px; }

.legend { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: var(--s-3); }
.legend .lg-item { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-xs); font-weight: 600; color: var(--c-ink-soft); }
.legend .lg-dot { width: 9px; height: 9px; border-radius: 3px; }

.sync-status { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-xs); font-weight: 600; color: var(--c-muted); }
.sync-status .sync-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c-pos); }
.sync-status.is-stale .sync-dot { background: var(--c-warn); }
.sync-status.is-off .sync-dot { background: var(--c-faint); }

#view { min-height: 40vh; }

/* =================== Daily helpers =================== */

/* Quick math under the amount field — a one-line tip, so it wears the hand font */
.amt-hint {
  margin-top: 7px;
  font-family: var(--font-hand);
  font-size: var(--fs-hand);
  line-height: 1.2;
  color: var(--c-violet-deep);
}
.amt-hint[hidden] { display: none; }

/* Android's decimal keypad has no operators — these do */
.amt-ops { display: grid; grid-auto-flow: column; gap: var(--s-2); margin-top: 9px; }
.amt-ops[hidden] { display: none; }
.amt-ops .amt-op {
  min-height: 40px;
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  border: 1.5px solid var(--c-border);
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--c-ink-soft);
}
.amt-ops .amt-op:active { transform: scale(0.95); }

/* Backup nudge — same shape as the install banner, warmer and never violet,
   so the two are never mistaken for each other */
.nudge-banner {
  display: flex; align-items: center; gap: var(--s-3);
  flex-wrap: wrap;
  background: var(--c-warn-soft);
  color: var(--c-ink);
  border: 1.5px solid rgba(232, 147, 12, 0.28);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  margin-bottom: var(--s-4);
}
.nudge-banner .nb-ico {
  width: 44px; height: 44px;
  border-radius: 14px;
  background: #fff;
  color: var(--c-warn);
  display: grid; place-items: center;
  flex: 0 0 auto;
}
.nudge-banner .grow { flex: 1 1 180px; min-width: 0; }
.nudge-banner strong { display: block; font-size: var(--fs-base); }
.nudge-banner p { font-size: var(--fs-sm); color: var(--c-ink-soft); }
/* beside the text when it fits, right-aligned on its own row when it doesn't */
.nudge-banner .nb-actions { display: flex; gap: var(--s-2); margin-left: auto; flex: 0 0 auto; }

/* Category limits — the same track everywhere it appears */
.limit-track {
  flex: 1;
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--c-border);
  box-shadow: var(--clay-sunken);
  overflow: hidden;
  display: block;
}
.limit-fill { display: block; height: 100%; border-radius: 2px; background: var(--c-pos); }
.limit-fill.is-warn { background: var(--c-warn); }
.limit-fill.is-over { background: var(--c-neg); }

/* Reports: second line under a category row */
.cat-limit {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: -4px 0 6px 22px;
  font-size: var(--fs-xs);
  color: var(--c-muted);
}
.cat-limit .cat-limit-txt { white-space: nowrap; text-align: right; font-weight: 600; }
.cat-limit .cat-limit-txt.is-warn { color: var(--c-warn); }
.cat-limit .cat-limit-txt.is-over { color: var(--c-neg); }

/* Home: compact limits card */
.limits-card { padding: var(--s-4); margin-bottom: var(--s-4); }
.limits-card .limits-head {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink-soft);
  margin-bottom: var(--s-3);
}
.limits-card .limit-row {
  display: grid;
  grid-template-columns: 18px minmax(56px, 1fr) minmax(40px, 1.4fr) auto;
  align-items: center;
  gap: var(--s-2);
  padding: 5px 0;
}
.limits-card .limit-ico { color: var(--c-muted); display: inline-flex; }
.limits-card .limit-num { color: var(--c-muted); white-space: nowrap; }
.limits-card .limits-more { margin-top: var(--s-2); width: 100%; }

/* Settings: one row per category, with its cap */
.cat-limit-list { display: flex; flex-direction: column; }
.cat-limit-row {
  display: grid;
  grid-template-columns: 1fr 110px auto;
  align-items: center;
  gap: var(--s-3);
  padding: 8px 0;
}
/* Tapping the name (or its icon) opens the inline editor on that category. */
.cat-limit-row .cat-row-name {
  display: flex; align-items: center; gap: var(--s-3);
  min-width: 0; min-height: 40px;
  text-align: left; color: inherit;
}
.cat-limit-row .cat-row-name:active { transform: scale(0.98); }
.cat-limit-row.is-confirming { grid-template-columns: 1fr auto auto; }
.cat-limit-row .cat-confirm-txt { color: var(--c-neg); font-weight: 700; min-width: 0; }
.cat-limit-row + .cat-limit-row { border-top: 1px solid var(--c-border); }
.cat-limit-row .cat-limit-ico { color: var(--c-muted); display: inline-flex; }
.cat-limit-row .cat-limit-field { position: relative; display: block; }
.cat-limit-row .cat-limit-cur {
  position: absolute; left: 11px; top: 50%;
  transform: translateY(-50%);
  font-size: var(--fs-xs); font-weight: 700; color: var(--c-muted);
  pointer-events: none;
}
.cat-limit-row .input-sm {
  min-height: 40px;
  padding: 8px 10px 8px 34px;
  font-size: var(--fs-sm);
  font-weight: 700;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.cat-limit-row .cat-x-spacer { width: 24px; height: 24px; }

/* =================== Inline category editor ===================
   The "+" beside a Category dropdown opens a well INSIDE the sheet — never a
   nested sheet (openSheet closes the one already open). Sunken surface, so it
   reads as a recess in the card rather than a second card on top of it. */
.cat-row { display: flex; gap: var(--s-2); align-items: center; }
.cat-row select.input { flex: 1; min-width: 0; }
.cat-plus {
  width: 44px; height: 54px;
  border-radius: var(--r-md);
  flex: none;
  color: var(--c-violet);
}

/* ---- Category editor: a floating dialog above the sheet and the keyboard ----
   Mounted on <body> by catedit.js. Sheets sit at z 100/101, toasts at 200. */
.cat-dialog-layer { position: fixed; inset: 0; z-index: 150; --kb: 0px; }
.cat-dialog-layer[hidden] { display: none; }
.cat-dialog-backdrop {
  position: absolute; inset: 0;
  background: rgba(23, 19, 58, 0.28);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
html[data-perf="low"] .cat-dialog-backdrop {
  background: rgba(23, 19, 58, 0.4);
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
.cat-dialog {
  position: absolute; left: 12px; right: 12px;
  bottom: calc(var(--kb) + 12px + var(--safe-b));
  width: min(440px, calc(100% - 24px)); margin: 0 auto;
  max-height: calc(100% - var(--kb) - 24px - var(--safe-b));
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--s-4);
  border-radius: var(--r-xl);
  background: var(--c-surface); color: var(--c-ink);
  box-shadow: var(--clay-surface), 0 24px 60px -20px rgba(23, 19, 58, 0.45);
}
.cat-dialog.is-open { animation: cat-dialog-in 200ms var(--ease-out) both; }
@keyframes cat-dialog-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .cat-dialog.is-open { animation: none; } }
:root[data-theme="dark"] .cat-dialog-backdrop { background: rgba(0, 0, 0, 0.5); }
:root[data-theme="dark"] .cat-dialog { box-shadow: var(--clay-surface), 0 24px 60px -20px rgba(0, 0, 0, 0.8); }
/* :root[data-theme="dark"] (pseudo-class + attribute + class) outranks the
   plain html[data-perf="low"] (type + attribute + class) low-tier override
   above, so the dark background wins it back without this — same fix as
   tokens.css's shadow tokens. A bit more opaque than the dark rule above,
   since there's no blur left to help hide what's behind it. */
html[data-perf="low"][data-theme="dark"] .cat-dialog-backdrop { background: rgba(0, 0, 0, 0.65); }

.cat-dialog-tabs { margin-bottom: var(--s-3); }
.cat-dialog-tabs .segmented button { min-height: 40px; }
.cat-dialog .field { margin-bottom: var(--s-3); }
.cat-dialog .cat-dialog-name .input { min-height: 48px; }

/* one horizontal strip of icons keeps the dialog short above the keyboard */
.cat-ico-strip {
  display: flex; gap: 8px;
  overflow-x: auto; overscroll-behavior-x: contain;
  padding: 4px 2px 10px; margin: 0 -2px var(--s-2);
  scrollbar-width: none; scroll-snap-type: x proximity;
}
.cat-ico-strip::-webkit-scrollbar { display: none; }
.cat-ico-opt {
  flex: none; width: 46px; height: 46px;
  border-radius: 14px; display: grid; place-items: center;
  color: var(--c-ink-soft); background: var(--c-surface-2);
  box-shadow: var(--clay-soft);
  scroll-snap-align: center;
  transition: transform var(--t-fast) var(--ease-out);
}
.cat-ico-opt:active { transform: scale(0.94); box-shadow: var(--clay-soft-press); }
.cat-ico-opt.is-active { color: #fff; background: var(--grad-action); box-shadow: var(--clay-btn), var(--glow-violet); }

.cat-dialog-foot { display: flex; align-items: center; gap: var(--s-2); }
.cat-dialog-foot .cat-limit-field { position: relative; flex: 1; min-width: 0; display: block; }
.cat-dialog .cat-limit-cur {
  position: absolute; left: 13px; top: 50%;
  transform: translateY(-50%);
  font-size: var(--fs-xs); font-weight: 700; color: var(--c-muted);
  pointer-events: none;
}
.cat-dialog .cat-limit-field .input-sm {
  width: 100%; min-height: 48px;
  padding: 8px 12px 8px 36px;
  font-weight: 700; font-variant-numeric: tabular-nums;
}
.cat-dialog-actions { display: flex; gap: var(--s-2); flex: none; }
.cat-dialog-actions .btn-sm { min-height: 48px; }
.cat-dialog .is-disabled { opacity: 0.55; }
.cat-dialog .hint {
  font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.15;
  color: var(--c-muted); margin: 0 0 var(--s-2);
}
.cat-dialog .cat-err { display: none; margin: 0 0 var(--s-2); }
.cat-dialog .cat-err.is-on { display: block; }

/* =================== Dark theme touch-ups ===================
   The tokens carry almost everything; these are the few places that were
   painted with a literal light colour. Surfaces that sit on the violet
   gradient (lock screen, onboarding, hero) already read as dark and are
   deliberately left alone. */
:root[data-theme="dark"] .bottom-nav {
  background: rgba(26, 22, 56, 0.82);
  border-color: rgba(155, 133, 255, 0.14);
  box-shadow:
    inset 0 1px 0 rgba(155, 133, 255, 0.14),
    inset 0 -2px 0 rgba(0, 0, 0, 0.35),
    0 10px 30px -10px rgba(0, 0, 0, 0.7);
}
/* This dark rule (specificity 0,3,0) already outranks the plain low-tier
   html[data-perf="low"] .bottom-nav rule (0,2,1), so its own background
   value — not the light-theme rgba(255,255,255,0.94) above — wins by
   default. Restate it explicitly anyway, with a proper solid token rather
   than the incidental rgba this dark rule happens to carry, so the low-tier
   dark fill doesn't depend on that happy accident of selector counting. */
html[data-perf="low"][data-theme="dark"] .bottom-nav {
  background: var(--c-surface-2);
  box-shadow: var(--shadow-xs);
}
:root[data-theme="dark"] .badge-once {
  background: rgba(148, 160, 184, 0.16);
  color: #A9B4CA;
}
:root[data-theme="dark"] .warn-card {
  border-color: rgba(255, 180, 63, 0.28);
  color: #FFCE85;
}
:root[data-theme="dark"] .nudge-banner {
  border-color: rgba(255, 180, 63, 0.24);
  background: linear-gradient(135deg, rgba(255, 180, 63, 0.12), rgba(255, 112, 137, 0.08));
}
:root[data-theme="dark"] .nudge-banner .nb-ico {
  background: rgba(255, 255, 255, 0.1);
}
:root[data-theme="dark"] .sheet-backdrop {
  background: rgba(4, 2, 16, 0.62);
}
/* Same specificity story as .bottom-nav/.cat-dialog-backdrop above — this
   dark rule already outranks the plain low-tier html[data-perf="low"]
   .sheet-backdrop rule, so its own background wins by default. Restate it a
   touch more opaque, to make up for the blur that used to soften what's
   behind it. */
html[data-perf="low"][data-theme="dark"] .sheet-backdrop {
  background: rgba(4, 2, 16, 0.78);
}
/* Bank marks are transparent SVGs drawn for a white card — keep the card white
   in both themes, just soften its edge so it doesn't glare. */
:root[data-theme="dark"] .acc-logo {
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}
:root[data-theme="dark"] .acc-invite {
  border-color: rgba(155, 133, 255, 0.35);
  background: rgba(155, 133, 255, 0.07);
}
:root[data-theme="dark"] .amt-ops .amt-op {
  color: var(--c-ink);
}

/* =================== Shared spaces ===================
   Clay, level 1: pastel-to-white cards with the space colour as a strip, never
   a saturated fill (the canopy is the one saturated surface). Everything here
   is hidden entirely when the phone holds no spaces — the header button is the
   only entry point and app.js keeps it `.hidden`. */

/* ---- header button ---- */
.spaces-btn { position: relative; }
.spaces-btn.has-pending::after,
.spaces-btn.has-trouble::after {
  content: "";
  position: absolute; top: 8px; right: 8px;
  width: 8px; height: 8px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
  animation: sp-dot-in 240ms var(--ease-out) both;
}
.spaces-btn.has-trouble::after { background: #FFD48A; }
@keyframes sp-dot-in { from { transform: scale(0); } to { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .spaces-btn.has-pending::after { animation: none; } }

/* ---- switcher grid ---- */
.sp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.sp-grid-actions { margin-top: var(--s-3); }
.sp-card {
  position: relative;
  display: flex; flex-direction: column; align-items: flex-start; gap: 7px;
  min-height: 132px;
  padding: var(--s-4) var(--s-3) var(--s-3);
  border: none; border-radius: var(--r-lg);
  background: var(--c-surface); color: var(--c-ink);
  box-shadow: var(--clay-surface);
  text-align: left; overflow: hidden;
  transition: transform var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
}
.sp-card:active { transform: scale(0.97); box-shadow: var(--clay-soft-press); }
.sp-card .sp-strip {
  position: absolute; inset: 0 0 auto 0; height: 6px;
  background: var(--sp, var(--c-violet));
}
.sp-card-name { font-size: var(--fs-base); font-weight: 800; letter-spacing: -0.01em; max-width: 100%; }
.sp-net { font-size: var(--fs-xs); font-weight: 700; margin-top: auto; }
.sp-net.is-pos { color: var(--c-pos); }
.sp-net.is-neg { color: var(--c-warn); }
.sp-net.is-flat { color: var(--c-muted); }
.sp-warn { font-size: var(--fs-xs); font-weight: 700; color: var(--c-neg); }
.sp-pill {
  font-size: 10px; font-weight: 800; letter-spacing: 0.02em;
  padding: 3px 8px; border-radius: var(--r-pill);
  background: var(--c-violet-soft); color: var(--c-violet-deep);
}
.sp-card-ghost {
  min-height: 84px; align-items: center; justify-content: center; gap: var(--s-2);
  background: transparent; box-shadow: none;
  border: 1.5px dashed var(--c-border-strong);
  font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink-soft);
}
.sp-ghost-ico { color: var(--c-violet); display: inline-flex; }

/* ---- member avatars ---- */
.sp-stack { display: inline-flex; --av: 26px; }
.sp-stack .sp-av + .sp-av { margin-left: -8px; }
.sp-av {
  width: var(--av, 26px); height: var(--av, 26px);
  border-radius: 50%; flex: 0 0 auto;
  display: inline-grid; place-items: center;
  font-size: 10px; font-weight: 800; letter-spacing: 0.02em;
  color: #fff; background: var(--c-violet);
  border: 2px solid var(--c-surface);
}
.sp-av.is-more { background: var(--c-faint); }

/* ---- colour picker ---- */
.sp-colors { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.sp-color {
  width: 38px; height: 38px; border-radius: 50%;
  border: none; background: var(--dot);
  box-shadow: var(--clay-soft);
  transition: transform var(--t-fast) var(--ease-out);
}
.sp-color:active { transform: scale(0.9); }
.sp-color.is-active {
  outline: 2.5px solid var(--c-ink); outline-offset: 3px;
}

/* ---- invite ---- */
.sp-invite .sp-qr {
  width: min(232px, 68vw); margin: 0 auto var(--s-4);
  padding: var(--s-3); border-radius: var(--r-lg);
  background: #fff; color: #17133A;
  box-shadow: var(--clay-surface);
}
.sp-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-xs); font-weight: 500; line-height: 1.5;
  word-break: break-all; resize: none;
}
textarea.input.sp-code { min-height: 84px; resize: none; }
/* a confirm step inside a sheet gets a sentence, not a section label */
.sp-panel-title {
  font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.02em;
  margin-bottom: var(--s-2);
}
.sp-err {
  font-size: var(--fs-sm); font-weight: 600; color: var(--c-neg);
  margin: -4px 0 var(--s-3);
}
.sp-err[hidden] { display: none; }
.sp-hand {
  font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.2;
  color: var(--c-violet-deep); margin: 0 0 var(--s-3);
}
.sp-scan {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--r-lg); background: #000; margin-bottom: var(--s-3);
}

/* ---- confirm + members ---- */
.sp-confirm-head {
  position: relative; display: flex; flex-direction: column; gap: 4px;
  padding: var(--s-4) var(--s-4) var(--s-4) var(--s-5);
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--c-surface-2); box-shadow: var(--clay-surface);
  margin-bottom: var(--s-4);
}
.sp-confirm-head .sp-strip {
  position: absolute; inset: 0 auto 0 0; width: 6px; background: var(--sp);
}
.sp-confirm-name { font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.02em; }
.sp-subhead {
  font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--c-muted); margin: var(--s-5) 0 var(--s-2);
}
.sp-members { border-radius: var(--r-md); overflow: hidden; background: var(--c-surface-2); }
.sp-member {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 10px var(--s-3);
}
.sp-member + .sp-member { border-top: 1px solid var(--c-border); }

/* =================== Space view =================== */
.sv-head { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-4); }
.sv-title { flex: 1; min-width: 0; font-size: var(--fs-md); font-weight: 800; color: #fff; letter-spacing: -0.02em; }
.canopy .sv-back, .canopy .sv-head .icon-btn {
  background: rgba(255, 255, 255, 0.16); color: #fff;
  border: none; box-shadow: none; width: 38px; height: 38px;
}
.sv-head .sp-av { border-color: rgba(255, 255, 255, 0.28); }
.sv-hero .hero-label { color: rgba(255, 255, 255, 0.72); }
.sv-net.is-pos { color: #BFF5E2; }
.sv-net.is-neg { color: #FFE0AE; }
.sv-section .card { padding: var(--s-2) var(--s-4); }
.sv-quiet {
  font-size: var(--fs-sm); color: var(--c-muted);
  padding: var(--s-3) 0; margin: 0;
}
.sv-hand {
  font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.25;
  color: var(--c-muted); max-width: 260px; margin: 0 auto;
}
.sv-bal { display: flex; align-items: center; gap: var(--s-3); padding: 11px 0; }
.sv-bal + .sv-bal { border-top: 1px solid var(--c-border); }
.sv-row .sv-dots { display: inline-flex; gap: 4px; margin-top: 4px; }
.sv-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--dot); opacity: 0.35;
}
.sv-dot.is-accepted { opacity: 1; }
.sv-dot.is-rejected { background: var(--c-neg); opacity: 1; }
.sv-conflict {
  display: flex; flex-direction: column; gap: 2px; padding: 11px 0;
}
.sv-conflict + .sv-conflict { border-top: 1px solid var(--c-border); }

@media (prefers-reduced-motion: reduce) {
  .sp-card, .sp-color { transition: none; }
}


/* =================== Shared expenses (M3) ===================
   Three surfaces: the "Share with" row inside the expense sheet, the accept
   card that answers a proposal, and the Home/Reports summaries. All clay
   level 1 — quiet cards, sunken wells for anything you type into — and all of
   it invisible on a phone that holds no spaces. */

/* ---- "Share with" inside the expense form ---- */
.share-with .filter-row { flex-wrap: wrap; }
.share-chip .share-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
}
.share-panel {
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-3) var(--s-2);
  border-radius: var(--r-lg);
  background: var(--c-surface-2);
  box-shadow: var(--clay-sunken);
}
.share-panel[hidden] { display: none; }
.share-panel .field { margin-bottom: var(--s-3); }
.share-panel label { font-size: var(--fs-xs); }
.share-who .sp-av { border-width: 1.5px; }
.share-who.is-locked { opacity: 0.9; cursor: default; }
.share-custom { display: grid; gap: 8px; margin-bottom: var(--s-2); }
.share-cell {
  display: flex; align-items: center; gap: var(--s-3);
}
.share-cell-name {
  flex: 1; min-width: 0;
  font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink-soft);
}
.share-cell-in { position: relative; flex: 0 0 132px; }
.share-cell-in .cur-prefix {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  font-size: var(--fs-xs); font-weight: 700; color: var(--c-muted); pointer-events: none;
}
.share-cell-in .input { padding-left: 34px; text-align: right; }
.share-cell-in .input:disabled { opacity: 0.55; }
.share-rem {
  font-size: var(--fs-xs); font-weight: 800; margin: 0 0 6px;
}
.share-rem.is-ok { color: var(--c-pos); }
.share-rem.is-off { color: var(--c-warn); }
.share-rem[hidden] { display: none; }
.share-hand {
  font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.2;
  color: var(--c-violet-deep); margin: 0 0 4px;
}
.share-hand[hidden] { display: none; }

/* ---- the accept card ---- */
.pend-card {
  padding: var(--s-4);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  box-shadow: var(--clay-surface);
}
.pend-card + .pend-card { margin-top: var(--s-3); }
.pend-card.is-done { opacity: 0.5; pointer-events: none; }
.pend-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-3); }
.pend-space-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.pend-title {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-md); font-weight: 800; letter-spacing: -0.02em;
}
.pend-cat { color: var(--c-violet); display: inline-flex; flex: 0 0 auto; }
.pend-total { margin-left: auto; font-weight: 800; }
.pend-sub { margin: 2px 0 var(--s-3); }
.pend-changed {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 0 0 var(--s-3); padding: 9px 11px;
  border-radius: var(--r-md);
  background: var(--c-warn-soft); color: var(--c-warn);
  font-size: var(--fs-xs); font-weight: 700; line-height: 1.35;
}
.pend-share {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: var(--s-3);
  border-radius: var(--r-md);
  background: var(--c-violet-soft);
  margin-bottom: var(--s-4);
}
.pend-share-label { font-size: var(--fs-xs); font-weight: 700; color: var(--c-violet-deep); }
.pend-share-amt {
  font-size: 1.5rem; font-weight: 800; letter-spacing: -0.02em; color: var(--c-violet-deep);
}
.pend-actions { display: flex; gap: var(--s-2); }
.pend-actions .btn { flex: 1; }
.pend-panel { margin-top: var(--s-3); }
.pend-panel[hidden] { display: none; }
.pend-edit, .pend-reject {
  padding: var(--s-3);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  box-shadow: var(--clay-sunken);
}
.pend-list .pend-card { margin-bottom: var(--s-3); }

/* ---- waiting on others ---- */
.pend-card.is-waiting { padding: var(--s-3) var(--s-4); }
.pend-who { display: flex; flex-wrap: wrap; gap: 6px 12px; margin-top: 6px; }
.pend-who-one {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-xs); font-weight: 700; color: var(--c-muted);
  text-transform: capitalize;
}
.pend-who-one.is-accepted { color: var(--c-pos); }
.pend-who-one.is-rejected { color: var(--c-neg); }
.pend-unassigned {
  margin-top: var(--s-3); padding: var(--s-3);
  border-radius: var(--r-md);
  background: var(--c-warn-soft);
}
.pend-unassigned p { margin: 0 0 4px; }
.pend-unassigned .strong { color: var(--c-warn); }
.pend-unassigned .form-actions { margin-top: var(--s-3); }

/* ---- Home: shared requests ---- */
.shared-card {
  margin-bottom: var(--s-3);
  padding: var(--s-3) var(--s-4) var(--s-2);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  box-shadow: var(--clay-surface);
}
.shared-head { display: flex; align-items: center; gap: var(--s-2); margin-bottom: 4px; }
.shared-ico {
  width: 28px; height: 28px; border-radius: 9px;
  display: inline-grid; place-items: center;
  background: var(--c-violet-soft); color: var(--c-violet);
}
.shared-row {
  display: flex; align-items: center; gap: var(--s-3);
  width: 100%; padding: 9px 0;
  border: none; background: none; text-align: left; color: inherit;
}
.shared-row + .shared-row { border-top: 1px solid var(--c-border); }
.shared-row:active { opacity: 0.6; }
.shared-amt { font-weight: 800; font-size: var(--fs-sm); }
.shared-all {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  width: 100%; padding: 10px 0 8px;
  border: none; border-top: 1px solid var(--c-border); background: none;
  font-size: var(--fs-xs); font-weight: 800; color: var(--c-violet);
}

/* ---- History + Reports ---- */
.hist-space-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 6px; vertical-align: 1px;
}
.shared-report { padding: var(--s-4); }
.shared-report-grid { display: grid; gap: 10px; margin-top: var(--s-3); }
.shared-stat { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.shared-stat-label { font-size: var(--fs-sm); color: var(--c-ink-soft); font-weight: 600; }
.shared-stat-value { font-size: var(--fs-base); font-weight: 800; }
.shared-stat.is-out .shared-stat-value { color: var(--c-warn); }
.shared-stat.is-owe .shared-stat-value { color: var(--c-neg); }

/* a section whose children are cards in their own right */
.sv-section.is-loose .pend-card:first-of-type { margin-top: 0; }

/* Dark: the amber wells read muddy over the violet-tinted dark surface at full
   strength, so they become a tint of the warn colour instead of a fill. */
:root[data-theme="dark"] .pend-unassigned,
:root[data-theme="dark"] .pend-changed {
  background: rgba(255, 180, 63, 0.10);
  box-shadow: inset 0 0 0 1px rgba(255, 180, 63, 0.22);
}
:root[data-theme="dark"] .pend-share {
  background: rgba(155, 133, 255, 0.12);
}


/* =================== Settlements (M4) ===================
   The transfer sheet grows a second kind of destination — a person — and a
   Covers list that says what the money pays off. Both sit inside the sheet
   that is already open: no nesting, ever (inline-category-editor.md). */

/* ---- the People group on the To side ---- */
.to-people { margin-top: var(--s-3); }
.to-people-label {
  display: block; margin: 0 0 6px;
  font-size: var(--fs-xs); font-weight: 800; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--c-faint);
}
.to-people .filter-row { flex-wrap: wrap; }
.to-people .to-spaces { margin-top: 2px; }
.person-chip { align-items: center; }
.person-chip .sp-av { border-width: 1.5px; flex: 0 0 auto; }
.person-chip-text {
  display: flex; flex-direction: column; align-items: flex-start;
  line-height: 1.15; min-width: 0;
}
.person-chip-text small {
  font-size: 10px; font-weight: 700; color: var(--c-faint); letter-spacing: 0;
}
.person-chip.is-active .person-chip-text small { color: var(--c-violet); }
.person-chip.has-sub { padding-top: 5px; padding-bottom: 5px; }

/* ---- Covers ---- */
.covers-field[hidden] { display: none; }
.cover-row {
  display: flex; align-items: center; gap: var(--s-3);
  width: 100%; padding: 10px var(--s-3);
  margin-bottom: 6px;
  border: none; border-radius: var(--r-md);
  background: var(--c-surface-2);
  box-shadow: var(--clay-sunken);
  color: inherit; text-align: left;
}
.cover-row.is-on { background: var(--c-violet-soft); box-shadow: none; }
.cover-row:active { transform: scale(0.99); }
.cover-box {
  width: 20px; height: 20px; flex: 0 0 auto;
  display: inline-grid; place-items: center;
  border-radius: 7px;
  border: 1.75px solid var(--c-border-strong, var(--c-border));
  color: #fff;
}
.cover-row.is-on .cover-box { background: var(--c-violet); border-color: var(--c-violet); }
.cover-amt { font-size: var(--fs-sm); font-weight: 800; flex: 0 0 auto; }
.cover-hand {
  font-family: var(--font-hand); font-size: var(--fs-hand); line-height: 1.2;
  color: var(--c-muted); margin: 2px 0 0;
}

/* ---- the settlement accept card ---- */
.pend-card.is-settlement .pend-share { background: var(--c-aqua-soft); }
.pend-card.is-settlement .pend-share-label,
.pend-card.is-settlement .pend-share-amt { color: var(--c-aqua); }

/* ---- both sides, in the detail sheet ---- */
.settle-sides .settle-side {
  display: flex; align-items: center; gap: 8px; margin: 0 0 4px;
  font-size: var(--fs-sm); font-weight: 700; color: var(--c-muted);
}
.settle-side.is-accepted { color: var(--c-pos); }
.settle-side.is-rejected { color: var(--c-neg); }

/* ---- History: unconfirmed, and the one thing to do about it ---- */
/* The nudge drops onto its own line rather than squeezing the row: a settlement
   row already carries a title, a space, a status and an amount, and shaving
   any of them to fit a button would hide the very thing the button is about. */
.hist-row.is-unconfirmed { flex-wrap: wrap; }
.hist-nudge {
  flex: 0 0 auto; margin: 6px 0 0 auto;
  padding: 5px 12px; font-size: 11px;
}
.chip-writeoff { color: var(--c-neg); }

:root[data-theme="dark"] .pend-card.is-settlement .pend-share {
  background: rgba(47, 182, 233, 0.12);
}
:root[data-theme="dark"] .cover-row.is-on {
  background: rgba(155, 133, 255, 0.12);
}

/* ============================================================
   M6 — upkeep surfaces: contact links, across-spaces, compaction
   ============================================================ */

/* The switcher card is a div with role=button now, because the member heads
   inside it are buttons of their own. Keyboard focus has to look deliberate. */
.sp-card[role="button"] { cursor: pointer; }
.sp-card[role="button"]:focus-visible {
  outline: 2px solid var(--c-violet);
  outline-offset: 2px;
}

/* A tappable member head: same disc, but it answers to a finger. */
.sp-av.is-tappable {
  /* keeps .sp-av's 2px surface ring so the overlapping stack still reads */
  padding: 0;
  cursor: pointer;
  font: inherit;
  font-size: 10px;
  font-weight: 800;
  color: #fff;
  transition: transform 0.16s ease;
}
.sp-av.is-tappable:active { transform: scale(0.9); }
.sp-av.is-tappable:focus-visible { outline: 2px solid var(--c-violet); outline-offset: 2px; }

/* Across spaces: one human, one row, their balance in each space spelled out. */
.sp-across-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 2px;
}
.sp-across-row + .sp-across-row { border-top: 1px solid var(--c-hairline); }
.sp-across-row .sp-net { flex: 0 0 auto; font-size: var(--fs-xs); font-weight: 700; }

/* A member row that leads somewhere. Same metrics as the static one. */
.sp-member-btn {
  width: 100%;
  background: transparent;
  border: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}
.sp-member-btn:active { background: var(--c-faint); }
.sp-member-btn .chev { color: var(--c-muted); font-size: 18px; }

.sp-storage { padding: var(--s-4); }
.sp-storage .sp-compact-btn { margin-top: 4px; }

.sp-rotate-owed b { font-weight: 800; }

/* The mirror of .warn-card for the "all clear" line after a rotate. */
.ok-card {
  background: var(--c-pos-soft);
  border: 1px solid rgba(18,183,127,0.28);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  font-size: var(--fs-sm);
  color: #0B6A4B;
  font-weight: 500;
}
.ok-card svg.ico { vertical-align: -0.18em; margin-right: 2px; }
:root[data-theme="dark"] .ok-card { color: #7BE3BC; border-color: rgba(18,183,127,0.34); }

/* An archived record reads the same as a live one, just quieter. */
.sv-row.is-archived { opacity: 0.72; }
.sv-archive-note {
  font-size: var(--fs-xs);
  color: var(--c-muted);
  padding: 10px 2px 2px;
  text-align: center;
}
/* ============================================================
   Batwa — first-run onboarding
   Rides on .lock-screen (same aurora canvas, same white-on-gradient
   ink) and only adds what the welcome / choice / accounts steps need.
   ============================================================ */

/* Sheets are z 100 and toasts 200 — the accounts step must sit UNDER both
   so "Add account" can open over it. The lock screen's 300 would swallow them. */
.onb-screen {
  z-index: 95;
  justify-content: flex-start;
  overflow: hidden auto;
  padding: calc(var(--s-7) + var(--safe-t)) var(--s-5);
  padding-bottom: calc(var(--s-7) + var(--safe-b));
  gap: 0;
}

/* the accounts step sits over a live home screen — freeze it */
body.is-onboarding { overflow: hidden; }

.onb-card {
  margin: auto 0;               /* centred when it fits, scrolls when it doesn't */
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-4);
  text-align: center;
  position: relative;
  z-index: 1;
}

/* ---- brand block ---- */
.onb-logo {
  width: 84px; height: 84px;
  border-radius: 26px;
  background: #fff;
  display: grid; place-items: center;
  box-shadow: 0 18px 40px -14px rgba(9, 5, 40, 0.55), inset 0 1px 0 rgba(255,255,255,0.6);
}
.onb-logo img { display: block; }
.onb-logo-sm {
  width: 60px; height: 60px;
  border-radius: 20px;
  background: rgba(255,255,255,0.16);
  backdrop-filter: blur(8px);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.3);
}
/* onboarding always sits on the violet gradient, same as the lock screen —
   a flatter fill stands in for the blur on low tier, in both themes */
html[data-perf="low"] .onb-logo-sm,
html[data-perf="low"] .onb-opt {
  background: rgba(255, 255, 255, 0.24);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.onb-brand {
  font-size: var(--fs-hero);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1;
  margin-top: calc(var(--s-1) * -1);
}
.onb-title {
  font-size: var(--fs-xl);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.15;
}
.onb-lead {
  font-size: var(--fs-md);
  font-weight: 500;
  line-height: 1.45;
  color: rgba(255,255,255,0.82);
  max-width: 300px;
}
.onb-lead-sm { font-size: var(--fs-base); max-width: 320px; }

/* ---- value props ---- */
.onb-points {
  list-style: none;
  display: flex; flex-direction: column;
  gap: 10px;
  text-align: left;
  width: 100%;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: rgba(255,255,255,0.88);
}
.onb-points li { display: flex; align-items: center; gap: 10px; }
.onb-tick {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(255,255,255,0.2);
  display: grid; place-items: center;
  color: #fff;
}

/* ---- buttons ---- */
.onb-actions {
  width: 100%;
  display: flex; flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-2);
}
.onb-btn {
  width: 100%;
  min-height: 54px;
  border-radius: var(--r-pill);
  background: #fff;
  /* onboarding always sits on the violet gradient, so this white pill needs the
     light-theme violet in both themes — the dark token is too pale on white */
  color: #4630C9;
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: -0.01em;
  display: grid; place-items: center;
  box-shadow:
    inset 3px 3px 8px rgba(255, 255, 255, 0.9),
    inset -3px -4px 8px rgba(70, 48, 201, 0.18),
    0 14px 30px -12px rgba(9, 5, 40, 0.6);
  transition: transform var(--t-fast) var(--ease-out), background var(--t-fast);
}
.onb-btn:active {
  transform: scale(0.97);
  box-shadow:
    inset 3px 4px 10px rgba(70, 48, 201, 0.22),
    0 4px 10px -6px rgba(9, 5, 40, 0.5);
}
.onb-btn[disabled] { opacity: 0.6; }
.onb-btn-ghost {
  background: transparent;
  color: rgba(255,255,255,0.9);
  border: 1.5px solid rgba(255,255,255,0.32);
  box-shadow: none;
  font-weight: 700;
}
.onb-btn-ghost:active { background: rgba(255,255,255,0.14); }

/* ---- security choice ---- */
.onb-opts {
  width: 100%;
  display: flex; flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-1);
}
.onb-opt {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-4);
  text-align: left;
  color: #fff;
  border-radius: var(--r-lg);
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  backdrop-filter: blur(8px);
  transition: transform var(--t-fast) var(--ease-out), background var(--t-fast);
}
.onb-opt:active { transform: scale(0.98); background: rgba(255,255,255,0.22); }
.onb-opt-ico {
  width: 42px; height: 42px;
  border-radius: 14px;
  background: rgba(255,255,255,0.2);
  display: grid; place-items: center;
  flex: 0 0 auto;
}
.onb-opt-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.onb-opt-title {
  font-size: var(--fs-base);
  font-weight: 800;
  letter-spacing: -0.01em;
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
}
.onb-opt-sub {
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: 1.4;
  color: rgba(255,255,255,0.76);
}
.onb-opt-sub strong { font-weight: 800; color: rgba(255,255,255,0.92); }
.onb-badge {
  font-style: normal;
  font-size: 0.625rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 3px 8px;
  border-radius: var(--r-pill);
  background: rgba(255,255,255,0.24);
}
.onb-opt-chev { color: rgba(255,255,255,0.55); display: grid; place-items: center; }
.onb-foot {
  font-size: var(--fs-xs);
  font-weight: 500;
  color: rgba(255,255,255,0.6);
  max-width: 300px;
}

/* ---- accounts step ---- */
.onb-list {
  width: 100%;
  display: flex; flex-direction: column;
  gap: var(--s-2);
}
.onb-list:empty { display: none; }
.onb-acc {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 10px var(--s-3);
  border-radius: var(--r-md);
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.16);
  font-size: var(--fs-base);
  font-weight: 700;
  text-align: left;
}
.onb-acc > .truncate { flex: 1 1 auto; min-width: 0; }
.onb-acc-tick { color: #9CF7D2; display: grid; place-items: center; flex: 0 0 auto; }

/* ---- back chevron (also used by the embedded PIN setup) ---- */
.lock-back {
  position: absolute;
  top: calc(var(--safe-t) + 10px);
  left: 8px;
  width: 44px; height: 44px;
  border-radius: 50%;
  color: #fff;
  background: transparent;
  display: grid; place-items: center;
  z-index: 2;
  transition: background var(--t-fast);
}
.lock-back:active { background: rgba(255,255,255,0.18); }

/* ---- tight phones ---- */
@media (max-height: 700px) {
  .onb-logo { width: 68px; height: 68px; border-radius: 22px; }
  .onb-logo-sm { width: 54px; height: 54px; }
  .onb-brand { font-size: var(--fs-xl); }
  .onb-card { gap: var(--s-3); }
  .onb-btn { min-height: 50px; }
}
