/* Indigo Noir — the whole design system and every screen's layout.
 *
 * Extracted from the inline <style> in index.html. It lives here so the service
 * worker can cache it: index.html is served no-cache (it is the boot document),
 * so while this sat inline every launch re-downloaded ~156KB of CSS that rarely
 * changes. As a cached shell file it is fetched once per CACHE_NAME bump.
 *
 * The <link> replaces the <style> at exactly the position it occupied, and this
 * is still one stylesheet in one file, so source order — and therefore the whole
 * cascade — is unchanged. Order matters more than usual here: several rules in
 * the mobile block deliberately override the shared layer above them by coming
 * later at equal specificity. Do not reorder, and do not split this file into
 * several <link>s without re-checking `npm run test:mobile`, which measures the
 * geometry those overrides produce.
 *
 * admin.html keeps its own copy of the token block on purpose (see AGENTS.md);
 * it is a separate document and does not load this file. Change a token here
 * and change it there too.
 */
  /* The amounts are set in a slab serif, everything else in the grotesk.
     This is the brand's own device, not a new idea: the logo is a serif $ on a
     cream tile against a sans wordmark, described in BRAND.md as "deliberate
     contrast". Applying it to the numbers puts that contrast where the content
     actually is — money is what this app is about, so money is what gets the
     voice. Bitter because its lining tabular figures hold a column; Georgia,
     the brand's logo serif, has old-style figures that wander off the baseline
     and cannot align in a ledger. */
  @font-face {
    font-family: "Bitter";
    src: url("/fonts/bitter-latin.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
  }
  @font-face {
    font-family: "Hanken Grotesk";
    src: url("/fonts/hanken-grotesk.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
  }
  :root {
    --container-pad: 1.5rem;
    /* The palette in assets/BRAND.md — the one the live emails already use.
       The app had drifted three times over (Indigo Noir, then warm paper, then
       Apple's own system colours) until it matched neither its brand nor its own
       transactional mail. Cream ground, brand navy, and the documented status
       set. --on-accent is per-theme because a filled navy needs white on it and
       a filled pale blue needs ink. */
    --bg: #faf9f5;
    --surface: #ffffff;
    --surface-2: #f4f2ec;
    --surface-3: #ebe9e1;
    --border: rgba(0,0,0,0.08);
    --border-strong: rgba(0,0,0,0.15);
    --text: #1a1a1a;
    --text-muted: #5f5e5a;
    --text-subtle: #888780;
    /* CLVRD electric blue — the brand's own colour, already on the logo and the
       boot spinner and nowhere else. The navy it replaces read as a safe
       banking default; this is the one the product is actually named after. */
    --accent: #1254f5;
    --accent-soft: #e8efff;   /* accent at reading weight, for fills behind text */
    --accent-deep: #0b3ac0;   /* pressed / gradient end */
    --on-accent: #ffffff;
    --good: #1d9e75;
    --good-bg: #e1f5ee;
    --warn: #ba7517;
    --warn-bg: #faeeda;
    --bad: #d85a30;
    --bad-bg: #faece7;
    --info: #378add;
    --info-bg: #e6f1fb;
    --purple: #534ab7;
    --purple-bg: #eeedfe;
    --font-money: "Bitter", Georgia, "Times New Roman", serif;
    /* The device's own notch/home-bar insets, behind a variable rather than
       used as env() directly. env() cannot be simulated, so every safe-area bug
       could only be found on a physical phone; with a token a test can set a
       notch and check nothing lands underneath it. */
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    /* Space to leave below scrolling content so the last row clears the floating
       tab bar. setupDockPin() overwrites this with the bar's MEASURED position
       every time it reflows; the value here is only the pre-measurement
       fallback. It is deliberately generous — too much padding is dead scroll,
       too little hides a row behind the bar. Do not redefine --tabbar-h here:
       that one is measured at runtime for the FAB and setting it in CSS fights
       the measurement and resolves to 0. */
    --tabbar-clear: calc(6.5rem + var(--safe-bottom));
    --shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.05);
    --radius: 10px;
    --radius-lg: 14px;
    --radius-sm: 8px;
    --radius-pill: 999px;
    /* type scale (16px root) */
    --fs-2xs: 0.625rem;  /* 10 */
    --fs-xs: 0.6875rem;  /* 11 */
    --fs-sm: 0.75rem;    /* 12 */
    --fs-md: 0.8125rem;  /* 13 */
    --fs-base: 0.875rem; /* 14 */
    --fs-lg: 1rem;       /* 16 */
    --fs-xl: 1.125rem;   /* 18 */
    --fs-2xl: 1.375rem;  /* 22 */
    --fs-display: 1.875rem; /* 30 */
    /* spacing (4px base + 2px hairline) */
    --sp-0: 2px; --sp-1: 4px; --sp-2: 8px; --sp-3: 12px;
    --sp-4: 16px; --sp-5: 20px; --sp-6: 24px; --sp-8: 32px;
    /* motion */
    --dur-fast: 120ms; --dur: 200ms; --dur-slow: 320ms;
    --ease: cubic-bezier(.2,.6,.2,1);
    /* editorial palette (promoted from .retro-card) */
    --bone: #f6f1e8; --ink: #24211c;
  }
  

  /* There is no boot spinner. The lock screen IS the boot screen: it paints
     from the first frame with the logo already in place, so on iOS the mark
     the launch screen drew is still there when the web takes over, and simply
     starts animating. A spinner in between is what made it look like the logo
     was being loaded twice. #app-loader is now built only by the stale-code
     recovery path below, which needs an opaque sheet to write its message on. */
  #app-loader {
    position: fixed; inset: 0; z-index: 9999;
    display: flex; align-items: center; justify-content: center;
    background: var(--bg);
    transition: opacity 0.3s ease;
  }
  #app-loader.hidden { opacity: 0; pointer-events: none; }

  /* Boot skeletons: shimmering placeholders shown in the home pane while the
     first /api/data load is in flight, so the shell appears instantly and fills
     in, rather than the user waiting on the lock screen for a blank reveal. */
  .skel-row, .skel-block {
    background: linear-gradient(90deg,
      var(--surface-2) 25%, var(--border) 37%, var(--surface-2) 63%);
    background-size: 400px 100%;
    animation: skelShimmer 1.2s ease-in-out infinite;
    border-radius: 6px;
  }
  .skel-row { height: 14px; margin: 12px 0; }
  .skel-row.w40 { width: 40%; } .skel-row.w60 { width: 60%; }
  .skel-row.w80 { width: 80%; } .skel-row.w100 { width: 100%; }
  .skel-block { height: 56px; width: 100%; }
  body.booting .hero-value, body.booting #week-total { opacity: 0.35; }
  @keyframes skelShimmer { to { background-position: -400px 0; } }
  


  .bell-btn {
    display: flex; position: relative; padding: var(--sp-2); border: none; background: none;
    cursor: pointer; border-radius: var(--radius-pill); color: var(--text-muted);
    transition: background 0.15s, color 0.15s; font-size: var(--fs-xl); line-height: 1;
  }
  .bell-btn:hover { background: var(--surface-2); color: var(--text); }
  .bell-badge {
    position: absolute; top: 5px; right: 5px;
    width: 8px; height: 8px; border-radius: var(--radius-pill);
    background: var(--bad); border: 2px solid var(--bg);
    display: none;
  }
  .bell-badge.visible { display: block; }
  #notif-panel {
    display: none; position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 800;
  }
  #notif-panel.open { display: block; }
  .notif-panel-backdrop { position: absolute; inset: 0; }
  .notif-panel-card {
    position: absolute; top: calc(60px + var(--safe-top)); right: 12px;
    width: min(340px, calc(100vw - 24px));
    overflow: hidden;
    animation: notifIn 0.18s ease-out;
  }
  @keyframes notifIn { from { opacity: 0; transform: translateY(-6px) scale(0.97); } to { opacity: 1; transform: none; } }
  .notif-panel-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--sp-3) var(--sp-4) var(--sp-3); border-bottom: 1px solid var(--border);
  }
  .notif-panel-title { font-size: var(--fs-md); font-weight: 600; margin: 0; }
  .notif-panel-controls { border-bottom: 1px solid var(--border); padding: var(--sp-3) var(--sp-4); display: grid; gap: var(--sp-2); }
  .notif-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-sm); color: var(--text-muted); }
  .notif-toggle-row b { color: var(--text); font-size: var(--fs-md); }
  .notif-toggle-row input { width: auto; min-width: 18px; }
  .notif-panel-body { max-height: 360px; overflow-y: auto; padding: var(--sp-3) var(--sp-4) var(--sp-4); }
  .notif-panel-empty { font-size: var(--fs-md); color: var(--text-muted); padding: var(--sp-2) 0; text-align: center; }


  .md-h { font-weight: 600; color: var(--text); margin: 0.85rem 0 0.2rem; font-size: var(--fs-md);
    padding-bottom: var(--sp-0); border-bottom: 1px solid var(--border); display: block; }
  .md-h1 { font-weight: 700; color: var(--accent); margin: 0.75rem 0 0.3rem; font-size: var(--fs-base); display: block; }
  .md-code { font-family: "SF Mono","Fira Code",ui-monospace,monospace; font-size: 0.85em;
    background: var(--surface-3); padding: var(--sp-0) var(--sp-1); border-radius: 4px; color: var(--text); }
  .md-hr { border: none; border-top: 1px solid var(--border); margin: 0.65rem 0; }
  .md-amount { font-weight: 600; color: var(--text); }
  .md-pos { font-weight: 600; color: var(--good); }
  .md-neg { font-weight: 600; color: var(--bad); }
  .md-debt { font-weight: 600; color: var(--warn); }
  .md-p { margin: 0.35rem 0; display: block; line-height: 1.65; }
  .md-ul, .md-ol { margin: 0.3rem 0 0.3rem 1rem; padding: 0; list-style: none; }
  .md-ul li { position: relative; padding: var(--sp-0) 0 var(--sp-0) 1rem; margin: var(--sp-0) 0; }
  .md-ul li::before { content: "·"; position: absolute; left: 0; color: var(--accent); font-weight: 700; }
  .md-ol { list-style: decimal; padding-left: 1.25rem; }
  .md-ol li { padding: var(--sp-0) 0; margin: var(--sp-0) 0; }

  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; overflow-x: hidden; width: 100%; max-width: 100%; touch-action: manipulation; }
  /* overscroll-behavior, both axes: only -x was set, so dragging past the top
     or the bottom still rubber-banded — past the top the visual viewport scaled
     and the page came back zoomed and too wide, past the bottom it juddered
     against the sticky tab bar. `none` also stops pull-to-refresh, which this
     app does not use since it syncs on its own.

     It belongs to the VIEWPORT SCROLLER, and that is html alone — never body
     as well, which is how it was first written and what this corrects.

     `overflow-x: hidden` computes the other axis to `auto`, so body is itself a
     scroll container; and because html is not `overflow: visible`, body's
     overflow does not propagate to the viewport, so it stays a real nested
     scroller rather than becoming the page's. Body's content grows to fit it,
     so that scroller can never move — its scrollHeight always equals its
     clientHeight. Everything you see is inside it.

     Add `overscroll-behavior: none` there and the wheel dies over the whole
     page: the event targets a scroll container that cannot move, and `none`
     forbids it handing the scroll up to html. Reported as "I cannot scroll
     unless the cursor is over the left menu" — and the menu was the tell. It is
     position:fixed, so its containing block is the viewport; scroll from it
     chains straight to html and skips body's refusal entirely.

     On html this still does the job it was added for: no vertical rubber-band,
     no pull-to-refresh. html IS the viewport scroller here (document
     .scrollingElement is HTML), so nothing about that intent is lost. */
  html { scrollbar-gutter: stable; overscroll-behavior: none; }
  body {
    font-family: "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    background: var(--bg); color: var(--text); line-height: 1.55; font-size: var(--fs-base);
    min-height: 100vh; min-height: 100dvh;
    -webkit-text-size-adjust: 100%;
    -webkit-tap-highlight-color: transparent;
  }
  /* --container-pad is the page gutter. Anything that wants to sit full-bleed
     inside the container cancels it with a negative margin of exactly this,
     so the two can never drift apart at a breakpoint. */
  .container { max-width: 1100px; margin: 0 auto; padding: calc(1.5rem + var(--safe-top)) var(--container-pad) 6rem; }

  .container > header { margin-bottom: 1.5rem; }
  .acct-menu { position: relative; }
  /* ── Unified menu-item pattern (dropdown + profile modal) ── */
  .menu-item {
    display: flex; align-items: center; gap: var(--sp-3); width: 100%; text-align: left;
    padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-sm); border: none; background: transparent;
    color: var(--text); font: inherit; font-size: var(--fs-md); cursor: pointer;
    transition: background 0.1s, color 0.1s;
  }
  .menu-item:hover { background: var(--surface-2); }
  .menu-item-icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-subtle); display: flex; align-items: center; justify-content: center; }
  .menu-item-icon svg { width: 15px; height: 15px; }
  .menu-item.active { background: color-mix(in srgb, var(--accent) 10%, var(--surface)); color: var(--accent); font-weight: 600; }
  .menu-item.active .menu-item-icon { color: var(--accent); }
  .menu-item.menu-danger { color: var(--bad); }
  .menu-item.menu-danger .menu-item-icon { color: var(--bad); }
  .menu-item.menu-danger:hover { background: var(--bad-bg); }
  .menu-item.menu-signout { color: var(--text-muted); }
  .menu-item.menu-signout:hover { background: var(--surface-2); color: var(--text); }
  .menu-section-label {
    font-size: var(--fs-2xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-subtle); padding: var(--sp-2) var(--sp-3) var(--sp-0); display: block;
  }
  .menu-divider { border: none; border-top: 1px solid var(--border); margin: var(--sp-1) 0; }

  /* ── Account dropdown ── */
  .acct-dropdown {
    display: none; position: absolute; right: 0; top: calc(100% + 8px);
    background: var(--surface); border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg); box-shadow: 0 8px 32px rgba(0,0,0,0.16), 0 2px 8px rgba(0,0,0,0.06);
    min-width: 230px; z-index: 200; padding: var(--sp-2);
  }
  .acct-dropdown.open { display: block; animation: menuReveal 0.15s ease; }
  @keyframes menuReveal { from { opacity: 0; transform: translateY(-4px) scale(0.98); } to { opacity: 1; transform: none; } }
  .acct-user-header {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-3) var(--sp-3); border-bottom: 1px solid var(--border); margin-bottom: var(--sp-1);
  }
  .acct-user-avatar {
    width: 30px; height: 30px; border-radius: var(--radius-pill); flex-shrink: 0;
    background: var(--accent); color: var(--on-accent);
    display: flex; align-items: center; justify-content: center;
    font-size: var(--fs-sm); font-weight: 700; letter-spacing: 0; text-transform: uppercase;
  }
  .acct-user-name { font-size: var(--fs-md); font-weight: 500; color: var(--text); }
  .acct-divider { border: none; border-top: 1px solid var(--border); margin: var(--sp-1) 0; }
  .header-top { display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 1rem; margin-bottom: 0.5rem; }
  .header-right { display: none; align-items: center; gap: var(--sp-2); }
  h1 { font-size: var(--fs-2xl); font-weight: 600; margin: 0; letter-spacing: -0.01em; }
  h1 span { color: var(--text-subtle); font-weight: 400; }
  .meta-line { font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-1) 0 0; }
  .meta-line b { color: var(--text); font-weight: 500; }

  .add-btn {
    position: fixed; bottom: 1.5rem; right: 1.5rem;
    background: var(--text); color: var(--bg); border: none;
    padding: var(--sp-4) var(--sp-6); border-radius: var(--radius-pill); font-size: var(--fs-base); font-weight: 500;
    cursor: pointer; box-shadow: 0 4px 14px rgba(0,0,0,0.15);
    display: flex; align-items: center; gap: var(--sp-2); font-family: inherit;
    transition: transform 0.2s ease, box-shadow 0.15s, opacity 0.2s ease; z-index: 100;
  }
  .add-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.2); }
  .add-btn.fab-hidden { transform: translateY(calc(100% + 2.5rem)); opacity: 0; pointer-events: none; }
  .add-btn .plus { font-size: var(--fs-xl); line-height: 1; }

  .hero { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); margin-bottom: 1.5rem; }
  /* Hero cards carry their status colour as a wash, not just a 3px edge. The
     tint was 5% — technically present, invisible in daylight on a phone — so
     the one surface that is meant to answer "am I OK?" at a glance answered in
     grey. At 12% with a 4px edge the colour is doing the work the border was
     failing to do alone, and the fill still stays far under any text-contrast
     threshold: this is a wash behind ink, never a coloured block. */
  .hero-card { padding: var(--sp-4) var(--sp-5); border-left: 4px solid var(--border); }
  .hero-card.accent-good { border-left-color: var(--good); background: color-mix(in srgb, var(--good) 12%, var(--surface)); }
  .hero-card.accent-bad { border-left-color: var(--bad); background: color-mix(in srgb, var(--bad) 12%, var(--surface)); }
  .hero-card.accent-warn { border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--surface)); }
  .hero-card.accent-purple { border-left-color: var(--purple); background: color-mix(in srgb, var(--purple) 12%, var(--surface)); }
  .hero-card.accent-info { border-left-color: var(--info); background: color-mix(in srgb, var(--info) 12%, var(--surface)); }
  .hero-card.primary { grid-column: 1 / -1; padding: 1.35rem 1.5rem; background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 4%, var(--surface)) 0%, color-mix(in srgb, var(--accent) 14%, var(--surface)) 100%); border-left-color: var(--accent); }
  /* Status beats brand on the one card that answers "am I OK?". `.primary` and
     `.accent-good` are both (0,2,0), so the gradient declared later was winning
     and painting the net-cash card brand blue whether the number was positive
     or negative — the colour said "CLVRD", not "you are in the green". These
     out-specify both, so the headline card carries its own answer. */
  .hero-card.primary.accent-good { background: linear-gradient(135deg, color-mix(in srgb, var(--good) 5%, var(--surface)) 0%, color-mix(in srgb, var(--good) 18%, var(--surface)) 100%); border-left-color: var(--good); }
  .hero-card.primary.accent-bad { background: linear-gradient(135deg, color-mix(in srgb, var(--bad) 5%, var(--surface)) 0%, color-mix(in srgb, var(--bad) 18%, var(--surface)) 100%); border-left-color: var(--bad); }
  .hero-card.primary .hero-value { font-size: var(--fs-display); line-height: 1.1; letter-spacing: -0.02em; }
  .hero-card.primary .hero-label { color: var(--text-subtle); }
  .hero-card.primary .hero-sub { font-size: var(--fs-md); margin-top: var(--sp-2); }
  .hero-label { font-size: var(--fs-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 500; margin: 0 0 var(--sp-2); }
  .hero-label-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin: 0 0 var(--sp-2); }
  .hero-label-row .hero-label, .hero-label-row .mobile-hero-label { margin: 0; }
  .reveal-btn { background: transparent; border: 0; padding: var(--sp-1); margin: 0; color: var(--text-muted); cursor: pointer; border-radius: var(--radius-pill); display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
  .reveal-btn:hover { color: var(--text); background: color-mix(in srgb, var(--text) 6%, transparent); }
  .reveal-btn .reveal-off { display: none; }
  .reveal-btn[aria-pressed="true"] .reveal-on { display: none; }
  .reveal-btn[aria-pressed="true"] .reveal-off { display: inline-block; }
  .net-cash-masked { letter-spacing: 0.08em; }
  .hero-value { font-size: var(--fs-2xl); font-weight: 600; margin: 0; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
  .hero-sub { font-size: var(--fs-sm); color: var(--text-subtle); margin: var(--sp-1) 0 0; }
  .hero-sub.pos { color: var(--good); }
  .hero-sub.neg { color: var(--bad); }
  .hero-sub.warn { color: var(--warn); }
  .hero-trend { display: inline-block; font-size: var(--fs-2xs); font-weight: 500; padding: var(--sp-0) var(--sp-2); border-radius: var(--radius-pill); margin-top: var(--sp-1); }
  .hero-trend.up { background: rgba(29,158,117,0.14); color: var(--good); }
  .hero-trend.down { background: rgba(216,90,48,0.14); color: var(--bad); }
  .hero-trend.flat { background: rgba(130,130,130,0.12); color: var(--text-muted); }
  [data-goto] { cursor: pointer; transition: transform 0.12s, box-shadow 0.12s; }
  [data-goto]:hover { transform: translateY(-1px); box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
  [data-goto]:active { transform: translateY(0); }
  [data-goto]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .profile-btn { display: none; }
  /* ---- One list row ----
     Eight row types had drifted into three different separator techniques
     (border-bottom + :last-child, sibling border-top, and none), four vertical
     paddings and two alignments. Same object, drawn eight ways — which is what
     makes one screen feel unlike the next even when both are "a list of things
     with an amount on the right".

     The shared contract is a horizontal row on one vertical rhythm, with a
     single hairline between rows and none after the last. Real differences —
     a grid for the bar chart, top alignment where content wraps to two lines,
     horizontal padding inside a bordered group — stay as overrides on the
     individual rules below, which win on source order.

     .wallet-row and .referral-row are deliberately not in this set: they are
     bordered boxes stacked in a list, a different object, not drift. */
  .tx-row, .item-row, .sub-row, .task, .merge-variant, .notice-item, .breakdown-row,
  .mobile-up-row, .mobile-fact {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--border);
  }
  .tx-row:last-child, .item-row:last-child, .sub-row:last-child, .task:last-child,
  .merge-variant:last-child, .notice-item:last-child, .breakdown-row:last-child,
  .mobile-up-row:last-child, .mobile-fact:last-child { border-bottom: none; }

  .mobile-overview { display: none; }
  .mobile-hero-panel {
    background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: 1.15rem; margin-bottom: 0.85rem; overflow: hidden;
    border-left: 3px solid var(--accent);
  }
  .mobile-hero-label { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.68; margin: 0 0 var(--sp-1); }
  .mobile-hero-value { font-size: var(--fs-display); line-height: 1; font-weight: 700; margin: 0; letter-spacing: 0; font-variant-numeric: tabular-nums; }
  .mobile-hero-sub { color: var(--text-muted); }
  .mobile-verdict {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: 0.95rem 1.1rem; margin-bottom: 0.85rem;
    display: flex; align-items: center; gap: var(--sp-3);
  }
  .mobile-verdict p { margin: 0; }
  .mobile-verdict-text { flex: 1; min-width: 0; }
  .mobile-verdict-main { font-size: var(--fs-lg); font-weight: 650; }
  .mobile-verdict-sub { font-size: var(--fs-sm); color: var(--text-muted); margin-top: var(--sp-0) !important; }
  .mobile-verdict-spark { width: 60px; height: 28px; flex-shrink: 0; overflow: visible; }
  .mobile-verdict-spark:empty { display: none; }
  .mobile-verdict-chev { font-size: var(--fs-2xl); color: var(--text-subtle); flex-shrink: 0; line-height: 1; }
  .mobile-facts { display: flex; flex-direction: column; gap: 0; padding: var(--sp-1) 0; margin-bottom: var(--sp-4); }
  .mobile-fact { padding-left: var(--sp-4); padding-right: var(--sp-4); cursor: pointer; transition: background 0.12s ease; }
  .mobile-fact:active { background: var(--surface-2); }
  .mobile-kpi {
    padding: var(--sp-4); min-height: 96px; min-width: 0; overflow: hidden;
    transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
  }
  .mobile-kpi:active { transform: scale(0.98); }
  .mobile-kpi[role="button"]:hover { border-color: var(--border-strong); box-shadow: 0 2px 8px rgba(0,0,0,0.04); }
  .mobile-kpi-sub { font-size: var(--fs-xs); color: var(--text-muted); margin: var(--sp-1) 0 0; line-height: 1.4; overflow-wrap: anywhere; }
  .mobile-net-title { font-size: var(--fs-xs); opacity: 0.72; margin: 1rem 0 0.45rem; }
  .mobile-net-wrap { position: relative; padding: var(--sp-3) 0; }
  .mobile-net-wrap::before {
    content: ""; position: absolute; left: 0; right: 0; top: 50%;
    border-top: 1px solid var(--border-strong);
  }
  .mobile-net-bars { display: flex; align-items: center; gap: var(--sp-1); height: 62px; position: relative; z-index: 1; }
  .mobile-net-bar {
    flex: 1; border-radius: var(--radius-pill); min-height: 6px; opacity: 0.95;
    height: var(--bar-height, 8px); background: var(--surface-3);
  }
  .mobile-net-bar.pos { align-self: flex-start; background: var(--good); }
  .mobile-net-bar.neg { align-self: flex-end; background: var(--bad); }
  .mobile-net-bar.zero { align-self: center; height: 6px; background: var(--surface-3); }
  .mobile-net-legend { display: flex; justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-xs); color: var(--text-muted); margin-top: 0.15rem; }
  /* Reusable mobile card chrome (Upcoming money, Safe to spend, …) */
  .mobile-card { padding: var(--sp-4); margin-bottom: var(--sp-3); }
  .mobile-card-head { margin-bottom: var(--sp-2); }
  .mobile-card-meta { color: var(--text-subtle); opacity: 0.7; font-weight: 600; letter-spacing: 0.04em; margin-left: var(--sp-1); text-transform: none; }
  .mobile-card-link { background: none; border: none; color: var(--accent); font-size: var(--fs-sm); font-weight: 600; cursor: pointer; padding: var(--sp-0) var(--sp-1); font-family: inherit; flex-shrink: 0; }
  .mobile-card-link:active { opacity: 0.6; }
  /* Upcoming money rows */
  .mobile-up-badge { width: 38px; height: 38px; border-radius: 11px; display: flex; flex-direction: column; align-items: center; justify-content: center; flex-shrink: 0; font-variant-numeric: tabular-nums; }
  .mobile-up-badge--date { background: var(--surface-2); }
  .mobile-up-badge--date .m { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; line-height: 1; color: var(--text-subtle); }
  .mobile-up-badge--date .d { font-size: var(--fs-lg); font-weight: 700; line-height: 1.15; color: var(--text); }
  .mobile-up-badge--in { background: color-mix(in srgb, var(--good) 16%, var(--surface)); color: var(--good); font-size: var(--fs-xl); font-weight: 700; }
  .mobile-up-body { flex: 1; min-width: 0; }
  .mobile-up-name { font-size: var(--fs-md); font-weight: 600; margin: 0; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .mobile-up-when { font-size: var(--fs-xs); color: var(--text-subtle); margin: var(--sp-0) 0 0; }
  .mobile-up-done { display: inline-block; margin: var(--sp-1) 0 0; padding: 0; background: none; border: none; font-size: var(--fs-2xs); font-weight: 600; color: var(--text-subtle); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .mobile-up-done:active { color: var(--text); }
  .mobile-up-amt { font-size: var(--fs-base); font-weight: 700; font-variant-numeric: tabular-nums; flex-shrink: 0; }
  .mobile-up-amt.good { color: var(--good); }
  .mobile-up-amt.bad { color: var(--bad); }
  .mobile-up-row--tap { cursor: pointer; border-radius: 10px; -webkit-tap-highlight-color: transparent; }
  .mobile-up-row--tap:active { background: var(--surface-2); }
  .mobile-up-log { flex-shrink: 0; font-size: var(--fs-2xs); font-weight: 700; color: var(--accent); background: color-mix(in srgb, var(--accent) 13%, var(--surface)); border-radius: var(--radius-pill); padding: var(--sp-0) var(--sp-2); margin-left: var(--sp-0); }
  .mobile-up-hint { font-size: var(--fs-xs); color: var(--text-subtle); text-align: center; margin: var(--sp-2) 0 0; }
  /* Safe to spend */
  .mobile-sts-value { font-size: var(--fs-display); font-weight: 700; margin: var(--sp-0) 0 0; color: var(--good); font-variant-numeric: tabular-nums; line-height: 1.05; }
  .mobile-sts-per { font-size: var(--fs-base); font-weight: 600; color: var(--text-muted); margin-left: var(--sp-0); }
  .mobile-sts-sub { font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-1) 0 0; }
  /* Free-to-spend receipt: aligned label/amount rows ruled off to the total */
  .mobile-sts-ledger { margin: var(--sp-4) 0 var(--sp-2); }
  .sts-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-1) 0; font-size: var(--fs-md); }
  .sts-row-label { color: var(--text-muted); }
  .sts-row-amt { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); white-space: nowrap; }
  .sts-row-amt--neg { color: var(--text-subtle); font-weight: 500; }
  .sts-row--sub .sts-row-label, .sts-row--sub .sts-row-amt { font-weight: 700; color: var(--text); }
  .sts-row--total { padding-top: var(--sp-2); }
  .sts-row--total .sts-row-label { font-weight: 700; color: var(--text); text-transform: uppercase; letter-spacing: 0.05em; font-size: var(--fs-sm); }
  .sts-row--total .sts-row-amt { font-weight: 700; color: var(--good); font-size: var(--fs-lg); }
  .sts-rule { height: 1px; background: var(--border); margin: var(--sp-1) 0; }
  .sts-rule--total { height: 0; border-top: 2px double var(--text-subtle); opacity: 0.5; margin: var(--sp-2) 0 var(--sp-1); }
  .mobile-sts-perday-note { font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-1) 0 0; }
  /* Surplus ideas: title line + one pill per recommendation */
  .mobile-sts-recs { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
  .mobile-sts-recs:empty { display: none; }
  .mobile-sts-rec { font-size: var(--fs-sm); color: var(--text-muted); background: var(--surface-2); border-radius: var(--radius-pill); padding: var(--sp-1) var(--sp-3); }

  .tabs { display: flex; gap: var(--sp-1); margin-bottom: 1.25rem; border-bottom: 1px solid var(--border); flex-wrap: wrap; position: sticky; top: 0; z-index: 50; background: var(--bg); }
  /* Desktop left sidebar — hidden by default, shown at >=1024px (see media query). */
  .side-nav { display: none; }
  
  .tab-btn {
    padding: var(--sp-3) var(--sp-4); border: none; background: transparent; color: var(--text-muted);
    font-size: var(--fs-md); font-weight: 500; cursor: pointer; border-bottom: 2px solid transparent;
    margin-bottom: -1px; transition: all 0.15s; font-family: inherit;
  }
  .tab-btn:hover { color: var(--text); }
  .tab-btn.active { color: var(--text); border-bottom-color: var(--accent); }
  .tab-btn { position: relative; }
  .tab-badge {
    display: inline-block; width: 7px; height: 7px; border-radius: var(--radius-pill);
    background: var(--bad); margin-left: var(--sp-2); vertical-align: middle;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--bad) 22%, transparent);
  }
  .tab-badge[hidden] { display: none; }
  .tab-more { display: none; }
  /* The in-bar add button is a phone affordance; desktop keeps the labelled
     floating button, which has room and covers nothing. */
  .tab-add { display: none; }
  .more-sheet-list { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); margin: var(--sp-1) 0 0; }
  .more-sheet-item {
    display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
    padding: var(--sp-4) var(--sp-4); border-radius: var(--radius); background: var(--surface-2);
    border: 1px solid var(--border); color: var(--text); font: inherit; font-size: var(--fs-base);
    text-align: left; cursor: pointer; min-height: 64px; gap: var(--sp-2);
    transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
  }
  .more-sheet-item:hover { background: var(--surface-3); box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
  .more-sheet-item.active {
    background: color-mix(in srgb, var(--accent) 12%, var(--surface));
    border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
    color: var(--accent); font-weight: 600;
  }
  .more-sheet-item.active .more-item-icon { color: var(--accent); }
  .more-item-icon { color: var(--text-subtle); display: flex; margin-bottom: var(--sp-0); }
  .more-item-icon svg { width: 18px; height: 18px; }
  .more-item-label { font-size: var(--fs-md); line-height: 1.2; }
  .more-sheet-item .more-sheet-dot {
    width: 7px; height: 7px; border-radius: var(--radius-pill); background: var(--bad);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--bad) 22%, transparent);
    align-self: flex-end; position: absolute; top: 10px; right: 10px;
  }
  .more-sheet-item { position: relative; }
  .more-sheet-item[hidden] { display: none; }

  .pane { display: none; animation: fadeIn 0.2s ease; scroll-margin-top: 84px; }
  .pane.active { display: block; }
  @keyframes fadeIn { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } }

  .cash-rain {
    position: fixed;
    inset: 0;
    z-index: 900;
    pointer-events: none;
    overflow: hidden;
  }
  .cash-bill {
    position: absolute;
    top: -64px;
    width: 46px;
    height: 24px;
    border: 1px solid rgba(29, 158, 117, 0.45);
    border-radius: 4px;
    background: linear-gradient(135deg, #d9f2df, #87c993);
    box-shadow: 0 4px 14px rgba(0,0,0,0.12);
    animation: cashFall var(--fall-duration, 1.9s) ease-in forwards;
    animation-delay: var(--fall-delay, 0s);
    transform: translateY(-70px) rotate(var(--fall-rotate, -8deg));
  }
  .cash-bill::before {
    content: "$";
    position: absolute;
    inset: 4px 14px;
    border: 1px solid rgba(26, 58, 92, 0.25);
    border-radius: var(--radius-pill);
    display: grid;
    place-items: center;
    color: #1a5f35;
    font-size: var(--fs-sm);
    font-weight: 700;
  }
  @keyframes cashFall {
    0% { opacity: 0; transform: translate3d(0, -70px, 0) rotate(var(--fall-rotate, -8deg)); }
    12% { opacity: 1; }
    100% { opacity: 0; transform: translate3d(var(--fall-drift, 18px), calc(100dvh + 90px), 0) rotate(calc(var(--fall-rotate, -8deg) + 220deg)); }
  }

  /* ---- One card surface ----
     Ten near-copies of "a panel" had drifted apart in radius, shadow and border
     (.card, .hero-card, .mobile-card, .referral-card, .contact-card,
     .notif-panel-card each redeclared their own). Same object, so: one
     declaration for the surface, and each variant below keeps only what is
     genuinely different — its padding, margin, positioning or fill.
     .retro-card is deliberately excluded: it is an editorial paper object on
     purpose, not drift. */
  .card, .hero-card, .mobile-card, .referral-card, .contact-card, .notif-panel-card,
  .mobile-facts, .mobile-kpi {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
  }
  .card { padding: var(--sp-5) var(--sp-6); margin-bottom: var(--sp-4); }
  /* One card head, one section heading, one caption. There were two heads and
     six ways to set a small label — weights 500/600/650/700 across two letter
     spacings — which is why no two panels agreed on how loud their title was.
     Two roles are real and stay separate: a HEADING names a group, a CAPTION
     labels a number. Everything else collapses. */
  .card-head, .mobile-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--sp-4); flex-wrap: wrap; gap: var(--sp-2); }
  .card-title, .mobile-card-label, .mobile-feed-title {
    font-size: var(--fs-xs); font-weight: 600; color: var(--text-subtle);
    text-transform: uppercase; letter-spacing: 0.05em; margin: 0;
  }
  .mobile-feed-title { margin: var(--sp-1) 0 var(--sp-2); }
  .card h3 { font-size: var(--fs-lg); font-weight: 500; margin: 0 0 0.75rem; }

  .retro-wrap { max-width: 640px; margin: 0 auto; }
  .retro-intro { color: var(--text-subtle); font-size: var(--fs-md); margin: 0 0 var(--sp-4); }
  .retro-card {
    background: var(--bone); color: var(--ink);
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    border-radius: 14px; padding: var(--sp-5); margin-bottom: var(--sp-4);
    box-shadow: 0 1px 0 color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .retro-head { display: flex; justify-content: space-between; align-items: baseline; letter-spacing: .08em; }
  .retro-month { font-weight: 700; font-size: var(--fs-md); }
  .retro-status { font-size: var(--fs-2xs); color: color-mix(in srgb, var(--ink) 55%, transparent); }
  .retro-headline { font-size: var(--fs-2xl); font-weight: 650; margin: var(--sp-3) 0 var(--sp-0); line-height: 1.2; }
  .retro-secondary { color: color-mix(in srgb, var(--ink) 70%, transparent); margin: 0 0 var(--sp-4); font-size: var(--fs-base); }
  .retro-hero { font-size: var(--fs-display); font-weight: 700; font-variant-numeric: tabular-nums; margin: 0; }
  .retro-hero span { font-size: var(--fs-md); font-weight: 500; color: color-mix(in srgb, var(--ink) 55%, transparent); margin-left: var(--sp-2); }
  .retro-meta { font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: color-mix(in srgb, var(--ink) 65%, transparent); margin: var(--sp-1) 0 var(--sp-4); }
  .retro-section { text-transform: uppercase; letter-spacing: .07em; font-size: var(--fs-2xs); color: color-mix(in srgb, var(--ink) 55%, transparent); margin: var(--sp-3) 0 var(--sp-2); }
  .retro-catrow { display: flex; justify-content: space-between; font-variant-numeric: tabular-nums; font-size: var(--fs-md); padding: var(--sp-0) 0; }
  .retro-catrow em { font-style: normal; color: color-mix(in srgb, var(--ink) 50%, transparent); font-size: var(--fs-sm); margin-left: var(--sp-2); }
  .retro-stamps { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
  .retro-stamp {
    font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
    padding: var(--sp-0) var(--sp-2); border-radius: 4px; color: #6b5a2e;
    background: color-mix(in srgb, #c8a14a 22%, transparent);
    border: 1px solid color-mix(in srgb, #c8a14a 40%, transparent);
  }
  .retro-big { font-size: var(--fs-sm); color: color-mix(in srgb, var(--ink) 65%, transparent); margin: var(--sp-3) 0 0; }
  .retro-quote { font-size: var(--fs-base); font-style: italic; margin: var(--sp-4) 0 0; color: color-mix(in srgb, var(--ink) 80%, transparent); }
  

  /* Home recap: day 1-3 banner + "Looking back" picker card. Compact, reuses tokens. */
  .home-recap-card { padding: var(--sp-1) 0; }
  .home-recap-card--banner {
    background: var(--bone); color: var(--ink);
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    border-radius: 14px; padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-3);
    box-shadow: 0 1px 0 color-mix(in srgb, var(--ink) 8%, transparent);
  }
  
  .home-recap-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-2); }
  .home-recap-eyebrow { display: block; text-transform: uppercase; letter-spacing: .07em; font-size: var(--fs-2xs); color: var(--text-muted); }
  .home-recap-month { font-weight: 700; font-size: var(--fs-md); }
  .home-recap-x { background: none; border: none; cursor: pointer; font-size: var(--fs-md); line-height: 1; color: var(--text-muted); padding: var(--sp-1); border-radius: 6px; }
  .home-recap-x:hover { background: color-mix(in srgb, var(--ink) 10%, transparent); color: var(--text); }

  /* Apple Wallet inbox: home banner, review rows, Shortcut setup. */
  .wallet-inbox-banner {
    display: flex; align-items: center; gap: var(--sp-3);
    border: 1px solid var(--border); border-radius: 12px;
    padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-3);
    background: var(--surface); cursor: pointer; text-align: left;
  }
  .wallet-inbox-banner:hover { border-color: color-mix(in srgb, var(--ink) 25%, var(--border)); }
  .wallet-inbox-banner-main { flex: 1; min-width: 0; }
  .wallet-inbox-eyebrow { display: block; text-transform: uppercase; letter-spacing: .07em; font-size: var(--fs-2xs); color: var(--text-muted); }
  .wallet-inbox-headline { font-size: var(--fs-md); font-weight: 650; margin: var(--sp-1) 0 0; }
  .wallet-inbox-sub { font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-0) 0 0; }
  .wallet-inbox-chevron { color: var(--text-muted); font-size: var(--fs-lg); }
  .wallet-inbox-lead, .wallet-inbox-empty { font-size: var(--fs-md); color: var(--text-muted); margin: 0 0 var(--sp-3); }
  .wallet-inbox-empty { text-align: center; }
  .wallet-inbox-note { font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-2) 0 0; }
  .wallet-rows { display: grid; gap: var(--sp-2); }
  .wallet-row {
    display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: var(--sp-2);
    border: 1px solid var(--border); border-radius: 10px; padding: var(--sp-2) var(--sp-3);
  }
  .wallet-row-main { min-width: 0; }
  .wallet-row-merchant { font-weight: 600; font-size: var(--fs-md); margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .wallet-row-meta { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; }
  /* 16px keeps iOS from auto-zooming the page when the select is tapped. */
  .wallet-row-cat { width: auto; min-width: 120px; font-size: 16px; }
  .wallet-row-btns { display: flex; gap: var(--sp-1); }
  .wallet-row-btns button { padding: var(--sp-1) var(--sp-2); font-size: var(--fs-sm); }
  .wallet-inbox-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); flex-wrap: wrap; }
  .wallet-setup-steps { margin: 0 0 var(--sp-3); padding-left: 1.2em; font-size: var(--fs-md); display: grid; gap: var(--sp-1); }
  .wallet-setup-field { margin-bottom: var(--sp-3); }
  .wallet-setup-field label { display: block; font-size: var(--fs-sm); font-weight: 600; margin-bottom: var(--sp-1); }
  .wallet-copy-row { display: flex; align-items: center; gap: var(--sp-2); }
  .wallet-copy-row code {
    flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap;
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    padding: var(--sp-1) var(--sp-2); border-radius: 6px; font-size: var(--fs-sm);
  }
  .wallet-setup-table { width: 100%; font-size: var(--fs-sm); border-collapse: collapse; }
  .wallet-setup-table td { padding: var(--sp-1) 0; vertical-align: top; }
  .wallet-setup-table td:first-child { width: 32%; }
  .wallet-link { background: none; border: none; padding: 0; color: var(--accent, inherit); text-decoration: underline; cursor: pointer; font: inherit; }
  .asset-item-unpriced { font-size: var(--fs-sm); color: var(--text-muted); font-weight: 500; }
  .notice-section + .notice-section { margin-top: var(--sp-4); }
  .notice-section-head { font-size: var(--fs-sm); font-weight: 700; margin: 0 0 var(--sp-1); text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
  .notice-section-head.overdue { color: var(--bad, #d85a30); }
  .notice-section-note { font-size: var(--fs-xs); color: var(--text-muted); margin: 0 0 var(--sp-2); }
  .notice-actions { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-1); }
  .notice-act { font-size: var(--fs-xs); padding: var(--sp-0) var(--sp-2); }
  .past-due-banner {
    display: flex; align-items: center; gap: var(--sp-4); width: 100%;
    text-align: left; font: inherit; color: var(--text); cursor: pointer;
    background: var(--bad-bg, #fdf1ec);
    border: 1px solid color-mix(in srgb, var(--bad, #d85a30) 40%, transparent);
    border-radius: 14px; padding: var(--sp-4); margin-bottom: 1.25rem;
  }
  .past-due-banner:hover { border-color: var(--bad, #d85a30); }
  .past-due-banner:focus-visible { outline: 2px solid var(--bad, #d85a30); outline-offset: 2px; }
  .past-due-icon {
    width: 36px; height: 36px; border-radius: var(--radius-pill); flex-shrink: 0;
    display: grid; place-items: center; background: var(--surface); font-size: var(--fs-xl);
  }
  .past-due-banner-main { flex: 1; min-width: 0; }
  .past-due-eyebrow { display: block; text-transform: uppercase; letter-spacing: .07em; font-size: var(--fs-2xs); color: var(--bad, #d85a30); font-weight: 700; }
  .past-due-headline { display: block; font-size: var(--fs-base); font-weight: 600; margin: var(--sp-0) 0 0; }
  .past-due-sub { display: block; font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-0) 0 0; line-height: 1.45; }
  .past-due-cta { flex-shrink: 0; padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm); }
  
  .nw-stale { font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-2) 0 0; }
  
  .home-recap-headline { font-size: var(--fs-lg); font-weight: 650; margin: var(--sp-2) 0 var(--sp-0); line-height: 1.2; }
  .home-recap-secondary { color: var(--text-muted); margin: 0 0 var(--sp-2); font-size: var(--fs-sm); }
  .home-recap-hero { font-size: var(--fs-2xl); font-weight: 700; font-variant-numeric: tabular-nums; margin: 0; }
  .home-recap-hero.pos { color: var(--good); } .home-recap-hero.neg { color: var(--bad); }
  .home-recap-hero span { font-size: var(--fs-sm); font-weight: 500; color: var(--text-muted); margin-left: var(--sp-1); }
  .home-recap-meta { font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-0) 0 var(--sp-2); }
  .home-recap-catrow { display: flex; justify-content: space-between; font-variant-numeric: tabular-nums; font-size: var(--fs-md); padding: var(--sp-0) 0; }
  .home-recap-catrow em { font-style: normal; color: var(--text-muted); font-size: var(--fs-sm); margin-left: var(--sp-1); }
  .home-recap-big { font-size: var(--fs-sm); color: var(--text-muted); margin: var(--sp-2) 0 0; }
  .home-recap-more { font-size: var(--fs-sm); padding: var(--sp-1) 0; margin-top: var(--sp-2); }

  /* Home nudge: statements ready to reconcile (auto-clears once reconciled). */
  .home-statement-nudge {
    display: flex; align-items: center; gap: var(--sp-2); width: 100%;
    margin-bottom: var(--sp-3); padding: var(--sp-3) var(--sp-4);
    background: var(--warn-bg); color: var(--text);
    border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
    border-radius: var(--radius); cursor: pointer; font: inherit; text-align: left;
    transition: background 0.12s, box-shadow 0.12s;
  }
  .home-statement-nudge:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
  .home-statement-nudge-icon { font-size: var(--fs-lg); flex-shrink: 0; }
  .home-statement-nudge-text { flex: 1; font-size: var(--fs-md); font-weight: 500; }
  .home-statement-nudge-cta { font-size: var(--fs-sm); font-weight: 600; color: var(--warn); flex-shrink: 0; }

  /* Export dialog format picker */
  .export-fmt-btn {
    padding: var(--sp-3) var(--sp-2); border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
    font: inherit; font-size: var(--fs-md); font-weight: 600; cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
  }
  .export-fmt-btn:hover { background: var(--surface-3); }
  .export-fmt-btn.active {
    background: color-mix(in srgb, var(--accent) 12%, var(--surface));
    border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
    color: var(--accent);
  }

  .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 700; padding: 1rem; align-items: flex-start; justify-content: center; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
  .modal-overlay.open { display: flex; }
  body.modal-open { position: fixed; width: 100%; overflow: hidden; }
  .modal { background: var(--surface); border-radius: var(--radius-lg); width: 100%; max-width: 480px; padding: 1.5rem; margin-top: 4rem; }
  .modal-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.75rem; }
  .tx-modal-head { flex-direction: column; align-items: stretch; gap: 0.75rem; }
  .tx-modal-head-row { display: flex; justify-content: space-between; align-items: center; }
  .tx-submit-row { margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--border); }
  .tx-submit-actions { display: flex; gap: var(--sp-3); }
  .modal-title { font-size: var(--fs-lg); font-weight: 600; margin: 0; }
  .close-x { background: transparent; border: none; font-size: var(--fs-2xl); cursor: pointer; color: var(--text-muted); padding: 0; width: 28px; height: 28px; }
  .close-x:hover { color: var(--text); }
  .profile-actions { display: flex; flex-direction: column; gap: var(--sp-0); margin-top: 0.25rem; }

  .toggle-group { display: flex; gap: var(--sp-1); padding: var(--sp-1); background: var(--surface-2); border-radius: var(--radius-sm); margin-bottom: 1rem; }
  .toggle-btn {
    flex: 1; padding: var(--sp-2) var(--sp-4); border: none; background: transparent;
    color: var(--text-muted); font-size: var(--fs-md); font-weight: 500;
    cursor: pointer; border-radius: 6px; font-family: inherit; transition: all 0.15s;
  }
  .toggle-btn.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 2px rgba(0,0,0,0.05); }

  .voice-btn {
    display: flex; align-items: center; gap: var(--sp-3); width: 100%;
    padding: var(--sp-3) var(--sp-4); border: 1px dashed var(--border-strong);
    background: var(--surface-2, var(--surface)); color: var(--text-muted);
    border-radius: 10px; font-size: var(--fs-md); font-family: inherit;
    cursor: pointer; transition: all 0.15s; text-align: left;
    -webkit-user-select: none; user-select: none; touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
  }
  .voice-btn:hover { border-color: var(--accent); color: var(--text); }
  .voice-mic { font-size: var(--fs-xl); line-height: 1; }
  .voice-btn.recording { border-style: solid; border-color: var(--bad, #dc2626); color: var(--text); animation: voicePulse 1.2s ease-in-out infinite; }
  @keyframes voicePulse { 0%,100% { box-shadow: 0 0 0 0 rgba(220,38,38,0.35); } 50% { box-shadow: 0 0 0 6px rgba(220,38,38,0); } }
  .voice-transcript { margin: var(--sp-2) 0 0; padding: var(--sp-2) var(--sp-3); background: var(--surface); border-radius: var(--radius-sm); font-size: var(--fs-md); color: var(--text); font-style: italic; }

  .form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--sp-3); margin-bottom: var(--sp-3); }
  .form-row.two { grid-template-columns: 1fr 1fr; }
  label { display: block; font-size: var(--fs-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 500; margin-bottom: var(--sp-1); }
  input[type="text"], input[type="number"], input[type="date"], input[type="month"], input[type="password"], input[type="email"], input[type="search"], textarea, select {
    width: 100%; max-width: 100%; min-width: 0; padding: var(--sp-2) var(--sp-3); font-size: var(--fs-base); border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font-family: inherit;
    font-variant-numeric: tabular-nums;
  }
  input[type="date"], input[type="month"] { appearance: none; -webkit-appearance: none; }
  /* Without this a search field keeps the platform's own pill shape, inset
     shadow and clear affordance, so it sits taller and lighter than the select
     beside it however it is painted. */
  input[type="search"] { appearance: none; -webkit-appearance: none; }
  input[type="search"]::-webkit-search-decoration,
  input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
  /* A date/month field and a select each render an internal widget that makes
     them 2px taller than a text-shaped field at the same font size and padding.
     Left alone, a text or search input sitting beside a select in the same form
     row is visibly short. Pad the text-shaped ones to match, so every control
     in a row lines up. Measured, not guessed — see tests/browser-mobile-ux.mjs. */
  input[type="text"], input[type="number"], input[type="password"],
  input[type="email"], input[type="search"] {
    padding-top: calc(var(--sp-2) + 1px); padding-bottom: calc(var(--sp-2) + 1px);
  }
  input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); }
  input[aria-invalid="true"],
  textarea[aria-invalid="true"],
  select[aria-invalid="true"] {
    border-color: var(--bad);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--bad) 16%, transparent);
  }

  /* ---- Show / hide password ----
     A password nobody can read is a password typed three times. The toggle is a
     real button sitting inside the field's own padding, never an overlay on the
     text.

     Both type="password" AND type="text" are selected because the toggle swaps
     the type at runtime: style only one and the field changes shape mid-typing.
     Two attribute selectors also out-specify the phone `padding` shorthand in
     the mobile block further down, which would otherwise reset padding-right
     and drop the word "Hide" on top of the last characters typed. Nothing here
     sets font-size — the field must stay >=16px on iOS in both states. */
  .pw-field { position: relative; }
  .pw-field input[type="password"], .pw-field input[type="text"] { padding-right: 62px; }
  /* The lock screen centres its inputs, so the room made for the button on the
     right is mirrored on the left or the placeholder sits visibly off-centre. */
  .pw-field.centered input[type="password"], .pw-field.centered input[type="text"] { padding-left: 62px; }
  .pw-toggle {
    position: absolute; top: 0; right: 0; height: 100%; min-width: 62px;
    display: flex; align-items: center; justify-content: center;
    padding: 0 var(--sp-2); background: none; border: none;
    border-radius: var(--radius-sm); color: var(--text-muted);
    font-family: inherit; font-size: var(--fs-sm); font-weight: 600;
    letter-spacing: 0.03em; cursor: pointer;
    transition: color var(--dur-fast) var(--ease);
  }
  .pw-toggle:hover { color: var(--text); }
  .pw-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

  

  .cat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: var(--sp-2); margin: 0.5rem 0 1rem; }
  .cat-btn {
    padding: var(--sp-3) var(--sp-3); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
    background: var(--surface); cursor: pointer; font-size: var(--fs-sm); font-weight: 500;
    color: var(--text); text-align: left; transition: all 0.1s; font-family: inherit;
    display: flex; flex-direction: column; gap: var(--sp-0);
  }
  .cat-btn:hover { border-color: var(--text-muted); }
  .cat-btn.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
  .cat-btn.active .cat-group { color: var(--on-accent); opacity: 0.7; }
  .cat-add-btn { border-style: dashed; color: var(--text-subtle); }
  .cat-add-btn:hover { color: var(--text); border-color: var(--text-muted); }
  .cat-btn[data-group="income"]        { border-left: 3px solid var(--good); }
  .cat-btn[data-group="discretionary"] { border-left: 3px solid var(--purple); }
  .cat-btn[data-group="fixed"]         { border-left: 3px solid var(--text-subtle); }
  .cat-btn[data-group="variable"]      { border-left: 3px solid var(--info); }
  .cat-btn[data-group="debt"]          { border-left: 3px solid var(--bad); }
  .cat-btn[data-group="savings"]       { border-left: 3px solid var(--good); }
  .cat-btn.active[data-group]          { border-left-color: rgba(255,255,255,0.5); }
  .cat-del { position: absolute; top: 4px; right: 5px; font-size: var(--fs-md); line-height: 1; color: var(--text-subtle); cursor: pointer; }
  .cat-del:hover { color: var(--bad); }
  .cat-group { font-size: var(--fs-2xs); color: var(--text-subtle); text-transform: uppercase; letter-spacing: 0.04em; }
  .inline-note { font-size: var(--fs-sm); color: var(--text-subtle); margin: var(--sp-2) 0 0; }
  .receipt-preview {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
    border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); margin-top: var(--sp-2);
    background: var(--surface-2);
  }
  .receipt-name { font-size: var(--fs-sm); color: var(--text-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .receipt-actions { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
  .camera-only { display: none; }
  .touch-only { display: none; }
  
  .receipt-viewer { width: 100%; min-height: 55vh; max-height: 72vh; overflow: auto; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); display: grid; place-items: center; }
  .receipt-viewer img { max-width: 100%; height: auto; display: block; }
  .receipt-viewer iframe { width: 100%; height: 70vh; border: 0; background: var(--surface); }
  .login-insight-modal { max-width: 380px; padding-top: 1.75rem; }
  .login-insight-modal .modal-head { margin-bottom: 1.35rem; }
  .login-insight-list { display: grid; gap: var(--sp-3); margin: 0 0 1.25rem; }
  .login-insight-item {
    border: 1.5px solid var(--border-strong); border-radius: 10px; padding: var(--sp-3) var(--sp-3);
    background: var(--surface-2);
  }
  .login-insight-label {
    margin: 0 0 var(--sp-1); font-size: var(--fs-2xs); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-subtle);
  }
  .login-insight-text { margin: 0; font-size: var(--fs-base); line-height: 1.45; color: var(--text); }
  .login-insight-item.good { border-color: color-mix(in srgb, var(--good) 46%, var(--border-strong)); }
  .login-insight-item.watch { border-color: color-mix(in srgb, var(--warn) 42%, var(--border-strong)); }
  .modal-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
  .modal-actions button { min-height: 44px; padding: var(--sp-3) var(--sp-4); }

  button.primary {
    background: var(--accent); color: var(--on-accent); border: 1px solid var(--accent); padding: var(--sp-3) var(--sp-5);
    border-radius: var(--radius-sm); font-size: var(--fs-md); font-weight: 600; cursor: pointer; font-family: inherit;
    transition: opacity 0.15s, transform 0.12s;
  }
  button.primary:hover { opacity: 0.88; transform: translateY(-1px); }
  button:disabled { opacity: 0.55; cursor: not-allowed; }
  button.ghost {
    background: var(--surface); color: var(--text-muted); border: 1px solid var(--border-strong); padding: var(--sp-2) var(--sp-4);
    border-radius: var(--radius-sm); font-size: var(--fs-sm); cursor: pointer; font-family: inherit;
    transition: background 0.12s, color 0.12s, box-shadow 0.12s;
  }
  button.ghost:hover { background: var(--surface-2); color: var(--text); box-shadow: 0 1px 4px rgba(0,0,0,0.07); }
  button.danger { color: var(--bad); border-color: rgba(216,90,48,0.3); }
  button.danger:hover { background: var(--bad-bg); }
  button.btn-add {
    background: color-mix(in srgb, var(--good) 11%, var(--surface));
    color: var(--good); border: 1px solid color-mix(in srgb, var(--good) 38%, var(--border));
    padding: var(--sp-2) var(--sp-4); border-radius: var(--radius-sm); font-size: var(--fs-sm); cursor: pointer; font-family: inherit;
    transition: background 0.12s, box-shadow 0.12s; font-weight: 500;
  }
  button.btn-add:hover { background: color-mix(in srgb, var(--good) 20%, var(--surface)); box-shadow: 0 1px 4px rgba(0,0,0,0.07); }
  button.btn-ai {
    background: color-mix(in srgb, var(--purple) 11%, var(--surface));
    color: var(--purple); border: 1px solid color-mix(in srgb, var(--purple) 38%, var(--border));
    padding: var(--sp-2) var(--sp-4); border-radius: var(--radius-sm); font-size: var(--fs-sm); cursor: pointer; font-family: inherit;
    transition: background 0.12s, box-shadow 0.12s; font-weight: 500;
  }
  button.btn-ai:hover { background: color-mix(in srgb, var(--purple) 20%, var(--surface)); box-shadow: 0 1px 4px rgba(0,0,0,0.07); }

  .tx-row { cursor: pointer; transition: background 0.12s ease; }
  .tx-row:hover { background: var(--surface-2); margin: 0 -1.5rem; padding-left: 1.5rem; padding-right: 1.5rem; }
  .tx-icon { width: 38px; height: 38px; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: var(--fs-base); font-weight: 600; box-shadow: 0 1px 0 rgba(0,0,0,0.02) inset; }
  .tx-icon.income { background: var(--good-bg); color: var(--good); }
  .tx-icon.fixed { background: var(--surface-3); color: var(--text-muted); }
  .tx-icon.variable { background: var(--info-bg); color: var(--info); }
  .tx-icon.discretionary { background: var(--purple-bg); color: var(--purple); }
  .tx-icon.debt { background: var(--bad-bg); color: var(--bad); }
  .tx-icon.savings { background: var(--good-bg); color: var(--good); }
  .tx-body { flex: 1; min-width: 0; }
  .tx-cat { font-size: var(--fs-base); font-weight: 600; margin: 0; letter-spacing: -0.005em; }
  .tx-meta {
    font-size: var(--fs-sm); color: var(--text-subtle); margin: var(--sp-0) 0 0;
    display: flex; align-items: center; gap: var(--sp-2); min-width: 0;
  }
  .tx-note { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
  .tx-amt { font-size: var(--fs-lg); font-weight: 600; font-variant-numeric: tabular-nums; flex-shrink: 0; letter-spacing: -0.005em; }
  .tx-receipt-dot { width: 6px; height: 6px; border-radius: var(--radius-pill); background: var(--info); flex-shrink: 0; margin-right: -4px; box-shadow: 0 0 0 3px var(--info-bg); }
  .tx-chip { display: inline-flex; align-items: center; padding: var(--sp-0) var(--sp-2); border-radius: var(--radius-pill); background: var(--surface-2); color: var(--text-muted); font-size: var(--fs-xs); font-weight: 500; line-height: 1.5; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; }
  .tx-amt.income { color: var(--good); }
  .tx-amt.expense { color: var(--bad); }
  .tx-amt.transfer { color: var(--text-muted); }
  .tx-amt.transfer::before { content: "↔ "; }
  .tx-icon.transfer { background: var(--surface-2); color: var(--text-muted); font-size: var(--fs-lg); }
  .tx-icon.other { background: var(--surface-3); color: var(--text-muted); }
  .date-total .income { color: var(--good); }
  .date-total .expense { color: var(--bad); }

  .tba-banner { padding: 0.875rem 0; margin: 0 0 0.5rem; border-bottom: 1px solid var(--border); }
  .tba-disclosure { }
  .tba-disclosure > summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: 1rem; padding: var(--sp-1) 0; }
  .tba-disclosure > summary::-webkit-details-marker { display: none; }
  .tba-disclosure > summary > .tba-heading { flex: 1; min-width: 0; }
  .tba-label { font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-muted); font-weight: 600; }
  .tba-value { font-size: var(--fs-2xl); font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.2; }
  .tba-value.good { color: var(--good, #16a34a); }
  .tba-value.bad { color: var(--bad, #dc2626); }
  .tba-value.warn { color: var(--warn, #d97706); }
  .tba-chevron { width: 28px; height: 28px; display: grid; place-items: center; border-radius: var(--radius-pill); color: var(--text-muted); flex-shrink: 0; transition: transform 0.2s ease; }
  .tba-disclosure[open] > summary .tba-chevron { transform: rotate(180deg); }
  .tba-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); flex-wrap: wrap; }
  .tba-actions button { font-size: var(--fs-md); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius); white-space: nowrap; flex: 1; min-height: 38px; min-width: 120px; }
  .tba-carryin { font-size: var(--fs-sm); color: var(--text-subtle); margin-top: var(--sp-2); }
  .budget-cat.recently-changed { background: color-mix(in srgb, var(--good) 14%, transparent); border-radius: var(--radius); padding-left: var(--sp-2); padding-right: var(--sp-2); margin-left: -8px; margin-right: -8px; animation: budgetChangedFade 60s linear forwards; }
  @keyframes budgetChangedFade { 0% { background: color-mix(in srgb, var(--good) 18%, transparent); } 100% { background: transparent; } }

  /* Reusable polish utilities used across newer surfaces (Plan, Closeout,
     Accounts, Subscriptions) so they stop looking home-made with inline
     styles everywhere. Keep them small and obvious. */
  /* Two chips that read as one thing until you tap them. A plan adjustment is
     quiet; repairing an overspend takes money out of another envelope, so it
     wears the warning colour and states its consequence. */
  .chip {
    font-size: var(--fs-xs); font-weight: 600; padding: var(--sp-0) var(--sp-2);
    margin-left: var(--sp-0); border-radius: var(--radius-pill); cursor: pointer;
    font-family: inherit; border: 1px solid var(--border-strong);
    background: var(--surface); color: var(--text-muted);
  }
  .chip:hover { border-color: var(--text-subtle); }
  .chip-fix {
    border-color: color-mix(in srgb, var(--warn) 45%, transparent);
    background: var(--warn-bg); color: var(--warn);
  }
  .chip-fix:hover { border-color: var(--warn); }

  .hint-chip { display: inline-flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-2xs); font-weight: 500; padding: var(--sp-0) var(--sp-2); border-radius: var(--radius-pill); color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent); cursor: pointer; line-height: 1.4; transition: background 0.15s ease; white-space: nowrap; }
  .hint-chip:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
  .notice-line { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs); }
  .notice-line.good { color: var(--good); }
  .notice-line.warn { color: var(--warn); }
  .notice-line.bad  { color: var(--bad); }
  .notice-banner { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3) var(--sp-3); border-radius: 10px; font-size: var(--fs-sm); line-height: 1.5; margin-top: var(--sp-2); }
  .notice-banner.warn { background: color-mix(in srgb, var(--warn) 10%, transparent); border: 1px solid color-mix(in srgb, var(--warn) 25%, transparent); color: var(--text); }
  .notice-banner.good { background: color-mix(in srgb, var(--good) 10%, transparent); border: 1px solid color-mix(in srgb, var(--good) 25%, transparent); color: var(--text); }
  .notice-banner b { font-weight: 600; }
  .metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: var(--sp-4); margin-top: var(--sp-4); }
  .metric-tile { padding: var(--sp-3) var(--sp-4); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); }
  .metric-tile-value { font-size: var(--fs-2xl); font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; letter-spacing: -0.01em; }
  .metric-tile-label, .mobile-kpi-label, .mobile-sts-recs-title {
    font-size: var(--fs-2xs); color: var(--text-subtle); text-transform: uppercase;
    letter-spacing: 0.05em; font-weight: 600; margin: var(--sp-1) 0 0;
  }
  .mobile-kpi-label { margin: 0 0 var(--sp-2); }
  .mobile-sts-recs-title { flex-basis: 100%; margin: 0; }
  .item-row-main { flex: 1; min-width: 0; }
  .item-row-title { font-size: var(--fs-md); font-weight: 500; margin: 0; color: var(--text); display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
  .item-row-title .amount { font-variant-numeric: tabular-nums; font-weight: 600; }
  .item-row-meta { font-size: var(--fs-xs); color: var(--text-subtle); margin: var(--sp-0) 0 0; }
  .item-row-aside { font-size: var(--fs-sm); color: var(--text-muted); white-space: nowrap; flex-shrink: 0; }
  .item-row-when { font-size: var(--fs-xs); color: var(--text-subtle); font-weight: 400; margin-left: auto; padding-left: var(--sp-2); }
  .card-head-meta { font-size: var(--fs-xs); color: var(--text-subtle); }
  .empty-hint { font-size: var(--fs-md); color: var(--text-muted); margin: 0; line-height: 1.5; }
  /* Dashboard time-range "Trends" card */
  .trends-range { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-3); }
  .trends-chip { font-size: var(--fs-sm); font-weight: 600; padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-pill); border: 1px solid var(--border); background: var(--surface-2); color: var(--text-muted); cursor: pointer; font-family: inherit; transition: background 0.12s, color 0.12s, border-color 0.12s; }
  .trends-chip:hover { border-color: var(--border-strong); }
  .trends-chip.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
  .trends-compare { font-size: var(--fs-sm); color: var(--text-muted); display: flex; align-items: center; gap: var(--sp-2); cursor: pointer; white-space: nowrap; }
  .trends-compare input { width: auto; margin: 0; cursor: pointer; }
  .trends-custom { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); font-size: var(--fs-sm); color: var(--text-muted); flex-wrap: wrap; }
  .trends-custom input { width: auto; padding: var(--sp-2) var(--sp-2); font-size: var(--fs-md); }
  .trends-summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); }
  .trends-stat { background: var(--surface-2); border-radius: var(--radius); padding: var(--sp-3) var(--sp-3); min-width: 0; }
  .trends-stat-label { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-subtle); font-weight: 600; margin: 0 0 var(--sp-1); }
  .trends-stat-value { font-size: var(--fs-xl); font-weight: 700; margin: 0; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; white-space: nowrap; }
  .trends-stat-delta { font-size: var(--fs-xs); font-weight: 600; margin: var(--sp-1) 0 0; line-height: 1.3; }
  .trends-stat-delta.good { color: var(--good); }
  .trends-stat-delta.bad { color: var(--bad); }
  .trends-stat-delta.muted { color: var(--text-subtle); font-weight: 500; }
  .insight-group-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin: 1rem 0 0.4rem; flex-wrap: wrap; }
  .insight-group-head .card-title { margin: 0; }
  .insight-group-head .primary { font-size: var(--fs-sm); padding: var(--sp-2) var(--sp-3); }
  /* Money weather: one-line vibe at the top of Today. Tap to expand. */
  .weather-pill {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4); margin: 0 0 var(--sp-3);
    border-radius: var(--radius); cursor: pointer;
    background: var(--surface); border: 1px solid var(--border);
    box-shadow: 0 1px 2px rgba(0,0,0,0.04);
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.1s ease, box-shadow 0.15s ease;
  }
  .weather-pill:active { transform: scale(0.99); }
  .weather-pill.tone-sunny { background: linear-gradient(135deg, color-mix(in srgb, var(--warn) 12%, var(--surface)), var(--surface)); border-color: color-mix(in srgb, var(--warn) 28%, transparent); }
  .weather-pill.tone-partly { background: linear-gradient(135deg, color-mix(in srgb, var(--info) 12%, var(--surface)), var(--surface)); border-color: color-mix(in srgb, var(--info) 26%, transparent); }
  .weather-pill.tone-cloudy { background: linear-gradient(135deg, color-mix(in srgb, var(--warn) 18%, var(--surface)), var(--surface)); border-color: color-mix(in srgb, var(--warn) 34%, transparent); }
  .weather-pill.tone-storm  { background: linear-gradient(135deg, color-mix(in srgb, var(--bad) 16%, var(--surface)), var(--surface)); border-color: color-mix(in srgb, var(--bad) 36%, transparent); }
  .weather-pill.expanded .weather-chevron { transform: rotate(90deg); }
  .weather-icon { font-size: var(--fs-2xl); line-height: 1; flex-shrink: 0; }
  .weather-headline { flex: 1; font-size: var(--fs-base); font-weight: 500; color: var(--text); line-height: 1.4; min-width: 0; }
  .weather-chevron { font-size: var(--fs-xl); color: var(--text-subtle); transition: transform 0.2s ease; flex-shrink: 0; }
  .weather-detail {
    padding: var(--sp-3) var(--sp-4) var(--sp-4); margin: -6px 0 var(--sp-3);
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    border-top-left-radius: 0; border-top-right-radius: 0;
    font-size: var(--fs-md); color: var(--text-muted); line-height: 1.55;
  }
  .weather-detail ul { margin: var(--sp-1) 0 0; padding-left: var(--sp-5); }
  .weather-detail li { margin: var(--sp-0) 0; }
  .weather-detail .num { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

  /* Goal coach: surface where slack in user spending could go toward
     their savings goals. */
  .goal-coach-headline {
    font-size: var(--fs-base); color: var(--text); line-height: 1.5;
    margin: 0 0 var(--sp-3); padding-bottom: var(--sp-3); border-bottom: 1px solid var(--border);
  }
  .goal-coach-headline .num { font-weight: 700; font-variant-numeric: tabular-nums; }
  .goal-coach-headline .goal-name { font-weight: 600; }
  .goal-coach-goals { display: flex; flex-direction: column; gap: var(--sp-2); margin: 0 0 var(--sp-3); }
  .goal-coach-goal-row {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
    font-size: var(--fs-md); color: var(--text-muted);
  }
  .goal-coach-goal-row .name { color: var(--text); font-weight: 500; }
  .goal-coach-goal-row .need { font-variant-numeric: tabular-nums; }
  .goal-coach-goal-row .need.short { color: var(--bad); }
  .goal-coach-goal-row .need.met { color: var(--good); }
  .goal-coach-section-label {
    font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-subtle); font-weight: 600; margin: var(--sp-4) 0 var(--sp-2);
  }
  .goal-coach-suggestion {
    display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3) 0;
    font-size: var(--fs-md); line-height: 1.45;
  }
  .goal-coach-suggestion + .goal-coach-suggestion { border-top: 1px solid var(--border); }
  .goal-coach-suggestion .marker {
    flex-shrink: 0; width: 22px; height: 22px; border-radius: 6px;
    background: color-mix(in srgb, var(--good) 18%, var(--surface));
    color: var(--good); font-weight: 700; font-size: var(--fs-md);
    display: flex; align-items: center; justify-content: center;
  }
  .goal-coach-suggestion .text { color: var(--text); }
  .goal-coach-suggestion .text b { font-variant-numeric: tabular-nums; }
  .goal-coach-suggestion .saved {
    font-size: var(--fs-sm); color: var(--good); font-weight: 600;
    font-variant-numeric: tabular-nums; white-space: nowrap; margin-left: auto;
  }
  .goal-coach-apply {
    font-size: var(--fs-sm); font-weight: 600; padding: var(--sp-1) var(--sp-3);
    border: 1px solid var(--accent); background: var(--accent); color: var(--on-accent);
    border-radius: 6px; cursor: pointer; flex-shrink: 0;
    font-family: inherit; transition: all 0.15s;
  }
  .goal-coach-apply:hover { background: color-mix(in srgb, var(--accent) 88%, #000); }
  .goal-coach-apply:disabled { opacity: 0.5; cursor: not-allowed; }
  .goal-coach-foot {
    margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--border);
    font-size: var(--fs-sm); color: var(--text-subtle); line-height: 1.5;
  }

  /* Week-vs-week comparison rows. */
  .compare-headline {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
    padding: var(--sp-2) 0 var(--sp-3); border-bottom: 1px solid var(--border);
  }
  .compare-headline-amount { font-size: var(--fs-2xl); font-weight: 700; font-variant-numeric: tabular-nums; }
  .compare-headline-delta { font-size: var(--fs-md); font-weight: 600; font-variant-numeric: tabular-nums; }
  .compare-headline-delta.up { color: var(--bad); }
  .compare-headline-delta.down { color: var(--good); }
  .compare-headline-delta.flat { color: var(--text-subtle); }
  .compare-row { display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; gap: var(--sp-3); align-items: center; padding: var(--sp-2) 0; }
  .compare-row + .compare-row { border-top: 1px solid var(--border); }
  .compare-arrow { font-size: var(--fs-base); line-height: 1; }
  .compare-arrow.up { color: var(--bad); }
  .compare-arrow.down { color: var(--good); }
  .compare-label { font-size: var(--fs-md); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .compare-amounts { font-size: var(--fs-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
  .compare-amounts .delta { font-weight: 600; }
  .compare-amounts .delta.up { color: var(--bad); }
  .compare-amounts .delta.down { color: var(--good); }

  .breakdown-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; padding: var(--sp-2) 0; }
  .breakdown-label { font-size: var(--fs-md); font-weight: 500; color: var(--text); display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
  .breakdown-label-dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
  .breakdown-label-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .breakdown-amount { font-size: var(--fs-md); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; flex-shrink: 0; }
  .breakdown-bar-wrap { grid-column: 1 / -1; height: 6px; background: var(--surface-3); border-radius: 3px; overflow: hidden; margin-top: var(--sp-1); }
  .breakdown-bar { height: 100%; border-radius: 3px; transition: width 0.4s ease; }
  .breakdown-bar.fixed         { background: var(--text-muted); }
  .breakdown-bar.variable      { background: var(--info); }
  .breakdown-bar.discretionary { background: var(--purple); }
  .breakdown-bar.debt          { background: var(--bad); }
  /* Subscription sources: one accent for every bar. Each row is named right
     beside its bar, so colour carries no information here — a hue per card
     would be decoration competing with the numbers. */
  .breakdown-bar.sub-bar          { background: var(--accent); }
  .breakdown-bar.sub-bar-unlinked { background: var(--text-subtle); }
  .sub-src-unlinked { color: var(--text-muted); font-style: italic; }
  .sub-per-mo { font-size: var(--fs-xs); font-weight: 500; color: var(--text-subtle); margin-left: 2px; }
  .sub-sources { margin-top: var(--sp-2); }
  /* The plain-English line under a number. Deliberately a full sentence: the
     people this app is for need to be told what the number means for them, not
     handed a metric and left to infer the consequence. */
  .sub-plain { font-size: var(--fs-md); color: var(--text-muted); line-height: 1.6; margin: var(--sp-3) 0 0; }
  .sub-plain strong { color: var(--text); }
  .card-load { color: var(--warn); margin-top: 2px; }
  .breakdown-bar.savings       { background: var(--good); }
  .breakdown-bar.other         { background: var(--text-subtle); }
  .breakdown-label-dot.fixed         { background: var(--text-muted); }
  .breakdown-label-dot.variable      { background: var(--info); }
  .breakdown-label-dot.discretionary { background: var(--purple); }
  .breakdown-label-dot.debt          { background: var(--bad); }
  .breakdown-label-dot.savings       { background: var(--good); }
  .breakdown-label-dot.other         { background: var(--text-subtle); }
  .breakdown-pct { font-size: var(--fs-xs); color: var(--text-subtle); margin-left: var(--sp-2); }

  .budget-cat { padding: 0.875rem 0; border-bottom: 1px solid var(--border); }
  .budget-cat:last-child { border-bottom: none; }
  .budget-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--sp-2); }
  .budget-name { font-size: var(--fs-md); font-weight: 500; }
  .budget-nums { font-size: var(--fs-sm); color: var(--text-subtle); font-variant-numeric: tabular-nums; }
  .budget-nums b { color: var(--text); font-weight: 500; }
  .budget-group-header { display: flex; justify-content: space-between; align-items: center; padding: var(--sp-2) var(--sp-3); border-radius: var(--radius); background: var(--surface-2); margin: 1rem 0 var(--sp-0); border-left: 3px solid var(--border-strong); }
  .budget-group-header-name { font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-muted); }
  .budget-group-header-total { font-size: var(--fs-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; font-weight: 500; }
  .progress-track { height: 8px; background: var(--surface-3); border-radius: 4px; overflow: hidden; }
  .progress-fill { height: 100%; border-radius: 4px; transition: width 0.45s cubic-bezier(0.4,0,0.2,1); background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 70%, var(--info))); }
  .progress-fill.good { background: linear-gradient(90deg, var(--good), color-mix(in srgb, var(--good) 75%, #3ecfb0)); }
  .progress-fill.warn { background: linear-gradient(90deg, var(--warn), color-mix(in srgb, var(--warn) 80%, var(--bad))); }
  .progress-fill.bad { background: linear-gradient(90deg, color-mix(in srgb, var(--bad) 85%, var(--warn)), var(--bad)); }
  .progress-wrap { display: flex; align-items: center; gap: var(--sp-2); }
  .progress-wrap .progress-track { flex: 1; margin: 0; }
  .progress-pct { font-size: var(--fs-xs); font-variant-numeric: tabular-nums; min-width: 30px; text-align: right; flex-shrink: 0; color: var(--text-subtle); }
  .progress-pct.good { color: var(--good); }
  .progress-pct.warn { color: var(--warn); }
  .progress-pct.bad { color: var(--bad); }

  .phase-section { margin-bottom: 0.5rem; }
  .phase-header { font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-subtle); padding: 1rem 0 0.5rem; border-bottom: 1px solid var(--border); margin-bottom: 0.5rem; }
  .phase-tag { display: inline-block; margin-left: var(--sp-2); padding: var(--sp-0) var(--sp-2); font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); background: var(--info-bg); border-radius: var(--radius-pill); }
  .task { cursor: pointer; align-items: flex-start; }
  .task.task-swipeable { display: block; position: relative; padding: 0; border-bottom: none; overflow: hidden; }
  .task-swipe-inner {
    position: relative; z-index: 1;
    display: flex; gap: var(--sp-3); padding: var(--sp-3) 0; align-items: flex-start;
    width: 100%;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    transition: transform 0.18s ease;
    will-change: transform;
    touch-action: pan-y;
  }
  .task-swipeable:last-child .task-swipe-inner { border-bottom: none; }
  .task-swipeable.swiping .task-swipe-inner { transition: none; }
  .task-swipe-actions {
    position: absolute; inset: 0; display: flex; align-items: stretch;
    pointer-events: none;
  }
  .task-swipe-action {
    flex: 1; display: flex; align-items: center;
    padding: 0 var(--sp-5); font-size: var(--fs-md); font-weight: 600; color: white;
  }
  .task-swipe-action.archive { background: var(--good); justify-content: flex-start; }
  .task-swipe-action.delete { background: var(--bad); justify-content: flex-end; }
  .check {
    width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--border-strong);
    flex-shrink: 0; margin-top: var(--sp-0); display: flex; align-items: center; justify-content: center;
    transition: all 0.15s;
  }
  .check.checked { background: var(--good); border-color: var(--good); box-shadow: 0 1px 4px rgba(29,158,117,0.25); }
  .check svg { display: none; width: 11px; height: 8px; }
  .check.checked svg { display: block; }
  input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; }
  .task.done .task-title { text-decoration: line-through; color: var(--text-subtle); }
  .task.done .task-detail { color: var(--text-subtle); }
  .task-content { flex: 1; }
  .task-title { font-size: var(--fs-base); font-weight: 500; margin: 0 0 var(--sp-0); line-height: 1.4; }
  .task-detail { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; line-height: 1.5; }
  .badge { display: inline-block; font-size: var(--fs-2xs); padding: var(--sp-0) var(--sp-2); border-radius: 4px; font-weight: 500; margin-left: var(--sp-2); vertical-align: middle; text-transform: uppercase; letter-spacing: 0.04em; }
  .badge.fire { background: var(--bad-bg); color: var(--bad); }
  .badge.warn { background: var(--warn-bg); color: var(--warn); }
  .badge.info { background: var(--info-bg); color: var(--info); }
  .badge.good { background: var(--good-bg); color: var(--good); }

  .ref-grid { display: grid; grid-template-columns: 140px 1fr; gap: var(--sp-2) var(--sp-4); align-items: baseline; }
  .ref-grid dt { font-size: var(--fs-sm); color: var(--text-muted); }
  .ref-grid dd { font-size: var(--fs-md); margin: 0; font-variant-numeric: tabular-nums; }
  details summary { cursor: pointer; font-weight: 500; padding: var(--sp-2) 0; font-size: var(--fs-md); }
  details[open] summary { margin-bottom: var(--sp-2); }

  .drop-zone {
    border: 1.5px dashed var(--border-strong); border-radius: var(--radius-sm); padding: 1rem;
    background: var(--surface-2); cursor: pointer; transition: border-color 0.15s, background 0.15s;
  }
  .drop-zone:hover, .drop-zone.dragging { border-color: var(--accent); background: var(--info-bg); }
  .drop-title { font-size: var(--fs-md); font-weight: 500; margin: 0; color: var(--text); }
  .drop-sub { font-size: var(--fs-sm); color: var(--text-subtle); margin: var(--sp-0) 0 0; }
  .file-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
  .file-chip { font-size: var(--fs-xs); color: var(--text-muted); border: 1px solid var(--border); border-radius: var(--radius-pill); padding: var(--sp-0) var(--sp-2); background: var(--surface); }
  .receipt-drop { padding: 0.85rem; }
  .receipt-drop .receipt-actions { margin-top: var(--sp-3); }
  .insight-grid { display: grid; gap: var(--sp-2); margin: 0.75rem 0 1rem; }
  .insight-item { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--sp-3) var(--sp-3); background: var(--surface); }
  .insight-title { font-size: var(--fs-md); font-weight: 500; margin: 0 0 var(--sp-0); color: var(--text); }
  .insight-meta { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; }

  .empty { text-align: center; color: var(--text-subtle); font-size: var(--fs-md); padding: 2rem 1rem; }
  .empty-state {
    text-align: center; padding: 1.5rem 1rem; border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text-muted);
  }
  .empty-state-title { margin: 0 0 0.35rem; color: var(--text); font-size: var(--fs-base); font-weight: 600; }
  .empty-state-detail { margin: 0 auto 0.9rem; max-width: 440px; font-size: var(--fs-md); line-height: 1.5; color: var(--text-muted); }
  .empty-actions { display: flex; justify-content: center; gap: var(--sp-2); flex-wrap: wrap; }
  .closeout-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.75rem; margin: 1rem 0; }
  .closeout-metric { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.85rem; background: var(--surface); }
  .closeout-label { margin: 0 0 0.35rem; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-subtle); font-weight: 650; }
  .closeout-value { margin: 0; font-size: var(--fs-2xl); font-weight: 650; font-variant-numeric: tabular-nums; }
  .recon-line { margin-top: 0.65rem; padding: 0.65rem 0.75rem; border-radius: var(--radius-sm); background: var(--surface-2); font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.5; }
  .recon-line.good { background: var(--good-bg); color: var(--good); }
  .recon-line.warn { background: var(--warn-bg); color: var(--warn); }
  .recon-line.bad { background: var(--bad-bg); color: var(--bad); }
  body.locked #app-shell, body.locked #quick-add { display: none !important; }
  body.unlocked #lock-screen { display: none !important; }

  .filters { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: 1rem; }
  .filter-pill {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
    background: var(--surface); color: var(--text-muted); font-size: var(--fs-sm); cursor: pointer;
    font-family: inherit; transition: all 0.1s;
  }
  .filter-pill.active { background: var(--text); color: var(--bg); border-color: var(--text); }
  .filter-total {
    font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--text-subtle);
    padding-left: var(--sp-2); border-left: 1px solid var(--border);
  }
  .filter-total:empty { display: none; }
  .filter-pill.active .filter-total { color: var(--bg); opacity: 0.7; border-left-color: rgba(255,255,255,0.2); }

  .date-section { margin-bottom: 1.25rem; }
  .date-header {
    font-size: var(--fs-xs); font-weight: 600; color: var(--text-subtle); text-transform: uppercase; letter-spacing: 0.05em;
    padding: var(--sp-2) var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border); margin-bottom: var(--sp-1);
    display: flex; justify-content: space-between; align-items: baseline;
    position: sticky; top: 0; z-index: 2;
    background: var(--bg);
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter: saturate(140%) blur(6px);
    -webkit-backdrop-filter: saturate(140%) blur(6px);
  }
  .date-total { color: var(--text); font-weight: 500; font-variant-numeric: tabular-nums; }

  .chart-wrap { position: relative; height: 220px; }
  .desktop-insights { display: grid; grid-template-columns: 1fr; gap: 1rem; margin-bottom: 1rem; }
  .desktop-insights > *, .desktop-chart-grid > * { min-width: 0; }
  .desktop-chart-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1rem; }
  .mini-chart-wrap { position: relative; height: 190px; }

  
  .chat-card { display: flex; flex-direction: column; }
  .chat-messages {
    display: flex; flex-direction: column; gap: var(--sp-4); min-height: 240px; max-height: 520px;
    overflow-y: auto; padding: var(--sp-1) var(--sp-0); margin-bottom: 1rem; scroll-behavior: smooth;
    overscroll-behavior: contain;
  }
  .chat-row { display: flex; align-items: flex-end; gap: var(--sp-2); }
  .chat-row.user { justify-content: flex-end; }
  .chat-row.assistant { justify-content: flex-start; }
  .chat-avatar {
    width: 28px; height: 28px; border-radius: var(--radius-pill); flex-shrink: 0;
    display: grid; place-items: center; font-size: var(--fs-sm); font-weight: 600;
    background: var(--accent); color: var(--on-accent); letter-spacing: 0;
    box-shadow: 0 2px 6px rgba(26,58,92,0.18);
  }
  
  .chat-bubble {
    max-width: min(82%, 660px); padding: var(--sp-3) var(--sp-4); font-size: var(--fs-base);
    word-break: break-word; overflow-wrap: anywhere;
    box-shadow: 0 1px 2px rgba(0,0,0,0.04);
  }
  .chat-bubble.user {
    background: var(--accent); color: var(--on-accent);
    border-radius: 16px 16px 4px 16px; line-height: 1.5;
  }
  .chat-bubble.assistant {
    background: var(--surface-2); color: var(--text);
    border-radius: 4px 16px 16px 16px; line-height: 1.6;
  }
  .chat-feedback {
    display: flex; align-items: center; gap: var(--sp-1);
    margin-top: var(--sp-2); padding-top: var(--sp-2);
    border-top: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
  }
  .chat-feedback-label { font-size: var(--fs-2xs); color: var(--text-muted); margin-right: var(--sp-1); }
  .chat-feedback-btn {
    background: none; border: none; cursor: pointer; font-size: var(--fs-md);
    line-height: 1; padding: var(--sp-1); border-radius: 6px; opacity: 0.65;
    transition: opacity .12s ease, background .12s ease;
  }
  .chat-feedback-btn:hover { opacity: 1; background: color-mix(in srgb, var(--text) 8%, transparent); }
  .chat-feedback--rated { font-size: var(--fs-2xs); color: var(--text-muted); }
  .chat-typing { display: inline-flex; gap: var(--sp-1); padding: var(--sp-1) var(--sp-0); align-items: center; }
  .chat-typing span {
    width: 6px; height: 6px; border-radius: var(--radius-pill); background: var(--text-subtle);
    animation: chatBlink 1.2s infinite ease-in-out;
  }
  .chat-typing span:nth-child(2) { animation-delay: 0.15s; }
  .chat-typing span:nth-child(3) { animation-delay: 0.3s; }
  @keyframes chatBlink {
    0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
    30% { opacity: 1; transform: translateY(-2px); }
  }
  .chat-empty { text-align: center; padding: 1.5rem 0 0.5rem; }
  .chat-empty-icon {
    width: 44px; height: 44px; border-radius: var(--radius-pill); margin: 0 auto var(--sp-3);
    display: grid; place-items: center; background: var(--info-bg); color: var(--info);
    font-size: var(--fs-2xl);
  }
  .chat-empty-title { font-size: var(--fs-lg); font-weight: 600; margin: 0 0 var(--sp-1); color: var(--text); }
  .chat-empty-sub { font-size: var(--fs-sm); color: var(--text-muted); margin: 0 0 var(--sp-4); }
  .chat-suggestions { display: flex; flex-direction: column; gap: var(--sp-2); max-width: 420px; margin: 0 auto; }
  .chat-suggest {
    padding: var(--sp-2) var(--sp-4); border-radius: var(--radius-pill); border: 1px solid var(--border-strong);
    background: var(--surface); color: var(--text); font-size: var(--fs-md); cursor: pointer;
    text-align: left; transition: background 0.15s, border-color 0.15s; font-family: inherit;
  }
  .chat-suggest:hover { background: var(--surface-2); border-color: var(--accent); }
  .chat-composer {
    display: flex; gap: var(--sp-2); align-items: flex-end; padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
    background: var(--surface); border: 1px solid var(--border-strong); border-radius: 22px;
    transition: border-color 0.15s, box-shadow 0.15s;
  }
  .chat-composer:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
  .chat-disclaimer { font-size: var(--fs-xs); color: var(--text-subtle); margin: var(--sp-2) 0 0; text-align: center; }
  .chat-quota { font-size: var(--fs-xs); color: var(--warn); margin: var(--sp-2) 0 0; text-align: center; font-weight: 500; }
  #chat-input {
    flex: 1; resize: none; overflow: hidden; min-height: 36px; max-height: 140px;
    font-family: inherit; font-size: var(--fs-lg); line-height: 1.45;
    border: none; background: transparent; padding: var(--sp-2) 0;
  }
  #chat-input:focus { outline: none; }
  #chat-send-btn {
    border-radius: var(--radius-pill); padding: var(--sp-2) var(--sp-5); flex-shrink: 0;
    background: var(--accent); color: var(--on-accent); border: none; font-family: inherit;
    font-size: var(--fs-md); font-weight: 600; cursor: pointer; min-height: 36px;
    transition: opacity 0.15s, transform 0.1s;
  }
  #chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
  #chat-send-btn:not(:disabled):hover { transform: translateY(-1px); }
  .notice-item { align-items: flex-start; }
  .notice-dot {
    width: 10px; height: 10px; border-radius: var(--radius-pill); flex-shrink: 0; margin-top: var(--sp-2);
    background: var(--info);
  }
  .notice-dot.fire { background: var(--bad); }
  .notice-dot.warn { background: var(--warn); }
  .notice-title { font-size: var(--fs-base); font-weight: 500; margin: 0 0 var(--sp-0); }
  .notice-detail { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; line-height: 1.45; }
  .pref-grid { display: grid; gap: var(--sp-2); margin-top: 0.75rem; }
  .pref-row {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
    padding: var(--sp-3) 0; border-bottom: 1px solid var(--border);
  }
  .pref-row:last-child { border-bottom: none; }
  .pref-row input { width: auto; min-width: 18px; }

  footer { margin-top: 2rem; padding-top: 1.25rem; border-top: 1px solid var(--border); display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-3); font-size: var(--fs-xs); color: var(--text-subtle); }
  footer a { color: var(--text-muted); text-decoration: none; cursor: pointer; }
  footer a:hover { color: var(--text); }



  

  

  


  


  


  /* Upcoming-bill rows on the Plan tab. The actions used to be an invisible
     whole-row click, which is why nobody could tell a bill could be logged from
     here; they are buttons now, and a logged row goes quiet rather than
     disappearing. */
  .plan-up-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
  .plan-up-row .task-content { flex: 1; min-width: 0; }
  .plan-up-actions { display: flex; gap: var(--sp-2); flex-shrink: 0; }
  .plan-up-actions button { font-size: var(--fs-xs); padding: var(--sp-1) var(--sp-3); white-space: nowrap; }
  .plan-up-row--done { opacity: 0.62; }
  .plan-up-row--done .task-title { text-decoration: none; }
  

  #ios-large-title { display: none; }
  /* ---- The signature ----
     Every figure that represents money wears the slab; every label, control and
     nav item stays in the grotesk. One voice for what you have, another for what
     the app is saying about it. tnum + lnum force tabular lining figures so a
     column of amounts aligns on the decimal like a register, which is the whole
     reason for a second face rather than a style. */
  .hero-value, .mobile-hero-value, .metric-tile-value, .tx-amt, .amount,
  .breakdown-amount, .date-total, .tba-value, .mobile-up-amt,
  .merge-variant-amt, .sts-row-amt, .retro-big, .trends-stat-value, .nw-value,
  .closeout-value, .asset-item-value, .compare-headline-amount, .budget-nums,
  .home-recap-big, .mobile-sts-value, .item-row-title .amount, .sub-per-mo,
  .mobile-verdict-main, .mobile-net-legend b, .cal-summary b {
    font-family: var(--font-money);
    font-feature-settings: "tnum" 1, "lnum" 1;
    font-variant-numeric: tabular-nums lining-nums;
    letter-spacing: -0.01em;
  }
  /* The headline balance is the one place the slab is allowed to be large. */
  .hero-value, .mobile-hero-value { font-weight: 600; letter-spacing: -0.02em; }
  .tx-amt, .breakdown-amount, .mobile-up-amt { font-weight: 600; }
  /* A currency symbol set at full size shouts; the amount is the information. */
  .mobile-hero-value .cur, .hero-value .cur { font-size: 0.62em; font-weight: 500; opacity: 0.6; }

  /* ---- Money timeline ----
     One chronological run: settled behind, expected ahead, with the balance
     after each future step. The point is that it reads in one pass — no
     cross-checking one card against another. */
  .tl-summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-3); margin: var(--sp-3) 0 var(--sp-4); }
  .tl-stat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .tl-stat-label { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-subtle); font-weight: 600; }
  .tl-stat-value { font-family: var(--font-money); font-size: var(--fs-lg); font-weight: 600; font-variant-numeric: tabular-nums lining-nums; }
  .tl-stat-value.good { color: var(--good); }
  .tl-stat-value.bad { color: var(--bad); }
  .tl-date { font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-subtle); padding: var(--sp-3) 0 var(--sp-1); }
  .tl-date--future { color: var(--text-muted); }
  /* The line between what has happened and what has not. */
  .tl-now { display: flex; align-items: center; gap: var(--sp-3); margin: var(--sp-3) 0 var(--sp-1); font-size: var(--fs-xs); font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.06em; }
  .tl-now::after { content: ""; flex: 1; height: 2px; background: var(--accent); opacity: 0.35; border-radius: 2px; }
  .tl-row { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); }
  .tl-row:last-child { border-bottom: none; }
  .tl-row--future .tl-name { color: var(--text); }
  .tl-main { flex: 1; min-width: 0; }
  .tl-name { font-size: var(--fs-md); font-weight: 500; color: var(--text); display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
  .tl-meta { font-size: var(--fs-xs); color: var(--text-subtle); margin-top: 2px; }
  .tl-amounts { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
  .tl-amt { font-family: var(--font-money); font-size: var(--fs-md); font-weight: 600; font-variant-numeric: tabular-nums lining-nums; }
  .tl-amt.good { color: var(--good); }
  .tl-amt.bad { color: var(--text); }
  .tl-bal { font-family: var(--font-money); font-size: var(--fs-xs); color: var(--text-subtle); font-variant-numeric: tabular-nums lining-nums; }
  .tl-bal.bad { color: var(--bad); font-weight: 600; }
  .tl-tag { font-size: var(--fs-2xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 1px var(--sp-2); border-radius: var(--radius-pill); }
  .tl-tag.paid { background: var(--good-bg); color: var(--good); }
  .tl-tag.expected { background: var(--surface-2); color: var(--text-subtle); }
  .tl-tag.overdue { background: var(--bad-bg); color: var(--bad); }
  .tl-tag.payday { background: var(--good-bg); color: var(--good); }
  /* Money that was due and has not arrived. Warn, not bad: it is almost
     always a late payroll run rather than something wrong. */
  .tl-tag.late { background: var(--warn-bg); color: var(--warn); }
  .tl-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
  .tl-actions button { font-size: var(--fs-xs); padding: var(--sp-1) var(--sp-3); }
  .tl-match-list { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0 var(--sp-3); }
  @media (max-width: 560px) {
    .tl-summary { grid-template-columns: 1fr 1fr; }
  }

  .tx-search-row { display: flex; gap: var(--sp-2); align-items: center; margin: var(--sp-2) 0 var(--sp-4); }
  /* Element + class, so this bespoke look holds against the base input rule
     (input[type="search"], same specificity, earlier in the sheet). */
  input.tx-search-input {
    flex: 1; width: auto; padding: var(--sp-2) var(--sp-3); font-size: var(--fs-md); border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: var(--bg); color: var(--text);
  }
  /* ...which also means the phone font-size rule further up can no longer reach
     it. Restate it here, after the declaration above, or this one field sits
     under 16px and iOS zooms the page when it is focused. */
  
  .tx-search-clear { padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm); }


  .pin-prompt {
    display: flex; align-items: center; gap: var(--sp-4);
    background: var(--info-bg); border: 1px solid color-mix(in srgb, var(--info) 30%, transparent);
    border-radius: 14px; padding: var(--sp-4) var(--sp-4); margin-bottom: 1.25rem;
  }
  .pin-prompt[hidden] { display: none; }
  .pin-prompt-icon {
    width: 36px; height: 36px; border-radius: var(--radius-pill); flex-shrink: 0;
    display: grid; place-items: center; background: var(--surface); font-size: var(--fs-xl);
  }
  .pin-prompt-body { flex: 1; min-width: 0; }
  .pin-prompt-title { font-size: var(--fs-base); font-weight: 600; margin: 0 0 var(--sp-0); color: var(--text); }
  .pin-prompt-sub { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; line-height: 1.45; }
  .pin-prompt-actions { display: flex; gap: var(--sp-2); flex-shrink: 0; }
  .pin-prompt-actions button { padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm); }
  .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; }
  .faceid-btn {
    margin-top: var(--sp-3); width: 100%; min-height: 52px; display: inline-grid; place-items: center;
  }
  .faceid-mark {
    width: 30px; height: 30px; position: relative; display: block;
    border: 2px solid currentColor; border-radius: 9px;
  }
  .faceid-mark::before,
  .faceid-mark::after {
    content: ''; position: absolute; width: 4px; height: 4px; top: 8px;
    background: currentColor; border-radius: var(--radius-pill);
  }
  .faceid-mark::before { left: 7px; }
  .faceid-mark::after { right: 7px; }
  .faceid-mouth {
    position: absolute; left: 8px; right: 8px; bottom: 7px; height: 6px;
    border-bottom: 2px solid currentColor; border-radius: 0 0 999px 999px;
  }
  


  .referral-card {
    background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 10%, var(--surface)), var(--surface));
    padding: var(--sp-4); margin-bottom: var(--sp-2);
  }
  .referral-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-1); }
  .referral-title { font-size: var(--fs-base); font-weight: 600; margin: 0; color: var(--text); }
  .referral-mult { font-size: var(--fs-xs); font-weight: 700; padding: var(--sp-0) var(--sp-2); border-radius: var(--radius-pill); background: var(--accent); color: var(--on-accent); letter-spacing: 0.02em; }
  .referral-mult.boosted { background: var(--good); }
  .referral-sub { font-size: var(--fs-sm); color: var(--text-muted); margin: 0 0 var(--sp-3); line-height: 1.5; }
  .referral-form { display: flex; gap: var(--sp-2); }
  .referral-form input { flex: 1; }
  .referral-form button { white-space: nowrap; padding: var(--sp-2) var(--sp-4); font-size: var(--fs-md); }
  .referral-form[aria-busy="true"] input { opacity: 0.72; }
  .referral-error { font-size: var(--fs-sm); color: var(--bad); margin: var(--sp-2) 0 0; }
  .referral-error[hidden] { display: none; }
  .referral-list { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
  .referral-row {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
  }
  .referral-contact { color: var(--text); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
  .referral-status { color: var(--text-subtle); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em; flex-shrink: 0; }
  .referral-status.joined { color: var(--good); }
  .referral-remove { background: transparent; border: none; color: var(--text-subtle); cursor: pointer; padding: 0 var(--sp-1); font-size: var(--fs-base); line-height: 1; }
  .referral-remove:hover { color: var(--bad); }
  


  #toast-container {
    position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
    z-index: 1000; display: flex; flex-direction: column; gap: var(--sp-2);
    pointer-events: none; max-width: calc(100vw - 32px);
  }
  .toast {
    pointer-events: auto;
    padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-sm); font-size: var(--fs-md); font-weight: 500;
    background: var(--text); color: var(--bg);
    box-shadow: 0 4px 14px rgba(0,0,0,0.18);
    display: flex; align-items: center; gap: var(--sp-3);
    animation: toastIn 0.18s ease-out;
    max-width: 420px;
  }
  .toast.error { background: var(--bad); color: white; }
  .toast.success { background: var(--good); color: white; }
  .toast.warn { background: var(--warn); color: white; }
  .toast button {
    background: transparent; border: none; color: inherit;
    font-size: var(--fs-sm); padding: var(--sp-0) var(--sp-2); cursor: pointer;
    text-decoration: underline; opacity: 0.9;
  }
  .toast.leaving { animation: toastOut 0.18s ease-in forwards; }
  @keyframes toastIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
  @keyframes toastOut { to { opacity: 0; transform: translateY(8px); } }
  .sync-pill {
    position: fixed; left: 50%; bottom: calc(5.5rem + var(--safe-bottom)); transform: translateX(-50%);
    z-index: 620; display: none; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--border-strong); border-radius: var(--radius-pill); background: var(--surface);
    color: var(--text-muted); font-size: var(--fs-sm); box-shadow: 0 10px 30px rgba(0,0,0,0.12);
    max-width: min(92vw, 360px);
  }
  .sync-pill.visible { display: inline-flex; }
  .sync-dot { width: 7px; height: 7px; border-radius: var(--radius-pill); background: var(--warn); }
  .sync-pill.synced .sync-dot { background: var(--good); }
  .sync-pill.saving .sync-dot { background: var(--info); }
  .sync-pill.pending .sync-dot { background: var(--warn); }
  .sync-pill.offline .sync-dot { background: var(--warn); }


  .contact-card {
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface-2);
    margin-top: var(--sp-4);
  }
  .contact-card p { margin: 0; }
  .contact-title { font-size: var(--fs-md); font-weight: 600; color: var(--text); }
  .contact-sub { font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.45; margin-top: var(--sp-1) !important; }
  .contact-email { color: var(--accent); font-weight: 600; text-decoration: none; }
  .feedback-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); flex-wrap: wrap; }
  #feedback-message { min-height: 120px; resize: vertical; }
  .welcome-logo {
    width: min(200px, 56vw);
    height: auto;
    display: block;
    margin: 0 auto 1rem;
  }
  .brand-wordmark {
    margin: 0 0 0.25rem;
    font-size: var(--fs-2xl);
    font-weight: 800;
    letter-spacing: 0.08em;
  }
  .lock-tagline { color: var(--text-muted); font-size: var(--fs-md); margin: 0; }

  .lock-brand {
    flex: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center;
    padding: calc(2rem + var(--safe-top)) 2rem 1rem;
    width: 100%;
  }
  .lock-form-card {
    width: 100%; max-width: 400px;
    padding: 1.5rem 1.5rem calc(1.5rem + var(--safe-bottom));
    text-align: center;
  }
  /* Space reserved, nothing drawn: the logo above must sit at its final
     position from the first frame, or it jumps when the fields appear. */
  #lock-screen.booting .lock-form-card { visibility: hidden; }




  @keyframes lockFormUp {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }

  /* The one time the form is worth animating in is the first: the logo has
     already been on screen since the launch screen, so the fields arriving
     under it is the whole boot sequence. Every later showLockScreen() — an
     idle lock, a sign-out — is an interruption, and sliding a sheet up at the
     user there is why this was turned off for the app in the first place. */
  @keyframes lockFormIn {
    /* pointer-events too: an invisible form still hit-tests, and a tap landing
       on Sign in during the delay would answer with an empty-field error. */
    from { opacity: 0; transform: translateY(18px); pointer-events: none; }
    to   { opacity: 1; transform: none; pointer-events: auto; }
  }
  /* `both`, not `backwards`: at phone width the card's base transform is
     translateY(100%) (the sheet parked off-screen), so an animation that stops
     holding its end state drops the whole form off the bottom the moment it
     finishes. */
  .lock-form-card.lock-boot-in {
    animation: lockFormIn 0.5s var(--ease) 0.45s both;
  }

  .ios-app .lock-form-card:not(.lock-boot-in),
  .ios-app .lock-form-card.lock-visible:not(.lock-boot-in) {
    transform: none !important;
    animation: none !important;
  }
  /* ── Assets ── */
  .asset-item { display:flex; align-items:flex-start; justify-content:space-between; padding:var(--sp-3) 0; border-bottom:1px solid var(--border); gap:var(--sp-3); }
  .asset-item:last-child { border-bottom:none; }
  .asset-item-left { flex:1; min-width:0; }
  .asset-item-name { font-size:var(--fs-base); font-weight:600; color:var(--text); margin:0 0 var(--sp-0); }
  .asset-item-meta { font-size:var(--fs-sm); color:var(--text-subtle); margin:0; }
  .asset-item-price-note { font-size:var(--fs-xs); color:var(--text-muted); margin:var(--sp-0) 0 0; }
  .asset-item-right { text-align:right; flex-shrink:0; }
  .asset-item-value { font-size:var(--fs-lg); font-weight:600; font-variant-numeric:tabular-nums; color:var(--text); margin:0 0 var(--sp-1); }
  .asset-type-badge { display:inline-block; font-size:var(--fs-2xs); font-weight:600; letter-spacing:0.05em; text-transform:uppercase; background:var(--surface-2); color:var(--text-subtle); border-radius:4px; padding:var(--sp-0) var(--sp-1); }
  .asset-empty { padding:2rem 0; text-align:center; color:var(--text-muted); font-size:var(--fs-md); }
  .net-worth-row { display:flex; justify-content:space-between; align-items:center; padding:var(--sp-2) 0; border-bottom:1px solid var(--border); font-size:var(--fs-base); }
  .net-worth-row:last-child { border-bottom:none; }
  .net-worth-row.total { font-weight:700; font-size:var(--fs-lg); padding-top:var(--sp-3); border-top:2px solid var(--border); border-bottom:none; }
  .net-worth-row .nw-label { color:var(--text-muted); }
  .net-worth-row .nw-value { font-variant-numeric:tabular-nums; font-weight:600; }
  .nw-value.good { color:var(--good); }
  .nw-value.bad { color:var(--bad); }
  /* ── FAQ ── */
  .faq-section { margin-bottom:1.5rem; }
  .faq-section:last-child { margin-bottom:0; }
  .faq-section-title { font-size:var(--fs-xs); font-weight:600; letter-spacing:0.08em; text-transform:uppercase; color:var(--text-subtle); margin:0 0 var(--sp-3); }
  .faq-item { margin-bottom:0; border-bottom:1px solid var(--border); }
  .faq-item:last-child { border-bottom:none; }
  .faq-q { font-size:var(--fs-md); font-weight:600; color:var(--text); margin:0; padding:var(--sp-3) var(--sp-6) var(--sp-3) 0; cursor:pointer; position:relative; user-select:none; }
  .faq-q::after { content:'▾'; position:absolute; right:6px; top:50%; transform:translateY(-50%); transition:transform .2s ease; color:var(--text-subtle); font-size:var(--fs-xs); }
  .faq-item.faq-open > .faq-q::after { transform:translateY(-50%) rotate(180deg); }
  /* An answer is every part of the answer. The collapse used to name .faq-a
     only, and a formula chip is a <code>, not a .faq-a — so every balance
     formula in the app sat on screen with its question still closed. Both
     halves hide together; each comes back with its own display, since the chip
     is inline-block and the prose is block. */
  .faq-a, .faq-formula { display:none; }
  .faq-a { font-size:var(--fs-md); color:var(--text-muted); margin:0; line-height:1.6; }
  .faq-item.faq-open .faq-a { display:block; }
  .faq-item.faq-open .faq-formula { display:inline-block; }
  /* Bottom breathing room for whichever half ends the answer. On the chip it
     has to be margin — padding would inflate the box the background paints. */
  .faq-item.faq-open .faq-a:last-child { padding-bottom:var(--sp-3); }
  .faq-item.faq-open .faq-formula:last-child { margin-bottom:var(--sp-3); }
  .faq-formula { background:var(--surface-2); border-radius:6px; padding:var(--sp-2) var(--sp-3); font-size:var(--fs-sm); font-family:ui-monospace,monospace; color:var(--text); margin:var(--sp-2) 0 0; line-height:1.5; }
  .sub-row { justify-content: space-between; }
  .sub-row-name { font-size:var(--fs-base); font-weight:600; color:var(--text); }
  .sub-row-meta { font-size:var(--fs-sm); color:var(--text-subtle); margin-top:var(--sp-0); }
  .sub-row-actions { display:flex; gap:var(--sp-2); flex-shrink:0; }
  .sub-alert { font-size:var(--fs-md); color:var(--text-muted); line-height:1.5; padding:var(--sp-2) var(--sp-3); border-radius:var(--radius-sm); background:var(--surface-2); margin-bottom:var(--sp-2); }
  .sub-alert:last-child { margin-bottom:0; }
  .sub-alert strong { color:var(--text); }
  /* A straggler asks "cancelled, or forgot?" — so it carries the two answers. */
  .sub-alert--actionable { display:flex; align-items:center; gap:var(--sp-3); flex-wrap:wrap; }
  .sub-alert-text { flex:1; min-width:220px; }
  .sub-alert-actions { display:flex; gap:var(--sp-2); flex-shrink:0; }
  .sub-alert-actions button { font-size:var(--fs-xs); padding:var(--sp-1) var(--sp-3); white-space:nowrap; }

  /* Merge review: one candidate at a time, both amounts visible, because two
     labels charging the same amount is what actually settles the question. */
  .merge-proposal { font-size:var(--fs-md); color:var(--text-muted); margin:var(--sp-2) 0 var(--sp-3); }
  .merge-proposal strong { color:var(--text); }
  .merge-variants { border:1px solid var(--border); border-radius:var(--radius-sm); overflow:hidden; }
  .merge-variant { padding: var(--sp-3); }
  .merge-variant-main { flex:1; min-width:0; }
  .merge-variant-name { font-size:var(--fs-md); color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .merge-variant-meta { font-size:var(--fs-xs); color:var(--text-subtle); }
  .merge-variant-amt { font-size:var(--fs-md); font-variant-numeric:tabular-nums; white-space:nowrap; }
  .merge-verdict-line { margin:var(--sp-3) 0 0; }
  .merge-verdict { font-size:var(--fs-sm); font-weight:600; }
  .merge-verdict.good { color:var(--good); }
  .merge-verdict.warn { color:var(--warn); }
  .merge-actions { display:flex; gap:var(--sp-3); margin-top:var(--sp-3); }
  
  .cal-wrap { display:grid; grid-template-columns:1fr; gap:var(--sp-4); }
  .cal-main { background:var(--surface,#faf9f5); border:1px solid var(--border,#e6e4de); border-radius:16px; padding:var(--sp-4); }
  .cal-head { display:flex; align-items:center; gap:var(--sp-3); flex-wrap:wrap; margin-bottom:var(--sp-3); }
  .cal-title { font-size:var(--fs-xl); font-weight:800; letter-spacing:-.3px; }
  .cal-nav { display:flex; gap:var(--sp-1); }
  .cal-nav button, .cal-today { border:1px solid var(--border,#e6e4de); background:var(--surface-2,#fff); border-radius:var(--radius-sm); padding:var(--sp-1) var(--sp-2); font-size:var(--fs-sm); cursor:pointer; color:var(--text,#1a1a1a); }
  .cal-summary { margin-left:auto; display:flex; gap:var(--sp-4); font-size:var(--fs-sm); }
  .cal-summary b { display:block; font-size:var(--fs-base); }
  .cal-chips { display:flex; gap:var(--sp-2); margin-bottom:var(--sp-3); flex-wrap:wrap; }
  .cal-chip { font-size:var(--fs-xs); border:1px solid var(--border,#e6e4de); background:var(--surface-2,#fff); border-radius:var(--radius-pill); padding:var(--sp-0) var(--sp-3); cursor:pointer; color:var(--text-subtle,#888); }
  .cal-chip.on { background:#1a3a5c; color:#fff; border-color:#1a3a5c; }
  .cal-dow { display:grid; grid-template-columns:repeat(7,1fr); gap:var(--sp-1); margin-bottom:var(--sp-1); }
  .cal-dow span { text-align:center; font-size:var(--fs-2xs); text-transform:uppercase; letter-spacing:.06em; color:var(--text-subtle,#888); }
  .cal-grid { display:grid; grid-template-columns:repeat(7,1fr); gap:var(--sp-1); }
  .cal-cell { background:var(--surface-2,#fff); border:1px solid var(--border,#e6e4de); border-radius:var(--radius-sm); min-height:60px; padding:var(--sp-1) var(--sp-1); display:flex; flex-direction:column; gap:var(--sp-0); cursor:pointer; overflow:hidden; }
  .cal-cell.cal-out { background:var(--surface-3,#f4f2ec); opacity:.5; cursor:default; }
  .cal-cell.cal-today { border:1.5px solid #1a3a5c; box-shadow:0 0 0 2px rgba(26,58,92,.08); }
  .cal-cell.cal-sel { border-color:#1a3a5c; background:#f3f6fb; }
  .cal-dn { font-size:var(--fs-2xs); color:var(--text-subtle,#888); font-weight:700; }
  .cal-cell.cal-today .cal-dn { color:#1a3a5c; }
  .cal-ev { font-size:var(--fs-2xs); line-height:1.25; border-radius:4px; padding:var(--sp-0) var(--sp-1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .cal-ev.cal-inc { background:rgba(29,158,117,.14); color:#13714f; }
  .cal-ev.cal-exp { background:rgba(216,90,48,.13); color:#a23c18; }
  .cal-ev.cal-bill{ background:rgba(55,138,221,.14); color:#205f96; }
  .cal-ev.cal-sched { background:transparent !important; border:1px dashed currentColor; }
  .cal-ev.cal-overdue { background:transparent !important; border:1px dashed #ba7517; color:#8a5a12; }
  .cal-dot { display:inline-block; width:6px; height:6px; border-radius:50%; }
  .cal-dot.cal-inc{ background:#1d9e75 } .cal-dot.cal-exp{ background:#d85a30 } .cal-dot.cal-bill{ background:#378add }
  .cal-dots { display:flex; gap:var(--sp-0); margin-top:auto; }
  .cal-more { font-size:var(--fs-2xs); color:var(--text-subtle,#888); }
  
  
  
  .cal-d-title { font-size:var(--fs-base); font-weight:700; margin:0 0 var(--sp-0); }
  .cal-d-sub { font-size:var(--fs-xs); color:var(--text-subtle,#888); margin:0 0 var(--sp-3); }
  .cal-prow { display:flex; align-items:center; gap:var(--sp-2); padding:var(--sp-2) 0; border-bottom:1px solid var(--border,#f0efe9); font-size:var(--fs-sm); }
  .cal-prow .cal-pd { width:8px; height:8px; border-radius:50%; flex:0 0 auto; }
  .cal-prow.cal-prow-sched .cal-pd { background:transparent; border:1.5px dashed #205f96; }
  .cal-prow .cal-pa { margin-left:auto; font-weight:700; }
  .cal-tag { font-size:var(--fs-2xs); color:var(--text-subtle,#888); border:1px solid var(--border,#e6e4de); border-radius:var(--radius-pill); padding:0 var(--sp-1); }
  .cal-logbtn { font-size:var(--fs-2xs); color:#205f96; border:1px solid var(--border,#e6e4de); border-radius:6px; padding:var(--sp-0) var(--sp-2); background:var(--surface-2,#fff); cursor:pointer; }
  .cal-addbtn { margin-top:var(--sp-3); width:100%; border:1px dashed var(--border,#e6e4de); background:var(--surface-2,#fff); border-radius:var(--radius-sm); padding:var(--sp-2); font-size:var(--fs-sm); color:#1a3a5c; cursor:pointer; }
  /* Phone: the day detail flows inline directly under the calendar (single-column
     .cal-wrap), so it's part of the normal scroll — never hidden behind the glass
     nav bar or the FAB the way a fixed bottom sheet was. */

  /* ==========================================================================
     RESPONSIVE & PREFERENCE QUERIES
     --------------------------------------------------------------------------
     Every @media block lives here, exactly one per query, in a fixed order:

       1. prefers-color-scheme      theme tokens and their component overrides
       2. min-width, ascending      desktop enhancements
       3. max-width, DESCENDING     narrower wins, because it comes later
       4. capability / motion       hover, pointer, reduced-motion

     They used to be scattered through the file — 27 blocks, with max-width:560px
     appearing four times and 768px three times, interleaved 560 -> 768 -> 900 ->
     768. A 900px rule could therefore beat a 768px one, and a rule near the
     bottom of one long block silently beat the layer near its top. That cost
     four real bugs in one sitting: .card, .task and .tx-row each re-declared
     their padding lower down and stripped the grouped-list inset off exactly one
     screen, and .notice-item was simply missed.

     Rules within a query keep their original relative order. Add a rule to the
     block that already exists; never open a second one for the same query.
     ========================================================================== */

  @media (prefers-color-scheme: dark) {

    :root {
      /* Obsidian — premium dark */
      /* BRAND.md dark. Warm charcoal rather than true black, so the cream
         identity survives the toggle instead of becoming a different product. */
      --bg: #1a1a1a;
      --surface: #242422;
      --surface-2: #2c2c2a;
      --surface-3: #353532;
      --border: rgba(244,242,236,0.10);
      --border-strong: rgba(244,242,236,0.20);
      --text: #f4f2ec;
      --text-muted: #b4b2a9;
      --text-subtle: #888780;
      /* Lifted off the light value rather than reused: #1254f5 on a near-black
         ground is a muddy 3:1 and unreadable as link text. This is the same
         hue carried up to ~11:1 on --bg, and dark ink still clears 5.8:1 when
         it is used as a fill. */
      --accent: #7da3ff;
      --accent-soft: rgba(18,84,245,0.22);
      --accent-deep: #b8cdfb;
      --on-accent: #1a1a1a;
      --good: #5dcaa5;
      --good-bg: rgba(29,158,117,0.15);
      --warn: #ef9f27;
      --warn-bg: rgba(186,117,23,0.18);
      --bad: #f0997b;
      --bad-bg: rgba(216,90,48,0.18);
      --info: #85b7eb;
      --info-bg: rgba(55,138,221,0.18);
      --purple: #afa9ec;
      --purple-bg: rgba(83,74,183,0.2);
      --shadow: 0 1px 2px rgba(0,0,0,0.4), 0 8px 24px rgba(0,0,0,0.3);
      --bone: #1b1814; --ink: #ece6da;
    }
  


    .chat-avatar,
    .chat-bubble.user,
    .cat-btn.active,
    .referral-mult,
    #chat-send-btn,
    button.primary { color: var(--bg); }
    .bell-btn { color: var(--text-muted); }
  
  }

  @media (min-width: 641px) {

    #lock-screen { justify-content: center; }
    .lock-brand { flex: none; padding-bottom: 0.5rem; }
    .lock-form-card {
      border: 1px solid var(--border);
      border-radius: 16px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.06);
    }
  
  }

  @media (min-width:681px) {
 .cal-cell .cal-dots { display:none; } 
  }

  @media (min-width:861px) {
 .cal-wrap { grid-template-columns:1fr 250px; } #cal-detail { border-left:1px solid var(--border,#e6e4de); padding-left:var(--sp-4); } 
  }

  @media (min-width: 1024px) {

    .side-nav {
      display: flex; flex-direction: column;
      position: fixed; top: 0; left: 0; bottom: 0; width: 236px; z-index: 60;
      background: var(--surface); border-right: 1px solid var(--border);
      padding: var(--sp-6) var(--sp-4) var(--sp-5);
    }
    .side-brand { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-1) var(--sp-2) var(--sp-5); }
    .side-brand-icon { width: 26px; height: 26px; border-radius: 7px; }
    .side-brand-name { font-size: var(--fs-xl); font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
    .side-links { display: flex; flex-direction: column; gap: var(--sp-0); flex: 1; overflow-y: auto; }
    .side-foot { display: flex; flex-direction: column; gap: var(--sp-0); border-top: 1px solid var(--border); padding-top: var(--sp-3); margin-top: var(--sp-2); }
    .side-link {
      display: flex; align-items: center; gap: var(--sp-3); width: 100%;
      padding: var(--sp-2) var(--sp-3); border-radius: 10px; border: none; background: none;
      color: var(--text-muted); font-size: var(--fs-base); font-weight: 550; font-family: inherit;
      cursor: pointer; text-align: left; transition: background 0.12s, color 0.12s;
    }
    .side-link svg { width: 18px; height: 18px; flex-shrink: 0; }
    .side-link:hover { background: var(--surface-2); color: var(--text); }
    .side-link.active { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); color: var(--accent); font-weight: 650; }
    /* Offset the app content to the right of the fixed sidebar; the sidebar
       replaces the horizontal tab bar on desktop. */
    .container { margin-left: 236px; max-width: 1180px; }
    .tabs { display: none; }
    /* The KPI hero + insight charts are the Overview dashboard — show them only
       on the Overview/Today tab so other sidebar destinations render clean. */
    /* Home's content lives OUTSIDE #today — .mobile-overview and .hero are
       siblings of the panes, not children — so nothing hid them when another
       tab was open. They sat above every screen, pushing it down: the chat pane
       began 579px into an 844px phone, so its composer landed below the fold and
       the whole thing read as "taller than my phone". The pane was the right
       height all along; it just started too far down.
       This only started working once #app-shell[data-tab] was actually kept in
       sync — it had been hardcoded to "today", so the .hero half of this rule
       had never matched either. */
    #app-shell:not([data-tab="today"]) .hero,
    #app-shell:not([data-tab="today"]) .desktop-insights { display: none; }

    /* Phase 3 — Overview dashboard grid.
       Row 1: KPI tiles in a tidy auto-fit row.
       Then the Today cards arrange into two columns, with wide cards spanning. */
    #app-shell[data-tab="today"] .hero { grid-template-columns: repeat(auto-fit, minmax(186px, 1fr)); gap: var(--sp-4); }
    #app-shell[data-tab="today"] .hero-card.primary { grid-column: auto; }
    #app-shell[data-tab="today"] .hero-card.primary .hero-value { font-size: 27px; }
    #today.pane.active { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); align-items: start; }
    #today.pane.active > .card,
    #today.pane.active > .weather-pill,
    #today.pane.active > .weather-detail { margin: 0; }
    #today.pane.active > #money-weather,
    #today.pane.active > #weather-detail,
    #today.pane.active > #trends-card,
    #today.pane.active > #recent-card { grid-column: 1 / -1; }
  
  }

  @media (max-width: 900px) {

    .desktop-chart-grid { grid-template-columns: 1fr; }
  

    .hero { grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
  
  }

  @media (max-width:860px) {

    #cal-detail:not(:empty) { margin-top:var(--sp-3); padding:var(--sp-4); background:var(--surface,#faf9f5); border:1px solid var(--border,#e6e4de); border-radius:var(--radius); }
  
  }

  @media (max-width: 768px) {

  


    /* Keyboard-bringing inputs need to render at >=16px on iOS Safari or the
       OS auto-zooms the page on focus — and on this app's fixed viewport it
       can get stuck zoomed in until the app is closed and reopened. Never set
       a smaller inline font-size on an input/select/textarea (it beats this
       rule); if a row needs to look compact, shrink padding/width instead. */
    input[type="text"], input[type="number"], input[type="date"],
    input[type="month"], input[type="password"], input[type="email"],
    input[type="search"], textarea, select {
      font-size: var(--fs-lg); padding: var(--sp-3) var(--sp-3);
    }
    input[type="text"], input[type="number"], input[type="password"],
    input[type="email"], input[type="search"] {
      padding-top: calc(var(--sp-3) + 1px); padding-bottom: calc(var(--sp-3) + 1px);
    }
    button.primary { padding: var(--sp-3) var(--sp-5); font-size: var(--fs-base); min-height: 44px; }
    button.ghost { padding: var(--sp-3) var(--sp-4); font-size: var(--fs-md); min-height: 40px; }
    .toggle-btn { padding: var(--sp-3) var(--sp-4); font-size: var(--fs-base); min-height: 40px; }

    :root { --container-pad: 1rem; }
    .container { padding: calc(0.25rem + var(--safe-top)) var(--container-pad) var(--tabbar-clear); }
    h1 { font-size: var(--fs-xl); }
    .meta-line { font-size: var(--fs-sm); }


    .container > header {
      position: sticky;
      top: 0;
      z-index: 55;
      margin: calc(-0.25rem - var(--safe-top)) -1rem 0.85rem;
      padding: calc(0.25rem + var(--safe-top)) 1rem 0.7rem;
      background: var(--bg);
      background: color-mix(in srgb, var(--bg) 92%, transparent);
      backdrop-filter: blur(14px);
      -webkit-backdrop-filter: blur(14px);
      border-bottom: 1px solid var(--border);
    }
    .header-top { flex-direction: row; justify-content: space-between; align-items: center; gap: 0.5rem; }
    .header-right { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }
    #account-actions .bell-btn { display: none; }
    #account-actions { display: none !important; }
    .bell-btn { display: flex; width: 40px; height: 40px; border-radius: var(--radius-pill); background: var(--surface-2); border: 1px solid var(--border-strong); align-items: center; justify-content: center; flex-shrink: 0; }
    .profile-btn {
      display: inline-flex; align-items: center; gap: var(--sp-2);
      min-width: 0; width: auto; height: 42px; padding: 0 var(--sp-3);
      border-radius: var(--radius-pill); background: var(--surface-2);
      border-color: var(--border-strong); color: var(--text);
      box-shadow: 0 1px 3px rgba(0,0,0,0.08);
    }
    .profile-btn::before {
      content: ""; width: 8px; height: 8px; border-radius: var(--radius-pill); background: var(--good);
      box-shadow: 0 0 0 3px var(--good-bg);
    }
    .profile-btn::after { content: "›"; font-size: var(--fs-xl); line-height: 1; color: var(--text-muted); }
    #app-shell[data-tab="today"] .mobile-overview { display: block; }
    .hero { display: none; }
    .desktop-insights { display: none; }


    .hero { grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin-bottom: 1rem; }
    .hero-card { padding: 0.875rem 1rem; border-radius: var(--radius); }
    .hero-value { font-size: var(--fs-xl); }
    .hero-label { font-size: var(--fs-2xs); }
    .hero-sub { font-size: var(--fs-xs); }


    /* ---- Home leads with the number ----
       The balance sat 363px down an 844px screen: greeting, meta line, streak
       chip and a heading all landed before the one figure the screen exists to
       show. The greeting stays — it is the only warmth in the app — but the
       supporting chrome gets out of the way of the money. */
    #app-shell[data-tab="today"] > header .meta-line { display: none; }
    #app-shell[data-tab="today"] > header h1 { font-size: var(--fs-lg); font-weight: 600; }
    .mobile-hero-panel { margin-top: 0; }
    .mobile-hero-value { font-size: clamp(40px, 13vw, 56px); }
    .mobile-hero-label { font-size: var(--fs-sm); color: var(--text-subtle); letter-spacing: 0.01em; }

    /* ---- Large title ----
       The single most recognisable Apple navigation pattern: the screen names
       itself in large bold type above its content, rather than relying on a
       highlighted tab to say where you are. Injected by setTab, so it always
       matches the visible pane. */
    #ios-large-title {
      display: block;
      font-size: 32px; line-height: 1.15; font-weight: 700; letter-spacing: -0.022em;
      color: var(--text); margin: var(--sp-2) 0 var(--sp-4);
      padding: 0;
    }

    /* ---- Disclosure ----
       A row that opens something says so. Without this every list row looks
       inert, which is the difference between "a list of facts" and "a list of
       things you can go into". */
    /* Room for the chevron is set in the grouped-list block below, AFTER the
       padding shorthand that would otherwise overwrite it. Setting it here cost
       an 11px overlap between the chevron and the amount. */
    .tx-row::before, .item-row[onclick]::before, .task[onclick]::before {
      content: ""; position: absolute; right: var(--sp-4); top: 50%;
      width: 7px; height: 7px; margin-top: -4px;
      border-right: 2px solid var(--text-subtle);
      border-bottom: 2px solid var(--text-subtle);
      transform: rotate(-45deg);
      opacity: 0.55;
    }
    .item-row[onclick], .task[onclick] { position: relative; }


    /* ================= iOS grouped-inset lists (mobile) =================
       The pattern every Apple app and every banking app uses: a recessive
       grouped background, content in inset rounded groups, rows separated by
       hairlines that start at the text rather than the card edge, and a section
       title sitting ABOVE its group rather than inside it.

       Applied to the existing .card and row classes rather than to new markup,
       so every screen adopts it at once and no render function has to change.
       Desktop keeps the card look; this is a mobile-only reading of the same
       components. */

    /* The group itself: a flat inset panel on the grouped background. Shadows
       and heavy borders are a desktop idea — iOS separates by surface, not by
       elevation. */
    .card, .mobile-card, .mobile-facts, .mobile-kpi, .hero-card {
      border-radius: 12px;
      border: none;
      box-shadow: none;
      background: var(--surface);
      overflow: hidden;
    }
    /* …but not the hero cards' status edge. `border: none` above is a flat-panel
       rule meant for the group surface, and it was silently eating the 4px
       colour bar that tells you at a glance whether a number is good or bad —
       on the phone, which is where this app is actually used. Restated after
       the rule that clears it, exactly as the mobile-block hazard note says to. */
    .hero-card { border-left: 4px solid var(--border); }
    .hero-card.accent-good { border-left-color: var(--good); }
    .hero-card.accent-bad { border-left-color: var(--bad); }
    .hero-card.accent-warn { border-left-color: var(--warn); }
    .hero-card.accent-purple { border-left-color: var(--purple); }
    .hero-card.accent-info { border-left-color: var(--info); }
    .hero-card.primary { border-left-color: var(--accent); }
    .hero-card.primary.accent-good { border-left-color: var(--good); }
    .hero-card.primary.accent-bad { border-left-color: var(--bad); }
    .card { padding: 0; margin-bottom: var(--sp-5); }
    /* These three carry their own inset because their content is not a list. */
    .mobile-card, .mobile-kpi { padding: var(--sp-4); }
    .mobile-facts { padding: 0; }
    /* Anything that is prose rather than a list still needs its inset back. */
    .card > p, .card > div:not(.card-head):not(.insight-grid):not(.metric-grid):not(.progress-track),
    .card > table, .card > ul, .card > h3 {
      padding-left: var(--sp-4); padding-right: var(--sp-4);
    }
    /* A progress track is a rail, not prose. Padding it pushed the fill 16px
       inside its own rail, so the bar started and finished short of the track
       it is meant to fill — it read as a bar with a gap either side. The rail
       itself takes the inset, as a margin, and the fill spans all of it. */
    .card > .progress-track { margin-left: var(--sp-4); margin-right: var(--sp-4); }
    .card > p:first-child { padding-top: var(--sp-4); }
    .card > p:last-child { padding-bottom: var(--sp-4); }
    /* ...but :last-child never matches when the trailing children are hidden.
       This card keeps two collapsed panels after its description, so the text
       sat flush on the card's bottom edge. The card carries its own inset. */
    #statement-import-card { padding-bottom: var(--sp-4); }

    /* Section header: outside the group, quiet, sentence-height — the iOS
       "grouped table header", not a card title bar. */
    .card-head, .mobile-card-head {
      margin: 0; padding: var(--sp-3) var(--sp-4) var(--sp-2);
      background: transparent;
    }
    /* A head inside a self-padding panel would double its inset. */
    .mobile-card .mobile-card-head { padding: 0 0 var(--sp-2); }
    .card-title, .mobile-card-label, .mobile-feed-title {
      font-size: var(--fs-sm); font-weight: 600; letter-spacing: 0.02em;
      text-transform: none; color: var(--text-subtle);
    }

    /* The row. 44px is Apple's minimum touch target and it is not negotiable on
       a screen people tap while walking. The separator is inset from the leading
       edge so the eye reads a continuous list rather than stacked boxes. */
    .item-row, .sub-row, .task, .merge-variant, .wallet-row, .plan-up-row, .breakdown-row,
    .mobile-fact, .mobile-up-row, .notice-item {
      min-height: 44px;
      padding: var(--sp-3) var(--sp-4);
      border-bottom: none;
      position: relative;
      margin: 0;
    }
    .item-row::after, .sub-row::after, .task::after, .merge-variant::after,
    .wallet-row::after, .plan-up-row::after, .breakdown-row::after,
    .mobile-fact::after, .mobile-up-row::after, .notice-item::after {
      content: ""; position: absolute; left: var(--sp-4); right: 0; bottom: 0;
      height: 1px; background: var(--border);
    }
    .item-row:last-child::after, .sub-row:last-child::after, .task:last-child::after,
    .merge-variant:last-child::after, .wallet-row:last-child::after,
    .plan-up-row:last-child::after, .breakdown-row:last-child::after,
    .mobile-fact:last-child::after, .mobile-up-row:last-child::after,
    .notice-item:last-child::after { display: none; }
    /* The breakdown bar spans the row, so its separator would cut through it. */
    .breakdown-bar-wrap { margin-bottom: var(--sp-1); }

    /* A tappable row gets the iOS press feedback and a chevron, so "this opens
       something" is legible without reading the label. */
    .tx-row, .task[onclick], .item-row[onclick] { -webkit-tap-highlight-color: transparent; }
    .tx-row:active, .task[onclick]:active, .mobile-up-row:active { background: var(--surface-2); }

    /* Transaction rows are the densest list in the app and the one people scan
       fastest: fixed leading icon, flexible middle, right-aligned tabular money. */
    .tx-row { padding: var(--sp-3) var(--sp-4); min-height: 44px; border-bottom: none; position: relative; }
    /* The chevron sits 16px from the edge and is 7px wide, so an amount ending
       at the normal 16px inset lands underneath it. */
    .tx-row, .item-row[onclick], .task[onclick], .sub-row[onclick] { padding-right: 34px; }
    .tx-row::after {
      content: ""; position: absolute; left: 56px; right: 0; bottom: 0;
      height: 1px; background: var(--border);
    }
    .tx-row:last-child::after { display: none; }
    .date-header {
      padding: var(--sp-3) var(--sp-4) var(--sp-1);
      font-size: var(--fs-sm); color: var(--text-subtle); font-weight: 600;
      background: transparent; border: none;
    }
    .date-section { background: var(--surface); border-radius: 12px; overflow: hidden; margin-bottom: var(--sp-4); }
    .date-section .date-header { background: var(--bg); }

    /* Buttons inside a grouped row read as links, the way iOS does it — a filled
       button per row turns a calm list into a wall of chrome. */
    .sub-row-actions button, .plan-up-actions button, .merge-actions button {
      min-height: 40px;
    }

    .tabs {
      position: fixed;
      top: auto;
      /* Floating, centered glass pill (iOS 26 "Liquid Glass" look): detached
         from the screen edges and sitting above the safe area. */
      left: 50%;
      right: auto;
      transform: translateX(-50%);
      width: calc(100% - 24px);
      max-width: 460px;
      bottom: calc(10px + var(--safe-bottom) + var(--vv-lift, 0px));
      z-index: 620;
      display: flex;
      align-items: center;
      flex-wrap: nowrap;
      gap: var(--sp-1);
      overflow-x: hidden;
      justify-content: space-around;
      margin: 0;
      padding: var(--sp-2) var(--sp-2);
      border-radius: 28px;
      /* Translucent surface + heavy blur/saturation, a hairline highlight edge,
         and a soft drop shadow so it reads as floating glass. Opaque var(--surface)
         is the fallback for engines without color-mix. */
      background: var(--surface);
      background: color-mix(in srgb, var(--surface) 60%, transparent);
      border: 1px solid rgba(255, 255, 255, 0.16);
      box-shadow: 0 10px 34px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.22);
      backdrop-filter: blur(28px) saturate(180%);
      -webkit-backdrop-filter: blur(28px) saturate(180%);
      -webkit-overflow-scrolling: touch;
      scroll-snap-type: x proximity;
      scrollbar-width: none;
    }
    .tabs::-webkit-scrollbar { display: none; }

    .tab-btn { display: none; }
    .tab-btn.tab-primary, .tab-btn.tab-more {
      display: grid;
      min-width: 0;
      flex: 1 1 0;
      height: 50px;
      min-height: 50px;
      padding: var(--sp-2) var(--sp-1);
      border: 1px solid transparent;
      border-radius: 14px;
      font-size: var(--fs-xs);
      line-height: 1.2;
      margin-bottom: 0;
      border-bottom: 1px solid transparent;
      place-items: center;
    }
    /* The active destination wears the brand, not ink. A black pill in a bar
       whose add-button is already accent-filled read as two unrelated
       emphases; one colour family makes the bar look like one control. The
       shadow is tinted with the accent for the same reason — a neutral drop
       shadow under a saturated fill reads as grey fog. */
    .tab-btn.active {
      background: var(--accent);
      color: var(--on-accent);
      border-color: var(--accent);
      box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 32%, transparent);
    }

    /* Adding money moved out of the floating button and into the bar.
       A fixed button in the bottom-right corner always sits over the column
       where cards put their amounts; in the bar it covers nothing, never moves,
       and lands under the thumb. Accent-filled so it reads as the one action
       among four destinations, and kept inside the bar's height — .tabs
       computes overflow-y from overflow-x: hidden, so anything protruding above
       would be clipped rather than floating. */
    .tab-add {
      display: grid;
      place-items: center;
      flex: 0 0 auto;
      width: 46px;
      height: 46px;
      margin: 0 var(--sp-1);
      padding: 0;
      border: none;
      border-radius: 50%;
      background: var(--accent);
      color: #fff;
      font-size: 1.75rem;
      font-weight: 400;
      line-height: 1;
      font-family: inherit;
      cursor: pointer;
      box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 45%, transparent);
      transition: transform 0.12s ease;
    }
    .tab-add:active { transform: scale(0.92); }
    .tab-add span { display: block; margin-top: -2px; }

    /* The floating button would now duplicate the bar's +. */
    .add-btn { display: none; }
    .pane { scroll-margin-top: calc(78px + var(--safe-top)); }


    /* .card / .card-head padding used to be re-declared here; the grouped-list
       layer above owns them now. Re-adding an inset on the group would indent
       every row twice — once by the group, once by the row itself. */
    .closeout-grid { grid-template-columns: 1fr 1fr; }


    /* ---- Segmented control ----
       Apple's control for "pick one of a few". This was two separate .filters
       declarations — an outlined-pill row and a scrollable masked row — with the
       second silently winning. One block now.
       Seven filters do not fit 390px, so it scrolls, and the mask tells you so
       rather than slicing the last label mid-word for no apparent reason. The
       fade is dropped once you reach the end (syncFilterFade in
       lib/frontendTxList.js), so a fully-scrolled control stops implying there
       is more to come. */
    .filters {
      display: flex; flex-wrap: nowrap; overflow-x: auto;
      gap: 2px; margin: 0 -1rem var(--sp-4); padding: 2px 1rem;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;
      scroll-padding-right: 2rem;
      -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
      mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
    }
    .filters.at-end { -webkit-mask-image: none; mask-image: none; }
    .filters::-webkit-scrollbar { display: none; }
    .filter-pill {
      flex: 0 0 auto; border: none; background: transparent; border-radius: 7px;
      padding: var(--sp-2) var(--sp-4); font-size: var(--fs-md); font-weight: 500;
      color: var(--text-muted); min-height: 36px; white-space: nowrap;
    }
    .filter-pill.active {
      background: var(--surface); color: var(--text); font-weight: 600;
      box-shadow: 0 1px 3px rgba(0,0,0,0.12);
    }
    .filter-total { display: none; }


    /* .tx-row padding was re-declared here too and won on source order, undoing
       the grouped-list inset. Third one of these found by measuring: the mobile
       block is long enough that a rule near the bottom silently beats the layer
       near the top. */
    .tx-row:hover { margin: 0; padding-left: 0; padding-right: 0; }
    .tx-icon { width: 38px; height: 38px; font-size: var(--fs-md); }
    .tx-cat { font-size: var(--fs-base); }
    .tx-amt { font-size: var(--fs-base); }


    /* .task padding used to be re-declared here and won on source order, which
       stripped the horizontal inset the grouped-list layer gives every row —
       Plan rows sat flush to the card edge while every other list was inset. */
    .check { width: 22px; height: 22px; }


    /* No floating button on phones — the + in the tab bar replaces it. The
       rules that positioned it clear of the nav are gone with it; nothing here
       needs to know the nav's height any more. */


    .modal-overlay {
      padding: 0;
      align-items: flex-end;
    }
    /* tx modal owns its own scroll (flex column, contained form). The
       overlay must NOT also scroll, or iOS leaks the drag between the two. */
    #tx-modal.modal-overlay { overflow: hidden; }
    .modal {
      max-width: 100%;
      min-width: 0;
      margin-top: 0;
      padding: 1.25rem 1.25rem calc(1rem + var(--safe-bottom));
      border-radius: 18px 18px 0 0;
      max-height: 90dvh;
      overflow-x: hidden;
      overflow-y: auto;
      animation: slideUp 0.22s ease-out;
      -webkit-overflow-scrolling: touch;
      overscroll-behavior-x: none;
      touch-action: pan-y;
    }
    #tx-modal, #tx-modal .modal, #tx-modal form { max-width: 100%; overflow-x: hidden; overscroll-behavior-x: none; }
    #tx-modal .modal {
      display: flex;
      flex-direction: column;
      overflow: hidden;
      height: 90dvh;
    }
    #tx-modal .modal-head {
      position: relative;
      top: auto;
      z-index: 5;
      flex: 0 0 auto;
      margin: -1.25rem -1.25rem 0;
      padding: 1rem 1.25rem 0.875rem;
      border-bottom: 1px solid var(--border);
      box-shadow: 0 1px 0 var(--surface);
    }
    #tx-modal form {
      flex: 1 1 auto;
      min-height: 0;
      overflow-y: auto;
      padding-top: 1rem;
      -webkit-overflow-scrolling: touch;
      overscroll-behavior: contain;
    }
    #tx-modal .form-row > div { min-width: 0; max-width: 100%; }
    #tx-date { min-width: 0; width: 100%; max-width: 100%; }
    #profile-modal .modal {
      max-height: 78dvh;
      max-height: min(78dvh, 640px);
    }
    #profile-modal .modal-head {
      position: static;
      margin: 0 0 0.875rem;
      padding: 0;
      background: transparent;
      border-radius: 0;
    }
    .modal-head {
      position: sticky;
      top: 0;
      z-index: 2;
      margin: -1.25rem -1.25rem 1.75rem;
      padding: 1.625rem 1.25rem 0.875rem;
      background: var(--surface);
      border-radius: 18px 18px 0 0;
      box-shadow: 0 1px 0 var(--border);
    }
    .close-x {
      width: 44px;
      height: 44px;
      display: inline-grid;
      place-items: center;
      margin-right: -10px;
      border-radius: var(--radius-pill);
      font-size: var(--fs-2xl);
    }
    .close-x:active { background: var(--surface-2); }
    .profile-actions {
      gap: var(--sp-3);
      margin-top: 1.5rem;
      padding-top: 1.25rem;
      padding-bottom: max(0.5rem, var(--safe-bottom));
      border-top: 1px solid var(--border);
    }
    .profile-actions button {
      flex: 1;
      min-width: 0;
      display: flex;
      align-items: center;
      min-height: 44px;
      justify-content: center;
    }
    @keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }

    .tx-submit-row {
      flex: 0 0 auto;
      margin: 0 -1.25rem -1.25rem;
      padding: 0.75rem 1.25rem calc(0.75rem + var(--safe-bottom));
      background: var(--surface);
      border-top: 1px solid var(--border);
      z-index: 2;
    }
    .tx-submit-actions {
      flex: 1;
      min-width: 0;
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: var(--sp-3);
    }
    .tx-submit-actions button {
      min-height: 46px;
      justify-content: center;
    }


    .form-row, .form-row.two { grid-template-columns: 1fr; gap: var(--sp-3); }
    .cat-grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-2); }
    .cat-btn { padding: var(--sp-3); font-size: var(--fs-md); min-height: 56px; }


    .ref-grid { grid-template-columns: 1fr; gap: var(--sp-1) 0; }
    .ref-grid dt { font-size: var(--fs-xs); margin-top: var(--sp-2); }
    .ref-grid dd { font-size: var(--fs-md); }


    .phase-header { font-size: var(--fs-xs); padding: 0.875rem 0 0.5rem; }
    .task-title { font-size: var(--fs-base); }
    .task-detail { font-size: var(--fs-md); line-height: 1.5; }
    /* Chat fills the screen: the conversation scrolls, the page does not. The
       height budget below assumes the pane starts right under the header, so
       nothing else may be inserted above it inside the pane — that is why the
       large title is skipped for this tab. */
    /* Chat's height is measured from the top of the container, so ANY sibling
       inserted above the pane pushes the composer off the bottom of the phone.
       The overview was one; the past-due banner and the PIN nudge are the
       others. They are prompts about money, and they are still on every other
       screen — the one place they must not appear is the one screen sized to
       the viewport. */
    #app-shell[data-tab="chat"] > footer,
    #app-shell[data-tab="chat"] > #past-due-banner,
    #app-shell[data-tab="chat"] > #pin-prompt { display: none; }
    /* The container reserves 8.75rem at the bottom so scrolling tabs clear the
       floating tab bar. The chat pane subtracts the same 8.75rem in its own
       height, so keeping the padding here counts it twice and leaves the screen
       scrolling past a composer that is already in the right place. */
    #app-shell[data-tab="chat"] { padding-bottom: 0; }
    #chat.pane.active {

      height: calc(100dvh - var(--safe-top) - var(--safe-bottom) - 8.75rem);
      display: flex;
      flex-direction: column;
      padding-top: 0;
    }
    .chat-card {
      flex: 1;
      display: flex;
      flex-direction: column;
      min-height: 0;
      padding-bottom: 0;

      margin-bottom: 0;
    }
    .chat-messages {
      flex: 1;
      min-height: 0;
      max-height: none;
      padding: var(--sp-0) 0 var(--sp-3);
      overscroll-behavior: contain;
    }
    .chat-bubble { max-width: 94%; font-size: var(--fs-base); }
    /* Full-bleed composer. It used to cancel the gutter with a hardcoded
       -1.1rem, which matches neither mobile gutter (1rem, and 0.875rem below
       380px), so it hung ~2px past both edges — and it kept the base rule's
       1px border and 22px radius on all four sides, so the left and right
       borders were drawn off-screen. Cancel the real gutter, and since the bar
       now spans the full width, square it off and keep only the top rule. */
    .chat-composer {
      position: sticky;
      bottom: 0;
      z-index: 5;
      margin-inline: calc(var(--container-pad) * -1);
      margin-bottom: 0;
      padding: var(--sp-3) var(--container-pad) calc(var(--sp-3) + var(--safe-bottom));
      background: var(--surface);
      border: none;
      border-top: 1px solid var(--border);
      border-radius: 0;
    }
    /* The focus ring was a full-perimeter glow on a rounded box; on the
       squared-off bar it would bleed off both edges. */
    .chat-composer:focus-within { box-shadow: none; border-top-color: var(--accent); }
    #chat-input { min-height: 46px; }


    .lock-brand { padding-top: calc(1.5rem + var(--safe-top)); }


    footer { flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
  

    input.tx-search-input { font-size: var(--fs-lg); }
  
  }

  @media (max-width:680px) {

    .cal-cell { min-height:44px; padding:var(--sp-0); }
    .cal-ev { display:none; }
    .cal-cell .cal-dots { display:flex; }
    .cal-summary { width:100%; margin-left:0; }
  
  }

  @media (max-width: 640px) {

    .pin-prompt { flex-direction: column; align-items: flex-start; }
    .pin-prompt-actions { width: 100%; }
    .pin-prompt-actions button { flex: 1; }
  

    #lock-screen { justify-content: space-between; }
    .lock-form-card {
      max-width: 100%;
      background: var(--surface);
      border-radius: 24px 24px 0 0;
      box-shadow: 0 -6px 32px rgba(0,0,0,0.09);
      transform: translateY(100%);
    }
    .lock-form-card.lock-visible {
      animation: lockFormUp 0.52s cubic-bezier(0.32, 0.72, 0, 1) forwards;
    }
  
  }

  @media (max-width: 560px) {

    .past-due-banner { gap: var(--sp-3); padding: var(--sp-3); }
    .past-due-icon { display: none; }
    .past-due-cta { display: none; }
  

    .wallet-row { grid-template-columns: 1fr; }
    .wallet-row-cat { width: 100%; }
  

    .plan-up-actions { width: 100%; }
    .plan-up-actions button { flex: 1; }
  

    .sub-alert-actions, .merge-actions { width:100%; }
    .sub-alert-actions button, .merge-actions button { flex:1; }
  
  }

  @media (max-width: 520px) {

    .referral-form { flex-direction: column; }
    .referral-form button { width: 100%; }
  
  }

  @media (max-width: 380px) {

    .hero { grid-template-columns: 1fr; }
    .cat-grid { grid-template-columns: 1fr; }
    .closeout-grid { grid-template-columns: 1fr; }
    h1 { font-size: var(--fs-lg); }
    :root { --container-pad: 0.875rem; }
    .container { padding: calc(0.25rem + var(--safe-top)) var(--container-pad) var(--tabbar-clear); }
    .container > header { margin-left: -0.875rem; margin-right: -0.875rem; padding-left: 0.875rem; padding-right: 0.875rem; }
  
  }

  @media (hover: none) {

    .add-btn:hover { transform: none; box-shadow: 0 4px 14px rgba(0,0,0,0.15); }
    .tx-row:hover { background: transparent; margin: 0; padding-left: 0; padding-right: 0; }
    .tab-btn:hover { color: var(--text-muted); }
    .tab-btn.active:hover { color: var(--bg); }
    .ghost:hover, .primary:hover { background: initial; opacity: 1; }
  

    .tab-btn:active, .filter-pill:active, button.ghost:active,
    .toggle-btn:active, .cat-btn:active, .task:active, .tx-row:active {
      opacity: 0.6;
    }
    button.primary:active, .add-btn:active { transform: scale(0.97); }
  
  }

  @media (pointer: coarse) {

    .camera-only { display: inline-block; }
    .touch-only { display: block; }
    .no-touch-only { display: none; }
  
  }

  @media (prefers-reduced-motion: no-preference) {
 .retro-card { animation: fadeIn .25s ease; } 
 .home-recap-card--banner { animation: fadeIn .25s ease; } 
  }

  @media (prefers-reduced-motion: reduce) {

    .skel-row, .skel-block { animation: none; }
  
  }
