/* ---------------------------------------------------------------------------
   Design tokens — the single source of truth for color, spacing, radius and
   type. Components below reference these via var(); change a value here and it
   propagates everywhere, which is what keeps the UI consistent.
   --------------------------------------------------------------------------- */
:root,
[data-theme="instrument"] {
  /* ====================================================================
     THEME TOKENS — the default values below ARE the "Instrument" theme.
     Every [data-theme="…"] block at the end of this file overrides this
     set. Structural tokens (spacing, radius, type *scale*) are not themed.
     The [data-theme="instrument"] alias keeps elements tagged with it
     (the picker's preview swatch) on these tokens even when <html>
     carries another theme; the later theme blocks still win on <html>
     itself (equal specificity, later in the file).
     ==================================================================== */

  /* Color — surfaces & text */
  --bg:            #f4f5f2;
  --surface:       #ffffff;
  --surface-2:     #f8f8f5;
  --surface-3:     #eeefe9;
  --text:          #1c2839;
  --text-strong:   #13243b;
  --text-2:        #2a3a52;
  --muted:         #5d6b7d;
  --border:        #e2e4e0;
  --border-subtle: #eef0ec;
  --border-strong: #cfd2cd;

  /* Color — intent */
  --primary:       #9a5e22;
  --primary-hover: #80501d;
  --primary-soft:  #c0843a;
  --primary-text:  #ffffff;   /* text/icon on a filled --primary surface */
  --danger:        #dc2626;
  --danger-hover:  #b91c1c;
  --danger-bg:     #fde8e8;
  --danger-text:   #b91c1c;
  --success:       #16a34a;
  --neutral:       #5d6b7d;  /* transfers / net-worth-neutral */
  /* Derived once here; the color-mix re-resolves per theme because it references
     the theme's own --success/--surface/--primary (custom props substitute lazily). */
  --success-bg:    color-mix(in srgb, var(--success) 14%, var(--surface));
  --focus-ring:    color-mix(in srgb, var(--primary) 22%, transparent);

  /* Color — nav */
  --nav-bg:          #13243b;
  --nav-border:      #0d1a2c;
  --nav-link:        #aeb9c8;
  --nav-active:      #24364f;
  --nav-text:        #ffffff;   /* brand + plain nav text */
  --nav-active-text: #ffffff;   /* text on the active nav pill */

  /* Color — category accents (uncategorized / needs-attention) */
  --warning:            #c0843a;
  --warning-bg:         #fff3e6;
  --warning-bg-hover:   #ffe9cf;
  --warning-text:       #9a5e22;
  --warning-text-hover: #80501d;
  --uncat-text:         #9a5e22;

  /* Chart / pie slice palette — 10 hues (see slice-colors in views.cljs) */
  --chart-1:  #e08a1e; --chart-2:  #0fa3a3; --chart-3:  #2f6fd6; --chart-4:  #36b34a;
  --chart-5:  #ef4d54; --chart-6:  #8b5cf6; --chart-7:  #f0a92e; --chart-8:  #18b5c9;
  --chart-9:  #d4546e; --chart-10: #6b8f3a;

  /* Type families (themed) — display for headings, num for money figures */
  --font-sans:    "IBM Plex Sans", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  --font-display: "Space Grotesk", var(--font-sans);
  --font-num:     "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Spacing scale */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;

  /* Radius scale */
  --radius-sm:   4px;
  --radius-md:   6px;
  --radius-pill: 999px;

  /* Type scale */
  --fs-xs:   0.75rem;
  --fs-sm:   0.85rem;
  --fs-base: 0.95rem;
  --fs-md:   1rem;
  --fs-lg:   1.25rem;
  --fs-xl:   1.5rem;

  /* Native widgets (select dropdown lists, date pickers, scrollbars) render
     from this, not the tokens above — the dark themes override it to dark. */
  color-scheme: light;
}

* { box-sizing: border-box; }

/* Respect users who prefer reduced motion: near-instant animations/transitions. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

html, body {
  margin: 0;
  padding: 0;
  /* We set our own mobile type scale (the --fs-* overrides in the 640px media
     query below); stop iOS Safari from also applying its heuristic text
     inflation on top of it. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  font-family: var(--font-sans);
  color: var(--text);
  background: var(--bg);
  accent-color: var(--primary);   /* checkboxes/radios in the theme's accent */
}

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

.app { min-height: 100vh; }

main {
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) var(--space-6);
}

h1 { margin-top: 0; }
h2 { margin: 0 0 var(--space-2); }
h3 { font-size: var(--fs-md); }
.muted { color: var(--muted); font-size: var(--fs-sm); margin-top: 0; }

/* Global loading bar — driven by the re-frame :loading? counter */
.loading-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--primary), var(--primary-soft), var(--primary));
  background-size: 200% 100%;
  animation: loading-slide 1s linear infinite;
  z-index: 50;
}
@keyframes loading-slide {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Pull-to-refresh (iOS shell) — the ↻ chip that rides a drag down from the
   top of the page. .armed = past the release-to-refresh trigger; feedback
   after release is the .loading-bar above. */
.ptr {
  position: fixed;
  top: env(safe-area-inset-top, 0px); left: 50%;
  width: 38px; height: 38px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  font-size: 20px;
  pointer-events: none;
  z-index: 60;
}
.ptr.armed { color: var(--primary); border-color: var(--primary-soft); }

/* Full-screen boot loader: the "Loading…" shown before the shell mounts (the
   static index.html placeholder) and while auth resolves on a cold start.
   Pinned to the viewport centre so it never lands in a corner under the
   loading-bar / nav — both index.html and the (not checked?) app state use it. */
.app-loading {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  text-align: center;
}
.app-loading p { margin: 0; }

/* Content-area loading state: shown in <main> while a page waits on data it
   can't render without. The nav/shell stays put, so switching is instant. */
.loading-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 40vh;
}
.spinner {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 3px solid var(--border);
  border-top-color: var(--primary);
  animation: spinner-rotate 0.7s linear infinite;
}
@keyframes spinner-rotate { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2s; }
}

/* Nav — sticky: it's the app's primary switcher and pages get long, so it
   stays reachable. Above row-menu dropdowns (30), below modals/loading (50).
   sticky still acts as the containing block for the compact ☰ dropdown. */
.top-nav {
  position: sticky;
  top: 0;
  z-index: 45;
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-5);
  background: var(--nav-bg);
  color: var(--nav-text);
  border-bottom: 1px solid var(--nav-border);
}
.top-nav .brand { font-weight: 700; letter-spacing: 0.02em; color: var(--nav-text); }
.top-nav .links { display: flex; gap: var(--space-3); white-space: nowrap; overflow: hidden; }
.top-nav .nav-link {
  color: var(--nav-link);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
}
.top-nav .nav-link.active { background: var(--nav-active); color: var(--nav-active-text); }
.top-nav .nav-link:hover  { background: var(--nav-active); text-decoration: none; }
.nav-toggle {
  display: none;            /* hidden until .compact (tabs would wrap) reveals it */
  background: none;
  border: none;
  color: var(--nav-link);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--space-2);
}
.nav-toggle:hover { color: var(--nav-text); }
.nav-mid { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; }
/* Hidden inline copy of the tabs; its scrollWidth vs .nav-mid's width tells JS
   the instant the real tabs would wrap. width/height 0 + overflow hidden so it
   never adds a scrollbar, yet scrollWidth still reports the natural tab width. */
.nav-measure {
  position: absolute; left: 0; top: 0; width: 0; height: 0;
  overflow: hidden; visibility: hidden; pointer-events: none;
  display: flex; gap: var(--space-3);
}
.nav-measure .nav-link { flex: 0 0 auto; }
/* .compact is set by JS the moment the tabs no longer fit — collapse to ☰. */
.top-nav.compact .nav-toggle { display: block; }
.top-nav.compact .nav-mid .links {
  position: absolute; top: 100%; left: 0; right: 0;
  flex-direction: column; align-items: flex-start; gap: var(--space-1);
  background: var(--nav-bg); border-bottom: 1px solid var(--nav-border);
  padding: var(--space-2) var(--space-5); overflow: visible;
  display: none; z-index: 40;
}
.top-nav.compact.open .nav-mid .links { display: flex; }
/* In compact the ☰ sits top-left; roomy mode hides it, so order is moot. */
.top-nav.compact .nav-toggle { padding-left: 0; }

/* Drawer-only items — account + site links live in the ☰ drawer alongside
   the tabs, so everything is reachable from one place on a phone. Hidden
   entirely in roomy mode (the top tabs + avatar menu cover it). */
.top-nav .drawer-extra { display: none; }
.top-nav.compact .drawer-extra {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1);
  width: 100%;
  border-top: 1px solid var(--nav-border);
  margin-top: var(--space-2); padding-top: var(--space-2);
}
.drawer-logout {
  background: none; border: none; cursor: pointer;
  font: inherit; text-align: left;
}
.drawer-site {
  display: flex; gap: var(--space-4);
  padding: var(--space-1) var(--space-2) var(--space-1);
  font-size: var(--fs-sm);
}
.drawer-site a, .drawer-site button.link { color: var(--nav-link); }
.drawer-site a:hover, .drawer-site button.link:hover { color: var(--nav-text); }
.drawer-site button.link { font-size: inherit; text-decoration: none; }

/* iOS-style bottom tab bar — compact mode's primary nav (the ☰ drawer keeps
   the long tail). Fixed to the viewport; body.has-bottom-tabs (toggled by the
   same measurement that sets .compact) pads main so content scrolls clear. */
.bottom-tabs {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  display: flex;
  background: var(--nav-bg);
  border-top: 1px solid var(--nav-border);
  padding: var(--space-1) env(safe-area-inset-right, 0px)
           env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
}
.bottom-tabs .tab {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: var(--space-1) 0;
  color: var(--nav-link); font-size: 0.66rem; line-height: 1;
}
.bottom-tabs .tab:hover { text-decoration: none; }
.bottom-tabs .tab.active { color: var(--nav-active-text); }

/* Safe-area (iOS notch / Dynamic Island / home-bar) — additive and a no-op on
   browsers without the insets, since env(..., 0px) falls back to 0. The nav is
   the sticky element pinned to the top edge, so it absorbs the top inset; body
   background paints under the status bar (viewport-fit=cover, set in the iOS
   index.html) so the tint runs to the physical edge. main and the compact ☰
   dropdown pick up the horizontal + bottom insets for landscape + home-bar. */
.top-nav {
  padding-top: calc(var(--space-3) + env(safe-area-inset-top, 0px));
  padding-left: calc(var(--space-5) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--space-5) + env(safe-area-inset-right, 0px));
}
.top-nav.compact .nav-mid .links {
  padding-left: calc(var(--space-5) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--space-5) + env(safe-area-inset-right, 0px));
}
main {
  padding-left: calc(var(--space-4) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--space-4) + env(safe-area-inset-right, 0px));
  padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
}
body.has-bottom-tabs main {
  padding-bottom: calc(var(--space-6) + 58px + env(safe-area-inset-bottom, 0px));
}

/* Error banner */
.error {
  background: var(--danger-bg);
  color: var(--danger-text);
  padding: var(--space-2) var(--space-4);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.error button {
  background: transparent;
  border: none;
  color: var(--danger-text);
  cursor: pointer;
  font-size: 1.1rem;
}

/* Neutral, dismissible notice (e.g. "already added") — informational, not an
   error, so it uses the primary accent rather than the danger palette. */
.notice {
  background: var(--surface-2);
  color: var(--text-strong);
  border-left: 3px solid var(--primary);
  padding: var(--space-2) var(--space-4);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.notice button {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 1.1rem;
}

/* Email-verification nudge — shown until the signup address is confirmed. */
.verify-banner {
  background: var(--warning-bg);
  color: var(--warning-text);
  border-left: 3px solid var(--warning);
  padding: var(--space-2) var(--space-4);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.verify-banner button {
  background: transparent;
  border: none;
  color: var(--warning-text);
  cursor: pointer;
  text-decoration: underline;
  padding: 0;
  font-size: inherit;
  white-space: nowrap;
}
.verify-banner button:hover { background: transparent; color: var(--warning-text-hover); }

/* Tables */
table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: var(--space-5);
}
th, td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
}
th { background: var(--surface-2); font-size: var(--fs-sm); font-weight: 600; }
td.right, th.right { text-align: right; font-variant-numeric: tabular-nums; }

/* Empty state */
.empty {
  color: var(--muted);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  text-align: center;
}

/* Buttons */
button {
  background: var(--primary);
  color: var(--primary-text);
  border: none;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--fs-sm);
}
button:hover { background: var(--primary-hover); }
button:disabled { opacity: 0.6; cursor: not-allowed; }
button.danger { background: var(--danger); }
button.danger:hover { background: var(--danger-hover); }
button.link {
  background: transparent;
  color: var(--primary);
  padding: 0;
  text-decoration: underline;
  font-size: var(--fs-sm);
}
button.link:hover { background: transparent; }

/* ---------------------------------------------------------------------------
   Form controls — the themed baseline every input/select/textarea starts from.
   Form controls do NOT inherit the page font, and unstyled ones render with
   browser chrome (UA font, white fields on the dark themes, native select
   look) — off-theme the moment a control sits outside a styled container.
   :where() keeps these at element specificity so any single-class rule below
   (.type-select, .money-input, .inline-edit, …) still overrides cleanly.
   --------------------------------------------------------------------------- */
input:where(:not([type="checkbox"], [type="radio"], [type="file"], [type="range"], [type="color"])),
select,
textarea {
  font: inherit;
  font-size: var(--fs-base);
  color: var(--text);
  background-color: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
}
input:where(:not([type="checkbox"], [type="radio"], [type="file"], [type="range"], [type="color"])):focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
::placeholder { color: var(--muted); opacity: 1; }
/* Kill Safari's rounded native search chrome; the baseline above restyles it. */
input:where([type="search"]) { -webkit-appearance: none; appearance: none; }
/* Selects drop the native chrome for the baseline look; the arrow is redrawn
   as a two-triangle chevron in --muted so it re-themes with everything else. */
select {
  appearance: none;
  padding-right: calc(var(--space-2) * 2 + 0.6em);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position:
    right calc(var(--space-2) + 0.3em) top 50%,
    right var(--space-2) top 50%;
  background-size: 0.3em 0.3em;
  background-repeat: no-repeat;
}

/* Forms */
form.create {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  align-items: end;
}
form.create h3 {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--fs-md);
}
form.create label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-xs);
  color: var(--text-2);
}
form.create button { justify-self: start; }

/* Reusable dollar-amount field (money-input): a currency affix fused to a text
   field sized per site, with in-field arithmetic. The wrapper owns the border so
   affix + field read as one control; the field's width is set inline per site. */
.money-field {
  display: inline-flex;
  align-items: stretch;
  max-width: 100%;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  overflow: hidden;
}
.money-field.affix-right { flex-direction: row-reverse; }
.money-field:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--focus-ring); }
.money-affix {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  background: var(--surface-2);
  color: var(--muted);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.money-field .money-input {
  border: none;
  background: transparent;
  padding: var(--space-2);
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
  min-width: 0;
}
/* The wrapper owns the focus treatment — the inner field stays bare. */
.money-field .money-input:focus { outline: none; box-shadow: none; }

/* KPI block */
.kpi {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-4);
}
.kpi > div {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
.kpi > div div { font-size: var(--fs-xl); font-variant-numeric: tabular-nums; }

/* Bulk-action strip above transactions */
.bulk-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-2) 0 var(--space-4);
}
.bulk-actions .uncat-count {
  color: var(--uncat-text);
  font-weight: 600;
  font-size: var(--fs-sm);
}
.bulk-actions.warn .uncat-count { color: var(--warning-text); }
.bulk-actions .over-note { font-weight: 500; color: var(--warning-text); }

/* Inline account-type editor in the accounts table: compact + quiet-bordered.
   Longhands only — a padding/background shorthand here would wipe the chevron
   and its right-side room from the select baseline. */
.type-select {
  padding-top: var(--space-1);
  padding-bottom: var(--space-1);
  font-size: var(--fs-sm);
  border-color: var(--border);
}

/* Category cell — three visual states for category-source. */
td.cat-cell { padding: var(--space-1) var(--space-2); }

.cat {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-size: var(--fs-sm);
  line-height: 1.2;
}
.cat:hover { background: var(--surface-3); }

/* Categorized: confident, plain (user- or auto-assigned; not distinguished). */
.cat-user { color: var(--text-strong); }
.cat-user .cat-icon { color: var(--success); font-weight: 700; }

/* Uncategorized: the attention state. Distinct shape (rectangle, not pill),
   warning color, animated pulse so it's hard to overlook. */
.cat-none {
  background: var(--warning-bg);
  color: var(--warning-text);
  border: 1px dashed var(--warning);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  cursor: pointer;
  animation: cat-none-pulse 2.4s ease-in-out infinite;
}
.cat-none:hover {
  background: var(--warning-bg-hover);
  color: var(--warning-text-hover);
}
@keyframes cat-none-pulse {
  /* halo derives from the theme's uncategorized colour, not a fixed orange */
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--uncat-text) 22%, transparent); }
}

/* Category picker — a full overlay (category-picker-modal). The transaction
   summary is pinned at the top; every category, grouped under its root and
   filterable, scrolls below for easy browsing. */
.modal.cat-modal {
  width: min(560px, calc(100vw - 2rem));
  max-height: min(80vh, 640px);
  display: flex;
  flex-direction: column;
  padding: 0;                 /* the sections below own their padding */
}
.cat-modal-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--border);
}
.cat-modal-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cat-modal-amt { font-weight: 700; font-size: var(--fs-lg); font-variant-numeric: tabular-nums; color: var(--text-strong); }
.cat-modal-route { color: var(--text-2); font-size: var(--fs-sm); }
.cat-modal-desc { color: var(--muted); font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-modal-date { color: var(--muted); font-size: var(--fs-xs); }
.cat-modal-close {
  margin-left: auto; background: none; border: none; color: var(--muted);
  font-size: 1.5rem; line-height: 1; cursor: pointer; padding: 0 var(--space-1);
}
.cat-modal-close:hover { color: var(--text-strong); background: none; }
.cat-modal-search {
  margin: var(--space-3) var(--space-4) 0;
  padding: var(--space-2) var(--space-3);
}
.cat-modal-body { overflow-y: auto; padding: var(--space-3) var(--space-4) var(--space-4); }
.cat-group { margin-top: var(--space-4); }
.cat-group:first-of-type { margin-top: var(--space-3); }
.cat-group-root {
  display: inline-block; background: none; border: none;
  padding: 0 0 var(--space-2); margin: 0;
  font-size: var(--fs-sm); font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-strong); cursor: pointer;
}
.cat-group-root:hover { background: none; color: var(--primary); text-decoration: underline; }
.cat-group-root.sel { color: var(--primary); }
.cat-children { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.cat-opt {
  background: var(--surface-2); color: var(--text-2);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3); font-size: var(--fs-sm); cursor: pointer;
}
.cat-opt:hover { background: var(--surface-3); border-color: var(--border-strong); color: var(--text-strong); }
.cat-opt.sel, .cat-opt.sel:hover { background: var(--primary); border-color: var(--primary); color: var(--primary-text); }
.cat-opt-clear { display: block; margin-bottom: var(--space-2); }

/* Money-flow picker (movement-picker) — sits in the categorization popups
   between the pinned transaction summary and whatever scrolls below. */
.mv-picker {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
}
.mv-picker-label {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted);
  margin-bottom: var(--space-2);
}
.mv-picker-opts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.mv-help {
  margin: 0 0 var(--space-3);
  color: var(--text-2); font-size: var(--fs-sm); line-height: 1.5;
}
.cat-modal-note {
  margin: var(--space-3) var(--space-4) var(--space-4);
  color: var(--muted); font-size: var(--fs-sm);
}

/* In-app legal viewer (legal-doc-modal) — Terms / Privacy readable without
   leaving the app, on web and iOS alike (Apple guideline 5.1.1). */
.modal.legal-modal {
  width: min(720px, calc(100vw - 2rem));
  max-height: min(85vh, 760px);
  display: flex;
  flex-direction: column;
  padding: 0;
}
.legal-modal-head {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--border);
}
.legal-modal-title { font-weight: 600; color: var(--text-strong); }
.legal-modal-body { overflow-y: auto; padding: var(--space-3) var(--space-4) var(--space-4); }
.legal-doc { color: var(--text-2); font-size: var(--fs-sm); line-height: 1.6; }
.legal-doc h1 { font-size: var(--fs-lg); margin: 0 0 var(--space-2); color: var(--text-strong); }
.legal-doc h2 { font-size: var(--fs-md); margin: var(--space-5) 0 var(--space-2); color: var(--text-strong); }
.legal-doc p { margin: 0 0 var(--space-3); }
.legal-doc ul { margin: 0 0 var(--space-3); padding-left: 1.25rem; }
.legal-doc li { margin-bottom: var(--space-1); }
.legal-doc .updated { color: var(--muted); font-size: var(--fs-xs); }

/* Income attribution (money-in's category mirror). The unattributed pill is
   deliberately quiet — no warning colour, no pulse — because unmatched money-in
   is usually a one-off (a gift), not something needing the user's attention. */
.inc-none {
  background: transparent;
  color: var(--muted);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  font-size: var(--fs-xs);
  cursor: pointer;
}
.inc-none:hover { color: var(--text-strong); border-color: var(--border-strong); }
.inc-muted {
  background: transparent; border: none; color: var(--muted);
  padding: var(--space-1) var(--space-2); cursor: pointer;
}
.inc-muted:hover { color: var(--text-strong); }

/* Money-neutral's category-cell stand-in — quiet like .inc-muted (a transfer
   between own accounts rarely needs attention); opens the category picker so
   its money-flow control is one click away. */
.mv-neutral {
  background: transparent; border: none; color: var(--muted);
  padding: var(--space-1) var(--space-2); cursor: pointer;
  font-size: var(--fs-xs);
}
.mv-neutral:hover { color: var(--text-strong); }
.inc-opt-amt { margin-left: var(--space-2); font-size: var(--fs-xs); opacity: 0.75; font-variant-numeric: tabular-nums; }
.income-picker-opts .cat-opt { display: block; width: 100%; text-align: left; margin-bottom: var(--space-2); }

/* Bulk "apply this category to recurring matches" offer */
.bulk-cat-modal { max-width: 32rem; }
.bulk-cat-list {
  max-height: 50vh; overflow-y: auto;
  margin: var(--space-3) 0; border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.bulk-cat-row {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding: var(--space-2) var(--space-3); cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.bulk-cat-row:last-child { border-bottom: none; }
.bulk-cat-row:hover { background: var(--surface-2); }
.bulk-cat-row input { flex: none; align-self: center; cursor: pointer; }
.bulk-cat-amt { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-strong); }
.bulk-cat-route { color: var(--text-2); font-size: var(--fs-sm); }
.bulk-cat-desc {
  color: var(--muted); font-size: var(--fs-sm); margin-left: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40%;
}
.bulk-cat-date { color: var(--muted); font-size: var(--fs-xs); flex: none; }

/* Plaid section */
.plaid {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin: 0 0 var(--space-5);
}
.plaid h3 { margin: 0 0 var(--space-1); font-size: var(--fs-md); }
.plaid .plaid-actions {
  margin: var(--space-2) 0 var(--space-3);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.plaid table { margin: var(--space-3) 0 var(--space-2); }

/* Nav account menu (right side) */
.top-nav .nav-user { margin-left: auto; display: flex; align-items: center; gap: var(--space-3); }

/* Global "Send feedback" — the quiet pill beside the avatar, on every page.
   Compact (phone) mode keeps just the speech-bubble icon. */
.top-nav .nav-feedback {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 1px solid var(--nav-border);
  color: var(--nav-link); border-radius: 999px;
  padding: 0.3rem 0.7rem;
  font-size: var(--fs-sm); cursor: pointer; white-space: nowrap;
}
.top-nav .nav-feedback:hover { background: var(--nav-active); color: var(--nav-active-text); }
.top-nav.compact .nav-feedback { padding: 0.3rem 0.45rem; }
.top-nav.compact .nav-feedback .nav-feedback-label { display: none; }

.feedback-modal textarea {
  width: 100%; min-height: 140px; resize: vertical;
  margin-top: var(--space-2);
  font: inherit; color: var(--text-strong);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: var(--space-2);
}
.feedback-error { color: var(--danger-text); font-size: var(--fs-sm); margin-top: var(--space-2); }

/* Profile / user-account menu: avatar trigger + dropdown (email, Profile, Log out). */
.account-menu { position: relative; display: inline-flex; }
.avatar {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--nav-active); color: var(--nav-text);
  border: none; padding: 0; cursor: pointer; overflow: hidden;
  font-weight: 600; font-size: var(--fs-sm);
  display: inline-flex; align-items: center; justify-content: center;
}
.avatar:hover, .avatar[aria-expanded="true"] { background: var(--primary); }
.avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Profile photo controls */
.avatar-lg { width: 72px; height: 72px; font-size: 1.6rem; cursor: default; flex: 0 0 auto; }
.avatar-lg:hover { background: var(--nav-active); }   /* not a button — neutralize the trigger hover */
.avatar-row { display: flex; align-items: center; gap: var(--space-4); }
.avatar-controls { display: flex; flex-direction: column; gap: var(--space-2); }
.avatar-buttons { display: flex; align-items: center; gap: var(--space-3); }
.btn-file {
  display: inline-block; cursor: pointer;
  background: var(--surface); color: var(--text-strong);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3); font-size: var(--fs-sm);
}
.btn-file:hover { border-color: var(--text-strong); }
.account-dropdown {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  min-width: 200px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(17,24,39,0.10), 0 2px 6px rgba(17,24,39,0.06);
  padding: 0.25rem 0;
}
.account-email {
  padding: 0.5rem 0.85rem; font-size: var(--fs-xs); color: var(--muted);
  border-bottom: 1px solid var(--border); margin-bottom: 0.25rem;
  word-break: break-all;
}
.account-item {
  display: block; width: 100%; text-align: left;
  padding: 0.5rem 0.85rem; font-size: var(--fs-sm); color: var(--text);
  background: none; border: none; cursor: pointer; text-decoration: none;
}
.account-item:hover { background: var(--surface-3); text-decoration: none; }

/* Settings page. */
.settings { max-width: 640px; }
.settings-section {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: var(--space-4); margin-bottom: var(--space-4);
}
.settings-section h3 { margin: 0 0 var(--space-2); }
.settings-email { font-size: var(--fs-md); color: var(--text-strong); margin: 0; }
.password-form { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-3); max-width: 22rem; }
.password-form label { display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--fs-sm); }
.password-form button { align-self: flex-start; }
.google-btn.reauth { margin-top: var(--space-2); }
.profile-ok { color: var(--success); font-size: var(--fs-sm); margin-top: var(--space-2); }
.password-reset { margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--border); }
.password-reset h4 { margin: 0 0 var(--space-1); font-size: var(--fs-sm); }
/* Settings → Subscription plans. The catalog is informational until the final
   release: pre-release the grid greys out under the note (billing-section). */
.plan-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr));
  gap: var(--space-3); margin: var(--space-3) 0;
}
.plan-card {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface);
  padding: var(--space-3);
  display: flex; flex-direction: column; gap: var(--space-1);
}
.plan-card.current { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.plan-card .plan-name {
  font-weight: 600; color: var(--text-strong);
  display: flex; gap: var(--space-2); align-items: baseline; flex-wrap: wrap;
}
.plan-card .plan-tag {
  font-size: var(--fs-xs); font-weight: 400; color: var(--muted);
  border: 1px solid var(--border-strong); border-radius: 999px; padding: 0 var(--space-2);
}
.plan-card .plan-price { font-size: var(--fs-md); color: var(--text-strong); }
.plan-card .plan-desc { margin: 0; font-size: var(--fs-xs); color: var(--muted); }
.plan-card .plan-current { margin-top: auto; font-size: var(--fs-xs); font-weight: 600; color: var(--success); }
.plan-cards.pre-release .plan-card { opacity: 0.55; }
.pre-release-note { color: var(--muted); font-size: var(--fs-sm); margin: var(--space-2) 0 0; }

.danger-zone { border-left: 3px solid var(--danger-text); }
.close-account-form label {
  display: flex; flex-direction: column; gap: var(--space-1);
  font-size: var(--fs-sm); margin-top: var(--space-3);
}
.settings-links {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  margin-top: var(--space-5); font-size: var(--fs-sm);
}
.settings-links a, .settings-links button.link { color: var(--text-muted); }
.settings-links a:hover, .settings-links button.link:hover { color: var(--text-strong); }
.settings-links button.link { text-decoration: none; }
.btn-danger {
  background: none; color: var(--danger-text);
  border: 1px solid var(--danger-text); border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3); cursor: pointer; font-size: var(--fs-sm);
}
.btn-danger:hover { background: var(--danger-bg); }

/* Profile → Notifications: one preference per line, each threshold indented
   under the toggle it qualifies. */
.alert-prefs { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); }
.alert-prefs .checkbox { display: inline-flex; align-items: center; gap: var(--space-1); }
.alert-prefs .alert-threshold {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-left: var(--space-4); font-size: var(--fs-sm); color: var(--muted);
}
.alert-prefs .alert-threshold input { width: 6.5rem; }

/* Auth (sign in / register) */
.auth {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}
.auth-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-6);
  width: 100%;
  max-width: 360px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.auth-card h1 { margin: 0 0 var(--space-1); }
.auth-card form { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-4); }
.auth-card label { display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--fs-xs); color: var(--text-2); }
.auth-card button[type="submit"] { margin-top: var(--space-2); }
.name-fields { display: grid; grid-template-columns: 1fr; gap: var(--space-3); }
.field-hint { font-size: var(--fs-xs); color: var(--muted); margin-top: 2px; line-height: 1.4; }
.auth-error {
  color: var(--danger-text);
  background: var(--danger-bg);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  font-size: var(--fs-sm);
  margin: 0;
}
.auth-switch { margin-top: var(--space-4); }
.auth-pending {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--muted);
  font-size: var(--fs-sm);
  margin: 0;
}
.auth-pending::before {
  content: "";
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: auth-spin 0.7s linear infinite;
}
@keyframes auth-spin { to { transform: rotate(360deg); } }
.auth-notice {
  color: var(--success);
  background: var(--success-bg);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  font-size: var(--fs-sm);
  margin: 0 0 var(--space-3);
}

/* Google sign-in button — follows Google's brand guidance: white surface,
   Roboto/system font, 1px gray border, the colored "G" mark on the left. */
.google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  width: 100%;
  padding: 0.55rem 0.85rem;
  background: #fff;
  color: #3c4043;
  border: 1px solid #dadce0;
  border-radius: var(--radius-sm);
  font-weight: 500;
  font-size: var(--fs-base);
  text-decoration: none;
  transition: background 120ms ease, box-shadow 120ms ease;
}
.google-btn:hover { background: #f8faff; box-shadow: 0 1px 2px rgba(60,64,67,0.18); text-decoration: none; }
.google-btn:active { background: #f1f3f4; }
.google-btn svg { flex: 0 0 auto; }

/* Sign in with Apple — Apple's guideline is a solid black button. */
.apple-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  width: 100%;
  padding: 0.55rem 0.85rem;
  background: #000;
  color: #fff;
  border: 1px solid #000;
  border-radius: var(--radius-sm);
  font-weight: 500;
  font-size: var(--fs-base);
  text-decoration: none;
  transition: background 120ms ease, box-shadow 120ms ease;
}
.apple-btn:hover { background: #1a1a1a; box-shadow: 0 1px 2px rgba(0,0,0,0.28); text-decoration: none; color: #fff; }
.apple-btn:active { background: #333; }
.apple-btn svg { flex: 0 0 auto; }

/* "or" divider between Google and email/password form */
.auth-divider {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
  color: var(--text-2);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* Responsive: let wide tables scroll instead of overflowing on small screens */
@media (max-width: 640px) {
  /* Phone type scale: the desktop tokens read fine at arm's length but are too
     small in the hand, so bump the small end of the scale (spacing tokens stay
     put — horizontal room is the scarce resource here). --fs-base must be
     ≥ 16px: inputs render at it, and iOS Safari force-zooms the viewport when
     a focused input is any smaller. */
  :root {
    --fs-xs:   0.8125rem;   /* 13px */
    --fs-sm:   0.9375rem;   /* 15px */
    --fs-base: 1rem;        /* 16px */
    --fs-md:   1.0625rem;   /* 17px */
  }

  main { padding: var(--space-4) var(--space-3) var(--space-5); }

  /* --- Tables become stacked cards; each cell shows its column via data-label --- */
  thead { display: none; }
  table, tbody, tr, td { display: block; }
  tbody tr {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
  }
  td {
    display: flex; flex-wrap: wrap; justify-content: space-between;
    gap: var(--space-1) var(--space-3);
    border: none !important; padding: var(--space-1) 0;
    text-align: left !important; white-space: normal;
    /* Plaid merchant strings can be one long unbreakable token; without this
       a single such cell widens the whole page past the viewport. */
    overflow-wrap: anywhere;
  }
  td::before { content: attr(data-label); font-weight: 600; color: var(--muted); }
  td.arrow { display: none; }                 /* the "→" cell is noise in a card */
  td.actions { justify-content: flex-end; }
  td.actions::before { content: none; }

  .headline-num { font-size: 2rem; }
  .tx-filters label { width: 100%; }
  .tx-filters label > select { width: 100%; }
}

/* Seed-defaults call-to-action shown to users without the default tree.
   Quiet — primary content is the user's own categories, not us nagging. */
.seed-banner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius-sm);
  padding: 0.9rem 1rem;
  margin: var(--space-3) 0;
}
.seed-banner p { margin: 0.15rem 0 0; }
.muted-small { font-size: var(--fs-xs); }

/* ---------------------------------------------------------------------------
   Row actions: ⋮ kebab + dropdown menu + confirm modal
   ---------------------------------------------------------------------------
   Don Norman / NN/g shaped this: destructive actions are rare and shouldn't
   visually compete with the primary data. The kebab is a quiet signifier
   ("more here if you want it") and the dropdown carries the destructive
   wording. The follow-up modal restates *what* is being deleted, satisfying
   the "error prevention" and "visibility of system status" heuristics.
   --------------------------------------------------------------------------- */

td.actions {
  text-align: right;
  white-space: nowrap;
}
td.actions > * + * { margin-left: var(--space-2); }

.row-menu { position: relative; display: inline-block; }

.kebab {
  background: transparent;
  color: var(--muted);
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.25rem 0.45rem;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  /* The dots should sit centered and unanimated. */
  font-weight: 700;
  letter-spacing: 0.02em;
}
.kebab:hover, .kebab[aria-expanded="true"] {
  background: var(--surface-3);
  color: var(--text-strong);
}
.kebab:focus-visible {
  outline: 2px solid var(--primary-soft);
  outline-offset: 1px;
}

.row-menu-list {
  position: absolute;
  right: 0;
  top: calc(100% + 2px);
  z-index: 30;
  min-width: 180px;
  list-style: none;
  margin: 0;
  padding: 0.25rem 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(17,24,39,0.10), 0 2px 6px rgba(17,24,39,0.06);
}
.row-menu-list li {
  padding: 0.5rem 0.85rem;
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--text);
  user-select: none;
}
.row-menu-list li:hover { background: var(--surface-3); }
.row-menu-list li.danger { color: var(--danger-text); }
.row-menu-list li.danger:hover { background: var(--danger-bg); }

/* Inline rename (categories): the name cell becomes an editable field. */
.inline-edit {
  padding: 0.25rem 0.4rem;
  width: 100%;
  max-width: 18rem;
}
.inline-edit-actions { white-space: nowrap; }

/* Subtle lineage hint on a renamed default category. */
.cat-origin {
  margin-left: var(--space-2);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-style: italic;
}

/* ---------------------------------------------------------------------------
   Manual (user-maintained) indicators
   --------------------------------------------------------------------------- */

/* A small amber "Manual" pill on accounts/transactions the user added by hand
   (not synced from a bank) — they must maintain these themselves. */
.manual-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--warning-bg);
  color: var(--warning-text);
  padding: 0.05rem 0.45rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  vertical-align: middle;
}
.manual-line { margin-top: var(--space-1); }

/* A calm blue chip on plans that were auto-detected as recurring (vs hand-made). */
.detected-badge {
  display: inline-flex;
  align-items: center;
  background: var(--surface-3);
  color: var(--primary);
  border: 1px solid var(--primary-soft);
  padding: 0.05rem 0.45rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  margin-right: var(--space-1);
  vertical-align: middle;
}

/* Recurring reconciliation: an upcoming item's status, plus the nudge shown
   when a recurring payment was due but no matching transaction has posted.
   Uses the warning palette (same as the uncategorized pill). */
.recon-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem 0.45rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  vertical-align: middle;
}
.recon-badge.expected { background: var(--surface-3); color: var(--muted); }
.recon-badge.realized {
  background: var(--surface-3);
  color: var(--success);
  border: 1px solid var(--success);
}
.recon-badge.missed {
  background: var(--warning-bg);
  color: var(--warning-text);
  border: 1px solid var(--warning);
}
.recon-badge.missed.clickable {
  font-family: inherit;
  cursor: pointer;
  transition: background 0.12s ease, box-shadow 0.12s ease;
}
.recon-badge.missed.clickable:hover {
  background: var(--warning);
  color: var(--surface);
}
.recon-badge.missed.clickable:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--warning);
}
tr.row-missed { background: var(--warning-bg); }
.recon-nudge {
  background: var(--warning-bg);
  color: var(--warning-text);
  border-left: 3px solid var(--warning);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-4);
  margin: var(--space-3) 0;
}
.recon-nudge-title { font-weight: 700; }
.recon-nudge-detail .link { color: var(--warning-text); font-weight: 600; }

/* CSV import modal */
.import-modal { width: min(560px, calc(100vw - 2rem)); }
.import-csv-file { display: block; margin-top: var(--space-2); font-size: var(--fs-sm); }
.import-csv-text {
  width: 100%; margin-top: var(--space-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-xs);
  resize: vertical;
}
.import-result { margin-top: var(--space-3); }
.import-summary { font-weight: 600; margin: 0; }
.import-errors { margin-top: var(--space-1); color: var(--muted); font-size: var(--fs-sm); }
.import-errors ul { margin: var(--space-1) 0 0; padding-left: 1.1rem; }
.import-error { color: var(--danger-text); font-weight: 600; }

/* Bulk (multi-account) import */
.bulk-table { width: 100%; margin-top: var(--space-3); }
.bulk-table th { text-align: left; font-size: var(--fs-sm); color: var(--muted); }
.bulk-table td { vertical-align: middle; }
.bulk-table input { width: 100%; }
.bulk-results { margin: var(--space-2) 0 0; padding-left: 1.1rem; }
.bulk-results li { margin-bottom: var(--space-1); }

/* Section header with an action on the right */
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
}
.section-head h2 { margin: 0; }

/* Empty-state call-to-action (e.g. import onboarding) */
.empty-cta { margin: var(--space-2) 0; }
.empty-cta p { color: var(--muted); max-width: 46rem; }

/* Period-over-period spending trend (Insights "vs prev"): up = more spending
   (a warning for expenses), down = less (good). */
.trend-up   { color: var(--warning-text); font-weight: 600; white-space: nowrap; }
.trend-down { color: var(--success); font-weight: 600; white-space: nowrap; }

/* Dashboard "Coming up" list */
.coming-up { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.coming-up li {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding: var(--space-2) 0; border-bottom: 1px solid var(--border-subtle);
}
.coming-up li:last-child { border-bottom: none; }
.coming-up .cu-date { color: var(--muted); min-width: 4.5rem; font-variant-numeric: tabular-nums; }
.coming-up .cu-desc { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coming-up .cu-amt  { font-weight: 600; font-variant-numeric: tabular-nums; }
.coming-up .cu-amt.neg { color: var(--danger-text); }

/* Trusted-device list (Profile) */
.device-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.device-list li {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-2) 0; border-bottom: 1px solid var(--border-subtle);
}
.device-list li:last-child { border-bottom: none; }
.device-info { display: flex; flex-direction: column; min-width: 0; }
.device-ua { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 28rem; }
.device-meta { color: var(--muted); font-size: var(--fs-sm); }

/* Projections low-balance watch: red if an account is projected below $0,
   yellow if within the (adjustable) near-zero threshold. */
.balance-watch { margin: var(--space-2) 0 var(--space-3); }
.balance-alert {
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-sm);
  margin-bottom: var(--space-2);
}
.balance-alert.danger { background: var(--danger-bg); color: var(--danger-text); border-left: 3px solid var(--danger); }
.balance-alert.warn   { background: var(--warning-bg); color: var(--warning-text); border-left: 3px solid var(--warning); }
.threshold-control { display: inline-flex; align-items: center; gap: 0.25rem; color: var(--muted); font-size: var(--fs-sm); }
.threshold-control input { width: 5rem; padding: 0.2rem 0.4rem; }

/* Future expenses: lumpy one-off / yearly costs with a monthly set-aside and
   a readiness badge per expense. */
.future-expenses { margin-top: var(--space-4); }
.fe-summary { display: flex; gap: var(--space-5); flex-wrap: wrap; margin: var(--space-2) 0; }
.fe-kpi .num { display: block; font-weight: 700; font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.fe-kpi .lbl { color: var(--muted); font-size: var(--fs-sm); }
.fe-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.fe-list li {
  display: grid;
  grid-template-columns: 5.5rem minmax(min(100%, 10rem), 1fr) auto auto auto;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}
.fe-list li:last-child { border-bottom: none; }
.fe-date { color: var(--muted); font-variant-numeric: tabular-nums; }
.fe-desc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fe-amt  { font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
.fe-save { color: var(--muted); font-size: var(--fs-sm); }
.fe-badge { font-size: var(--fs-xs); padding: 2px 8px; border-radius: 999px; white-space: nowrap; justify-self: end; }
.fe-badge.ok     { background: color-mix(in srgb, var(--success) 14%, var(--surface)); color: var(--success); }
.fe-badge.warn   { background: var(--warning-bg); color: var(--warning-text); }
.fe-badge.danger { background: var(--danger-bg); color: var(--danger-text); }
@media (max-width: 640px) {
  .fe-list li { grid-template-columns: auto 1fr; }
  .fe-desc { white-space: normal; }
  .fe-amt { text-align: left; }
  .fe-badge { justify-self: start; }
}

/* "Mark as paid" picker: a scrollable list of candidate transactions inside
   the modal, each a full-width quiet button. */
.reconcile-candidates { list-style: none; margin: var(--space-2) 0 0; padding: 0; max-height: 16rem; overflow-y: auto; }
.reconcile-candidates li { margin: 0; }
.reconcile-candidates .link {
  display: flex; gap: var(--space-2); align-items: baseline;
  width: 100%; text-align: left;
  padding: var(--space-2); border-radius: var(--radius-sm);
}
.reconcile-candidates .link:hover { background: var(--surface-3); }
.reconcile-candidates .rc-date { color: var(--muted); min-width: 4.5rem; }
.reconcile-candidates .rc-amt { font-weight: 600; min-width: 5.5rem; text-align: right; }
.reconcile-candidates .rc-desc { color: var(--text-2); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reconcile-cancel {
  display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-2);
  align-items: baseline; justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
}
.reconcile-cancel-note { color: var(--muted); font-size: var(--fs-sm); }
.reconcile-cancel-btn { color: var(--danger); font-weight: 600; }

/* Projection plots (Projections tab): one balance line chart per account / set. */
.plots { display: grid; gap: var(--space-3); margin: var(--space-3) 0; }
.plot-card {
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
}
.plot-head { display: flex; justify-content: space-between; align-items: baseline; }
.plot-label { font-weight: 600; }
.plot-remove {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 1.1rem; line-height: 1; padding: 0 var(--space-1);
}
.plot-remove:hover { color: var(--danger); }
.plot-figures { margin: var(--space-1) 0 var(--space-2); }
.projection-chart { width: 100%; height: auto; display: block; overflow: visible; }
.projection-chart .proj-line { fill: none; stroke: var(--primary); stroke-width: 2; }
.projection-chart .chart-grid { stroke: var(--border); stroke-width: 1; opacity: 0.6; }
.projection-chart .chart-axis { stroke: var(--border); stroke-width: 1; }
.projection-chart .chart-zero  { stroke: var(--muted); stroke-width: 1.25; }
.projection-chart .chart-start { stroke: var(--primary-soft); stroke-width: 1.25; stroke-dasharray: 5 3; }
.projection-chart .chart-ylabel,
.projection-chart .chart-xlabel { fill: var(--muted); font-size: 11px; }
.projection-chart .chart-ylabel-r { fill: var(--primary-soft); font-size: 11px; }
.projection-chart .chart-startlabel { fill: var(--primary-soft); font-size: 10px; font-style: italic; }

/* Pace Engine chart: expected (with tolerance band) vs actual, with a hover readout. */
.pace-chart { width: 100%; height: auto; display: block; overflow: visible; }
.pace-chart .chart-grid { stroke: var(--border); stroke-width: 1; opacity: 0.6; }
.pace-chart .chart-axis { stroke: var(--border); stroke-width: 1; }
.pace-chart .chart-ylabel, .pace-chart .chart-xlabel { fill: var(--muted); font-size: 11px; }
.pace-chart .pace-band { fill: var(--primary-soft); opacity: 0.16; stroke: none; }
.pace-chart .pace-expected { fill: none; stroke: var(--primary-soft); stroke-width: 1.75; stroke-dasharray: 5 3; }
.pace-chart .pace-actual { fill: none; stroke: var(--primary); stroke-width: 2.25; }
.pace-chart .pace-today { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; }
.pace-chart .pace-todaylabel { fill: var(--muted); font-size: 10px; }
.pace-chart .pace-hoverline { stroke: var(--muted); stroke-width: 1; opacity: 0.7; }
.pace-chart .pace-dot-exp { fill: var(--primary-soft); }
.pace-chart .pace-dot-act { fill: var(--primary); }
.pace-chart .pace-tipbox { fill: var(--surface); stroke: var(--border); }
.pace-chart .pace-tipdate { fill: var(--text); font-size: 11px; font-weight: 600; }
.pace-chart .pace-tipline { fill: var(--muted); font-size: 11px; }

/* Pace Engine card: chart + the four-part coach conversation + daily guidance. */
.pace-card {
  border: 1px solid var(--border); border-radius: 0.5rem;
  padding: var(--space-3); background: var(--surface);
}
.pace-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.pace-head h3 { margin: 0; }
.pace-status { font-size: 1.15rem; font-weight: 650; margin: var(--space-2) 0; }
.pace-status.on    { color: var(--success); }
.pace-status.under { color: var(--success); }
.pace-status.over  { color: var(--danger); }
.pace-unbudgeted {
  display: block; margin: var(--space-2) 0;
  padding: var(--space-2) var(--space-3);
  background: var(--warning-bg); color: var(--warning-text);
  border: 1px dashed var(--warning); border-radius: var(--radius-sm);
  font-size: var(--fs-sm); font-weight: 600; text-decoration: none;
}
.pace-unbudgeted:hover { background: var(--warning-bg-hover); color: var(--warning-text-hover); }
.tx-unbudgeted {
  margin-left: var(--space-2); font-size: var(--fs-xs); font-weight: 600;
  color: var(--warning-text); text-decoration: none; white-space: nowrap;
}
.tx-unbudgeted:hover { color: var(--warning-text-hover); text-decoration: underline; }
.pace-convo { position: relative; display: grid; gap: var(--space-2); margin: var(--space-2) 0; padding-right: 1.5rem; }
.pace-convo .pace-line { color: var(--text); display: flex; align-items: baseline; gap: var(--space-2); }
.pace-convo .pace-label {
  flex: none; align-self: flex-start;
  display: inline-block; min-width: 4.75rem; text-align: center;
  padding: 0.1rem 0.5rem;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--muted); font-size: var(--fs-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.03em;
}
.pace-convo .pace-text { flex: 1; }
.pace-convo > .info-tip { position: absolute; top: 0; right: 0; }
.pace-daily {
  display: flex; gap: var(--space-4); flex-wrap: wrap;
  margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--border);
}
.pace-daily .pace-kpi { display: flex; flex-direction: column; }
.pace-daily .pace-kpi .num { font-size: 1.25rem; font-weight: 650; }
.pace-daily .pace-kpi .lbl { color: var(--muted); font-size: 0.85rem; }
.pace-legend { display: flex; gap: var(--space-3); font-size: 0.8rem; color: var(--muted); }
.pace-legend .swatch { display: inline-block; width: 14px; height: 0; border-top-width: 3px; border-top-style: solid; vertical-align: middle; margin-right: 4px; }
.pace-legend .swatch.expected { border-top-style: dashed; border-top-color: var(--primary-soft); }
.pace-legend .swatch.actual { border-top-color: var(--primary); }
.pace-empty {
  border: 1px dashed var(--border); border-radius: 0.5rem;
  padding: var(--space-3); background: var(--surface); text-align: center;
}
.pace-empty h3 { margin-top: 0; }
.pace-empty .btn-primary { margin-top: var(--space-2); }

/* Budgets — the category table + the per-category editor modal */
/* Subcategory rows sit under their parent with a static left "grouping rail" +
   indentation — a non-interactive nesting cue (like indent guides / nested
   lists). Deliberately NOT a chevron/triangle, which reads as an expand toggle
   and wrongly suggests the row can be opened. Consecutive child rows' rails
   abut into one continuous line bracketing the group. */
.budgets-table .budget-child td:first-child {
  padding-left: var(--space-6);
  color: var(--muted);
  position: relative;
}
.budgets-table .budget-child td:first-child::before {
  content: "";
  position: absolute;
  left: var(--space-3);
  top: 0;
  bottom: 0;
  border-left: 2px solid var(--border-strong);
}
.budgets-table tr.needs-row td { background: var(--warning-bg); }
.budget-edit { text-align: left; }
.budget-set { color: var(--muted); }
/* The needs-a-budget flag: the same attention language as uncategorized. */
.needs-budget {
  background: var(--warning-bg);
  color: var(--warning-text);
  border: 1px dashed var(--warning);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
}
.needs-budget:hover { background: var(--warning-bg-hover); color: var(--warning-text-hover); }

/* A settable cell that pairs an action with a small trailing note (the
   uncategorized tolerance row's "$X/mo"). */
.budget-set-wrap { display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); }
/* Where a pre-filled amount came from, so "just Save" is obviously safe. */
.budget-suggest-note { margin: 0; color: var(--muted); font-size: var(--fs-xs); }

/* Deep-linked focus: when the user clicks "⚠ unbudgeted" on a transaction, the
   matching category row is scrolled into view and highlighted so it's an
   unmistakable attention grabber (a brass tint + left accent bar, plus a short
   pulse). The class is removed after the pulse; under reduced-motion the global
   rule drops the animation but the static tint + bar remain. */
.budgets-table tr.budget-focus td {
  background: color-mix(in srgb, var(--primary) 16%, var(--surface));
  animation: budget-focus-pulse 0.9s ease-out 2;
}
.budgets-table tr.budget-focus td:first-child {
  box-shadow: inset 3px 0 0 0 var(--primary);
}
@keyframes budget-focus-pulse {
  0%   { background: color-mix(in srgb, var(--primary) 42%, var(--surface)); }
  100% { background: color-mix(in srgb, var(--primary) 16%, var(--surface)); }
}

/* Budget-fit headline: monthly income vs. planned spending + what's left, and
   the income sources that feed it. */
.budget-summary {
  display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-6);
  justify-content: space-between; align-items: flex-start;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-2);
  padding: var(--space-3) var(--space-4);
  margin: var(--space-3) 0;
}
.fit-figures { display: flex; flex-wrap: wrap; gap: var(--space-5) var(--space-6); }
.fit-stat { display: flex; flex-direction: column; gap: 2px; }
.fit-stat .lbl { color: var(--muted); font-size: var(--fs-sm); }
.fit-stat .num { font-size: var(--fs-xl); font-weight: 650; font-variant-numeric: tabular-nums; }
.fit-sub { color: var(--muted); font-size: var(--fs-xs); }
.fit-leftover.ok .num  { color: var(--success); }
.fit-leftover.over .num { color: var(--danger-text); }
.fit-leftover.warn .num { color: var(--warning-text); }

.income-block { display: flex; flex-direction: column; gap: var(--space-1); min-width: 14rem; }
.income-head { display: flex; align-items: baseline; gap: var(--space-3); }
.income-head .lbl { color: var(--muted); font-size: var(--fs-sm); font-weight: 600; }
.income-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.income-item { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--fs-sm); }
.income-item .income-name { font-weight: 600; }
.income-item .income-amt { font-variant-numeric: tabular-nums; }
.income-item .income-actions { margin-left: auto; display: flex; gap: var(--space-2); }
/* Detected recurring deposits offered as income sources — quieter than the list above. */
.income-suggest { margin-top: var(--space-2); display: flex; flex-direction: column; gap: var(--space-1); }
.income-suggest > .lbl { color: var(--muted); font-size: var(--fs-xs); font-weight: 600; }
.income-suggest-item { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--fs-sm); }
.income-suggest-item .income-name { color: var(--muted); }
.income-suggest-item .income-amt { font-variant-numeric: tabular-nums; }
.income-suggest-item .income-actions { margin-left: auto; }

/* Completeness banner: what still needs a budget, or an all-covered confirmation. */
.budget-status {
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
  margin: var(--space-3) 0; font-size: var(--fs-sm);
}
.budget-status.warn { background: var(--warning-bg); color: var(--warning-text); }
.budget-status.ok   { background: var(--success-bg); color: var(--success); }
.budget-status-msg { margin: 0 0 var(--space-2); }
.budget-status-cats { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.status-chip {
  background: var(--surface); color: var(--warning-text);
  border: 1px dashed var(--warning); border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3); font-size: var(--fs-xs); font-weight: 600;
  cursor: pointer;
}
.status-chip:hover { background: var(--warning-bg-hover); color: var(--warning-text-hover); }
.status-chip .chip-amt { color: var(--muted); font-weight: 500; }

/* Savings (Budgets page): goals with derived balances. Tags ride the goal name;
   the meter shows progress toward an optional target; the surplus line carries a
   rolling goal's "move it along" suggestion; the backing note is the quiet
   arithmetic tying goal balances to real account money. */
.goal-tag {
  display: inline-flex; align-items: center;
  background: var(--surface-2); color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 0.05rem 0.45rem; margin-left: var(--space-2);
  font-size: var(--fs-xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap;
  vertical-align: middle;
}
.goal-meter {
  height: 4px; margin-top: var(--space-1);
  background: var(--surface-3); border-radius: var(--radius-pill); overflow: hidden;
}
.goal-meter-fill { height: 100%; background: var(--success); border-radius: var(--radius-pill); }
.goal-surplus { margin-top: var(--space-1); font-size: var(--fs-xs); }
.goal-match { margin-top: var(--space-1); font-size: var(--fs-xs); display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.goal-ledger { border-top: 1px solid var(--border); padding-top: var(--space-2); font-size: var(--fs-sm); }
.goal-ledger .lbl { color: var(--muted); font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.goal-ledger-list { list-style: none; margin: var(--space-1) 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.savings-backing, .savings-backing-warn { margin-top: var(--space-2); }
.savings-backing-warn { color: var(--warning-text); }
/* "Cover from savings" alternative inside the budget editor: an aside, set off
   like the drill control, offering the one-time allocation instead of a budget. */
.cover-savings {
  display: flex; flex-direction: column; gap: var(--space-1);
  padding: var(--space-2) 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.cover-savings-opt { align-self: flex-start; font-weight: 600; }

/* Uncategorized review nudge (Overview): a calm presence indicator that escalates
   to a warning once the review queue exceeds tolerance. */
.review-nudge {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3);
  border-radius: var(--radius-md); padding: var(--space-2) var(--space-3);
  margin: var(--space-3) 0; font-size: var(--fs-sm);
}
.review-nudge.info { background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); }
.review-nudge.warn { background: var(--warning-bg); color: var(--warning-text); border: 1px solid var(--warning); }
.review-nudge .review-msg { flex: 1; }
.review-nudge .review-link { margin-left: auto; font-weight: 600; white-space: nowrap; }

.budget-editor { display: flex; flex-direction: column; gap: var(--space-3); min-width: 18rem; }
.budget-editor input[type=number], .budget-editor select { width: 100%; }
/* Drill control: retarget the budget to the parent category (or back). A quiet,
   self-contained row above the fields — an aside, not a primary field. */
.budget-scope {
  display: flex; flex-direction: column; gap: 2px;
  padding-bottom: var(--space-2); border-bottom: 1px solid var(--border);
}
.budget-scope-link {
  align-self: flex-start; font-size: var(--fs-sm); font-weight: 600;
}
.budget-field { display: flex; flex-direction: column; gap: 2px; font-size: 0.8rem; color: var(--muted); }
.budget-when { display: flex; gap: var(--space-2); align-items: center; }
.budget-when select { flex: 1; }
/* Must outweigh `.budget-editor input[type=number] { width: 100% }` above, or
   the count swallows the row and the months/years select collapses invisible. */
.budget-editor input.budget-day, .budget-day { width: 5rem; flex: none; }
.add-plot {
  margin-top: var(--space-3); padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--border); border-radius: 0.5rem;
}
.add-plot h4 { margin: 0 0 var(--space-2); }
.add-plot-accounts {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); margin-bottom: var(--space-2);
}
.add-plot-accounts .checkbox {
  display: inline-flex; align-items: center; gap: var(--space-1); font-weight: normal;
}
.add-plot input[type=text] { margin-right: var(--space-2); }

/* New-user setup: the Overview "Get started" hero + progress checklist.

   Every setup surface — hero, checklist card, collapsed chip, setup bar —
   wears the same primary-tinted band with the 3px left accent, so they read
   as one object wherever it appears. They also share a view-transition-name:
   navigating between the Overview and a step's page morphs one surface into
   the other (router.cljs decides which navigations animate), making the
   guide→bar continuity literal. At most one of them is ever rendered at a
   time, which is what keeps the shared name valid. */
.setup-hero, .setup-card, .setup-chip, .setup-bar { view-transition-name: setup-guide; }
/* Slow enough to actually read as "the card became the bar" — the morph is
   the message. Duration goes on the whole named subtree (the box movement
   AND its content crossfade); the rest of the page keeps the default quick
   swap, which leaves the eye on the moving box. */
::view-transition-group(setup-guide),
::view-transition-image-pair(setup-guide),
::view-transition-old(setup-guide),
::view-transition-new(setup-guide) { animation-duration: 0.5s; }
::view-transition-group(setup-guide) {
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

.setup-hero {
  background: color-mix(in srgb, var(--primary) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 30%, var(--surface));
  border-left: 3px solid var(--primary);
  border-radius: 0.5rem;
  padding: var(--space-4) var(--space-5);
  margin: var(--space-3) 0 var(--space-5);
}
.setup-hero h3 { margin: 0 0 var(--space-2); font-size: 1.3rem; }
.setup-lede { margin: 0 0 var(--space-3); color: var(--text-2); max-width: 42rem; }
.setup-hero-cta { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.setup-secondary { font-size: var(--fs-sm); color: var(--muted); text-decoration: none; }
.setup-secondary:hover { color: var(--text); text-decoration: underline; }
.setup-trust { margin: var(--space-3) 0 0; font-size: var(--fs-xs); color: var(--muted); }

.setup-card {
  background: color-mix(in srgb, var(--primary) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 30%, var(--surface));
  border-left: 3px solid var(--primary);
  border-radius: 0.5rem;
  padding: var(--space-3) var(--space-4);
  margin: var(--space-3) 0 var(--space-5);
}
/* everything done: the success shift the bar's .done state uses */
.setup-card.setup-complete {
  background: color-mix(in srgb, var(--success) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--success) 30%, var(--surface));
  border-left-color: var(--success);
}
.setup-card-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2); }
.setup-card-head h3 { margin: 0; }
/* first-run orientation under the heading — points at the tether banner */
.setup-card-sub { margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--text-2); max-width: 44rem; }
.setup-collapse, .setup-dismiss {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: var(--fs-sm); padding: var(--space-1) var(--space-2);
}
.setup-collapse:hover, .setup-dismiss:hover { color: var(--text); text-decoration: underline; }

.setup-meter { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-2) 0 var(--space-3); }
.setup-meter-track { flex: 1; height: 6px; background: var(--surface-2); border-radius: var(--radius-pill); overflow: hidden; }
.setup-meter-fill { height: 100%; background: var(--success); border-radius: var(--radius-pill); transition: width 0.4s ease; }
.setup-meter-label { flex: none; font-size: var(--fs-xs); color: var(--muted); font-weight: 600; white-space: nowrap; }

.setup-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.setup-step { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-2); border-radius: var(--radius-sm); }
/* deeper wash than the card's own tint, so the emphasized row still reads */
.setup-step.next { background: color-mix(in srgb, var(--primary) 16%, var(--surface)); }
/* the step being worked right now (its bar is live on the other pages):
   returning to the Overview mid-step, this row echoes the bar's left accent
   so the eye lands where the user left off */
.setup-step.active {
  background: color-mix(in srgb, var(--primary) 16%, var(--surface));
  box-shadow: inset 3px 0 0 var(--primary);
}
/* the whole incomplete row taps through to its page — hover deepens past the
   .next/.active resting tint (higher specificity), so the affordance reads */
.setup-step.clickable { cursor: pointer; }
.setup-step.clickable:hover { background: color-mix(in srgb, var(--primary) 20%, var(--surface)); }
.setup-mark {
  flex: none; width: 1.4rem; height: 1.4rem; margin-top: 0.1rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill); font-size: var(--fs-xs); font-weight: 700;
  border: 2px solid var(--border-strong); color: transparent;
}
.setup-step.done .setup-mark { background: var(--success); border-color: var(--success); color: var(--surface); }
/* A step completing while the card is on screen: the ✓ pops once. .just-done
   is applied only to steps that flipped during this mount (views), so nothing
   pops just for logging in or navigating back. */
.setup-step.just-done .setup-mark { animation: setup-mark-pop 0.35s cubic-bezier(0.2, 0, 0, 1.4); }
@keyframes setup-mark-pop {
  0%   { transform: scale(0.4); }
  60%  { transform: scale(1.25); }
  100% { transform: scale(1); }
}
.setup-step-body { flex: 1; min-width: 0; }
.setup-step-title { font-weight: 600; display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.setup-step.done .setup-step-title { color: var(--muted); font-weight: 500; }
.setup-count {
  font-size: var(--fs-xs); font-weight: 700; color: var(--primary);
  /* a tint of --primary, not --primary-soft: soft is a stroke tone and
     doesn't contrast with primary text in the darker themes */
  background: color-mix(in srgb, var(--primary) 24%, var(--surface));
  border-radius: var(--radius-pill);
  padding: 0.05rem 0.45rem;
}
.setup-step-blurb { font-size: var(--fs-sm); color: var(--muted); margin-top: 0.1rem; }
.setup-substat { font-size: var(--fs-sm); color: var(--muted); margin-top: 0.1rem; }
.setup-substat a { font-weight: 600; }
/* Inline waiting spinner for the "your bank is preparing transactions" states
   (setup card + the banks table). Em-sized so it sits in running text. */
.setup-spin {
  display: inline-block;
  width: 0.8em; height: 0.8em;
  margin-right: 0.4em;
  vertical-align: -0.05em;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--primary) 30%, transparent);
  border-top-color: var(--primary);
  animation: setup-spin 0.8s linear infinite;
}
@keyframes setup-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .setup-spin { animation: none; }
  .setup-step.just-done .setup-mark { animation: none; }
}
/* The CTA-slot placeholder while the first transactions load — a bigger
   spinner where "Categorize →" will land, so the slot never reads as blank. */
.setup-wait {
  display: inline-flex; align-items: center; gap: 0.5em;
  font-size: var(--fs-sm); color: var(--muted); white-space: nowrap;
}
.setup-wait .setup-spin { width: 1.3em; height: 1.3em; margin-right: 0; }
/* "Next" label on the upcoming checklist row — the row tint alone doesn't
   survive a glance. Solid primary, unlike the tinted count pill beside it. */
.setup-next-tag {
  margin-left: 0.5em;
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--primary-text); background: var(--primary);
  border-radius: var(--radius-pill);
  padding: 0.1rem 0.5rem;
  vertical-align: 0.1em;
}
/* The pinned mini-guide: shown only while the setup card is scrolled out of
   view (setup-sticky). Same band vocabulary as the card/bar, docked under the
   sticky nav (inline top offset = measured nav height); z below the nav (45)
   so menus stay on top. No view-transition-name here on purpose. */
.setup-pin {
  position: fixed; left: 0; right: 0; margin: 0 auto;
  width: calc(100% - 2 * var(--space-4) - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
  max-width: calc(1100px - 2 * var(--space-4));
  z-index: 44;
  display: flex; align-items: center; gap: 0.6em;
  padding: var(--space-2) var(--space-4);
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  color: inherit; text-align: left; cursor: pointer;
  background: color-mix(in srgb, var(--primary) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 30%, var(--surface));
  border-left: 3px solid var(--primary);
  border-radius: 0 0 0.5rem 0.5rem;
  box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.35);
  animation: setup-pin-in 0.2s ease-out;
}
@keyframes setup-pin-in { from { transform: translateY(-40%); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .setup-pin { animation: none; } }
.setup-pin-count {
  font-size: 0.75rem; font-weight: 700;
  background: color-mix(in srgb, var(--primary) 24%, var(--surface));
  border-radius: var(--radius-pill);
  padding: 0.05rem 0.45rem;
}
.setup-pin-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.setup-pin-up { margin-left: auto; color: var(--primary); }
.setup-step-go { flex: none; align-self: center; }
.setup-go {
  display: inline-block; white-space: nowrap;
  font-size: var(--fs-sm); font-weight: 600; text-decoration: none;
  color: var(--primary); background: var(--surface); font-family: inherit; cursor: pointer;
  border: 1px solid var(--primary-soft); border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
}
.setup-go:hover { background: color-mix(in srgb, var(--primary) 22%, var(--surface)); }
.setup-go:disabled { opacity: 0.6; cursor: default; }
.setup-card-foot { margin-top: var(--space-2); text-align: right; }
.setup-complete .setup-step-title { color: var(--success); }

.setup-chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  margin: var(--space-2) 0 var(--space-4);
  background: color-mix(in srgb, var(--primary) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 30%, var(--surface));
  border-radius: var(--radius-pill); cursor: pointer;
  padding: var(--space-1) var(--space-3);
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-2);
}
.setup-chip:hover { border-color: var(--primary); color: var(--text); }

/* The setup bar: tethers a non-Overview page back to the onboarding flow while
   a checklist step is active. Not a full-bleed banner: a strip sized and
   aligned to main's content column — the same width, tint, and left accent as
   the checklist card, because it IS the card, condensed (the view transition
   above morphs one into the other). Shifts to success green when the step's
   work is done and the CTA becomes the reward link. */
.setup-bar {
  display: flex; align-items: center; gap: var(--space-2) var(--space-4); flex-wrap: wrap;
  width: calc(100% - 2 * var(--space-4) - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
  max-width: calc(1100px - 2 * var(--space-4));
  margin: var(--space-3) auto 0;
  padding: var(--space-2) var(--space-4);
  background: color-mix(in srgb, var(--primary) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 30%, var(--surface));
  border-left: 3px solid var(--primary);
  border-radius: 0.5rem;
  font-size: var(--fs-sm);
  /* stays pinned under the nav while the user works down a long page — the
     live count/meter and "Done for now" are the point of the bar (--nav-h is
     published by the nav's own ResizeObserver) */
  position: sticky; top: var(--nav-h, 56px);
  /* the band's tint is translucent-over-surface; opaque enough to pin over
     content. Transition makes the done-flip a felt moment, and (unlike a
     keyframe) fires only on live change — remounts stay quiet. */
  transition: background-color 0.3s ease, border-color 0.3s ease;
}
.setup-bar.done {
  background: color-mix(in srgb, var(--success) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--success) 30%, var(--surface));
  border-left-color: var(--success);
}
.setup-bar-step { font-weight: 700; white-space: nowrap; }
.setup-bar-line { color: var(--text-2); }
.setup-bar-line.ok { color: var(--success); font-weight: 600; }
/* the checklist card's meter, carried into the bar: same object, tighter fit */
.setup-bar .setup-meter { margin: 0; flex: none; }
.setup-bar .setup-meter-track { width: 7rem; flex: none; }
.setup-bar-actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-3); }
.setup-bar.done .setup-go { color: var(--success); border-color: var(--success); }
.setup-bar.done .setup-go:hover { background: color-mix(in srgb, var(--success) 22%, var(--surface)); }
.setup-bar-close { background: none; border: none; cursor: pointer; color: var(--muted); padding: 0 var(--space-1); }
.setup-bar-close:hover { color: var(--text); }

/* First-arrival coach: a popover anchored under the bar's actions, its caret
   pointing up at "Done for now →". Below modals (50), above page content.
   view-transition-name makes the bar a stacking context, so it needs its own
   z-index for the coach to sit over main's positioned content. */
.setup-bar { z-index: 40; }  /* sticky (above) is the positioning context */
/* Sections the setup CTAs scroll to must clear the sticky nav + setup bar. */
.setup-scroll-target { scroll-margin-top: calc(var(--nav-h, 56px) + 3.5rem); }
.setup-coach {
  position: absolute; top: calc(100% + 10px); right: var(--space-5);
  z-index: 39; width: 22rem; max-width: calc(100vw - 2rem);
  background: var(--surface); border: 1px solid var(--primary-soft);
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
  box-shadow: 0 8px 24px rgba(17, 24, 39, 0.18);
  font-size: var(--fs-sm);
}
.setup-coach::before {
  content: ""; position: absolute; top: -6px; right: 3.5rem;
  width: 10px; height: 10px; transform: rotate(45deg);
  background: var(--surface);
  border-left: 1px solid var(--primary-soft); border-top: 1px solid var(--primary-soft);
}
.setup-coach-what { margin: 0 0 var(--space-2); }
.setup-coach-hint { margin: 0 0 var(--space-2); color: var(--muted); }
.setup-coach-got {
  display: block; margin-left: auto;
  background: var(--surface); color: var(--primary);
  border: 1px solid var(--primary-soft); border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
  font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
}
.setup-coach-got:hover { background: color-mix(in srgb, var(--primary) 12%, var(--surface)); }

/* The active step's promised click-target. While the setup bar tethers a page,
   the exact control its CTA promised ("＋ Add income", "＋ New savings goal",
   the needs-a-budget banner, the Accounts page's add/import controls) carries
   a pulsing primary ring — the guide's words say what to do, this says where
   to click, without requiring the user to read the page. setup-target-props
   (views.cljs) removes it the moment the step completes or exits. */
@keyframes setup-target-pulse {
  0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 50%, transparent); }
  50%      { box-shadow: 0 0 0 7px color-mix(in srgb, var(--primary) 14%, transparent); }
}
/* A steady pulse alone is too easy to miss on arrival, so the target first
   lands with one impactful beat — a converging glow + a small pop, the same
   voice as the unlocked-feature halo — then settles into the pulse. Kept to a
   single element's box-shadow/transform (no ::after) since the target is an
   arbitrary button/link/banner that may own its own pseudo-elements. `land`
   ends exactly on the pulse's resting ring and the pulse is delayed to start
   there, so the handoff is seamless; the later-listed pulse wins the shared
   box-shadow once it begins. */
@keyframes setup-target-land {
  0%   { transform: scale(1.07);
         box-shadow: 0 0 0 6px  color-mix(in srgb, var(--primary) 46%, transparent),
                     0 0 26px 12px color-mix(in srgb, var(--primary) 34%, transparent); }
  55%  { transform: scale(1); }
  100% { transform: scale(1);
         box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 50%, transparent),
                     0 0 0 0 transparent; }
}
.setup-target {
  animation:
    setup-target-land  1.1s cubic-bezier(0.2, 0, 0, 1)   both,
    setup-target-pulse 1.8s 1.1s ease-in-out infinite;
}
/* Bare text links get corners for the ring; container targets (the banner)
   keep their own radius. */
button.setup-target, a.setup-target { border-radius: var(--radius-sm); }
@media (prefers-reduced-motion: reduce) {
  .setup-target {
    animation: none;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 50%, transparent);
  }
}

/* One-shot reward reveal when the user lands back on the Overview from a
   completed setup step. celebrate-ref runs the clock (reveal → hold → point);
   these rules run the motion. Every animation ends on the tile's natural
   resting state, so nothing jumps when celebrate-props' classes are pulled. */
.reward-reveal { position: relative; }

/* The homing halo — a glow that starts large and diffuse and converges onto
   the tile, then fades as the tile's own reveal takes the eye: big and strong
   enough to pull attention, gone before it competes with the content. Drawn on
   ::after (no reward tile needs an extra element, clips overflow, or owns an
   ::after of its own) as a pure box-shadow glow, so it never covers content. */
.reward-reveal::after {
  content: ""; position: absolute; inset: -14px; z-index: 4;
  border-radius: 0.85rem; pointer-events: none;
  animation: reward-halo 1.5s ease-out both;
}
@keyframes reward-halo {
  0%   { opacity: 0; transform: scale(1.4);
         box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 42%, transparent),
                     0 0 40px 14px color-mix(in srgb, var(--primary) 30%, transparent); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1);
         box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; }
}
/* the finale homes in the success voice */
.reward-reveal.reward-done::after { animation-name: reward-halo-done; }
@keyframes reward-halo-done {
  0%   { opacity: 0; transform: scale(1.4);
         box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 46%, transparent),
                     0 0 40px 14px color-mix(in srgb, var(--success) 32%, transparent); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1);
         box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; }
}

/* :pie — the spending pie draws with a clockwise sweep from 12 o'clock, the
   order the wedges are laid down. A conic mask whose angle is animated through
   a registered custom property (plain custom props don't interpolate). The
   360deg initial-value means an unsupported browser, or the moment the class is
   pulled, shows the full pie — never a blank one. */
@property --reward-sweep {
  syntax: "<angle>"; inherits: false; initial-value: 360deg;
}
.reward-reveal.reward-pie .pie {
  animation: reward-pie-sweep 1.4s ease-out both;
  /* conic 0deg is 12 o'clock, clockwise — the pie's own first-wedge start */
  -webkit-mask: conic-gradient(#000 0 var(--reward-sweep), transparent var(--reward-sweep));
          mask: conic-gradient(#000 0 var(--reward-sweep), transparent var(--reward-sweep));
}
@keyframes reward-pie-sweep {
  from { --reward-sweep: 0deg; }
  to   { --reward-sweep: 360deg; }
}
/* the legend rises in just behind the wedges */
.reward-reveal.reward-pie .pie-legend { animation: reward-rise 0.8s 0.45s ease-out both; }

/* :num — the unlocked figure (net worth) rises and settles into place */
.reward-reveal.reward-num .headline-num {
  animation: reward-num-rise 1.1s cubic-bezier(0.2, 0, 0, 1.1) both;
}
@keyframes reward-num-rise {
  0%   { opacity: 0; transform: translateY(0.3em) scale(0.96); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: none; }
}

/* :card — the tile lifts into place (the pace card, the budget-fit glance).
   The finale card doesn't lift: it carries the guide↔all-set view-transition
   morph plus its own meter sweep, and a transform here would fight them — the
   green halo alone marks it. */
.reward-reveal.reward-card:not(.reward-done) { animation: reward-rise 0.9s ease-out both; }
@keyframes reward-rise {
  from { opacity: 0.4; transform: translateY(10px); }
  to   { opacity: 1;   transform: none; }
}

/* :pace — the spending-pace chart is drawn left to right: a vertical reveal
   edge sweeps across so the planned and actual curves (and their tolerance
   band and 'today' marker) appear the way you'd draw them, in time order. A
   clip-path wipe rather than a stroke dash-offset, because the planned line is
   already stroke-dasharray'd — a pen-trace would fight its dashes; a wipe keeps
   every element cohesive and needs no per-line path measurement. The card
   itself eases in around it (opacity only — a transform would slide the SVG
   mid-wipe). Both settle on the natural resting state, so nothing snaps when
   the class is pulled. */
.reward-reveal.reward-pace { animation: reward-fade-in 0.7s ease-out both; }
@keyframes reward-fade-in {
  from { opacity: 0.55; }
  to   { opacity: 1; }
}
.reward-reveal.reward-pace .pace-chart {
  animation: reward-pace-wipe 1.35s cubic-bezier(0.35, 0, 0.15, 1) both;
}
@keyframes reward-pace-wipe {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

@media (prefers-reduced-motion: reduce) {
  .reward-reveal::after,
  .reward-reveal.reward-pie .pie,
  .reward-reveal.reward-pie .pie-legend,
  .reward-reveal.reward-num .headline-num,
  .reward-reveal.reward-pace,
  .reward-reveal.reward-pace .pace-chart,
  .reward-reveal.reward-card:not(.reward-done) { animation: none; }
  .reward-reveal.reward-pie .pie { -webkit-mask: none; mask: none; }
  .reward-reveal.reward-pace .pace-chart { clip-path: none; }
}

/* The all-set meter sweeps the last stretch to full — the bar the user filled
   all flow finally reads 100%, visibly. */
.setup-complete .setup-meter-fill { animation: setup-meter-finish 0.7s ease-out; }
@keyframes setup-meter-finish { from { width: 80%; } }

/* Beat 3: the callout that points back up to the setup banner once a reward has
   revealed and settled (setup-continue-bubble). Fixed just under the pinned
   mini-guide so the reward the user scrolled to stays put; fades in with a
   short pulse, then holds — static and dismissable — while the user dwells on
   the new feature. Its caret points up at the pin/guide. */
.setup-continue {
  position: fixed; z-index: 46;
  top: calc(var(--nav-h, 56px) + 3rem);
  left: 0; right: 0; margin: 0 auto;
  width: calc(100% - 2 * var(--space-4) - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
  max-width: 27rem;
  display: flex; align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--primary-soft); border-left: 3px solid var(--primary);
  border-radius: var(--radius-md);
  box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.45);
  animation: setup-continue-in 1.5s cubic-bezier(0.2, 0, 0, 1) both;
}
.setup-continue::before {
  content: ""; position: absolute; top: -6px; left: 1.4rem;
  width: 10px; height: 10px; transform: rotate(45deg);
  background: var(--surface);
  border-left: 1px solid var(--primary-soft); border-top: 1px solid var(--primary-soft);
}
@keyframes setup-continue-in {
  0%   { opacity: 0; transform: translateY(-12px); }
  30%  { opacity: 1; transform: translateY(0); }
  46%  { transform: translateY(-4px); }
  62%  { transform: translateY(0); }
  78%  { transform: translateY(-2px); }
  100% { opacity: 1; transform: translateY(0); }
}
.setup-continue-body {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 0.7rem;
  background: none; border: none; cursor: pointer; text-align: left;
  font: inherit; color: inherit;
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  border-radius: var(--radius-md) 0 0 var(--radius-md);
}
.setup-continue-body:hover { background: color-mix(in srgb, var(--primary) 8%, var(--surface)); }
.setup-continue-arrow {
  flex: none; color: var(--primary); font-weight: 700; font-size: 1.25rem; line-height: 1;
}
.setup-continue-text { font-size: var(--fs-sm); line-height: 1.35; color: var(--text-2); }
.setup-continue-next { color: var(--primary); font-weight: 600; }
.setup-continue-dismiss {
  flex: none; align-self: flex-start;
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: var(--fs-sm); padding: var(--space-2) var(--space-3);
}
.setup-continue-dismiss:hover { color: var(--text); }
@media (prefers-reduced-motion: reduce) { .setup-continue { animation: none; } }
/* While the nudge shows there's no data behind the tiles below it — render
   them as a dimmed, non-interactive preview so nothing competes with it. */
.dash-preview {
  opacity: 0.35;
  filter: grayscale(1);
  pointer-events: none;
  user-select: none;
}

/* The ⓘ that explains, in a tooltip, what "manual" means. */
.info-tip {
  cursor: help;
  color: var(--muted);
  font-weight: 700;
  font-style: normal;
}
.info-tip:focus-visible {
  outline: 2px solid var(--primary-soft);
  outline-offset: 1px;
  border-radius: var(--radius-pill);
}
/* The ⓘ as a button (movement-picker's clickable variant) — same look as the
   span, so it needs its chrome stripped. */
button.info-tip {
  background: none; border: none; padding: 0;
  font: inherit; font-weight: 700; line-height: 1;
  cursor: help;
  color: var(--muted);
}
button.info-tip:hover { color: var(--text-strong); background: none; }
.manual-tag .info-tip { color: inherit; }

/* A quieter, grey pill for a Plaid account whose bank was disconnected — kept
   as a historical snapshot, no longer syncing. */
.disconnected-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--surface-3);
  color: var(--muted);
  padding: 0.05rem 0.45rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  vertical-align: middle;
}
.disconnected-tag .info-tip { color: inherit; }

/* Manual-account creation is tucked behind a link, not shown by default. */
.manual-account-cta { margin-top: var(--space-5); }
.manual-account-cta .info-tip { margin-left: var(--space-2); }
.manual-account { margin-top: var(--space-5); }
.manual-account .muted-small { margin-top: var(--space-2); }

/* ---------------------------------------------------------------------------
   Confirm modal
   --------------------------------------------------------------------------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(17, 24, 39, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  outline: none;
  animation: backdrop-in 120ms ease-out;
}
@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }

.modal {
  width: min(440px, calc(100vw - 2rem));
  background: var(--surface);
  border-radius: var(--radius-md, 8px);
  box-shadow: 0 20px 50px rgba(17,24,39,0.25), 0 6px 16px rgba(17,24,39,0.12);
  padding: 1.25rem 1.25rem 1rem;
  animation: modal-in 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
.modal-title {
  margin: 0 0 var(--space-3);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-strong);
}
.modal-body { color: var(--text-2); font-size: var(--fs-sm); }
.modal-body p { margin: 0 0 0.5rem; }
.modal-body p:last-child { margin-bottom: 0; }
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.consent-required { color: var(--danger-text); font-size: var(--fs-sm); margin-top: var(--space-2); }
.consent-check { display: flex; gap: var(--space-2); align-items: flex-start; font-size: var(--fs-sm); margin-top: var(--space-3); }
.consent-check input { flex: 0 0 auto; margin-top: 3px; }

/* Buttons inside the modal: Cancel is the safe default, Delete carries the
   destructive signal via *text colour* rather than a screaming red fill — the
   button is a deliberate choice, not the row's main affordance. */
.btn-secondary {
  background: var(--surface);
  color: var(--text-strong);
  border: 1px solid var(--border-strong);
  padding: 0.45rem 0.95rem;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 500;
  cursor: pointer;
}
.btn-secondary:hover { background: var(--surface-3); }

.btn-danger {
  background: var(--surface);
  color: var(--danger-text);
  border: 1px solid var(--border-strong);
  padding: 0.45rem 0.95rem;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}
.btn-danger:hover {
  background: var(--danger-bg);
  border-color: var(--danger);
  color: var(--danger);
}
/* Primary (recommended) modal action — filled, unlike the outline-style
   secondary/danger buttons. Used for constructive confirms like "Restore". */
.btn-primary {
  background: var(--primary);
  color: var(--primary-text);
  border: 1px solid var(--primary);
  padding: 0.45rem 0.95rem;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}
.btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
.btn-secondary:focus-visible,
.btn-danger:focus-visible,
.btn-primary:focus-visible {
  outline: 2px solid var(--primary-soft);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   Account rows open a detail sheet on click; the disconnect lives there behind
   a two-step typed confirm so it can't fire by accident.
   --------------------------------------------------------------------------- */

.account-row { cursor: pointer; }
.account-row:hover { background: var(--surface-3); }
/* The account name is the keyboard-focusable trigger — a real button styled to
   read as the plain name until you hover or focus it. */
.account-name {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--text-strong);
  cursor: pointer;
  text-align: left;
}
.account-name:hover { text-decoration: underline; }
.account-name:focus-visible {
  outline: 2px solid var(--primary-soft);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.row-open-hint {
  color: var(--muted);
  font-size: var(--fs-lg);
  line-height: 1;
}

.synced-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--success-bg);
  color: var(--success);
  padding: 0.05rem 0.45rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  vertical-align: middle;
}

.modal.account-detail { width: min(460px, calc(100vw - 2rem)); }
.detail-list {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
}
.detail-list dt { color: var(--muted); }
.detail-list dd { margin: 0; color: var(--text-strong); text-align: right; }
.detail-action { margin-top: var(--space-4); }
.detail-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
/* Quiet red-text link that reveals the disconnect controls — text only, never
   a filled button (the deliberate-choice pattern). */
.danger-link {
  color: var(--danger-text);
  font-weight: 500;
  text-decoration: none;
}
.danger-link:hover { color: var(--danger); text-decoration: underline; }
.detail-danger {
  margin-top: var(--space-4);
  padding-left: var(--space-3);
}
.detail-danger p { margin: 0 0 var(--space-2); }
.detail-danger label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-sm);
}

/* ---------------------------------------------------------------------------
   Overview dashboard
   --------------------------------------------------------------------------- */

.dashboard h3 { margin: var(--space-5) 0 var(--space-2); }

/* Net-worth headline — the single big "where do I stand" number. */
.headline { text-align: center; padding: var(--space-4) 0 var(--space-2); }
.headline-label {
  color: var(--muted);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.headline-num {
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--success);
}
.headline-num.neg { color: var(--danger); }
.headline .muted-small { color: var(--muted); font-size: var(--fs-sm); }

/* Money sign convention — see docs/sign-convention.md.
   Direction (▲ richer / ▼ poorer / ⇄ transfer) and below-zero levels carry a
   shape glyph or the minus sign; color (green good / red bad / slate neutral)
   only reinforces, so the meaning survives without color. */
.nw, .lvl { font-variant-numeric: tabular-nums; white-space: nowrap; }
.nw-glyph { font-size: 0.82em; margin-right: 0.22em; }
.m-pos  { color: var(--success); }
.m-neg  { color: var(--danger); }
.m-flat { color: var(--neutral); }
/* Separator in a transfer's "Checking → Savings" account cell. */
.tx-arrow { color: var(--muted); }

/* Two responsive chart cards side by side; they stack on narrow screens. */
.chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: var(--space-4);
  margin: var(--space-3) 0;
}
.chart-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  padding: var(--space-4);
}
.chart-card h3 { margin: 0 0 var(--space-1); }
.chart-card .muted-small { color: var(--muted); font-size: var(--fs-sm); margin: 0 0 var(--space-3); }
.chart-card svg { max-width: 100%; height: auto; }

/* Spending-by-category pie + legend. */
.pie-wrap { display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap; }
.pie { flex: 0 0 auto; }
.pie-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1;
  min-width: 150px;
  font-size: var(--fs-sm);
}
.pie-legend li { display: flex; align-items: center; gap: var(--space-2); padding: 2px 0; }
.pie-legend .swatch { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }
.pie-legend .lg-label { flex: 1; color: var(--text-strong); }
.pie-legend .lg-val { color: var(--muted); font-variant-numeric: tabular-nums; }
.pie-legend .lg-pct { color: var(--muted); margin-left: var(--space-2); min-width: 2.6em; text-align: right; }

/* Assets-vs-liabilities bar labels (SVG <text>). */
.bar-val { font-size: var(--fs-sm); font-weight: 600; fill: var(--text-strong); }
.bar-lbl { font-size: var(--fs-sm); fill: var(--muted); }

.dash-section { margin-top: var(--space-4); }
.dash-counts { margin-top: var(--space-4); }

/* Transactions: the always-visible primary search row — a broad text search
   (spanning date / amount / account / category / description), the Show-all
   escape hatch, a summary of what's applied, and the Advanced-search toggle.
   The structured, per-field filters live in the collapsible .tx-filters panel. */
.tx-search-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-3) 0;
}
.tx-search {
  display: flex;
  align-items: stretch;
  flex: 1 1 24rem;
  min-width: min(100%, 16rem);
  max-width: 36rem;
}
.tx-search input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.tx-search-go {
  flex: 0 0 auto;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}
.tx-show-all { white-space: nowrap; }
.tx-filter-summary { color: var(--muted); font-size: var(--fs-sm); }
.tx-advanced-toggle { margin-left: auto; white-space: nowrap; }

/* Transaction filter bar */
.tx-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-end;
  margin: var(--space-3) 0;
}
.tx-filters label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-sm);
  color: var(--muted);
}
.tx-filters .link { align-self: flex-end; padding-bottom: var(--space-1); }
.tx-filters .check { flex-direction: row; align-items: center; gap: var(--space-1); color: var(--text-strong); }
.range-custom { display: inline-flex; gap: var(--space-1); align-items: center; }
.range-custom input { width: 4rem; }

/* Pending / Completed groups in the merged Transactions tab. */
.txn-group { margin-bottom: var(--space-5); }
.txn-group h3 { margin: var(--space-4) 0 var(--space-2); }

.equity-section { margin-top: var(--space-5); }

/* ===========================================================================
   THEMES
   Pick a look by setting data-theme on <html> (see index.html default +
   balance.client.events :apply-theme). Each block overrides the token set
   declared in :root (the default "Instrument" theme). Body font flows from
   --font-sans (html,body); the two rules below route the display + number
   faces to headings and money figures.
   =========================================================================== */

h1, h2, h3, .top-nav .brand, .auth-card h1 { font-family: var(--font-display); }

.headline-num, .kpi > div div, td.right, th.right, .nw, .lvl,
.cat-modal-amt, .coming-up .cu-amt, .coming-up .cu-date,
.pie-legend .lg-val, .pie-legend .lg-pct {
  font-family: var(--font-num);
}

/* ---- Clarity — daylight & indigo, Sora + Inter ---- */
[data-theme="clarity"] {
  --bg:#f7f8fa; --surface:#ffffff; --surface-2:#fafbfc; --surface-3:#eef2ff;
  --text:#1e293b; --text-strong:#0f172a; --text-2:#334155; --muted:#64748b;
  --border:#e8ebef; --border-subtle:#f1f3f6; --border-strong:#d6dbe2;
  --primary:#4f46e5; --primary-hover:#4338ca; --primary-soft:#6366f1; --primary-text:#ffffff;
  --danger:#dc2626; --danger-hover:#b91c1c; --danger-bg:#fee2e2; --danger-text:#b91c1c;
  --success:#16a34a; --neutral:#64748b;
  --nav-bg:#ffffff; --nav-border:#e8ebef; --nav-link:#64748b; --nav-active:#eef2ff;
  --nav-text:#0f172a; --nav-active-text:#3730a3;
  --warning:#f43f5e; --warning-bg:#fff1f2; --warning-bg-hover:#ffe4e6;
  --warning-text:#be123c; --warning-text-hover:#9f1239; --uncat-text:#be123c;
  --chart-1:#4f46e5; --chart-2:#06b6d4; --chart-3:#10b981; --chart-4:#f59e0b;
  --chart-5:#f43f5e; --chart-6:#8b5cf6; --chart-7:#3b82f6; --chart-8:#fb923c;
  --chart-9:#ec4899; --chart-10:#14b8a6;
  --font-sans:"Inter", system-ui, -apple-system, sans-serif;
  --font-display:"Sora", var(--font-sans);
  --font-num:"Inter", var(--font-sans);
}

/* ---- Bold — blackout & voltage, Archivo Black + Spline Mono (dark) ---- */
[data-theme="bold"] {
  color-scheme: dark;
  --bg:#0a0a0b; --surface:#141417; --surface-2:#1c1c21; --surface-3:#26262c;
  --text:#d4d4d8; --text-strong:#f5f5f4; --text-2:#b4b4bc; --muted:#8b8b94;
  --border:#26262c; --border-subtle:#1c1c21; --border-strong:#34343c;
  --primary:#d4ff3f; --primary-hover:#e4ff6e; --primary-soft:#b9e636; --primary-text:#0a0a0b;
  --danger:#ff5d4d; --danger-hover:#ff7a6e; --danger-bg:#3a1714; --danger-text:#ff8a7e;
  --success:#3ee089; --neutral:#8b8b94;
  --nav-bg:#0a0a0b; --nav-border:#26262c; --nav-link:#8b8b94; --nav-active:#1c1c21;
  --nav-text:#f5f5f4; --nav-active-text:#d4ff3f;
  --warning:#ff5d4d; --warning-bg:#2a1512; --warning-bg-hover:#3a1714;
  --warning-text:#ff8a7e; --warning-text-hover:#ffa99e; --uncat-text:#ff8a7e;
  --chart-1:#d4ff3f; --chart-2:#3ee089; --chart-3:#36d6e0; --chart-4:#7c8bff;
  --chart-5:#c77dff; --chart-6:#ff5d8f; --chart-7:#ff9f43; --chart-8:#ffe14d;
  --chart-9:#5ad1a0; --chart-10:#9b8cff;
  --font-sans:"Archivo", system-ui, -apple-system, sans-serif;
  --font-display:"Archivo Black", var(--font-sans);
  --font-num:"Spline Sans Mono", ui-monospace, monospace;
}

/* ---- Zen — paper, ink & sage, Fraunces + Inter ---- */
[data-theme="zen"] {
  --bg:#fafaf7; --surface:#ffffff; --surface-2:#f6f6f1; --surface-3:#efefe8;
  --text:#3d3d39; --text-strong:#232320; --text-2:#55554e; --muted:#8f8f87;
  --border:#ecece6; --border-subtle:#f4f4ef; --border-strong:#deded6;
  --primary:#5f6f57; --primary-hover:#4c5a46; --primary-soft:#8a9a82; --primary-text:#ffffff;
  --danger:#cf4436; --danger-hover:#b23a2e; --danger-bg:#f7e7e4; --danger-text:#b23a2e;
  --success:#2f9460; --neutral:#8f8f87;
  --nav-bg:#fafaf7; --nav-border:#ecece6; --nav-link:#8f8f87; --nav-active:#efefe8;
  --nav-text:#232320; --nav-active-text:#5f6f57;
  --warning:#b08a3a; --warning-bg:#f5efe2; --warning-bg-hover:#efe6d2;
  --warning-text:#8a6a22; --warning-text-hover:#6f5519; --uncat-text:#9a8f7a;
  --chart-1:#7a9a86; --chart-2:#5fa8a0; --chart-3:#7e93c0; --chart-4:#9fb46a;
  --chart-5:#b5786c; --chart-6:#a07fb0; --chart-7:#d0a85a; --chart-8:#6fb0a2;
  --chart-9:#c98fa0; --chart-10:#c79a5e;
  --font-sans:"Inter", system-ui, -apple-system, sans-serif;
  --font-display:"Fraunces", Georgia, serif;
  --font-num:"Inter", var(--font-sans);
}

/* ---- Inviting — cream, coral & teal, Plus Jakarta Sans (rounder) ---- */
[data-theme="inviting"] {
  --bg:#fbf6f0; --surface:#ffffff; --surface-2:#fff6ee; --surface-3:#ffeede;
  --text:#4a4456; --text-strong:#2e2a39; --text-2:#5c5468; --muted:#897f93;
  --border:#f0e6da; --border-subtle:#f7efe6; --border-strong:#e6d8c8;
  --primary:#ff8a5c; --primary-hover:#f4683a; --primary-soft:#ffb695; --primary-text:#ffffff;
  --danger:#e8553f; --danger-hover:#d23e2a; --danger-bg:#fde7e2; --danger-text:#c43a28;
  --success:#2bb3a3; --neutral:#897f93;
  --nav-bg:#ffffff; --nav-border:#f0e6da; --nav-link:#897f93; --nav-active:#ffe9dd;
  --nav-text:#2e2a39; --nav-active-text:#f4683a;
  --warning:#ff8a5c; --warning-bg:#ffe9dd; --warning-bg-hover:#ffdcc9;
  --warning-text:#f4683a; --warning-text-hover:#d2542c; --uncat-text:#f4683a;
  --chart-1:#ff8a5c; --chart-2:#2bb3a3; --chart-3:#ffc15e; --chart-4:#7c9eff;
  --chart-5:#e8736b; --chart-6:#b48ce0; --chart-7:#5ec9b4; --chart-8:#ffac7a;
  --chart-9:#f49ac0; --chart-10:#8fd17a;
  --font-sans:"Plus Jakarta Sans", system-ui, -apple-system, sans-serif;
  --font-display:"Plus Jakarta Sans", var(--font-sans);
  --font-num:"Plus Jakarta Sans", var(--font-sans);
  --radius-sm:10px; --radius-md:14px;
}

/* ---- Beautiful — pine, ivory & antique gold, Playfair + Inter ---- */
[data-theme="beautiful"] {
  --bg:#f6f2e9; --surface:#fffdf8; --surface-2:#fbf8f0; --surface-3:#f1e8d2;
  --text:#3a473f; --text-strong:#1c2b25; --text-2:#2c3a32; --muted:#7c857c;
  --border:#e6dfd0; --border-subtle:#f0ebdd; --border-strong:#d8cfba;
  --primary:#18352c; --primary-hover:#1f4438; --primary-soft:#b08d3f; --primary-text:#cba85a;
  --danger:#bf3b30; --danger-hover:#a8332a; --danger-bg:#f6e4e1; --danger-text:#a8332a;
  --success:#2f7d57; --neutral:#7c857c;
  --nav-bg:#f6f2e9; --nav-border:#e6dfd0; --nav-link:#7c857c; --nav-active:#f1e8d2;
  --nav-text:#1c2b25; --nav-active-text:#18352c;
  --warning:#b08d3f; --warning-bg:#f1e8d2; --warning-bg-hover:#e9dcc0;
  --warning-text:#8a6a22; --warning-text-hover:#6f5519; --uncat-text:#b08d3f;
  --chart-1:#18352c; --chart-2:#2f7d57; --chart-3:#b08d3f; --chart-4:#7a6a93;
  --chart-5:#a8453a; --chart-6:#3a6b80; --chart-7:#c08a4a; --chart-8:#6b8f6e;
  --chart-9:#9a7a2f; --chart-10:#4f7d6a;
  --font-sans:"Inter", system-ui, -apple-system, sans-serif;
  --font-display:"Playfair Display", Georgia, serif;
  --font-num:"Inter", var(--font-sans);
}
/* the signature net-worth figure earns the display serif; tables stay aligned in Inter */
[data-theme="beautiful"] .headline-num { font-family: var(--font-display); font-weight: 600; }

/* ---- Accessible — colour-vision-deficiency safe, blue/orange instead of green/red ----
   Tuned for the most common CVD (red–green: deutan/protan, ~8% of men) and
   tritan. Money direction uses blue (in) vs vermillion-orange (out) — a pairing
   that stays distinct under every common CVD where green vs red collapses. The
   minus sign / ▲▼⇄ glyphs still carry the meaning without colour (see
   docs/sign-convention.md); colour only reinforces. Chart palette is the
   Okabe–Ito qualitative set, designed to be CVD-distinguishable. Inter
   throughout for legibility. */
[data-theme="accessible"] {
  --bg:#f6f7f9; --surface:#ffffff; --surface-2:#fafbfc; --surface-3:#e8f1f8;
  --text:#1d2733; --text-strong:#0c121b; --text-2:#33414f; --muted:#5b6b7b;
  --border:#e4e8ed; --border-subtle:#eef1f5; --border-strong:#d2d9e1;
  --primary:#0072b2; --primary-hover:#005d92; --primary-soft:#56b4e9; --primary-text:#ffffff;
  --danger:#d55e00; --danger-hover:#b34e00; --danger-bg:#fcebdd; --danger-text:#a84800;
  --success:#0072b2; --neutral:#5b6b7b;
  --nav-bg:#ffffff; --nav-border:#e4e8ed; --nav-link:#5b6b7b; --nav-active:#e8f1f8;
  --nav-text:#0c121b; --nav-active-text:#005d92;
  --warning:#b8860b; --warning-bg:#fbf3da; --warning-bg-hover:#f5e9c4;
  --warning-text:#8a6500; --warning-text-hover:#6f5200; --uncat-text:#b34e00;
  --chart-1:#0072b2; --chart-2:#e69f00; --chart-3:#009e73; --chart-4:#cc79a7;
  --chart-5:#56b4e9; --chart-6:#d55e00; --chart-7:#c9a227; --chart-8:#8c5800;
  --chart-9:#004d7a; --chart-10:#6b7785;
  --font-sans:"Inter", system-ui, -apple-system, sans-serif;
  --font-display:"Inter", var(--font-sans);
  --font-num:"Inter", var(--font-sans);
}

/* ---- Nocturne — midnight navy & starlight, a calm dark mode, Sora + Inter ----
   The restful counterpart to Bold: a navigator's night sky, not a neon panel.
   Base is deep navy (#0f1722, not black — distinct from Bold's blackout), with
   layered slate surfaces for depth. Money direction stays legible on dark via
   aurora-teal (in) vs warm amber-coral (out); the periwinkle primary is the
   north star you steer by. Numbers in a mono "instrument readout". */
[data-theme="nocturne"] {
  color-scheme: dark;
  --bg:#0f1722; --surface:#16202e; --surface-2:#1b2738; --surface-3:#223046;
  --text:#c3cedb; --text-strong:#eef3f9; --text-2:#aab8c8; --muted:#7689a0;
  --border:#243246; --border-subtle:#1b2738; --border-strong:#324562;
  --primary:#7aa2e3; --primary-hover:#93b4ec; --primary-soft:#5b82c4; --primary-text:#0f1722;
  --danger:#f0876a; --danger-hover:#f59f86; --danger-bg:#3a2018; --danger-text:#f0a78f;
  --success:#4fc9a8; --neutral:#7689a0;
  --nav-bg:#0c121b; --nav-border:#243246; --nav-link:#7689a0; --nav-active:#1b2738;
  --nav-text:#eef3f9; --nav-active-text:#7aa2e3;
  --warning:#e0b15a; --warning-bg:#2e2616; --warning-bg-hover:#3a3019;
  --warning-text:#e8c06a; --warning-text-hover:#f0d088; --uncat-text:#e8c06a;
  --chart-1:#7aa2e3; --chart-2:#4fc9a8; --chart-3:#e0b15a; --chart-4:#c78ce0;
  --chart-5:#f0876a; --chart-6:#5bb3d4; --chart-7:#9bd17a; --chart-8:#e08ab0;
  --chart-9:#6f8fd4; --chart-10:#d4b86a;
  --font-sans:"Inter", system-ui, -apple-system, sans-serif;
  --font-display:"Sora", var(--font-sans);
  --font-num:"Spline Sans Mono", ui-monospace, monospace;
}
/* the one accessory: the net-worth headline glows like a star read off the dark
   — display face + a faint halo in its own colour (teal when positive). */
[data-theme="nocturne"] .headline-num {
  font-family: var(--font-display); font-weight: 600; letter-spacing: -0.01em;
  text-shadow: 0 0 26px rgba(79,201,168,0.40);
}
[data-theme="nocturne"] .headline-num.neg { text-shadow: 0 0 26px rgba(240,135,106,0.40); }

/* ---- Theme picker (Profile › Appearance) ---- */
.theme-options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.theme-option {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-2);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  color: var(--text);
  text-align: left;
}
.theme-option:hover { border-color: var(--primary); }
.theme-option.active {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px var(--primary);
}
.theme-swatch {
  display: block;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg);
}
.theme-swatch .tsw-bar { display: block; height: 16px; background: var(--nav-bg); }
.theme-swatch .tsw-dots { display: flex; gap: 6px; padding: 8px; }
.theme-swatch .tsw-dots i { width: 15px; height: 15px; border-radius: 50%; display: inline-block; }
.theme-swatch .tsw-dots .d1 { background: var(--primary); }
.theme-swatch .tsw-dots .d2 { background: var(--success); }
.theme-swatch .tsw-dots .d3 { background: var(--danger); }
.theme-name { font-size: var(--fs-sm); font-weight: 600; }
.theme-check { color: var(--primary); }

/* Zen leans on light weights even for headings — part of its calm. */
[data-theme="zen"] h1, [data-theme="zen"] h2, [data-theme="zen"] h3 { font-weight: 400; }
