/* ── The [hidden] guard ───────────────────────────────────────────────────────
   `hidden` is ONLY the UA stylesheet's `display: none`, which loses to any
   author `display` at all — and this file sets a bare `display: inline-flex` on
   every `button`, `display: flex` on a lot of layout classes, and so on. The
   consequence is that an element written as `<button … hidden>` renders
   permanently, which is a silent bug every single time: the markup says hidden,
   the DOM says hidden, and it is on screen. It cost the Portfolio Value card a
   "Reset zoom" button floating over a chart that had never been zoomed.

   This had already been patched seven times element by element further down
   (`[data-wtab][hidden]`, `.split-row-value[hidden]`, `.filter-fab[hidden]`,
   `.calendar-day-sheet[hidden]`, `.calendar-install-hint[hidden]`,
   `.calendar-offline-save[hidden]`, `.calendar-time-slot[hidden]`) — each one
   found the same way, by someone noticing a thing on screen that should not be.
   One rule at the top ends the whole class of bug, so a new `[hidden]` element
   needs no CSS of its own. Those seven are left in place: they are now
   redundant rather than wrong, and deleting them is churn for no gain.

   `!important` is load-bearing — without it any author `display` still wins.
   Nothing may style a `[hidden]` element back into view; use a class for that. */
[hidden] { display: none !important; }

/* ── Theme Tokens ─────────────────────────────────────────────────────────── */

/* ── Premium redesign, stage 1: tokens ───────────────────────────────────
   Two themes only — "Blue" (internal key "orange") is retired; see the
   theme-switcher JS in each layout for the migration that coerces anyone
   with it saved onto dark. Same token NAMES as before (every component in
   this 8000+ line file references these), only the VALUES change, so this
   is a re-paint, not a rearchitecture. Every text/accent pair below is
   hand-verified via the WCAG relative-luminance formula against
   --bg-row-hover specifically (the darkest realistic surface text lands
   on), matching the methodology already established here — sizing against
   --bg-base is what caused the original dark-theme failure documented
   below. Re-run the full audit script (see Theme Tokens notes elsewhere in
   this file) before shipping stage 3's component pass. */

:root,
[data-theme="light"] {
  /* Warm paper, not stark white — reads calmer, less glare. */
  --bg-base: #F5F3EF;
  --bg-surface: #FFFFFF;
  /* --bg-elevated and --bg-row-alt were originally the same value — sticky
     table headers and even-numbered zebra rows rendered identically, so a
     header didn't visually separate from a striped row, and the plain jump
     from white straight to the elevated tone read as too heavy for a zebra
     stripe. Four distinct steps now: row-alt is a near-white whisper of
     warmth, row-hover sits between it and elevated, elevated stays the
     header/dropdown/popover tone. */
  --bg-elevated: #EDEAE3;
  --bg-row: #FFFFFF;
  --bg-row-alt: #F7F5F1;
  --bg-row-hover: #EAE6DC;
  --accent: #4A4E9E;
  --accent-dim: rgba(74, 78, 158, 0.09);
  --accent-amber: #34378A;     /* 8.14:1 on --bg-row-hover */
  /* Darker than a "plain" red would be if picked in isolation — the warm,
     darker paper surface here needs more weight than the old cool #C62828
     had, which measured 4.23:1 against the (now-lighter) --bg-row-hover. */
  --danger: #B02020;           /* 5.46:1 on --bg-row-hover */
  --text-primary: #1C1B1A;     /* soft ink, not pure black */
  --text-secondary: #4A453F;   /* 7.60:1 on --bg-row-hover */
  --text-muted: #625D57;       /* 5.22:1 on --bg-row-hover (spec's #6B6660 measured 4.28 against the original darker hover tone, fails) */
  --calendar-weekend: #B33057; /* 4.85:1 on --bg-row-hover */
  --positive: #2A6E2E;         /* 4.99:1 on --bg-row-hover */
  --on-accent: #FFFFFF;        /* 7.30:1 on --accent */
  --border: rgba(20, 20, 20, 0.08);
  --border-accent: rgba(74, 78, 158, 0.30);
  /* Light is the one theme allowed real shadows — they read naturally on a
     light surface, unlike dark. Soft and low-contrast, never harsh. */
  --shadow-deep: 0 1px 2px rgba(20, 20, 20, 0.04), 0 8px 24px rgba(20, 20, 20, 0.06);
  --radius: 6px;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%23625D57' d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
}

[data-theme="dark"] {
  /* Never pure #000 — depth comes from this surface ladder (~4-6% lighter
     per step) plus hairline borders, not drop shadows, which barely read
     on a dark background. */
  --bg-base: #0B0C0E;
  --bg-surface: #141518;
  --bg-elevated: #1B1D21;
  --bg-row: #141518;
  --bg-row-alt: #1B1D21;
  --bg-row-hover: #232629;
  --accent: #5E6AD2;
  --accent-dim: rgba(94, 106, 210, 0.14);
  --accent-amber: #8CA5FF;     /* 6.47:1 on --bg-row-hover */
  --danger: #FF7B72;           /* 6.03:1 on --bg-row-hover */
  --text-primary: #F5F6F7;
  --text-secondary: #B4B8BF;   /* 7.63:1 on --bg-row-hover */
  --text-muted: #8A8F98;       /* 4.68:1 on --bg-row-hover */
  --calendar-weekend: #FF8FAB; /* unchanged hue, still clears AA on this surface tier */
  --positive: #7FC97A;
  /* This accent is closer to mid-brightness than the old #58A6FF, so unlike
     the theme it replaces, near-black text on it fails (4.17:1) — white
     clears it instead, if only just (4.70:1). */
  --on-accent: #FFFFFF;
  --border: rgba(255, 255, 255, 0.09);
  --border-accent: rgba(94, 106, 210, 0.32);
  /* Not literally "none" yet — that needs every shadow-consuming component
     (dialogs, dropdowns, KPI cards) rebuilt on hairline borders first,
     which is stage 3's job. This is a placeholder step down, not the
     final state the brief describes. */
  --shadow-deep: 0 1px 2px rgba(0, 0, 0, 0.4);
  --radius: 6px;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%238A8F98' d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
}

/* Font tokens — theme-independent. Applied everywhere the app already used
   Cormorant Garamond / DM Sans as a literal font-family (both still sit in
   the fallback chain, so nothing breaks if the CDN request for the new
   families ever fails). Wordle's on-screen keyboard keys are the one
   deliberate holdout — a 12-key game control, not editorial prose, and not
   worth an unverified font swap. */
:root {
  --font-display: "Fraunces", "Cormorant Garamond", serif;
  --font-body: "Hanken Grotesk", "DM Sans", sans-serif;

  /* 8px base spacing scale (4px half-step). Registered for future use —
     not yet threaded through the ~9000 existing declarations that use
     hand-picked px values, which would need a real per-component pass to
     verify nothing's layout depends on today's exact numbers. */
  --space-1: 4px;   --space-2: 8px;   --space-3: 12px;  --space-4: 16px;
  --space-5: 20px;  --space-6: 24px;  --space-8: 32px;  --space-10: 40px;
  --space-12: 48px; --space-16: 64px; --space-24: 96px; --space-32: 128px;
}

/* ── Reset ────────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  min-height: 100%;
}

body {
  font-family: var(--font-body);
  background: var(--bg-base);
  color: var(--text-primary);
  font-size: 16px;
  line-height: 1.5;
  transition: background 0.25s ease, color 0.25s ease;
  overflow-x: hidden;
}

/* ── Typography ───────────────────────────────────────────────────────────── */

h1, h2, h3 { margin-top: 0; }

h1,
.page-head h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 2rem;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin: 0;
  color: var(--text-primary);
}

h2 {
  font-family: var(--font-display);
  font-weight: 600;
}

h3 {
  font-family: var(--font-display);
  font-weight: 600;
}

/* ── Layout ───────────────────────────────────────────────────────────────── */

.container {
  max-width: 100%;
  margin: 0 auto;
  padding: 28px 32px;
  min-height: calc(100vh - 52px);
}

/* ── Header & Nav ─────────────────────────────────────────────────────────── */

.main-header {
  background: var(--bg-surface);
  border-bottom: 2px solid var(--border-accent);
  height: 54px;
  box-shadow: 0 2px 16px rgba(21, 101, 192, 0.08);
  position: sticky;
  top: 0;
  z-index: 100;
  animation: navReveal 0.3s ease;
  transition: background 0.25s ease, border-color 0.25s ease;
}

.nav-container {
  height: 100%;
  max-width: 100%;
  margin: 0 auto;
  padding: 0 28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.logo strong {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.2;
  color: var(--accent);
  letter-spacing: -0.01em;
}

.nav-links {
  list-style: none;
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0;
  gap: 4px;
  flex: 1;
}

.nav-links a {
  color: var(--text-muted);
  text-decoration: none;
  padding: 5px 12px;
  border-radius: 4px;
  border: 1px solid transparent;
  transition: all 0.18s ease;
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0.04em;
  font-size: 0.82rem;
  text-transform: uppercase;
}

.nav-links a:hover {
  color: var(--text-primary);
  background: var(--bg-elevated);
}

.nav-links a.active {
  background: var(--accent-dim);
  color: var(--accent);
  border: 1px solid var(--border-accent);
}

/* ── Nav Dropdown ─────────────────────────────────────────────────────────── */

.nav-dropdown-item { position: relative; }

.nav-dropdown { display: block; }

.nav-dropdown-toggle {
  list-style: none;
  color: var(--text-muted);
  padding: 5px 12px;
  border-radius: 4px;
  border: 1px solid transparent;
  transition: all 0.18s ease;
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0.04em;
  font-size: 0.82rem;
  text-transform: uppercase;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
  user-select: none;
  white-space: nowrap;
}
.nav-dropdown-toggle::-webkit-details-marker { display: none; }
.nav-dropdown-toggle::marker { display: none; }
.nav-dropdown-toggle::after {
  content: '';
  display: inline-block;
  width: 0;
  height: 0;
  border-left: 3px solid transparent;
  border-right: 3px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.6;
  flex-shrink: 0;
}

.nav-dropdown-toggle:hover {
  color: var(--text-primary);
  background: var(--bg-elevated);
}

.nav-dropdown-toggle.active {
  background: var(--accent-dim);
  color: var(--accent);
  border: 1px solid var(--border-accent);
}

.nav-dropdown-menu {
  list-style: none;
  margin: 2px 0 0;
  padding: 4px;
}

.nav-dropdown-menu a {
  display: block;
  padding: 7px 12px;
  color: var(--text-muted);
  text-decoration: none;
  border-radius: 4px;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: all 0.18s ease;
  white-space: nowrap;
}
.nav-dropdown-menu a:hover { background: var(--bg-elevated); color: var(--text-primary); }
.nav-dropdown-menu a.active { background: var(--accent-dim); color: var(--accent); }

@media (min-width: 769px) {
  .nav-dropdown-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    min-width: 160px;
    z-index: 200;
    box-shadow: 0 4px 16px rgba(0,0,0,0.12);
    padding: 4px;
  }
}

/* ── Theme Switcher ───────────────────────────────────────────────────────── */

.theme-switcher {
  display: flex;
  gap: 4px;
  align-items: center;
  padding: 3px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.theme-btn {
  background: none;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 4px 9px;
  color: var(--text-muted);
  min-height: auto;
  box-shadow: none;
  transition: all 0.18s ease;
}

.theme-btn:hover {
  color: var(--text-primary);
  background: var(--bg-surface);
  transform: none;
}

.theme-btn.active {
  background: var(--accent);
  color: var(--bg-base);
  box-shadow: none;
  transform: none;
}

/* ── Mobile Nav ───────────────────────────────────────────────────────────── */

.nav-drawer {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 1;
}

/* On a phone the nav is reached from a floating button at the BOTTOM of the
   screen — the same affordance and thumb position as the filter FAB on the
   transaction sheet and dashboards, offset to the left so the two never
   collide — which slides the drawer in from the left edge. This replaced an
   in-header hamburger: a menu button in a 54px bar at the top of a phone is
   the hardest control on the page to reach one-handed.

   Geometry is unconditional and only `display` is media-gated, so Wordle —
   which has no header nav and wants the button at every width — turns it on
   with .nav-fab--always instead of restating the whole rule.

   z-index sits BELOW the day sheet (900) and the mobile filter sheet (1000) so
   any open overlay covers it, and is lifted above the drawer only while the
   drawer is open, so the ✕ it turns into stays tappable. */
.nav-fab {
  display: none;
  position: fixed;
  /* Centred, not bottom-left: dead centre is the easiest point on a phone to
     reach with either thumb, and it is where the filter button already was. The
     two are paired around the centre when a page has both (see below). */
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(18px + env(safe-area-inset-bottom));
  z-index: 880;
  /* Matches .filter-fab exactly — they sit side by side, so a 52px circle next
     to a 56px one reads as a mistake rather than as a hierarchy. The padding
     sets the hamburger bar width and scales with it. */
  width: 56px;
  height: 56px;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  padding: 0 16px;
  min-height: 0;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);
  cursor: pointer;
}

.nav-fab span {
  display: block;
  width: 100%;
  height: 2px;
  background: #fff;
  border-radius: 2px;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

/* Both must restate translateX — transform is one property, so a bare scale()
   here would drop the centring and jump the button half its width to the right
   on every press. */
.nav-fab:hover  { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28); transform: translateX(-50%); }
.nav-fab:active { transform: translateX(-50%) scale(0.94); }
.nav-fab--always { display: inline-flex; }

/* ── The menu/filter pair ───────────────────────────────────────────────────
   On a page with both, they sit side by side straddling the centre rather than
   one being exiled to a corner. The offset is margin-left, NOT transform: the
   transform slot is already carrying the centring and the :active scale, and a
   fourth thing competing for it is how these end up in the wrong place.

   :has() on <body> because the two buttons are not siblings — .nav-fab is a
   direct child of <body> and .filter-fab lives inside the page markup — so no
   combinator reaches from one to the other. :not([hidden]) matters because the
   dashboard hides its filter button while data loads, and the menu should not
   sit 34px off-centre next to nothing. */
body:has(.filter-fab:not([hidden])) .nav-fab    { margin-left: -34px; }
body:has(.filter-fab:not([hidden])) .filter-fab { margin-left: 34px; }

/* The transaction sheet lifts the filter button when the bulk-action bar slides
   up. The menu button has to go with it or the pair sits at two heights. */
body:has(#bulk-bar.is-visible) .nav-fab { bottom: calc(96px + env(safe-area-inset-bottom)); }
.nav-fab--open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-fab--open span:nth-child(2) { opacity: 0; }
.nav-fab--open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
body.nav-open .nav-fab { z-index: 1250; }

.nav-drawer-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1100;
  background: rgba(0, 0, 0, 0.28);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s ease;
}

/* Wordle's pair are children of <body>, not of a header, so they order against
   each other in the ROOT stacking context and keep the four-figure z-indexes.
   Two classes deep so the phone rule's `z-index: 1` (which is scoped to the
   in-header case) can't win over it. */
.nav-drawer-backdrop.nav-drawer-backdrop--always { display: block; z-index: 1100; }
.nav-drawer-backdrop.is-open { opacity: 1; visibility: visible; }

@media (max-width: 768px) {
  .nav-container {
    padding: 0 16px;
    flex-wrap: wrap;
    height: auto;
    min-height: 54px;
  }

  /* The header stays sticky, and while the drawer is open the WHOLE header is
     lifted above the page. This is the only way the drawer can cover the page:
     it lives inside the header (on desktop it IS the header nav) and a sticky
     element is ALWAYS its own stacking context, so a z-index on the drawer
     alone is capped at the header's 100 — the day sheet (900), the filter sheet
     (1000) and the calendar FAB (500) would all paint straight over it.
     Nothing else is ever open at the same time, so lifting the header is safe.
     Portaling the drawer to <body> instead is exactly what the
     data-portal-to-body trap warns against, since on desktop it sits inline. */
  body.nav-open .main-header { z-index: 1200; }

  .nav-fab { display: inline-flex; }
  .nav-drawer-backdrop { display: block; }

  /* Off-canvas panel over the page, rather than an accordion that pushes the
     header taller. Bottom padding clears the button, which floats above the
     drawer as a ✕ while it's open.

     z-index 2 against the backdrop's 1: both are children of the header, so
     these order them WITHIN the header's own stacking context. That's why the
     backdrop is rendered as the drawer's sibling inside <header> rather than at
     the end of <body> — the pair can't be split across two stacking contexts,
     and being inside means the backdrop dims the header bar too instead of
     leaving a bright strip across the top. */
  .nav-drawer {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 2;
    width: min(84vw, 300px);
    display: flex;
    flex: none;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    padding: calc(14px + env(safe-area-inset-top)) 0 calc(84px + env(safe-area-inset-bottom));
    background: var(--bg-surface);
    border-right: 1px solid var(--border);
    box-shadow: 6px 0 28px rgba(0, 0, 0, 0.22);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform 0.24s ease, visibility 0.24s ease;
  }

  /* display is restated so that nothing which sets `display: none` on the
     drawer for the closed state can leave it stuck invisible when opened. */
  .nav-drawer.nav-drawer--open {
    display: flex;
    transform: translateX(0);
    visibility: visible;
  }

  .nav-drawer-backdrop { z-index: 1; }

  .nav-links {
    flex-direction: column;
    gap: 2px;
    width: 100%;
    flex: none;
  }

  .nav-links li { width: 100%; }

  .nav-links a {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 14px;
    min-height: 44px;
    font-size: 0.9rem;
  }

  /* "Mappings" / "Other Apps" (<summary> dropdown toggles) must match the
     plain drawer links exactly — same font-size, padding, height, inset. */
  .nav-links .nav-dropdown { width: 100%; }

  .nav-links .nav-dropdown-toggle {
    width: 100%;
    padding: 10px 14px;
    min-height: 44px;
    font-size: 0.9rem;
    justify-content: space-between; /* caret sits at the right edge of the row */
  }

  .nav-links .nav-dropdown-toggle::after {
    transition: transform 0.18s ease;
  }

  .nav-links .nav-dropdown[open] > .nav-dropdown-toggle::after {
    transform: rotate(180deg);
  }

  /* Sub-items expand inline in the drawer, indented under their toggle */
  .nav-links .nav-dropdown-menu {
    margin: 0;
    padding: 0 0 4px;
  }

  .nav-links .nav-dropdown-menu a {
    padding-left: 28px;
  }

  .nav-user {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    border-top: 1px solid var(--border);
    margin-top: 4px;
    margin-right: 0;
  }

  .theme-switcher {
    margin: 4px 14px;
    align-self: flex-start;
  }

  .container {
    padding: 16px;
  }
  .mappings-sticky-bar { padding-left: 16px; padding-right: 16px; margin-top: -16px; margin-left: -16px; margin-right: -16px; }
}

/* ── Alerts ───────────────────────────────────────────────────────────────── */

.alert {
  padding: 10px 14px;
  margin-bottom: 16px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  font-size: 0.9rem;
}

.alert-success {
  background: #1a6e2e;
  color: #ffffff;
  border-color: #1a6e2e;
}

.alert-danger {
  background: #991b1b;
  color: #ffffff;
  border-color: #991b1b;
}

/* Flash toasts — fixed overlay so they never push content down */
.alert-toast {
  position: fixed;
  top: 62px;
  right: 20px;
  z-index: 200;
  max-width: 420px;
  margin-bottom: 0;
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
  animation: toastIn 0.2s ease, toastOut 0.4s ease 4s forwards;
}

@keyframes toastIn {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes toastOut {
  from { opacity: 1; }
  to   { opacity: 0; pointer-events: none; }
}

/* ── Links ────────────────────────────────────────────────────────────────── */

a { color: var(--accent); }

/* ── Buttons ──────────────────────────────────────────────────────────────── */

.button,
.btn,
input[type="submit"],
button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, background 0.18s ease, color 0.18s ease, filter 0.18s ease;
  min-height: 38px;
  padding: 8px 16px;
  font-size: 0.84rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  background: var(--bg-elevated);
  color: var(--text-primary);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
  font-family: var(--font-body);
}

.button:hover,
.btn:hover,
input[type="submit"]:hover,
button:hover {
  border-color: var(--border-accent);
  box-shadow: 0 3px 12px var(--accent-dim);
  transform: translateY(-1px);
}

.cleanup-toolbar .button.secondary {
  background: var(--accent);
  color: var(--bg-base);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.84rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 9px 22px;
  border-color: transparent;
  box-shadow: 0 2px 12px var(--accent-dim);
}

.cleanup-toolbar .button.secondary:hover {
  filter: brightness(1.1);
  box-shadow: 0 4px 20px var(--accent-dim);
  transform: translateY(-1px);
}

.cleanup-toolbar .button.secondary:active {
  transform: translateY(0);
  box-shadow: none;
}

.cleanup-page .btn.btn-success,
.cleanup-page .btn-success,
.cleanup-page .btn-save {
  background: var(--accent);
  color: var(--bg-base);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.86rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 10px 26px;
  border: none;
  border-radius: var(--radius);
  box-shadow: 0 3px 16px var(--accent-dim);
}

.cleanup-page .btn.btn-success:hover,
.cleanup-page .btn-success:hover,
.cleanup-page .btn-save:hover {
  filter: brightness(1.1);
  box-shadow: 0 5px 24px var(--accent-dim);
  transform: translateY(-1px);
}

.button.success {
  background: var(--accent);
  color: var(--bg-base);
  font-weight: 700;
  border-color: transparent;
}

.button.danger {
  background: rgba(180, 44, 26, 0.1);
  color: var(--danger);
  border: 1px solid rgba(180, 44, 26, 0.28);
}

.button.tiny {
  padding: 5px 9px;
  font-size: 0.78rem;
}

/* ── Search / Filter Bar ──────────────────────────────────────────────────── */

.search-form {
  display: flex;
  gap: 16px;
  align-items: center;
  flex-wrap: wrap;
}

.search-form .search-input,
.search-form .form-control,
.search-form .form-select {
  width: auto;
}

/* ── Cleanup Toolbar ──────────────────────────────────────────────────────── */

.cleanup-toolbar {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 18px;
  margin-bottom: 8px;
}

/* ── Labels & Utility Text ────────────────────────────────────────────────── */

.label-block {
  display: block;
  margin-bottom: 6px;
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.text-muted,
small.text-muted {
  color: var(--text-muted);
  font-size: 0.84rem;
  font-style: normal;
  letter-spacing: 0.01em;
}

/* ── Form Inputs ──────────────────────────────────────────────────────────── */

.search-input,
.form-control,
.form-select,
.inline-input,
input[type="text"],
input[type="number"],
input[type="date"],
select,
textarea {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 0.92rem;
  padding: 9px 12px;
  width: 100%;
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease, color 0.18s ease;
}

.search-input::placeholder,
.form-control::placeholder,
.inline-input::placeholder,
input[type="text"]::placeholder,
input[type="number"]::placeholder,
textarea::placeholder {
  color: var(--text-muted);
  font-style: italic;
}

.search-input:focus,
.form-control:focus,
.form-select:focus,
.inline-input:focus,
input[type="text"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
  background: var(--bg-surface);
  outline: none;
}

.form-select,
select {
  background-color: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  padding: 9px 36px 9px 12px;
  font-family: var(--font-body);
  font-size: 0.92rem;
  font-weight: 500;
  appearance: none;
  cursor: pointer;
  min-width: 140px;
  min-height: 40px;
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 10px 6px;
}

.form-select:hover,
select:hover {
  border-color: var(--border-accent);
  background-color: var(--bg-elevated);
}

.form-select:focus,
select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}

select option {
  background: var(--bg-surface);
  color: var(--text-primary);
}

/* ── Tables ───────────────────────────────────────────────────────────────── */

.table,
.mappings-table,
.management-table,
.cleanup-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: var(--shadow-deep);
}

.category-mappings-table { margin-bottom: 0; border-radius: 0; border: none; box-shadow: none; }

.mappings-table-scroll {
  max-height: calc(100vh - 220px);
  overflow-y: auto;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-deep);
  margin-bottom: 20px;
}

.mappings-table-scroll .mappings-table,
.mappings-table-scroll .data-table {
  border: none;
  border-radius: 0;
  box-shadow: none;
  margin-bottom: 0;
}

/* Global sticky table headers */
thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--bg-elevated);
}

.sort-link {
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}
.sort-link:hover { text-decoration: underline; }
.sort-link--active { color: var(--accent, #4a9eff); }

.cleanup-table-wrap {
  width: 100%;
  overflow-x: auto;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-deep);
}

.cleanup-table-wrap.mappings-table-scroll {
  overflow-y: auto;
  border-radius: 8px;
}


.cleanup-table {
  table-layout: fixed;
  min-width: 1240px;
  border: 0;
  box-shadow: none;
}

.table thead tr,
.mappings-table thead tr,
.management-table thead tr,
.cleanup-table thead tr,
.table-dark tr {
  background: var(--bg-elevated);
  border-bottom: 2px solid var(--border-accent);
}

.table th,
.table td,
.mappings-table th,
.mappings-table td,
.management-table th,
.management-table td,
.cleanup-table th,
.cleanup-table td {
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: 12px 16px;
  text-align: left;
  vertical-align: middle;
}

.table th,
.mappings-table th,
.management-table th,
.cleanup-table th {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--accent);
  padding: 13px 16px;
  white-space: nowrap;
}

.table th:last-child,
.table td:last-child,
.mappings-table th:last-child,
.mappings-table td:last-child,
.management-table th:last-child,
.management-table td:last-child,
.cleanup-table th:last-child,
.cleanup-table td:last-child {
  border-right: 0;
}

.table tbody tr,
.mappings-table tbody tr,
.management-table tbody tr,
.cleanup-table tbody tr {
  border-bottom: 1px solid var(--border);
  transition: background 0.14s ease;
  opacity: 0;
  animation: rowReveal 0.35s ease forwards;
  animation-delay: calc(var(--i, 0) * 0.04s);
}

.table-striped tbody tr:nth-child(odd),
.mappings-table tbody tr:nth-child(odd),
.management-table tbody tr:nth-child(odd),
.cleanup-table tbody tr:nth-child(odd) {
  background: var(--bg-row);
}

.table-striped tbody tr:nth-child(even),
.mappings-table tbody tr:nth-child(even),
.management-table tbody tr:nth-child(even),
.cleanup-table tbody tr:nth-child(even) {
  background: var(--bg-row-alt);
}

.table-hover tbody tr:hover,
.mappings-table tbody tr:hover,
.management-table tbody tr:hover,
.cleanup-table tbody tr:hover {
  background: var(--bg-row-hover);
  box-shadow: inset 4px 0 0 var(--accent);
}

.management-table thead th a,
.cleanup-table thead th a {
  color: var(--text-muted);
  text-decoration: none;
}

.row-muted td {
  opacity: 0.45;
  font-style: italic;
}

.management-table thead th a:hover,
.cleanup-table thead th a:hover {
  color: var(--accent);
}

.cleanup-table td:nth-child(1),
.cleanup-table td:nth-child(2),
.cleanup-table td:nth-child(4),
.cleanup-table td:nth-child(5),
.management-table td:nth-child(1),
.management-table td:nth-child(2),
.management-table td:nth-child(3),
.management-table td:nth-child(4) {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.88rem;
}

.cleanup-table td:nth-child(1) {
  font-size: 0.82rem;
  color: var(--text-muted);
  font-weight: 500;
}

.cleanup-table td:nth-child(2) {
  font-size: 0.86rem;
  color: var(--text-secondary);
}

.cleanup-table td:nth-child(3),
.cleanup-table td:nth-child(3) code {
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 500;
  background: transparent;
  max-width: 280px;
  white-space: normal;
  word-break: break-word;
}

.cleanup-table td:nth-child(4) {
  color: var(--danger);
  font-weight: 600;
}

.cleanup-table td:nth-child(5) {
  color: var(--text-secondary);
  font-size: 0.86rem;
}

.cleanup-table td:nth-child(6) {
  color: var(--text-secondary);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ── Checkboxes ───────────────────────────────────────────────────────────── */

.form-check-input,
input[type="checkbox"] {
  appearance: none;
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--text-muted);
  border-radius: 3px;
  background: transparent;
  cursor: pointer;
  transition: all 0.15s ease;
  position: relative;
  flex-shrink: 0;
}

.form-check-input:hover,
input[type="checkbox"]:hover {
  border-color: var(--accent);
}

.form-check-input:checked,
input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}

.form-check-input:checked::after,
input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 3px;
  top: 1px;
  width: 4px;
  height: 7px;
  border: 2px solid var(--bg-base);
  border-top: none;
  border-left: none;
  transform: rotate(45deg);
}

/* ── Sheet Head ───────────────────────────────────────────────────────────── */

/* ── Sheet select-all checkbox in thead ───────────────────────────────────── */
.th-select,
.td-select {
  width: 40px;
  text-align: center !important;
  padding: 0 8px !important;
  vertical-align: middle;
}

.select-all-label {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

tr.row-selected {
  background: var(--accent-dim) !important;
  box-shadow: inset 3px 0 0 var(--accent);
}

.sheet-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

.sheet-head.page-head { margin-bottom: 14px; }
.sheet-head.page-head h1 { margin-bottom: 0; }

/* Keeps the sum card and Save All Changes travelling together as ONE flex
   child of .sheet-head, so that rule's justify-content:space-between still
   only ever sees two items (title, this group) regardless of what's inside
   it. */
.sheet-head-actions { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }

@media (max-width: 700px) {
  /* On a phone the title can't share a row with the sum card AND a full
     button, so it wraps to its own line and the group takes the next one,
     right where it was pinned before -- but now that's right under the
     header instead of a whole filter panel further down the page. */
  .sheet-head { flex-wrap: wrap; }
  .sheet-head-actions { width: 100%; justify-content: space-between; }
  .sheet-sum-card { min-width: 0; }
}

.sheet-filter-form {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.sheet-filter-form .search-input { width: 220px; flex: 0 0 220px; }
.sheet-filter-form .form-control { width: 140px; flex: 0 0 140px; }
.sheet-filter-form .account-filter-box { width: fit-content; flex: 0 0 auto; position: relative; }
.sheet-filter-form .button,
.sheet-filter-form .clear-link { margin-left: 2px; }

/* ── Cards & Surfaces ─────────────────────────────────────────────────────── */

.sheet-sum-card,
.dashboard-controls,
.iframe-container,
.clean-name-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.sheet-sum-card {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  padding: 10px 12px;
  min-width: 220px;
}

.sheet-status,
.clear-link,
.action-link {
  color: var(--text-secondary);
  font-size: 0.9rem;
}

.badge,
.badge-cat {
  display: inline-block;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  padding: 3px 8px;
  border-radius: 3px;
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ── Page Head ────────────────────────────────────────────────────────────── */

.search-container,
.page-head,
.dashboard-header {
  margin-bottom: 20px;
}

.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.cleanup-page { padding: 0; margin: 0; }

.page-subtitle {
  margin: 4px 0 0;
  color: var(--text-secondary);
  font-size: 0.95rem;
}

/* ── Dashboard ────────────────────────────────────────────────────────────── */

.dashboard-header.page-head { align-items: flex-end; }

.dashboard-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
}

.dashboard-controls .label-block { margin: 0; min-width: 110px; }
.dashboard-controls #dash-select { min-width: 320px; width: 320px; }

.iframe-container { padding: 0; overflow: hidden; }

.placeholder-msg {
  margin: 0;
  min-height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  font-size: 0.95rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
}

.dashboard-iframe {
  display: none;
  border: 0;
  width: 100%;
  min-height: 800px;
}

/* ── Account Dropdown ─────────────────────────────────────────────────────── */

.account-dropdown-summary {
  list-style: none;
  cursor: pointer;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-surface);
  color: var(--text-primary);
  padding: 9px 12px;
  font-size: 0.9rem;
  font-weight: 600;
  min-height: 40px;
  /* Block, not flex — see the note on `.wealth-disclosure summary`: a flex
     summary stops opening its <details> on every iOS browser. The vertical
     centring that `align-items` was doing is line-height instead. */
  line-height: 22px;
}

.account-dropdown-summary::-webkit-details-marker { display: none; }
.account-dropdown-summary::marker { content: ""; }

.account-filter-list {
  max-height: 110px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-surface);
  padding: 6px 8px;
}

.account-dropdown[open] .account-filter-list {
  position: absolute;
  z-index: 200;
  min-width: 220px;
  width: max-content;
  margin-top: 4px;
}

/* ── Utility Layout Classes ───────────────────────────────────────────────── */

.account-option,
.clean-name-option,
.form-actions,
.d-flex {
  display: flex;
  align-items: center;
  gap: 8px;
}

.justify-content-between { justify-content: space-between; }
.align-items-center { align-items: center; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.form-row { margin-bottom: 12px; }
.mb-3 { margin-bottom: 1rem; }
.mt-4 { margin-top: 1.5rem; }
.button_to { display: inline-block; margin: 0; }

/* ── Scrollbar ────────────────────────────────────────────────────────────── */

::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: var(--bg-base); }
::-webkit-scrollbar-thumb {
  background: var(--border-accent);
  border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover { background: var(--accent); }

/* ── Animations ───────────────────────────────────────────────────────────── */

@keyframes navReveal {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes rowReveal {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ── Responsive ───────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .nav-container { padding: 8px 14px; flex-wrap: wrap; height: auto; }
  .main-header { height: auto; }
  .container { padding: 18px 14px; }
  .mappings-sticky-bar { padding-left: 14px; padding-right: 14px; margin-top: -18px; margin-left: -14px; margin-right: -14px; }
  .nav-links { flex-wrap: wrap; }
  .search-form { gap: 10px; }
  .sheet-filter-form .search-input,
  .sheet-filter-form .form-control { width: 100%; flex: 1 1 100%; }
  .sheet-filter-form .account-filter-box { width: 100%; flex: 1 1 100%; position: relative; }
  .dashboard-controls { flex-direction: column; align-items: flex-start; }
  .dashboard-controls #dash-select { width: 100%; min-width: 0; }
  .cleanup-toolbar .form-select { min-width: 120px; }
}

/* ── Upload Pages ─────────────────────────────────────────────────────────── */

.upload-page .page-subtitle {
  color: var(--text-secondary);
  margin-top: 6px;
  font-size: 0.95rem;
}

.upload-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  max-width: 560px;
}

.upload-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  border: 2px dashed var(--border-accent);
  border-radius: var(--radius);
  padding: 40px 32px;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
  position: relative;
  text-align: center;
}

.upload-zone:hover,
.upload-zone.drag-over {
  background: var(--accent-dim);
  border-color: var(--accent);
}

.upload-zone.has-file {
  border-style: solid;
  border-color: var(--accent);
  background: var(--accent-dim);
}

.upload-icon { color: var(--accent); opacity: 0.8; }

.upload-label {
  color: var(--text-secondary);
  margin: 0;
  font-size: 0.95rem;
}

.upload-browse {
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
}

.upload-hint {
  color: var(--text-muted);
  font-size: 0.82rem;
  font-family: "JetBrains Mono", monospace;
  margin: 0;
}

.upload-file-input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  width: 100%;
  height: 100%;
}

.upload-actions { display: flex; gap: 12px; align-items: center; }

.btn-primary {
  background: var(--accent);
  /* --on-accent, not --bg-base — this is text ON --accent, and the two tokens
     only happen to share a value in dark/blue today. See the Theme Tokens
     rule in CLAUDE.md: never hand-pick a colour for accent-foreground text. */
  color: var(--on-accent);
  font-weight: 700;
  border-color: transparent;
  box-shadow: 0 3px 16px var(--accent-dim);
}

.btn-primary:hover {
  filter: brightness(1.08);
  box-shadow: 0 5px 24px var(--accent-dim);
}

.btn-icon {
  padding: 6px 10px;
  font-size: 1rem;
  line-height: 1;
  min-width: 0;
}

.wealth-hero-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 8px;
}

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

.upload-info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 16px;
  margin-top: 28px;
  max-width: 560px;
}

.info-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 20px;
}

.info-card-title {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  font-weight: 700;
  margin-bottom: 8px;
}

.info-card-body {
  font-size: 0.88rem;
  color: var(--text-secondary);
  line-height: 1.6;
}

.info-card-body code {
  background: var(--bg-elevated);
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 0.82rem;
  color: var(--accent);
}

/* ── Column Mapping ───────────────────────────────────────────────────────── */

.col-mapping-section,
.override-section,
.preview-section { margin-top: 28px; }

.override-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}

.section-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.section-head h2 + * { margin-left: auto; }

.section-head h2 {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
  margin: 0;
  color: var(--text-primary);
}

.section-head small { color: var(--text-muted); font-size: 0.82rem; }

.col-map-table { width: 100%; }

.col-map-row.col-removed td {
  opacity: 0.3;
  text-decoration: line-through;
}

.col-source-name {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.85rem;
  color: var(--accent);
  background: var(--accent-dim);
  padding: 2px 7px;
  border-radius: 3px;
}

.sample-values {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}

.sample-badge {
  display: inline-block;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 1px 7px;
  font-size: 0.78rem;
  font-family: "JetBrains Mono", monospace;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* ── Preview Table ────────────────────────────────────────────────────────── */

.preview-table-wrap {
  overflow-x: auto;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.wealth-page-v2 .preview-table-wrap.cgt-table-wrap {
  max-height: 420px;
  overflow-y: auto;
}

.wealth-page-v2 .preview-table-wrap.cgt-table-wrap thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-surface);
}

.wealth-page-v2 .preview-table-wrap.investments-table-wrap {
  max-height: 420px;
  overflow-y: auto;
}

.wealth-page-v2 .preview-table-wrap.investments-table-wrap thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-surface);
}

.wealth-page-v2 .preview-table-wrap.investments-table-wrap thead th[data-sort] {
  cursor: pointer;
  user-select: none;
}

.wealth-page-v2 .preview-table-wrap.investments-table-wrap thead th[data-sort]:hover {
  background: var(--bg-hover, var(--bg-surface));
}

.sort-icon { margin-left: 4px; font-size: 0.75em; opacity: 0.4; }
.sort-icon.active { opacity: 1; }

.wealth-page-v2 .preview-table-wrap {
  background: var(--bg-base);
  overflow-x: auto;
  overflow-y: hidden;
}

.wealth-page-v2 .preview-table {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
  margin: 0;
  background: transparent;
  table-layout: fixed;
  min-width: 1080px;
}

.wealth-page-v2 .preview-table thead th {
  background: var(--bg-elevated);
  color: var(--text-muted);
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 10px 12px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.wealth-page-v2 .preview-table tbody td {
  padding: 10px 12px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wealth-page-v2 .preview-table thead th:last-child,
.wealth-page-v2 .preview-table tbody td:last-child { border-right: 0; }
.wealth-page-v2 .preview-table tbody tr:last-child td { border-bottom: 0; }
.wealth-page-v2 .preview-table tbody tr:nth-child(even) td { background: var(--bg-row-alt); }

.preview-table th.col-removed,
.preview-table td.col-removed { opacity: 0.25; text-decoration: line-through; }

.upload-form-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

/* ── Wealth Page ──────────────────────────────────────────────────────────── */

.wealth-page { display: flex; flex-direction: column; gap: 24px; }

.wealth-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(320px, 0.9fr);
  gap: 24px;
  padding: 32px;
  border: 1px solid var(--border-accent);
  border-radius: 18px;
  background: var(--bg-surface);
  box-shadow: var(--shadow-deep);
  border-top: 3px solid var(--accent);
}

.wealth-kicker {
  margin: 0 0 10px;
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 700;
}

.wealth-hero-panel,
.wealth-callout,
.wealth-form-shell,
.wealth-stat-card,
.wealth-summary-table-wrap {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-deep);
}

.wealth-hero-panel {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.wealth-month-picker {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

.wealth-month-picker .form-row { flex: 1 1 180px; margin: 0; }

.wealth-month-list { display: flex; flex-direction: column; gap: 8px; }

.wealth-chip-row { display: flex; flex-wrap: wrap; gap: 8px; }

.wealth-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.84rem;
  font-weight: 600;
  transition: all 0.18s;
}

.wealth-chip.active,
.wealth-chip:hover {
  color: var(--accent);
  border-color: var(--border-accent);
  background: var(--accent-dim);
}

.wealth-summary-grid,
.wealth-callout-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px;
}

.wealth-stat-card,
.wealth-callout { padding: 20px 22px; }

.wealth-stat-card strong {
  display: block;
  margin-top: 10px;
  font-family: var(--font-body);
  font-size: 1.7rem;
  letter-spacing: -0.02em;
  font-weight: 600;
  line-height: 1.1;
  overflow-wrap: anywhere;
}

.wealth-stat-card small,
.wealth-callout p { color: var(--text-secondary); }

.wealth-stat-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.wealth-stat-card.positive strong { color: var(--accent); }
.wealth-stat-card.negative strong { color: var(--danger); }

.wealth-stat-card.positive { border-left: 3px solid var(--accent); }
.wealth-stat-card.negative { border-left: 3px solid var(--danger); }

.wealth-stat-card--net {
  border-color: var(--border-accent);
  background: var(--accent-dim);
  border-left: 3px solid var(--accent);
}
.wealth-stat-card--net strong { color: var(--accent); }

.wealth-stat-card--featured {
  grid-column: span 2;
  border-top: 3px solid var(--accent);
  border-left: none;
}
.wealth-stat-card--featured strong {
  font-size: 2.4rem;
}
.wealth-stat-card--featured .wealth-stat-label {
  font-size: 0.8rem;
  letter-spacing: 0.14em;
}

/* Stat card: gear config button */
.wealth-stat-card--configurable .wealth-stat-label {
  display: flex;
  align-items: center;
  gap: 6px;
}
.wealth-stat-config-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.8rem;
  opacity: 0.4;
  padding: 0;
  line-height: 1;
  transition: opacity 0.15s;
}
.wealth-stat-card--configurable:hover .wealth-stat-config-btn { opacity: 0.9; }

/* Stat card: hover tooltip (tax breakdown) */
.wealth-stat-card--has-tooltip { position: relative; cursor: default; }
.wealth-stat-tooltip {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 200;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 14px;
  min-width: 240px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  font-size: 0.82rem;
}
.wealth-stat-card--has-tooltip:hover .wealth-stat-tooltip { display: block; }
.wealth-tooltip-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 3px 0;
  color: var(--text-secondary);
}
.wealth-tooltip-row--sep { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 6px; }
.wealth-tooltip-row--total {
  border-top: 1px solid var(--border);
  margin-top: 4px;
  padding-top: 6px;
  font-weight: 600;
  color: var(--text-primary);
}

.wealth-callout-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.wealth-callout p { margin: 0; line-height: 1.6; }

.wealth-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  align-items: start;
}

.wealth-form-shell { padding: 22px; }

.wealth-form-shell.accent-shell {
  border-color: var(--border-accent);
  background: var(--accent-dim);
}

.wealth-section-card {
  margin-top: 18px;
  padding: 16px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--bg-surface);
}

.wealth-section-card.accent-card {
  border-color: var(--border-accent);
  background: var(--accent-dim);
}

.wealth-section-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: baseline;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.wealth-section-head h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.1rem;
}

.wealth-section-head span { color: var(--text-muted); font-size: 0.82rem; }

.wealth-field-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 16px;
}

.wealth-dual-actions { justify-content: space-between; }

.wealth-summary-table-wrap { padding: 0; overflow: hidden; }
.wealth-summary-table { margin: 0; border: 0; box-shadow: none; }

@media (max-width: 1200px) {
  .wealth-summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .wealth-form-grid { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .wealth-hero,
  .wealth-summary-grid,
  .wealth-callout-grid,
  .wealth-field-grid { grid-template-columns: 1fr; }
  .wealth-hero { padding: 18px; }
  .wealth-form-shell { padding: 18px; }
  .wealth-dual-actions { flex-direction: column; align-items: stretch; }

  /* The featured card spans 2 columns on desktop; in the single-column mobile
     grid that forces an implicit overflow track and the big currency value
     spills outside the card. Reset the span and shrink the number to fit. */
  .wealth-stat-card--featured { grid-column: auto; }
  .wealth-stat-card--featured strong { font-size: 1.9rem; }
}

.wealth-page-v2 .wealth-form-grid,
.wealth-layout-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
}

/* ── Overview Grid (50/50 Net Worth + Budget) ─────────────────────────────── */

.wealth-overview-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: stretch;
}

.wealth-nw-panel,
.wealth-cf-panel {
  display: flex;
  flex-direction: column;
  gap: 0;
  overflow: hidden;
  padding: 0;
}

/* Net worth total — prominent headline */
.wealth-nw-total {
  padding: 20px 22px 16px;
  border-bottom: 1px solid var(--border);
}

.wealth-nw-figure {
  display: block;
  margin-top: 6px;
  font-family: var(--font-body);
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.1;
}

.wealth-nw-figure.positive { color: var(--accent); }
.wealth-nw-figure.negative { color: var(--danger); }

/* ===== The one "add a record" button ================================
   Every section that creates something uses this and nothing else. Before, the
   same action was a btn-primary in a toolbar row under the heading (Assets,
   Liabilities), a bare "+" icon button (Trades), and an inline <details>
   disclosure that opened a form in the page rather than a dialog (Properties) —
   so the Assets tab alone showed three different answers to "how do I add one".
   It lives in .section-head beside the heading in every case. */
.button.btn-add {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  white-space: nowrap;
}
.button.btn-add:hover { filter: brightness(1.08); }

/* Icon-only "+" add button — a compact accent-coloured circle in the section
   head's top-right corner (same margin-left:auto placement as .btn-add,
   above). Used where a labelled "+ Add X" button next to its own matching h2
   is redundant with the heading right above it (Properties, Custom Assets,
   Liabilities); .btn-add's "+ Add Trade"/"+ Add Bill" keep their label since
   those sit in a wider toolbar with other buttons, not alone under a heading. */
.btn-add-icon {
  width: 30px;
  height: 30px;
  min-height: 0;
  min-width: 0;
  padding: 0;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-size: 1.3rem;
  font-weight: 700;
  line-height: 1;
}
.btn-add-icon:hover { filter: brightness(1.08); }

/* ===== Chart card ====================================================
   A titled surface for a chart, so a plot is never a bare canvas floating in a
   panel. The head carries the current value, which is the thing being looked
   for most of the time the chart is on screen. */
.wealth-chart-card {
  margin-top: 20px;
  padding: 16px 18px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.wealth-chart-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-bottom: 10px;
}
.wealth-chart-card-label {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.wealth-chart-card-figure {
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.wealth-chart-card-delta {
  font-size: 0.8rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.wealth-chart-card-delta.is-up   { color: var(--positive); }
.wealth-chart-card-delta.is-down { color: var(--danger); }
/* The figure is market-only, which is not what a delta beside a portfolio value
   is normally assumed to mean — so it says so, rather than leaving a number
   that quietly disagrees with the value above it. */
.wealth-chart-card-delta-note {
  display: block;
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}
.wealth-chart-card-plot { position: relative; height: 260px; }
.wealth-chart-card-state {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 16px;
  font-size: 0.82rem;
  color: var(--text-muted);
}

/* --- Chart tooltip ---------------------------------------------------
   An HTML tooltip rather than Chart.js' canvas one, because it has to colour
   each holding's move independently and Chart.js paints every body line in a
   single bodyColor. Deliberately sets NO `display`: it is toggled with the
   `hidden` attribute, and a bare `display` here would override that and leave
   it stuck on screen. It is absolutely positioned against
   `.wealth-chart-card-plot` (its own parent, which is `position: relative`),
   never `fixed` — so it needs no portal and the kinRise containing-block trap
   doesn't apply. */
.chart-tip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  min-width: 210px;
  max-width: min(320px, 92%);
  padding: 9px 11px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-accent);
  border-radius: 9px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.22);
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.chart-tip-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.chart-tip-when  { color: var(--text-muted); font-size: 0.68rem; }
.chart-tip-total { color: var(--text-primary); font-size: 0.9rem; font-weight: 800; }
.chart-tip-sub {
  margin-top: 2px;
  font-weight: 700;
  font-size: 0.76rem;
  color: var(--text-secondary);
}
.chart-tip-sub.is-up   { color: var(--positive); }
.chart-tip-sub.is-down { color: var(--danger); }
.chart-tip-split {
  display: flex;
  gap: 10px;
  margin-top: 1px;
  font-size: 0.67rem;
  color: var(--text-muted);
}
.chart-tip-gain {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px dashed var(--border);
  font-size: 0.7rem;
  font-weight: 700;
}
.chart-tip-gain .is-up   { color: var(--positive); }
.chart-tip-gain .is-down { color: var(--danger); }
.chart-tip-rows {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 5px;
}
.chart-tip-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 10px;
}
.chart-tip-tkr { font-weight: 700; color: var(--text-primary); }
.chart-tip-val { font-weight: 700; }
.chart-tip-val.is-up   { color: var(--positive); }
.chart-tip-val.is-down { color: var(--danger); }
.chart-tip-meta {
  grid-column: 1 / -1;
  font-size: 0.66rem;
  color: var(--text-muted);
}
.chart-tip-note {
  margin-top: 5px;
  font-size: 0.66rem;
  color: var(--text-muted);
}
.chart-tip-more {
  margin-top: 5px;
  font-size: 0.66rem;
  font-weight: 700;
  color: var(--accent);
}
.chart-tip-trades {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 3px;
}
.chart-tip-trade {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 0 8px;
  align-items: baseline;
  font-size: 0.67rem;
}
.chart-tip-trade-side { font-weight: 800; }
.chart-tip-trade.is-buy  .chart-tip-trade-side { color: var(--positive); }
.chart-tip-trade.is-sell .chart-tip-trade-side { color: var(--danger); }
.chart-tip-trade-what { color: var(--text-primary); }
.chart-tip-trade-when { color: var(--text-muted); }

/* Pinned: the panel stops being a tooltip and becomes a thing you can reach
   into — pointer events back on, no row cap, and a scroll of its own so a
   portfolio with twenty holdings doesn't run off the bottom of the card. */
.chart-tip.is-pinned {
  pointer-events: auto;
  max-height: 320px;
  overflow-y: auto;
  z-index: 6;
  border-color: var(--accent);
}
.chart-tip-close {
  flex: 0 0 auto;
  margin: -3px -3px -3px 2px;
  padding: 0 4px;
  background: none;
  border: 0;
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  color: var(--text-muted);
}
.chart-tip-close:hover { color: var(--text-primary); }

/* --- Chart range selector + footer key ------------------------------- */
.chart-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin: 0 0 8px;
}
.chart-range {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.chart-zoom-reset { margin-left: auto; }
.chart-range-btn {
  padding: 3px 10px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}
.chart-range-btn:hover { border-color: var(--border-accent); color: var(--text-primary); }
.chart-range-btn.is-active {
  background: var(--accent);
  border-color: var(--accent);
  /* NOT #fff — on the dark themes the accent is a light blue and white on it
     measures under 3:1. See the Theme Tokens note in CLAUDE.md. */
  color: var(--on-accent);
}
.chart-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
  margin-top: 10px;
  font-size: 0.66rem;
  color: var(--text-muted);
}
.chart-foot-key { display: inline-flex; align-items: center; gap: 5px; }
/* CSS triangles, so the key matches the chart's own marker shape without an
   icon font or an inline SVG per theme. */
.chart-key-buy, .chart-key-sell {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
}
.chart-key-buy  { border-bottom: 8px solid var(--positive); }
.chart-key-sell { border-top: 8px solid var(--danger); }
.chart-foot-gran { margin-left: auto; font-variant-numeric: tabular-nums; }
@media (max-width: 700px) {
  .chart-foot-hint { display: none; }
  .chart-foot-gran { margin-left: 0; }
}

/* ===== Cash-flow KPI cards ===========================================
   Was .wealth-cf-row: four bordered rows stacked in a panel. Cards give each
   figure its own surface and let them reflow instead of always being a column. */
.wealth-kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
}
.wealth-kpi {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 15px 16px 16px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-accent);
  border-radius: 12px;
  box-shadow: var(--shadow-deep);
  min-width: 0;
}
.wealth-kpi.is-positive { border-left-color: var(--positive); }
.wealth-kpi.is-negative { border-left-color: var(--danger); }

.wealth-kpi-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.wealth-kpi-figure {
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.wealth-kpi.is-positive .wealth-kpi-figure { color: var(--positive); }
.wealth-kpi.is-negative .wealth-kpi-figure { color: var(--danger); }
.wealth-kpi-sub { font-size: 0.72rem; color: var(--text-muted); }

/* A card that IS the button. Every one of these resets exists because a
   <button> does not inherit them, and the card has to stay pixel-identical to
   the three <article> cards beside it — that row reads as broken the moment one
   card is a different height or its text is centred. */
.wealth-kpi--action {
  font: inherit;
  text-align: left;
  /* The shared button reset sets align-items: center for its usual row
     layout — inside .wealth-kpi's column flex that's the CROSS axis, so it
     centred the label/figure/sub horizontally regardless of text-align,
     which only governs inline text, not flex-item position. */
  align-items: flex-start;
  width: 100%;
  min-height: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
/* The global button:hover rule (application.css's shared .button/.btn/button
   reset) sets transform: translateY(-1px) at lower specificity than these two
   classes combined — but transform isn't among the properties re-declared
   below, so it applied unopposed and this one card visibly lifted on hover
   while its <article> siblings stayed put. Cancel it explicitly. */
.wealth-kpi--action:hover { border-color: var(--border-accent); background: var(--bg-elevated); transform: none; }
.wealth-kpi--action:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Inherits the label's font-size, so unlike the button it replaces it cannot
   make this card taller than its neighbours. */
.wealth-kpi--action .wealth-kpi-label::after {
  content: "⚙";
  margin-left: 5px;
  opacity: 0.55;
}
.wealth-kpi--action:hover .wealth-kpi-label::after { opacity: 1; }

/* ===== Asset mix bar (balance sheet panel) ===========================
   Same rounded-bar language as the budget cards. These are data fills with no
   text on them, so they're picked to stay distinguishable on every theme
   surface rather than to hit a text contrast ratio. */
.wealth-mix-bar {
  display: flex;
  height: 12px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--bg-row-alt);
  border: 1px solid var(--border);
  margin-bottom: 12px;
}
.wealth-mix-seg { display: block; height: 100%; min-width: 2px; }
.wealth-mix-seg--offset      { background: #4C8DD9; }
.wealth-mix-seg--cash        { background: #3FB6A8; }
.wealth-mix-seg--investments { background: #8A73D9; }
.wealth-mix-seg--property    { background: #E0913A; }
.wealth-mix-seg--custom      { background: #C56BA6; }
.wealth-mix-seg--loans        { background: #D9534F; }
.wealth-mix-seg--credit_cards { background: #A83246; }

/* ===== Net worth pie (balance sheet panel) ===========================
   One ring, sized by magnitude: assets in the existing wealth-mix palette,
   liabilities in the danger family so the two read apart at a glance without
   needing the pie to express a sign. Centre label sits over the canvas hole
   (a real doughnut, not a plugin-drawn circle) so it never has to be redrawn
   in step with the chart's own hover animation. */
.wealth-nw-pie-wrap { display: flex; flex-direction: column; align-items: center; gap: 4px; margin-bottom: 12px; }
.wealth-nw-pie-canvas-wrap { position: relative; width: 100%; max-width: 340px; aspect-ratio: 1 / 1; margin: 4px auto 10px; }
.wealth-nw-pie-canvas-wrap canvas { width: 100% !important; height: 100% !important; }
.wealth-nw-pie-center {
  position: absolute;
  z-index: 1;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  pointer-events: none;
  text-align: center;
  padding: 0 20%;
}
.wealth-nw-pie-center-label {
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.wealth-nw-pie-center-figure { font-size: 1.5rem; font-weight: 700; line-height: 1.15; }
.wealth-nw-pie-wrap .wealth-mix-legend { margin-bottom: 4px; max-width: 480px; }

/* An HTML tooltip, not Chart.js' canvas-drawn one: the centre net-worth label
   is itself a positioned overlay sitting on top of the canvas (see above), so
   a tooltip drawn INTO the canvas can never paint over it — a positioned
   sibling can. z-index puts it above both the centre label and the canvas. */
/* position: fixed + data-portal-to-body (see shared/_portal_overlays), not
   absolute inside .wealth-nw-pie-canvas-wrap — a breakdown list (up to 8
   holdings) can run wider than the 340px chart, and the chart itself sits
   inside a plain page card with no special stacking of its own, so the
   tooltip was getting clipped/buried under whatever painted after it. Fixed
   + portaled floats it above literally everything, coordinates computed in
   JS from the canvas's own getBoundingClientRect() since fixed positioning
   is viewport-relative once it's a direct child of <body>. */
.wealth-nw-pie-tooltip {
  position: fixed;
  z-index: 1300;
  top: 0;
  left: 0;
  pointer-events: none;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 0.78rem;
  line-height: 1.5;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.24);
  /* Wraps rather than nowrap now that it's capped to a max-width — a long
     holding name plus its dollar figure would otherwise overflow the box
     instead of wrapping onto a second line. */
  max-width: min(320px, calc(100vw - 16px));
}
.wealth-nw-pie-tooltip strong { display: block; color: var(--text-primary); }
.wealth-nw-pie-tooltip .wealth-nw-pie-tooltip-item { color: var(--text-secondary); }

.wealth-mix-legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 4px 16px;
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
}
.wealth-mix-legend li {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.78rem;
  min-width: 0;
}
.wealth-mix-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.wealth-mix-name {
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wealth-mix-value {
  margin-left: auto;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

/* ===== Wealth manager tab strip =====================================
   Same affordance as .wordle-tabs: a horizontal-scroll pill row that never
   wraps and never pushes the content down. It sticks below the app header, so
   the offset matches the other three sticky offsets in this file (54px
   desktop / 68px phone — see the media query near the bottom). */
/* The bar is what sticks and what carries the background/rule; .wealth-tabs is
   only the scrolling strip inside it, so the tools stay pinned while the tabs
   scroll under them. The bar must NOT be overflow:auto — the ⚙ popup is
   anchored in it. */
/* The bar is full-bleed and flush to the app header at every width, so it reads
   as chrome rather than as the first card on the page. That means cancelling
   .container's padding exactly — and .container has three tiers of it, so the
   page publishes them as variables instead of the bar hardcoding one. */
.wealth-page { --wp-pad-x: 32px; --wp-pad-y: 28px; }

.wealth-tabbar {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Sticky ONLY where .main-header is a known fixed 54px. Below 900px the
     header goes height:auto and wraps, so its height is not a number this can
     be written against — and the header's z-index of 100 beats the bar's 40, so
     an offset that is even slightly short does not misplace the bar, it slides
     the bar's top row UNDER the header and shears the month button and the ⚙
     circle in half. There is no offset to get wrong if it does not stick. */
  position: sticky;
  top: 54px;
  z-index: 40;
  margin: calc(var(--wp-pad-y) * -1) calc(var(--wp-pad-x) * -1) 0;
  padding: 9px var(--wp-pad-x);
  background: var(--bg-base);
  border-bottom: 1px solid var(--border);
}

.wealth-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* overflow-x:auto forces overflow-y to compute to auto as well — it cannot
     stay visible — so this IS a vertical scroll container and a pill sitting
     flush against its edges gets its rounded ends shaved. The padding is the
     clearance that makes that impossible; the negative margin keeps the strip
     aligned with everything else on the bar. */
  padding: 3px 0;
  margin: -3px 0;
}
.wealth-tabs::-webkit-scrollbar { display: none; }

/* ── Tab-bar tools: month button + ⚙ menu ─────────────────────────────── */
.wealth-tabbar-tools { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }

.wealth-month-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-surface);
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
  cursor: pointer;
}
.wealth-month-btn:hover { border-color: var(--border-accent); background: var(--bg-elevated); }
.wealth-month-btn svg { width: 9px; height: 6px; flex: 0 0 auto; color: var(--text-muted); }

.wealth-tool-menu { position: relative; }
.wealth-tool-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--bg-surface);
  font-size: 0.9rem;
  line-height: 1;
  color: var(--text-secondary);
  cursor: pointer;
}
.wealth-tool-btn:hover { border-color: var(--border-accent); background: var(--bg-elevated); color: var(--text-primary); }
.wealth-tool-btn[aria-expanded="true"] { border-color: var(--accent); color: var(--text-primary); }

.wealth-tool-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 60;
  min-width: 170px;
  padding: 5px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-accent);
  border-radius: 10px;
  box-shadow: var(--shadow-deep);
}
.wealth-tool-pop button {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  text-align: left;
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: pointer;
}
.wealth-tool-pop button:hover { background: var(--bg-row-hover); color: var(--text-primary); }

/* [hidden] is only the UA stylesheet's display:none, which loses to ANY author
   display on .wealth-panel — the same trap .is-section-hidden documents. The
   off-tab sections must lose unconditionally. */
[data-wtab][hidden] { display: none !important; }

.wealth-tab {
  flex: 0 0 auto;
  padding: 7px 15px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  font-family: inherit;
  font-size: 0.83rem;
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.wealth-tab:hover { background: var(--bg-elevated); color: var(--text-primary); }
.wealth-tab.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* ===== Budget by category — one card per category ====================
   Replaced a horizontal Chart.js bar chart. Cards read better on a phone (the
   chart's category labels were a fixed-width gutter squeezing the bars), and
   with no canvas the panel costs nothing when its tab is off screen. */
.budget-total-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  align-items: baseline;
  padding: 14px 16px;
  margin-bottom: 16px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.budget-total-figure { display: flex; flex-direction: column; gap: 2px; }
.budget-total-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.budget-total-figure strong { font-size: 1.25rem; font-weight: 800; letter-spacing: -0.02em; }
.budget-total-strip.is-good .budget-total-figure:first-child strong { color: var(--positive); }
.budget-total-strip.is-bad  .budget-total-figure:first-child strong { color: var(--danger); }
.budget-total-figure--muted strong { font-size: 1rem; color: var(--text-secondary); }
.budget-total-of { font-weight: 500; color: var(--text-muted); }

/* Explicit column counts per breakpoint, NOT `auto-fill, minmax(250px, 1fr)`.
   auto-fill packs in as many 250px columns as the panel can hold — six or more
   on a wide desktop — and 250px is not enough for a category name beside its
   percentage, so every card ellipsised its own title while the panel had room
   to spare. It also left whatever remainder the count happened to produce as a
   ragged last row. Four wide cards read as a deliberate grid; eight cramped
   ones read as a bug. `minmax(0, 1fr)` rather than `1fr` throughout for the
   reason documented on the calendar's seven-column tracks: bare `1fr` is
   `minmax(auto, 1fr)`, so a card whose content won't shrink drags its own
   column wider than its neighbours. */
.budget-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

/* The last row is filled out rather than left ragged — a lone card sitting
   under a full row looks like something failed to load. Only meaningful in
   the phone card grid below — the desktop row layout has no columns to fill. */
.budget-cards > .budget-card:last-child:nth-child(odd) { grid-column: auto / -1; }

/* ── Desktop: one row per category, not a card grid ──────────────────────
   A card grid is the right shape for a phone (scan a few at a time, tap
   into one) but wastes a wide desktop viewport on padding and whitespace
   between tiny tiles — a category name, a bar, and three numbers fit in
   one line with room to spare, and a row-per-item list scans top-to-bottom
   far faster than a grid of squares.

   No markup duplicated for this: .budget-card-head and .budget-card-meta
   are each just a wrapper around two of the five pieces a row needs
   (name+pct, variance+amounts) for the CARD layout's two-line stack.
   display:contents removes the wrapper from the box tree without touching
   the DOM, so its children become direct grid items of .budget-card in
   their existing DOM order — name, pct, variance, amounts, track — which
   is exactly this grid's column order already. Same trick as the calendar
   month grid's clone-based day sheet uses to reuse markup instead of a
   second template. */
@media (min-width: 900px) {
  .budget-cards {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .budget-cards > .budget-card:last-child:nth-child(odd) { grid-column: unset; }

  .budget-card {
    display: grid;
    grid-template-columns: minmax(140px, 1.6fr) 48px 116px minmax(140px, 1fr) minmax(110px, 1.3fr);
    align-items: center;
    gap: 16px;
    padding: 9px 16px;
    border-radius: 8px;
  }
  .budget-card-head, .budget-card-meta { display: contents; }
  .budget-card-pct, .budget-card-variance, .budget-card-amounts { text-align: right; }
  /* grid-column/width are explicit, not left to auto-placement + stretch --
     .budget-card-track is the one real grid-item child sitting alongside two
     display:contents wrappers, and that mix is exactly the case where Chromium
     mis-sizes a trailing item to zero width instead of stretching it to fill
     its column, so the bar silently didn't render on desktop. */
  .budget-card-track { grid-column: 5; width: 100%; min-width: 0; margin: 0; }
  .budget-card:hover { background: var(--bg-row-hover); }
}

.budget-card {
  --budget-tone: var(--accent);
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 14px 15px 15px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  min-width: 0;
}
.budget-card.is-good { --budget-tone: var(--positive); }
.budget-card.is-bad  { --budget-tone: var(--danger); }

.budget-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}
.budget-card-name {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.budget-card-pct {
  flex: 0 0 auto;
  font-size: 0.78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--budget-tone);
}

.budget-card-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 0.76rem;
  min-width: 0;
}
.budget-card-variance { font-weight: 600; color: var(--budget-tone); white-space: nowrap; }
.budget-card-amounts {
  flex: 0 1 auto;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The track's overflow:hidden is what rounds the fill's leading edge to match
   the track at 100%, so the fill needs no radius of its own. */
.budget-card-track {
  height: 9px;
  border-radius: 999px;
  background: var(--bg-row-alt);
  border: 1px solid var(--border);
  overflow: hidden;
}
.budget-card-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--budget-tone);
  transition: width 0.4s ease;
}
/* A full bar could mean "exactly on budget" or "triple it" — the stripe marks
   which, since the bar itself is capped and can't say. */
.budget-card.is-over .budget-card-fill {
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, 0.28) 0 5px,
    transparent 5px 10px
  );
}

@media (prefers-reduced-motion: reduce) {
  .budget-card-fill { transition: none; }
}

/* .container's padding tiers, mirrored so the full-bleed bar stays exact. */
@media (max-width: 900px) {
  .wealth-page { --wp-pad-x: 14px; --wp-pad-y: 18px; }
  /* See the note on .wealth-tabbar: this is the width where .main-header stops
     having a height worth writing a sticky offset against. */
  .wealth-tabbar { position: static; top: auto; }
}
@media (max-width: 430px) { .wealth-page { --wp-pad-x: 12px; --wp-pad-y: 14px; } }

@media (max-width: 640px) {
  /* One row could not hold five pills plus a month button plus a gear on a
     390px screen: the tools ate the strip's width, so the tabs were clipped
     mid-pill and the last one scrolled under the month button. The tools take
     their own slim row above (order:-1, since they come second in the DOM) and
     the strip gets the full width beneath it. */
  .wealth-tabbar { flex-wrap: wrap; row-gap: 9px; padding-top: 10px; }
  .wealth-tabbar-tools { order: -1; width: 100%; justify-content: space-between; }
  .wealth-tabs { order: 1; flex: 1 1 100%; }

  /* A pill cut off at the edge reads as broken; faded, it reads as "there is
     more this way". The strip is still edge-to-edge, so the fade is the only
     thing marking it as scrollable — the scrollbar is hidden. */
  .wealth-tabs {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  }
  .wealth-tab { padding: 6px 13px; }

  .wealth-month-btn { padding: 6px 10px; font-size: 0.75rem; }
  .wealth-tool-btn { width: 30px; height: 30px; }

  /* One column, laid out as a statement row: label and sub on the left, figure
     right-aligned. Two-up squares gave each figure ~150px of inner width, which
     a formatted currency value simply does not fit into. */
  .wealth-kpi-row { grid-template-columns: 1fr; gap: 8px; }
  .wealth-kpi {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 1px 12px;
    padding: 11px 14px;
    border-radius: 11px;
  }
  .wealth-kpi-label { grid-area: 1 / 1; min-width: 0; }
  .wealth-kpi-sub   { grid-area: 2 / 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .wealth-kpi-figure {
    grid-area: 1 / 2 / 3 / 3;
    font-size: 1.15rem;
    text-align: right;
    white-space: nowrap;
  }

  .budget-cards { grid-template-columns: 1fr; gap: 10px; }
  .budget-total-strip { gap: 8px 20px; padding: 12px 14px; }
  .budget-total-figure strong { font-size: 1.1rem; }
}

/* Small-phone tier: the KPI sub lines are the first thing to go — they are
   context for a figure that is already legible without them. */
@media (max-width: 380px) {
  .wealth-kpi-figure { font-size: 1.05rem; }
  .wealth-kpi-sub { display: none; }
  .wealth-kpi { gap: 0 10px; }
  .wealth-kpi-label { grid-area: 1 / 1; }
  .wealth-kpi-figure { grid-area: 1 / 2; }
}

/* Budget / cash-flow panel rows */
.wealth-cf-row {
  padding: 16px 22px;
  border-bottom: 1px solid var(--border);
  position: relative;
}

.wealth-cf-row:last-child { border-bottom: 0; }

.wealth-cf-row strong {
  display: block;
  margin-top: 6px;
  font-family: var(--font-body);
  font-size: 1.55rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.wealth-cf-row small {
  display: block;
  margin-top: 4px;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.wealth-cf-row.positive strong { color: var(--accent); }
.wealth-cf-row.negative strong { color: var(--danger); }
.wealth-cf-row.positive { border-left: 3px solid var(--accent); }
.wealth-cf-row.negative { border-left: 3px solid var(--danger); }

/* ── Wealth Disclosure ────────────────────────────────────────────────────── */

.wealth-disclosure {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-deep);
  margin-bottom: 18px;
  overflow: hidden;
}

.wealth-inline-disclosure { margin-bottom: 14px; }

/* A <summary> must NOT be display:flex (or grid). Safari — including every
   iOS browser, since they are all Safari underneath — stops treating a summary
   as its <details>' toggle once its display is changed away from the default,
   so the element still highlights on tap and simply never opens. That is why
   the Share Transactions ledger could not be opened on a phone while working
   fine on a desktop. The summary stays a block and the row layout it wanted is
   done with `position: relative` + an absolutely placed marker instead.
   Anything added here later must keep that rule. */
.wealth-disclosure summary {
  list-style: none;
  cursor: pointer;
  position: relative;
  padding: 18px 48px 18px 22px;
  background: var(--bg-elevated);
  transition: background 0.15s;
}

.wealth-disclosure summary:hover { background: var(--bg-row-hover); }
.wealth-disclosure summary::-webkit-details-marker { display: none; }
.wealth-disclosure summary::marker { content: ""; }

.wealth-disclosure summary::after {
  content: "+";
  position: absolute;
  right: 22px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-display);
  font-size: 1.4rem;
  font-weight: 600;
  color: var(--accent);
  line-height: 1;
}

.wealth-disclosure[open] summary::after { content: "−"; }

.wealth-disclosure summary span { display: flex; flex-direction: column; gap: 4px; }

.wealth-disclosure summary strong {
  font-family: var(--font-display);
  font-size: 1.05rem;
}

.wealth-disclosure summary small {
  color: var(--text-muted);
  font-size: 0.83rem;
}

.wealth-form-shell-full { margin: 0; border: 0; border-radius: 0; box-shadow: none; width: 100%; }
.wealth-panel-full { margin: 0; border: 0; border-radius: 0; box-shadow: none; width: 100%; }
.wealth-import-form { display: grid; gap: 12px; }

.wealth-panel {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-deep);
  padding: 22px;
}

.wealth-panel-wide { width: 100%; }

.wealth-checkbox-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.wealth-check-card {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.wealth-check-card span { display: flex; flex-direction: column; gap: 4px; }
.wealth-check-card strong { font-size: 0.9rem; }
.wealth-check-card small { color: var(--text-muted); line-height: 1.5; }

.wealth-mini-table td,
.wealth-mini-table th { white-space: nowrap; }

.wealth-page-v2 .preview-table td .form-control,
.wealth-page-v2 .preview-table td .form-select,
.wealth-page-v2 .preview-table td .button,
.wealth-page-v2 .preview-table td form { max-width: 100%; }

.wealth-page-v2 .wealth-mini-table .wealth-table-actions { white-space: normal; width: 1%; }

.wealth-inline-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px;
  align-items: center;
}

.wealth-table-actions { min-width: 420px; white-space: normal; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.wealth-table-actions form { display: contents; }
.wealth-table-actions-wide { min-width: 760px; }
/* lightweight action cell — no enforced min-width, used where 420px would break layout */
.wealth-card-act { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.wealth-card-act form { display: contents; }

@media (max-width: 640px) {
  .wealth-table-actions { min-width: 0; white-space: normal; }
  .wealth-table-actions-wide { min-width: 0; }
}

.wealth-inline-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.84rem;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* ── Wealth Config Grid ───────────────────────────────────────────────────── */

.wealth-config-grid {
  display: grid;
  grid-template-columns: 190px 120px 90px minmax(220px, 1fr);
  gap: 0;
  background: var(--bg-base);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.wealth-config-grid-budget {
  grid-template-columns: 200px 120px 90px 110px minmax(140px, 1fr);
}
.wealth-config-grid-budget .wealth-config-row {
  grid-template-columns: 200px 120px 90px 110px minmax(140px, 1fr);
}
.wealth-config-grid-budget .wealth-config-grid-header:nth-child(5),
.wealth-config-grid-budget .wealth-config-row .wealth-config-cell:last-child { border-right: 0; }

.wealth-config-grid-header,
.wealth-config-cell {
  padding: 10px 12px;
  min-width: 0;
  border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border);
}

.wealth-config-grid-header {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  background: var(--bg-elevated);
}

.wealth-config-row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 190px 120px 90px minmax(220px, 1fr);
  background: var(--bg-surface);
}

.wealth-config-row:nth-of-type(even) .wealth-config-cell { background: var(--bg-row-alt); }
.wealth-config-row-new { background: var(--accent-dim); }

.wealth-config-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.wealth-config-grid:not(.wealth-config-grid-budget) .wealth-config-grid-header:nth-child(4),
.wealth-config-grid:not(.wealth-config-grid-budget) .wealth-config-row .wealth-config-cell:last-child { border-right: 0; }
.wealth-config-row:last-child .wealth-config-cell { border-bottom: 0; }

.wealth-config-cell .form-control,
.wealth-config-cell .form-select { width: 100%; max-width: 100%; }

.wealth-toolbar {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.wealth-inline-form-property { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.wealth-inline-form-wide { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }

.wealth-property-table {
  table-layout: fixed;
  min-width: 1220px;
}

.wealth-property-table .wealth-col-name { width: 20%; }
.wealth-property-table .wealth-col-type,
.wealth-property-table .wealth-col-status { width: 10%; }
.wealth-property-table .wealth-col-money { width: 10%; }
.wealth-property-table .wealth-col-yield { width: 8%; }
.wealth-property-table .wealth-col-actions { width: 12%; }

.wealth-property-editor { margin-top: 14px; }

.wealth-property-form { display: flex; flex-direction: column; gap: 16px; }

.wealth-property-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.wealth-property-form-span { grid-column: 1 / -1; }

.wealth-trades-disclosure { margin-top: 16px; }

.wealth-page-v2 .wealth-mini-table { min-width: 1080px; }
.wealth-page-v2 .wealth-mini-table--fit { min-width: 0; width: 100%; }
.wealth-page-v2 .wealth-mini-table--fit th,
.wealth-page-v2 .wealth-mini-table--fit td { white-space: normal; }

.wealth-stat-stack {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.wealth-stat-stack div {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
}

.wealth-stat-stack-compact { min-width: 420px; }

.wealth-stat-stack span {
  display: block;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.wealth-stat-stack strong {
  display: block;
  margin-top: 6px;
  font-family: var(--font-body);
  font-size: 1.1rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ── Surplus Highlight ────────────────────────────────────────────────────── */

.wealth-surplus-highlight { border-width: 1px; border-style: solid; }

.wealth-surplus-highlight.positive {
  background: var(--accent-dim);
  border-color: var(--border-accent);
}

.wealth-surplus-highlight.positive strong { color: var(--accent); }

.wealth-surplus-highlight.negative {
  background: rgba(180, 44, 26, 0.08);
  border-color: rgba(180, 44, 26, 0.25);
}

.wealth-surplus-highlight.negative strong { color: var(--danger); }

/* ── Net Worth History Chart ─────────────────────────────────────────────── */

.wealth-nw-history-chart-wrap {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  height: 280px;
}

/* ── Dialogs ──────────────────────────────────────────────────────────────── */

.wealth-dialog {
  width: min(1180px, calc(100vw - 48px));
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  border: 1px solid var(--border-accent);
  border-radius: 16px;
  background: var(--bg-surface);
  color: var(--text-primary);
  padding: 20px;
  box-shadow: var(--shadow-deep);
}

.wealth-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }

.wealth-dialog-head {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: flex-start;
  margin-bottom: 16px;
}

.wealth-dialog-head h3 { margin: 0 0 6px; }
.wealth-dialog-head p { margin: 0; color: var(--text-secondary); }
.wealth-dialog-table-wrap { max-height: 56vh; overflow: auto; }

.text-positive { color: var(--accent); font-weight: 700; }
.text-danger   { color: var(--danger); font-weight: 700; }

/* ── Capital Gains ────────────────────────────────────────────────────────── */

.wealth-cgt-toolbar { margin-bottom: 12px; }

.wealth-cgt-toolbar .wealth-inline-form {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  flex-wrap: wrap;
}

.wealth-cgt-summary {
  margin-top: 16px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.wealth-cgt-summary-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 0.92rem;
}

.wealth-cgt-summary-row:last-child { border-bottom: 0; }

.wealth-cgt-summary-total {
  background: var(--accent-dim);
  font-weight: 700;
  font-size: 1rem;
}

/* ── FX Panel ─────────────────────────────────────────────────────────────── */

.wealth-fx-panel {
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin: 8px 0;
  font-size: 0.88rem;
}

.wealth-fx-row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.wealth-fx-row + .wealth-fx-row { margin-top: 6px; }

.wealth-dialog-wide { width: min(900px, calc(100vw - 48px)); }

/* ===== Dialogs on a phone: bottom sheets =============================
   A centred box at calc(100vw - 48px) leaves ~342px of a 390px screen and then
   spends 20px of each side on its own padding, with the Close button and the
   submit both somewhere off in the middle of a long scroll. Every one of these
   is a form, so the two controls that matter are "close" and "save" and both
   are pinned. Sliding up from the bottom also puts the sheet where the thumb
   already is.

   dvh, not vh: iOS' collapsing URL bar makes vh taller than the visible
   viewport, which would push the pinned actions off the bottom of the screen —
   the same reason the calendar month view uses 100dvh. */
@media (max-width: 640px) {
  /* [open] is load-bearing, not decoration. A closed <dialog> is hidden by the
     UA stylesheet's display:none, and an author `display` of any kind beats it
     — the same hazard [hidden] and .is-section-hidden carry. Without the
     attribute selector here every dialog on the page renders permanently, all
     twenty of them stacked down the phone. */
  .wealth-dialog[open],
  .wealth-dialog-wide[open] {
    width: 100%;
    max-width: 100%;
    max-height: 92dvh;
    /* A <dialog> is centred by its auto margins; this drops it to the bottom
       edge while keeping it horizontally centred. */
    margin: auto auto 0;
    padding: 0;
    border-radius: 18px 18px 0 0;
    border-bottom: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    animation: wealthSheetUp 0.22s cubic-bezier(0.32, 0.72, 0, 1);
  }

  /* The grab handle is the affordance that says "this came up from the bottom
     and goes back down" without costing a control. */
  .wealth-dialog[open]::before {
    content: "";
    flex: 0 0 auto;
    width: 36px;
    height: 4px;
    margin: 8px auto 0;
    border-radius: 999px;
    background: var(--border-accent);
  }

  .wealth-dialog .wealth-dialog-head {
    flex: 0 0 auto;
    margin: 0;
    padding: 10px 16px 12px;
    border-bottom: 1px solid var(--border);
  }
  .wealth-dialog-head h3 { font-size: 1.02rem; }
  .wealth-dialog-head p  { font-size: 0.78rem; }

  /* Everything that is not the head is the scrolling body. The forms are direct
     children of the dialog, so this reaches them without needing a wrapper
     added to twenty dialogs. */
  .wealth-dialog > *:not(.wealth-dialog-head) {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
  }

  /* Pinned to the bottom of the sheet, so "Save" is never something you have to
     scroll a long form to reach. */
  .wealth-dialog .upload-form-actions {
    position: sticky;
    bottom: calc(-14px - env(safe-area-inset-bottom));
    display: flex;
    flex-direction: column-reverse;
    gap: 8px;
    margin: 14px -16px calc(-14px - env(safe-area-inset-bottom));
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    background: var(--bg-surface);
    border-top: 1px solid var(--border);
  }
  .wealth-dialog .upload-form-actions .button,
  .wealth-dialog .upload-form-actions input[type="submit"] {
    width: 100%;
    justify-content: center;
    padding-top: 11px;
    padding-bottom: 11px;
  }

  .wealth-dialog-table-wrap { max-height: 50dvh; }

  /* A 44px target is the floor for anything typed into on a touch screen, and
     16px is the size below which iOS zooms the page on focus. */
  .wealth-dialog .form-control,
  .wealth-dialog .form-select {
    min-height: 44px;
    font-size: 16px;
  }

  .wealth-dialog .wealth-section-card { padding: 12px; }
  .wealth-dialog .wealth-checkbox-grid { grid-template-columns: 1fr; }

  /* Full-width on the sheet, matching the pinned actions. */
  .button.btn-add { width: 100%; justify-content: center; }
  .section-head { flex-wrap: wrap; gap: 10px; }
}

@keyframes wealthSheetUp {
  from { transform: translateY(12%); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .wealth-dialog { animation: none !important; }
}

@media (max-width: 1200px) {
  .wealth-page-v2 .wealth-form-grid,
  .wealth-layout-grid,
  .wealth-overview-grid,
  .wealth-checkbox-grid,
  .wealth-stat-stack,
  .wealth-property-form-grid { grid-template-columns: 1fr; }
}

/* ── SQL Page ─────────────────────────────────────────────────────────────── */

/* ── Clean Names Dialog ───────────────────────────────────────────────────── */

.search-form-spacer { flex: 1; }

.cleanup-badge {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-base); color: var(--accent);
  border-radius: 999px; font-size: 0.72rem; font-weight: 700;
  min-width: 18px; height: 18px; padding: 0 5px; margin-left: 4px;
  vertical-align: middle; line-height: 1;
}

.clean-names-dialog {
  width: min(1140px, calc(100vw - 40px));
  max-height: calc(100vh - 60px);
  border: 1px solid var(--border-accent);
  border-radius: 16px;
  background: var(--bg-surface);
  color: var(--text-primary);
  padding: 0;
  box-shadow: 0 24px 64px rgba(0,0,0,0.35);
  overflow: hidden;
}
.clean-names-dialog[open] {
  display: flex;
  flex-direction: column;
}
.clean-names-dialog::backdrop { background: rgba(0,0,0,0.48); backdrop-filter: blur(3px); }
.clean-names-dialog-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 22px 26px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.clean-names-dialog-head h2 { margin: 0; font-size: 1.3rem; }
.clean-names-dialog-head .page-subtitle { margin: 4px 0 0; font-size: 0.85rem; }
.clean-names-close {
  background: none; border: 1px solid var(--border); border-radius: 6px;
  color: var(--text-muted); cursor: pointer; font-size: 1rem; font-weight: 400;
  line-height: 1; padding: 5px 10px; min-height: auto; box-shadow: none;
  flex-shrink: 0; transition: all 0.15s;
}
.clean-names-close:hover { color: var(--text-primary); border-color: var(--border-accent); transform: none; box-shadow: none; }
.clean-names-dialog-body { flex: 1; overflow-y: auto; padding: 20px 26px 26px; }
.clean-names-section-head {
  font-family: var(--font-display); font-size: 1.05rem; font-weight: 600;
  margin: 0 0 12px; color: var(--text-secondary);
  padding-bottom: 8px; border-bottom: 1px solid var(--border);
}
.clean-names-hint { font-size: 0.78rem; color: var(--text-muted); font-weight: 400; font-family: var(--font-body); }
.clean-names-empty { margin: 0 0 20px; font-size: 0.88rem; }
.clean-names-bulk-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.clean-names-bulk-actions { display: flex; align-items: center; gap: 10px; }
.clean-names-bulk-submit { display: flex; justify-content: flex-end; margin-top: 14px; }
.clean-name-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; margin-bottom: 24px; }
.clean-name-card {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 16px; display: flex; flex-direction: column; gap: 10px;
}
.clean-name-group-label { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-muted); }
.clean-name-option { display: flex; align-items: center; gap: 8px; font-size: 0.86rem; padding: 2px 0; }

/* ── Mappings hub quick-links ─────────────────────────────────────────────── */

.mappings-hub-bar {
  display: flex; align-items: center; gap: 10px; margin-bottom: 18px;
  padding: 12px 16px;
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: 10px;
}
.mappings-hub-bar a, .mappings-hub-bar button {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.82rem; font-weight: 600; letter-spacing: 0.03em;
}

/* ── Upload bank config footer ────────────────────────────────────────────── */

.upload-bank-config-link {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 28px; padding: 14px 18px;
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: 10px;
  font-size: 0.88rem; color: var(--text-secondary);
  text-decoration: none; transition: border-color 0.18s, box-shadow 0.18s;
}
.upload-bank-config-link:hover {
  border-color: var(--border-accent); box-shadow: 0 2px 12px var(--accent-dim);
  color: var(--text-primary);
}
.upload-bank-config-link span { color: var(--accent); font-size: 0.9rem; font-weight: 600; }
.upload-bank-config-link .upload-bank-config-link-hint { color: var(--text-muted); font-size: 0.82rem; font-weight: 400; }

.sql-page .page-head { margin-bottom: 20px; }

.sql-layout {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 20px;
  align-items: start;
}

.sql-sidebar {
  display: flex;
  flex-direction: column;
  gap: 0;
  border-radius: var(--radius);
  overflow: hidden;
  position: sticky;
  top: 68px;
  max-height: calc(100vh - 88px);
  overflow-y: auto;
}

.sql-sidebar-section {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 12px;
}

.sql-sidebar-section:last-child { margin-bottom: 0; }

.sql-sidebar-section--saved .sql-saved-list {
  max-height: 220px;
  overflow-y: auto;
}

.sql-sidebar-header {
  padding: 9px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.sql-sidebar-header h3 {
  margin: 0;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.sql-icon-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 1rem;
  padding: 0 2px;
  line-height: 1;
  transition: color 0.15s;
  min-height: auto;
  box-shadow: none;
}

.sql-icon-btn:hover { color: var(--accent); transform: none; box-shadow: none; }

.sql-schema-browser {
  max-height: 380px;
  overflow-y: auto;
  padding: 4px 0;
}

.sql-schema-loading,
.sql-schema-error {
  display: block;
  padding: 10px 14px;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.sql-schema-error { color: var(--danger); }

.sql-schema-group { border-bottom: 1px solid var(--border); }
.sql-schema-group:last-child { border-bottom: none; }

.sql-schema-group-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 10px;
  cursor: pointer;
  user-select: none;
  background: var(--bg-elevated);
}

.sql-schema-group-header:hover { filter: brightness(1.06); }

.sql-schema-group-arrow { font-size: 0.6rem; color: var(--text-muted); width: 10px; }

.sql-schema-group-name {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  flex: 1;
}

.sql-schema-group-header--open .sql-schema-group-name { color: var(--accent); }

.sql-schema-group-body { padding-left: 8px; }

.sql-schema-table { border-bottom: 1px solid var(--border); }
.sql-schema-table:last-child { border-bottom: none; }

.sql-schema-table-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  cursor: pointer;
  transition: background 0.12s;
}

.sql-schema-table-header:hover { background: var(--bg-elevated); }

.sql-schema-arrow { font-size: 0.6rem; color: var(--text-muted); flex-shrink: 0; width: 10px; }

.sql-schema-table-name {
  background: none;
  border: none;
  color: var(--text-primary);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0;
  cursor: pointer;
  flex: 1;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-height: auto;
  box-shadow: none;
}

.sql-schema-table-name:hover { color: var(--accent); transform: none; box-shadow: none; }

.sql-schema-col-count {
  font-size: 0.7rem;
  color: var(--text-muted);
  background: var(--bg-base);
  border-radius: 3px;
  padding: 1px 5px;
  flex-shrink: 0;
}

.sql-schema-cols {
  border-top: 1px solid var(--border);
  background: var(--bg-elevated);
}

.sql-schema-col {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  background: none;
  border: none;
  padding: 4px 10px 4px 26px;
  cursor: pointer;
  transition: background 0.1s;
  text-align: left;
  min-height: auto;
  box-shadow: none;
}

.sql-schema-col:hover { background: var(--bg-row-hover); transform: none; box-shadow: none; }

.sql-schema-col-name {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.78rem;
  color: var(--text-secondary);
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sql-schema-col-type {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.76rem;
  color: var(--text-muted);
  flex-shrink: 0;
}

/* ── Saved Queries ────────────────────────────────────────────────────────── */

.sql-saved-list { padding: 4px 0; }

.sql-saved-empty {
  padding: 10px 14px;
  color: var(--text-muted);
  font-size: 0.82rem;
  margin: 0;
}

.sql-saved-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  transition: background 0.12s;
}

.sql-saved-item:hover { background: var(--bg-elevated); }

.sql-saved-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.sql-saved-name {
  background: none;
  border: none;
  color: var(--accent);
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 600;
  padding: 0;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 100%;
  min-height: auto;
  box-shadow: none;
}

.sql-saved-name:hover { text-decoration: underline; transform: none; box-shadow: none; }

.sql-saved-desc {
  font-size: 0.78rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sql-saved-delete {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
  padding: 2px 5px;
  cursor: pointer;
  border-radius: 4px;
  flex-shrink: 0;
  transition: color 0.15s, background 0.15s;
  min-height: auto;
  box-shadow: none;
}

.sql-saved-delete:hover {
  color: var(--danger);
  background: rgba(180, 44, 26, 0.10);
  transform: none;
  box-shadow: none;
}

/* ── SQL Main Panel ───────────────────────────────────────────────────────── */

.sql-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.sql-editor-panel {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.sql-editor-form { display: flex; flex-direction: column; }
.sql-editor-wrap { border-bottom: 1px solid var(--border); }
.sql-editor-wrap .CodeMirror { min-height: 180px; }

.sql-editor-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  gap: 8px;
}

.sql-editor-actions-left { display: flex; align-items: center; gap: 8px; }

.sql-hint-text {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-family: "JetBrains Mono", monospace;
}

.sql-save-form-wrap {
  border-top: 1px solid var(--border-accent);
  background: var(--accent-dim);
  padding: 12px 14px;
}

.sql-save-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

.sql-save-row .form-input {
  flex: 1;
  min-width: 120px;
  background: var(--bg-base);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 0.875rem;
  padding: 6px 10px;
  outline: none;
  transition: border-color 0.2s;
}

.sql-save-row .form-input:focus { border-color: var(--border-accent); }

.sql-results-panel { border-radius: var(--radius); overflow: hidden; }

#sql-results {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.sql-results-empty {
  padding: 20px 16px;
  color: var(--text-muted);
  font-size: 0.875rem;
  text-align: center;
}

.sql-result-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 14px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
}

.sql-result-status {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 4px;
  flex-shrink: 0;
}

.sql-result-status.ok    { background: var(--accent-dim); color: var(--accent); }
.sql-result-status.error { background: rgba(180, 44, 26, 0.12); color: var(--danger); }

.sql-result-query {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.78rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

.sql-error-box {
  padding: 14px 16px;
  color: var(--danger);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.82rem;
  white-space: pre-wrap;
  word-break: break-all;
}

.sql-empty-result { padding: 14px 16px; color: var(--text-muted); font-size: 0.875rem; }

.sql-result-table-wrap { overflow-x: auto; max-height: 480px; overflow-y: auto; }

.sql-result-table {
  width: 100%;
  border-collapse: collapse;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.8rem;
}

.sql-result-table thead { position: sticky; top: 0; z-index: 1; }

.sql-result-table th {
  background: var(--bg-elevated);
  color: var(--text-secondary);
  font-weight: 600;
  padding: 7px 12px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.sql-result-table td {
  padding: 5px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--text-primary);
  max-width: 360px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sql-result-table tbody tr:hover td { background: var(--bg-row-hover); }
.sql-result-table em.null { color: var(--text-muted); font-style: italic; }

@media (max-width: 960px) {
  .sql-layout { grid-template-columns: 1fr; }
  .sql-sidebar { position: static; max-height: none; flex-direction: row; overflow-x: auto; gap: 12px; }
  .sql-sidebar-section { min-width: 220px; margin-bottom: 0; }
}

/* ── Bank Profile / Upload Bank Selection ─────────────────────────────────── */

.bank-select-section {
  margin-bottom: 20px;
  padding: 16px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.bank-select-row {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  align-items: flex-end;
}

.bank-select-row > div {
  flex: 1;
  min-width: 200px;
}

.bank-select-row .form-select {
  width: 100%;
}

.bank-badge-row {
  margin-bottom: 16px;
}

.bank-badge {
  display: inline-block;
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  color: var(--accent);
  border-radius: var(--radius);
  padding: 5px 12px;
  font-size: 0.84rem;
}

.bp-add-account-form {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-top: 8px;
}

.bank-select-col {
  flex: 1;
  min-width: 200px;
}

.bank-status-row {
  margin-top: 10px;
  font-size: 0.85rem;
  display: flex;
  align-items: center;
  gap: 10px;
}

.bank-badge-ok {
  display: inline-block;
  background: #e6f4ea;
  border: 1px solid #a8d5b5;
  color: #2a6e3f;
  border-radius: var(--radius);
  padding: 3px 10px;
  font-size: 0.82rem;
}

.bank-badge-warn {
  display: inline-block;
  background: #fff8e1;
  border: 1px solid #f0cc6a;
  color: #7a5c00;
  border-radius: var(--radius);
  padding: 3px 10px;
  font-size: 0.82rem;
}

.bank-badge-link {
  font-size: 0.82rem;
  color: var(--accent);
  text-decoration: underline;
}

.new-bank-row {
  margin-top: 12px;
}

.new-bank-input-row {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 6px;
}

.new-bank-input-row .form-control {
  flex: 1;
}

.bp-row-unconfigured td {
  opacity: 0.75;
}

.bp-inline-badge {
  font-size: 0.75rem;
  padding: 2px 7px;
  margin-left: 6px;
  vertical-align: middle;
}

.bp-db-accounts {
  margin-bottom: 16px;
  padding: 12px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.bp-db-accounts-label {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.bp-db-accounts-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.bp-acct-chip {
  background: var(--bg);
  border: 1px solid var(--border-accent);
  color: var(--accent);
  border-radius: var(--radius);
  padding: 4px 12px;
  font-size: 0.82rem;
  cursor: pointer;
  transition: background 0.15s;
}

.bp-acct-chip:hover {
  background: var(--accent-dim);
}

.bp-acct-chip-added {
  background: var(--bg-elevated);
  border-color: var(--border);
  color: var(--text-muted);
  cursor: default;
}

.bp-index-table .sample-badge {
  font-size: 0.78rem;
}

/* ── Auth / Login ────────────────────────────────────────────────────────── */

.login-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: var(--bg-base);
}

.login-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 2.5rem 2rem;
  width: 100%;
  max-width: 400px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.07);
}

.login-header {
  text-align: center;
  margin-bottom: 1.75rem;
}

.login-header strong {
  display: block;
  font-size: 1.6rem;
  font-family: var(--font-serif);
  color: var(--text-primary);
  margin-bottom: 0.4rem;
}

.login-header p {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin: 0;
}

.login-card .field {
  margin-bottom: 1rem;
}

.login-card .field label {
  display: block;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-secondary);
  margin-bottom: 0.35rem;
}

.login-card .field input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-base);
  color: var(--text-primary);
  font-size: 0.95rem;
}

.login-card .actions {
  margin-top: 1.5rem;
}

.login-card .actions input[type="submit"] {
  width: 100%;
  padding: 0.65rem;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 6px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
}

.login-card .actions input[type="submit"]:hover {
  opacity: 0.9;
}

/* ── Nav user / sign-out ─────────────────────────────────────────────────── */

.nav-user {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-left: auto;
  margin-right: 1rem;
}

.nav-username {
  font-size: 0.85rem;
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.15s ease;
}

a.nav-username:hover {
  color: var(--accent);
}

.account-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 480px));
  gap: 24px;
  align-items: start;
}

.btn-signout {
  background: none;
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 0.3rem 0.65rem;
  font-size: 0.82rem;
  color: var(--text-secondary);
  cursor: pointer;
}

.btn-signout:hover {
  background: var(--bg-elevated);
  color: var(--text-primary);
}

/* ── Admin table ─────────────────────────────────────────────────────────── */

.btn-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  cursor: pointer;
  font-size: inherit;
  text-decoration: underline;
}

.btn-danger {
  color: var(--danger, #c0392b);
}

.field-checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.hint {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-weight: normal;
}

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.5rem;
}

/* ── Admin Users ──────────────────────────────────────────────────────────── */

.admin-user-table { width: 100%; }

.user-cell {
  display: flex;
  align-items: center;
  gap: 12px;
}

.user-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent-dim);
  border: 1.5px solid var(--border-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--accent);
  flex-shrink: 0;
  letter-spacing: 0.04em;
}

.user-avatar::after { content: attr(data-initials); }

.user-name-text {
  font-weight: 600;
  color: var(--text-primary);
  font-size: 0.9rem;
  line-height: 1.35;
}

.user-email-text {
  font-size: 0.79rem;
  color: var(--text-muted);
  line-height: 1.35;
}

.role-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 9px;
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

.role-badge--admin {
  background: var(--accent-dim);
  color: var(--accent);
  border: 1px solid var(--border-accent);
}

.role-badge--user {
  background: var(--bg-elevated);
  color: var(--text-secondary);
  border: 1px solid var(--border);
}

.role-badge--calendar {
  background: rgba(2, 119, 189, 0.1);
  color: var(--accent-amber);
  border: 1px solid var(--border-accent);
}

.user-date {
  color: var(--text-secondary);
  font-size: 0.84rem;
  white-space: nowrap;
}

.user-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  align-items: center;
}

.user-actions form { display: contents; }

.you-badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  background: var(--bg-elevated);
  color: var(--text-muted);
  border: 1px solid var(--border);
  vertical-align: middle;
  margin-left: 5px;
}

.admin-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 56px 24px;
  color: var(--text-muted);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  text-align: center;
}

.admin-empty-state p { margin: 0; font-size: 0.92rem; }

/* Admin form */
.admin-back-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.15s ease;
}

.admin-back-link:hover { color: var(--accent); }

.admin-form-wrap { max-width: 480px; }

.admin-form-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 28px 32px;
  box-shadow: 0 1px 12px rgba(0,0,0,0.05);
}

.admin-form-section-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  margin-bottom: 14px;
}

.admin-form-card .field {
  margin-bottom: 16px;
}

.admin-form-card .field label {
  display: block;
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 6px;
}

.admin-form-card .field input[type="text"],
.admin-form-card .field input[type="email"],
.admin-form-card .field input[type="password"] {
  width: 100%;
}

.admin-form-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 20px 0 18px;
}

.admin-checkbox-field {
  padding: 12px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  gap: 12px !important;
  align-items: flex-start !important;
  margin-bottom: 16px;
}

.admin-checkbox-field input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
  flex-shrink: 0;
  margin-top: 2px;
}

.admin-checkbox-field label {
  font-size: 0.88rem !important;
  font-weight: 600 !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
  color: var(--text-primary) !important;
  cursor: pointer;
  margin: 0 !important;
}

.admin-form-card .actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.admin-form-card .actions input[type="submit"] {
  background: var(--accent);
  color: var(--bg-base);
  font-weight: 700;
  border: none;
  box-shadow: 0 2px 10px var(--accent-dim);
}

.admin-form-card .actions input[type="submit"]:hover {
  filter: brightness(1.08);
  box-shadow: 0 4px 18px var(--accent-dim);
}

.admin-form-card .actions a {
  color: var(--text-muted);
  font-size: 0.84rem;
  text-decoration: none;
  padding: 8px 12px;
  border-radius: var(--radius);
  transition: color 0.15s ease, background 0.15s ease;
}

.admin-form-card .actions a:hover {
  color: var(--text-primary);
  background: var(--bg-elevated);
}

/* ── Admin Nav Badge (top bar) ───────────────────────────────────────────── */

.nav-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: 8px;
  font-size: 0.62rem;
  font-weight: 700;
  vertical-align: middle;
  margin-left: 3px;
}

/* ── Admin Shell / Sidebar ───────────────────────────────────────────────── */

.admin-shell {
  display: flex;
  min-height: calc(100vh - 54px);
}

.admin-sidebar {
  width: 210px;
  flex-shrink: 0;
  background: var(--bg-surface);
  border-right: 1px solid var(--border);
  padding: 20px 10px;
  position: sticky;
  top: 54px;
  height: calc(100vh - 54px);
  overflow-y: auto;
}

.admin-sidebar-label {
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  padding: 0 10px;
  margin: 0 0 6px;
}

.admin-sidebar-nav {
  list-style: none;
  margin: 0;
  padding: 0;
}

.admin-sidebar-nav li { margin: 1px 0; }

.admin-sidebar-link {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border-radius: var(--radius);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 400;
  transition: background 0.12s, color 0.12s;
}

.admin-sidebar-link:hover {
  background: var(--bg-elevated);
  color: var(--text-primary);
}

.admin-sidebar-link.active {
  background: var(--accent-dim);
  color: var(--accent);
  font-weight: 600;
}

.admin-sidebar-link svg { flex-shrink: 0; opacity: 0.8; }
.admin-sidebar-link.active svg { opacity: 1; }

.admin-sidebar-group {
  border: none;
}

.admin-sidebar-group-toggle {
  list-style: none;
  cursor: pointer;
  width: 100%;
}
.admin-sidebar-group-toggle::-webkit-details-marker { display: none; }
.admin-sidebar-group-toggle::marker { display: none; }

.admin-sidebar-chevron {
  margin-left: auto;
  opacity: 0.5;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}
details[open] .admin-sidebar-chevron {
  transform: rotate(180deg);
}

.admin-sidebar-subnav {
  list-style: none;
  margin: 2px 0 0 24px;
  padding: 0;
}

.admin-sidebar-subnav li { margin: 1px 0; }

.admin-sidebar-sublink {
  display: block;
  padding: 5px 10px;
  border-radius: var(--radius);
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.8rem;
  font-weight: 400;
  transition: background 0.12s, color 0.12s;
}
.admin-sidebar-sublink:hover { background: var(--bg-elevated); color: var(--text-primary); }
.admin-sidebar-sublink.active { background: var(--accent-dim); color: var(--accent); font-weight: 600; }

@media (max-width: 900px) {
  .admin-sidebar-subnav { display: none; }
  .admin-sidebar--expanded .admin-sidebar-subnav {
    display: block;
    margin-left: 4px;
  }
}

.admin-sidebar-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: 9px;
  font-size: 0.67rem;
  font-weight: 700;
  margin-left: auto;
}

.admin-content {
  flex: 1;
  min-width: 0;
  padding: 28px 32px;
}

@media (max-width: 900px) {
  .admin-sidebar { width: 48px; padding: 16px 6px; }
  .admin-sidebar-label,
  .admin-sidebar-link span,
  .admin-sidebar-badge { display: none; }
  .admin-sidebar-link { justify-content: center; padding: 10px; }
  .admin-content { padding: 18px 14px; }

  .admin-sidebar--expanded { width: 190px; }
  .admin-sidebar--expanded .admin-sidebar-group-toggle span { display: inline; }
  .admin-sidebar--expanded .admin-sidebar-group-toggle { justify-content: flex-start; }
}

/* ── Page Guide ───────────────────────────────────────────────────────────── */

.guide-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.52);
  z-index: 9000;
  align-items: center;
  justify-content: center;
  padding: 20px;
  backdrop-filter: blur(2px);
  animation: guideBackdropIn 0.2s ease;
}

.guide-overlay--visible {
  display: flex;
}

@keyframes guideBackdropIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.guide-modal {
  background: var(--bg-surface);
  border: 1px solid var(--border-accent);
  border-radius: 12px;
  width: 100%;
  max-width: 520px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.32);
  animation: guideModalIn 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
  overflow: hidden;
}

@keyframes guideModalIn {
  from { opacity: 0; transform: scale(0.94) translateY(8px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

.guide-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 22px 24px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
}

.guide-header-left { flex: 1; }

.guide-eyebrow {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
  margin-bottom: 4px;
}

.guide-title {
  font-family: var(--font-display);
  font-size: 1.45rem;
  font-weight: 700;
  color: var(--text-primary);
  margin: 0;
  line-height: 1.2;
}

.guide-close {
  width: 32px;
  height: 32px;
  min-height: 32px;
  padding: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-muted);
  cursor: pointer;
  flex-shrink: 0;
  box-shadow: none;
  transition: all 0.15s ease;
}

.guide-close:hover {
  color: var(--text-primary);
  border-color: var(--border-accent);
  background: var(--bg-surface);
  transform: none;
  box-shadow: none;
}

.guide-body {
  padding: 20px 24px;
  display: flex;
  flex-direction: column;
  gap: 0;
  max-height: 60vh;
  overflow-y: auto;
}

.guide-step {
  display: flex;
  gap: 14px;
  padding: 13px 0;
  border-bottom: 1px solid var(--border);
}

.guide-step:last-child { border-bottom: none; }

.guide-step-num {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  color: var(--accent);
  font-size: 0.68rem;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-top: 1px;
  letter-spacing: 0;
}

.guide-step-text {
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--text-secondary);
}

.guide-step-text strong {
  display: block;
  font-weight: 700;
  font-size: 0.88rem;
  color: var(--text-primary);
  margin-bottom: 2px;
}

.guide-footer {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 24px;
  border-top: 1px solid var(--border);
  background: var(--bg-elevated);
}

.guide-got-it {
  background: var(--accent);
  color: var(--bg-base);
  font-weight: 700;
  border: none;
  min-height: 36px;
  padding: 8px 22px;
  box-shadow: 0 2px 10px var(--accent-dim);
}

.guide-got-it:hover {
  filter: brightness(1.08);
  box-shadow: 0 4px 18px var(--accent-dim);
  transform: none;
}

.guide-footer-hint {
  font-size: 0.77rem;
  color: var(--text-muted);
  font-style: italic;
}

.nav-guide-btn {
  width: 28px;
  height: 28px;
  min-height: 28px;
  padding: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-muted);
  cursor: pointer;
  box-shadow: none;
  transition: all 0.18s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.nav-guide-btn:hover {
  color: var(--accent);
  border-color: var(--border-accent);
  background: var(--accent-dim);
  transform: none;
  box-shadow: none;
}

/* ── Bill Planning ──────────────────────────────────────────── */
.bill-summary-strip {
  display: flex; flex-wrap: wrap; gap: 12px 24px; align-items: center;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 20px; margin-bottom: 16px;
}
.bill-summary-stat { display: flex; flex-direction: column; gap: 2px; }
.bill-summary-stat--positive .bill-summary-label { color: var(--accent); }
.bill-summary-stat--positive strong { color: var(--accent); }
/* Your own share of what's upcoming — the figure that actually leaves your
   account, so it reads as the headline next to the gross total. */
.bill-summary-stat--yours .bill-summary-label { color: var(--text-secondary); }
.bill-summary-stat--yours strong { color: var(--text-primary); }
.bill-summary-label { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }
.bill-summary-stat strong { font-size: 1.3rem; font-family: var(--font-body); font-weight: 600; color: var(--text-primary); }

.bill-group { margin-bottom: 20px; }
.bill-group-heading {
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-muted); margin: 0 0 10px; display: flex; align-items: center; gap: 8px;
}
.bill-group-heading--overdue { color: var(--danger); }
.bill-group-heading--soon    { color: var(--accent-amber); }
.bill-group-count {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 12px; padding: 1px 8px; font-size: 0.72rem; color: var(--text-muted);
}

.bill-cards { display: flex; flex-direction: column; gap: 10px; }

/* A 2-column GRID, not a flex row: content | actions on the first row, and the
   owes card / pay links / notes spanning BOTH columns underneath. As a flex row
   those three were stuck inside the content column, sharing its width with the
   action buttons — which is what made the owes card and the buttons crowd each
   other. Spanning them is the fix; the buttons stay a normal horizontal row.
   `minmax(0, 1fr)`, never bare `1fr` — same reason as the calendar's 7-column
   tracks: bare 1fr floors at the content's min width, so a long bill name would
   push the actions column off the card instead of ellipsising. */
.bill-card {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: start; column-gap: 16px;
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 18px; transition: border-color 0.15s, box-shadow 0.15s;
}
.bill-card > .bill-split-row,
.bill-card > .bill-pay-row,
.bill-card > .bill-card-notes { grid-column: 1 / -1; }
.bill-card:hover { border-color: var(--border-accent); box-shadow: 0 2px 12px var(--accent-dim); }
.bill-card--overdue  { border-left: 3px solid var(--danger); }
.bill-card--due_soon { border-left: 3px solid var(--accent-amber); }
.bill-card--upcoming { border-left: 3px solid var(--border); }

.bill-card-main { grid-column: 1; min-width: 0; }
.bill-card-top  { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }

.bill-card-name-wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.bill-card-name   { font-weight: 600; font-size: 0.95rem; color: var(--text-primary); }
.bill-card-amount { font-family: var(--font-body); font-size: 1.05rem; font-weight: 700; color: var(--text-primary); flex-shrink: 0; }

.bill-cat-badge {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 4px;
  font-size: 0.70rem; font-weight: 700; padding: 2px 7px; color: var(--text-muted);
}
.bill-freq-badge {
  background: var(--accent-dim); border: 1px solid var(--border-accent); border-radius: 4px;
  font-size: 0.70rem; font-weight: 700; padding: 2px 7px; color: var(--accent);
}

.bill-payable-badge {
  background: transparent; border-color: var(--danger); color: var(--danger);
}

.bill-reminder-badge {
  background: var(--surface-alt, rgba(127, 127, 127, 0.10)); border: 1px solid var(--border); border-radius: 4px;
  font-size: 0.70rem; font-weight: 700; padding: 2px 7px; color: var(--text-muted); white-space: nowrap;
}
.bill-reminder-toggle {
  display: flex; align-items: center; gap: 8px; font-size: 0.9rem; cursor: pointer; user-select: none;
}
.bill-reminder-toggle input[type="checkbox"] { accent-color: var(--accent); width: 16px; height: 16px; cursor: pointer; }

.bill-card-meta { display: flex; align-items: center; gap: 16px; font-size: 0.82rem; margin-bottom: 6px; }
.bill-due          { color: var(--text-muted); }
.bill-due.overdue  { color: var(--danger); font-weight: 600; }
.bill-due.due-soon { color: var(--accent-amber); font-weight: 600; }
.bill-payee        { color: var(--text-muted); }

/* align-items: flex-start, not center — the people list grows a line per person,
   and centring floated the icon and the trailing button against the middle of a
   multi-line block. */
.bill-split-row {
  display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 12px; font-size: 0.82rem; margin-top: 6px;
}
/* Never shrink to fit beside the list — wrap to its own line instead. */
.bill-split-row > .button { margin-left: auto; flex-shrink: 0; align-self: center; }
.bill-split-icon   { color: var(--accent); font-size: 1rem; line-height: 1.5; }
.bill-split-name   { color: var(--text-secondary); }
/* A bill can be split with any number of people — one line each, so the row
   grows downward instead of running off the side of the card. */
.bill-split-people { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
.bill-split-person { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* The bottom line of the owes card is your own position, so it reads as a total
   rather than as one more person in the list. */
.bill-split-yours  {
  color: var(--text-muted); font-size: 0.78rem;
  border-top: 1px solid var(--border); margin-top: 2px; padding-top: 5px;
}
/* ── Where to pay (links + attachments) ────────────────────────────────── */
.bill-pay-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 8px; font-size: 0.8rem;
}
.bill-pay-label {
  font-size: 0.64rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--text-muted);
}
.bill-pay-link, .bill-pay-file {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg-elevated);
  color: var(--text-secondary); font-weight: 600; text-decoration: none;
  max-width: 15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bill-pay-link:hover, .bill-pay-file:hover { color: var(--accent); border-color: var(--border-accent); }

.bill-file-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.bill-file-item {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 6px 9px; border: 1px solid var(--border); border-radius: 7px;
  background: var(--bg-elevated); font-size: 0.82rem;
}
.bill-file-name { font-weight: 600; color: var(--text-primary); }
.bill-file-size { color: var(--text-muted); font-size: 0.76rem; }
.bill-file-remove {
  margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
  color: var(--danger); font-size: 0.76rem; font-weight: 700; cursor: pointer;
}

.bill-split-type {
  font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); margin-left: 5px;
}

/* ── Split editor (one row per person) ─────────────────────────────────── */
.split-editor-add { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.split-editor-add select { max-width: 320px; }

.split-rows { display: flex; flex-direction: column; gap: 6px; }

.split-row {
  display: grid; align-items: center; gap: 6px;
  grid-template-columns: minmax(0, 1.4fr) 8.5rem 6.5rem minmax(0, 1.4fr) 5.5rem auto;
}
.split-row .form-control { min-height: 0; padding: 6px 8px; font-size: 0.82rem; }
.split-row-value[hidden] { display: none; }
.split-row-computed {
  font-size: 0.82rem; font-weight: 700; color: var(--text-primary); text-align: right; white-space: nowrap;
}
.split-row-remove { padding: 0 9px; }

.split-editor-empty  { margin: 6px 0 0; font-size: 0.82rem; }
.split-editor-summary {
  margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 0.84rem; color: var(--text-secondary);
}

@media (max-width: 700px) {
  /* Stacked: a 6-column row is unusable at phone width, and every control has
     to stay reachable because there is no separate mobile markup. */
  .split-row {
    grid-template-columns: 1fr auto;
    padding: 8px; border: 1px solid var(--border); border-radius: 8px;
  }
  .split-row-name     { grid-column: 1; }
  .split-row-remove   { grid-column: 2; grid-row: 1; }
  .split-row-type,
  .split-row-value,
  .split-row-email    { grid-column: 1 / -1; }
  .split-row-computed { grid-column: 1 / -1; text-align: left; }
}

/* ── Bill contributors (the saved address book) ────────────────────────── */
.bill-contributors { margin-top: 18px; }
/* Block, not flex — see the note on `.wealth-disclosure summary`: a flex
   summary silently stops opening its <details> on every iOS browser. */
.bill-contributors-summary {
  cursor: pointer; font-weight: 700; font-size: 0.9rem; color: var(--text-secondary);
  list-style: none;
}
.bill-contributors-summary::-webkit-details-marker { display: none; }
.bill-contributors-summary::marker { content: ""; }
.bill-contributors-summary::before { content: "▶"; font-size: 0.65rem; margin-right: 8px; }
details[open] > .bill-contributors-summary::before { content: "▼"; }

.bill-contributor-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
/* The Save form and the Remove button-form are siblings (nesting forms is
   invalid HTML), so the row is a flex pair and the FIELDS carry the grid. */
.bill-contributor-row { display: flex; align-items: center; gap: 6px; }
.bill-contributor-row form { margin: 0; }
.bill-contributor-fields,
.bill-contributor-row--new {
  display: grid; align-items: center; gap: 6px; flex: 1; min-width: 0;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.4fr) 8.5rem 7rem auto;
}
.bill-contributor-fields .form-control,
.bill-contributor-row--new .form-control { min-height: 0; padding: 6px 8px; font-size: 0.82rem; }
.bill-contributor-row--new { padding-top: 10px; border-top: 1px solid var(--border); }

@media (max-width: 700px) {
  .bill-contributor-row { flex-wrap: wrap; }
  .bill-contributor-fields,
  .bill-contributor-row--new { grid-template-columns: 1fr 1fr; }
  .bill-contributor-fields > *:first-child,
  .bill-contributor-row--new > *:first-child { grid-column: 1 / -1; }
}

.bill-split-badge  { font-size: 0.72rem; font-weight: 700; border-radius: 4px; padding: 2px 8px; }
.bill-split-badge.received { background: var(--accent-dim); color: var(--accent); border: 1px solid var(--border-accent); }
.bill-split-badge.pending  { background: #fff3cd; color: #856404; border: 1px solid #ffc107; }
[data-theme="dark"] .bill-split-badge.pending { background: #3d2f00; color: #ffc107; border-color: #7a5c00; }

.bill-card-notes { font-size: 0.80rem; color: var(--text-muted); margin: 6px 0 0; font-style: italic; }

/* Buttons stay a horizontal row in their own grid column — stacking them into
   a vertical rail was tried and looked worse, and it was never the real
   problem: the crowding came from the owes card sharing the content column
   with them, which .bill-card's grid now solves by spanning it. */
.bill-card-actions {
  display: flex; flex-direction: row; gap: 6px; align-items: flex-start;
  flex-shrink: 0; grid-column: 2; grid-row: 1;
}

.bill-split-status { font-size: 0.75rem; font-weight: 600; }
.bill-split-status.received { color: var(--accent); }
.bill-split-status.pending  { color: var(--accent-amber); }

.bill-paid-history { margin-top: 20px; }
/* Block, not flex — see the note on `.wealth-disclosure summary`. */
.bill-paid-summary {
  cursor: pointer; font-size: 0.82rem; font-weight: 700; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.07em; padding: 8px 0;
  list-style: none;
}
.bill-paid-summary::-webkit-details-marker { display: none; }
.bill-paid-summary::marker { content: ""; }
.bill-paid-summary::before { content: "▶"; font-size: 0.65rem; margin-right: 6px; }
details[open] .bill-paid-summary::before { content: "▼"; }
.bill-paid-history .wealth-mini-table { font-size: 0.82rem; }

.text-danger { color: var(--danger); }

/* A wealth-manager panel the user switched off in Account → Wealth Manager
   Sections. Both selectors on purpose: the panels carry `display: grid/flex`
   from .wealth-panel, so the bare utility class alone would lose to it. */
.is-section-hidden,
.wealth-panel.is-section-hidden { display: none; }

/* The opt-in list itself, on the account page. */
.acct-section-toggles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 4px 14px;
  margin-bottom: 16px;
}
.acct-section-toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 2px;
  min-height: 40px; /* comfortable tap target on a phone */
  font-size: 0.88rem;
  color: var(--text-secondary);
  cursor: pointer;
}
.acct-section-toggle input { accent-color: var(--accent); width: 17px; height: 17px; flex-shrink: 0; }
.acct-section-toggle:hover { color: var(--text-primary); }

/* ── Mobile filter sheet (shared) ──────────────────────────────────────────
   A page's existing filter controls are PROMOTED into a full-screen sheet on
   phones, reached from a floating button — the markup is never duplicated for
   mobile, so every control keeps its single id and the page's filter JS is
   untouched. Used by the transaction sheet and the dashboard; each page adds
   its own rules for how its controls stack INSIDE .filter-sheet.

   Markup contract:
     <div class="filter-sheet" id="...">          the existing controls
       <div class="filter-sheet-head">…Done…</div>
     <div class="filter-sheet-backdrop" id="…">
     <button class="filter-fab" …><svg/><span class="filter-fab-badge">
   JS contract: toggle .is-open on the sheet and backdrop together. */

/* Inert on desktop — the controls sit inline exactly as they always have. */
.filter-sheet-head,
.filter-sheet-backdrop,
.filter-fab { display: none; }

@media (max-width: 700px) {
  .filter-sheet {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    background: var(--bg-base);
    padding: 0 14px calc(20px + env(safe-area-inset-bottom));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    transform: translateY(100%);
    visibility: hidden;
    transition: transform 0.24s ease, visibility 0.24s ease;
  }
  .filter-sheet.is-open { transform: translateY(0); visibility: visible; }

  .filter-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    position: sticky;
    top: 0;
    z-index: 2;
    padding: calc(12px + env(safe-area-inset-top)) 0 12px;
    margin-bottom: 4px;
    background: var(--bg-base);
    border-bottom: 1px solid var(--border);
  }
  .filter-sheet-head strong { font-size: 1.05rem; }
  .filter-sheet-head .button { min-height: 40px; padding: 0 18px; }

  .filter-sheet-backdrop {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0.2s ease;
  }
  .filter-sheet-backdrop.is-open { opacity: 1; visibility: visible; }

  /* Centred and thumb-reachable, accent-tinted so it tracks whichever colour
     the user picked in Account → Appearance. */
  .filter-fab {
    display: inline-flex;
    position: fixed;
    left: 50%;
    bottom: calc(18px + env(safe-area-inset-bottom));
    transform: translateX(-50%);
    z-index: 900;
    width: 56px;
    height: 56px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--accent);
    color: var(--on-accent);
    align-items: center;
    justify-content: center;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);
    cursor: pointer;
    transition: bottom 0.24s ease, opacity 0.2s ease, transform 0.1s ease;
  }
  .filter-fab:active { transform: translateX(-50%) scale(0.94); }
  .filter-fab[hidden] { display: none; }

  /* ── Open state: the button becomes the ✕, in its own place ──────────────
     Closing was only ever "Done" in the sheet's top corner or a tap on the
     backdrop — the opposite end of the screen from the thumb that opened it,
     and nothing at all where the button had just been. It stays put and turns
     into the close, which is exactly what .nav-fab--open does for the drawer.
     Driven off aria-expanded, which both sheet setters already toggle, so this
     needs no JS on either page and cannot fall out of step with the sheet. */
  .filter-fab[aria-expanded="true"] {
    /* The sheet is 1000. Below it the button is simply covered. */
    z-index: 1010;
    background: var(--bg-elevated);
    color: var(--text-primary);
    border: 1px solid var(--border-accent);
  }
  .filter-fab[aria-expanded="true"] svg,
  .filter-fab[aria-expanded="true"] .filter-fab-badge { display: none; }
  .filter-fab[aria-expanded="true"]::before {
    content: "✕";
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1;
  }

  /* Clearance so the last control in the sheet is not sitting under the ✕. */
  .filter-sheet { padding-bottom: calc(92px + env(safe-area-inset-bottom)); }
  .filter-fab-badge {
    position: absolute;
    top: -2px;
    right: -2px;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--bg-base);
    color: var(--accent);
    border: 2px solid var(--accent);
    font-size: 0.68rem;
    font-weight: 800;
    line-height: 16px;
    text-align: center;
  }
}

/* ── Sheet scroll wrapper ─────────────────────────────────────────────────── */

.sheet-scroll-wrap {
  height: calc(100vh - 290px);
  min-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.sheet-scroll-wrap .management-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--bg-surface);
  box-shadow: 0 1px 0 var(--border);
}

/* ── Mappings sticky top bar ──────────────────────────────────────────────── */

.mappings-sticky-bar {
  position: sticky;
  top: 54px;
  z-index: 50;
  background: var(--bg-surface);
  border-bottom: 2px solid var(--border-accent);
  padding: 10px 32px 12px;
  margin: -28px -32px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

/* While a re-sort is in flight. The table stays readable and in place — it is
   the same rows about to be reordered, so blanking it would be a worse answer
   than the full reload this replaced. */
.mappings-cleanup-section.is-resorting {
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.12s ease;
}

@media (max-width: 700px) {
  /* .mappings-sticky-search is declared LATER in this file than .filter-sheet,
     so at equal specificity it wins and the search would never leave the bar.
     Doubling the selector is what actually promotes it into a sheet. */
  .mappings-sticky-search.filter-sheet {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
    max-width: none;
    flex: none;
    background: var(--bg-base);
    /* Bottom lane clears the ✕ the filter button turns into. */
    padding: 0 14px calc(92px + env(safe-area-inset-bottom));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    transform: translateY(100%);
    visibility: hidden;
    transition: transform 0.24s ease, visibility 0.24s ease;
  }
  .mappings-sticky-search.filter-sheet.is-open { transform: translateY(0); visibility: visible; }
  .mappings-sticky-search.filter-sheet .filter-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    position: sticky;
    top: 0;
    margin: 0 -14px;
    padding: 14px calc(14px + env(safe-area-inset-right)) 12px calc(14px + env(safe-area-inset-left));
    background: var(--bg-base);
    border-bottom: 1px solid var(--border);
  }
  .mappings-sticky-search.filter-sheet .search-form {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    width: 100%;
  }
  /* 44px is the floor for a touch target and 16px is the size below which iOS
     zooms the page on focus. */
  .mappings-sticky-search.filter-sheet .search-input {
    width: 100%;
    max-width: none;
    min-height: 44px;
    font-size: 16px;
  }
  .mappings-sticky-search.filter-sheet .button,
  .mappings-sticky-search.filter-sheet input[type="submit"] {
    width: 100%;
    min-height: 44px;
  }
  .mappings-sticky-search.filter-sheet .filter-sheet-head .button { width: auto; min-height: 0; }
}

.mappings-sticky-search {
  flex: 1 1 300px;
  min-width: 0;
}

.mappings-sticky-search .search-form {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
}

.search-input--wide {
  min-width: 280px;
  flex: 1 1 280px;
}

.mappings-sticky-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  flex-wrap: wrap;
}

/* ── Mappings cleanup section ─────────────────────────────────────────────── */

.mappings-cleanup-section {
  margin-top: 40px;
  padding-top: 28px;
  border-top: 2px solid var(--border);
  scroll-margin-top: 130px;
}

.mappings-cleanup-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.mappings-cleanup-head .search-form {
  align-items: center;
  flex-wrap: wrap;
}

/* ── Wealth Page Mobile (phone ≤ 640px) ─────────────────────────────────── */

@media (max-width: 640px) {

  /* ─ Layout ─────────────────────────────────────────────────────────────── */

  .wealth-page-v2 .wealth-hero h1 { font-size: 1.2rem; }

  .wealth-overview-grid { grid-template-columns: 1fr; }

  .wealth-page-v2 .wealth-panel { padding: 16px; }

  .wealth-page-v2 .section-head { margin-bottom: 12px; }

  /* ─ Strip the table wrapper — cards render individually ─────────────────  */

  .wealth-page-v2 .preview-table-wrap {
    overflow: visible;
    background: transparent;
    border: none;
    border-radius: 0;
  }

  /* The Investments and CGT wrappers are 420px inner scroll panes with a sticky
     header — right for a wide table on a desktop, wrong on a phone where the
     rows are full-height CARDS. The rule above does NOT release them:
     `.preview-table-wrap.investments-table-wrap` is a more specific selector, so
     it keeps its own max-height wherever the plain one sits in the file (a media
     query grants no specificity). Same compound selector here, so this wins.
     The sticky thead is moot — thead is display:none at this width. */
  .wealth-page-v2 .preview-table-wrap.investments-table-wrap,
  .wealth-page-v2 .preview-table-wrap.cgt-table-wrap {
    max-height: none;
    overflow: visible;
  }


  /* Reset table/tbody to block flow; override the global overflow-x:auto
     scroll rule so card layout renders instead of a scrollable table */
  .wealth-page-v2 .preview-table,
  .wealth-page-v2 .preview-table tbody {
    display: block !important;
    min-width: 0 !important;
    width: 100%;
    overflow: visible !important;
    max-width: none !important;
  }

  .wealth-page-v2 .preview-table colgroup { display: none; }
  .wealth-page-v2 .preview-table thead  { display: none; }

  /* ─ Row → Card ──────────────────────────────────────────────────────────  */

  .wealth-page-v2 .preview-table tbody tr {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px 16px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-accent);
    border-radius: 10px;
    padding: 14px 16px;
    margin-bottom: 10px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  }

  /* Remove alternating bg from cells — handled at card level */
  .wealth-page-v2 .preview-table tbody tr:nth-child(even) td { background: transparent; }

  /* ─ Cells ───────────────────────────────────────────────────────────────  */

  .wealth-page-v2 .preview-table tbody td {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    border: none !important;
    padding: 0 !important;
    white-space: normal !important;
    font-size: 0.875rem;
    line-height: 1.45;
    color: var(--text-primary);
  }

  /* Micro-label above each value */
  .wealth-page-v2 .preview-table tbody td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 4px;
  }

  /* ─ Primary cell (first td) — card title row ───────────────────────────  */

  .wealth-page-v2 .preview-table tbody td:first-child {
    grid-column: 1 / -1;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-primary);
    padding-bottom: 10px !important;
    margin-bottom: 2px;
    border-bottom: 1px solid var(--border) !important;
  }

  /* Hide the micro-label on the primary cell — it reads as the card title */
  .wealth-page-v2 .preview-table tbody td:first-child[data-label]::before {
    display: none;
  }

  /* ─ Actions cell — footer row of card ─────────────────────────────────  */
  /* Use !important to guarantee flex-direction:row wins over the general
     flex-direction:column set on all td elements above. */

  .wealth-page-v2 .preview-table tbody td.wealth-table-actions,
  .wealth-page-v2 .preview-table tbody td.wealth-card-act {
    grid-column: 1 / -1 !important;
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: wrap !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 6px !important;
    min-width: 0 !important;
    width: 100% !important;
    white-space: normal !important;
    padding: 10px 0 0 !important;
    margin-top: 4px;
    border-top: 1px solid var(--border) !important;
    border-bottom: none !important;
  }

  .wealth-page-v2 .preview-table tbody td.wealth-table-actions::before,
  .wealth-page-v2 .preview-table tbody td.wealth-card-act::before { display: none !important; }

  /* forms inside action cells become transparent so buttons are direct flex items */
  .wealth-page-v2 .preview-table tbody td.wealth-table-actions form,
  .wealth-page-v2 .preview-table tbody td.wealth-card-act form { display: contents !important; }

  /* ensure buttons in action cells don't get squeezed by max-width:100% */
  .wealth-page-v2 .preview-table tbody td.wealth-table-actions .button,
  .wealth-page-v2 .preview-table tbody td.wealth-card-act .button {
    max-width: none !important;
    flex-shrink: 0;
    min-height: 32px;
  }

  /* ─ Empty-state cells (colspan rows) ───────────────────────────────────  */

  .wealth-page-v2 .preview-table tbody tr td[colspan] {
    grid-column: 1 / -1;
  }

  /* ─ Toolbar ─────────────────────────────────────────────────────────────  */

  .wealth-page-v2 .wealth-toolbar {
    flex-wrap: wrap;
    gap: 8px;
  }

  /* ─ Month history: hide table, show only the chart ────────────────────  */

  .wealth-history-table-wrap { display: none; }

  /* ─ Bill cards: stack actions below content on narrow screens ──────────  */

  /* One column, so the actions drop to their own full-width row under the
     content. `flex-direction` would do nothing here — .bill-card is a grid. */
  .bill-card {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 12px;
  }

  .bill-card-actions {
    flex-direction: row;
    flex-wrap: wrap;
    align-self: stretch;
    gap: 6px;
    /* Auto-placed after the content instead of pinned to a second column.
       `order` last, because the actions now sit BEFORE the owes card in the
       DOM (they're column 2 of row 1 on desktop) and would otherwise land in
       the middle of the stack here. Grid auto-placement honours `order`. */
    grid-column: 1;
    grid-row: auto;
    order: 9;
  }

  .bill-card-actions form { display: contents; }

  /* ─ Chip row: scroll horizontally instead of stacking ─────────────────  */

  .wealth-chip-row {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
  }

  /* ═ Investments panel overrides ═══════════════════════════════════════════
     These MUST stay below the generic row-to-card rules above and carry the
     .wealth-page-v2 prefix. They override those rules, and the generic ones are
     `.wealth-page-v2 .preview-table tbody td…` — equal specificity to a bare
     `#investments-table tbody td…` on the parts that matter, so placing this
     block earlier (as the first version did) silently lost every tie: Market
     stayed visible and the date cell kept its full-width `grid-column: 1 / -1`,
     landing on top of the cell beside it. */

  /* ─ Investments panel: reordered, because a long holdings list must not bury
       everything else ────────────────────────────────────────────────────────

     Sixteen holdings is ~2,000px of cards on a phone, and in DOM order the
     Portfolio Value chart and the Share Transactions ledger both sit AFTER it —
     so the two things you would open this tab to look at were several screens
     below a wall of rows, and the ledger read as simply missing. Releasing the
     420px pane (above) made that wall taller, not shorter.

     Order here, not a second copy of the markup: same promote-the-existing-
     markup rule the rest of the mobile CSS follows, so every id, dialog binding
     and the sort JS are untouched. Scoped by #investments-section rather than
     [data-wtab="investments"] because the CGT panel carries that same value. */
  #investments-section { display: flex; flex-direction: column; }
  #investments-section > .section-head          { order: 0; }
  #investments-section > .wealth-toolbar        { order: 1; }
  #investments-section > .wealth-chart-card     { order: 2; }
  #investments-section > .wealth-trades-disclosure { order: 3; }
  #investments-section > .investments-table-wrap   { order: 4; }

  /* The holdings list no longer follows its own toolbar, so it says what it is.
     The table's own thead is display:none at this width. */
  #investments-section > .investments-table-wrap::before {
    content: "Current holdings";
    display: block;
    margin: 4px 0 8px;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
  }

  /* A compact two-line card rather than the generic seven-field one: ticker and
     what it's worth on the first line, units and price on the second. Market and
     Status are dropped — the ticker implies the market, and Status only ever
     says "Live" for anything that has a price to show. Roughly halves the list. */
  .wealth-page-v2 #investments-table tbody tr {
    grid-template-columns: 1fr auto;
    gap: 2px 12px;
    padding: 10px 12px;
  }
  .wealth-page-v2 #investments-table tbody td[data-label="Market"],
  .wealth-page-v2 #investments-table tbody td[data-label="Name"],
  .wealth-page-v2 #investments-table tbody td[data-label="Status"] { display: none; }

  .wealth-page-v2 #investments-table tbody td[data-label="Ticker"]       { grid-area: 1 / 1; font-weight: 700; }
  .wealth-page-v2 #investments-table tbody td[data-label="Market Value"] { grid-area: 1 / 2; align-items: flex-end; font-weight: 700; }
  .wealth-page-v2 #investments-table tbody td[data-label="Units"]        { grid-area: 2 / 1; }
  .wealth-page-v2 #investments-table tbody td[data-label="Last Price"]   { grid-area: 2 / 2; align-items: flex-end; }

  /* The value needs no label beside a ticker; units and price still do. */
  .wealth-page-v2 #investments-table tbody td[data-label="Ticker"]::before,
  .wealth-page-v2 #investments-table tbody td[data-label="Market Value"]::before { display: none; }
  .wealth-page-v2 #investments-table tbody td[data-label="Units"]::before,
  .wealth-page-v2 #investments-table tbody td[data-label="Last Price"]::before { font-size: 0.6rem; }

  /* The generic card gives the FIRST cell a full-width title row with a rule
     under it. On the holdings card the first cell is the ticker, which now
     shares its line with the value — so it must stop spanning, or the value is
     pushed onto a line of its own and the "compact" card is three rows again. */
  .wealth-page-v2 #investments-table tbody td:first-child {
    grid-column: auto;
    padding-bottom: 0 !important;
    margin-bottom: 0;
    border-bottom: 0 !important;
  }

  /* ─ Trade ledger: the same compaction ──────────────────────────────────────
     115 trades as generic seven-field cards is 33,000px — about forty screens,
     and it buries everything after it the moment the ledger is opened. Three
     columns over two rows keeps every field (fees included: they are part of
     the cost base, so they are not something to drop) at about a third of the
     height. Market is dropped, as on the holdings card — the ticker implies it. */
  .wealth-page-v2 .wealth-trades-disclosure tbody tr {
    grid-template-columns: 1fr 1fr auto;
    gap: 6px 10px;
    padding: 10px 12px;
  }
  .wealth-page-v2 .wealth-trades-disclosure tbody td[data-label="Market"] { display: none; }

  .wealth-page-v2 .wealth-trades-disclosure tbody td[data-label="Ticker"] { grid-area: 1 / 1; font-weight: 700; }
  .wealth-page-v2 .wealth-trades-disclosure tbody td[data-label="Side"]   { grid-area: 1 / 2; }
  .wealth-page-v2 .wealth-trades-disclosure tbody td[data-label="Date"]   { grid-area: 1 / 3; align-items: flex-end; }
  .wealth-page-v2 .wealth-trades-disclosure tbody td[data-label="Qty"]    { grid-area: 2 / 1; }
  .wealth-page-v2 .wealth-trades-disclosure tbody td[data-label="Price"]  { grid-area: 2 / 2; }
  .wealth-page-v2 .wealth-trades-disclosure tbody td[data-label="Fees"]   { grid-area: 2 / 3; align-items: flex-end; }
  .wealth-page-v2 .wealth-trades-disclosure tbody td.wealth-card-act      { grid-area: 3 / 1 / 4 / 4; }

  /* Date is the first cell here, so the same span/rule reset applies. */
  .wealth-page-v2 .wealth-trades-disclosure tbody td:first-child {
    grid-column: auto;
    padding-bottom: 0 !important;
    margin-bottom: 0;
    border-bottom: 0 !important;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--text-muted);
  }
  .wealth-page-v2 .wealth-trades-disclosure tbody td:first-child[data-label]::before { display: none; }
}

/* ── All tables: horizontal scroll on small screens (global) ────────────── */
/* Every table on every page becomes a block-level scrolling container so
   it can never stretch the viewport wider than 100vw. Inner elements keep
   their UA table-display defaults automatically. */

@media (max-width: 768px) {
  table:not(.no-scroll) {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
}

/* ── Mobile responsive & touch fixes (audit) ─────────────────────────────── */
/* Scoped to <=768px so desktop rendering is unchanged. No class/ID renames —
   these only adjust sizing/widths that hurt phone usability. */

@media (max-width: 768px) {
  /* iOS Safari auto-zooms the page when a focused field is <16px, then leaves
     it zoomed. Type-attribute selectors are specific enough to override the
     smaller per-component sizes (.inline-input, .filter-more-input, etc.). */
  input[type="text"],
  input[type="number"],
  input[type="date"],
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="tel"],
  select,
  textarea {
    font-size: 16px;
  }

  /* Touch tap targets — Apple/Google 44px minimum. .button.tiny is used densely
     in data tables, so give it a usable height without bloating desktop. */
  .button,
  .btn,
  input[type="submit"],
  button {
    min-height: 44px;
  }
  .button.tiny {
    min-height: 40px;
    padding: 8px 12px;
    font-size: 0.82rem;
  }

  /* The 44px touch-target floor above (same specificity, later in the file)
     was winning over .btn-add-icon's fixed 30x30 circle on phones, stretching
     it back into an oval. Re-asserted here, after that rule, so the circle
     wins on mobile too. */
  .btn-add-icon {
    min-height: 0;
    height: 30px;
  }

  /* Fluid widths — fixed-px filter/select controls must not overflow narrow
     screens. Replaces the desktop px widths only within this breakpoint. */
  .sheet-filter-form .search-input,
  .sheet-filter-form .form-control,
  .sheet-filter-form .form-select {
    width: 100%;
    flex: 1 1 100%;
    min-width: 0;
  }
  .dashboard-controls #dash-select {
    width: 100%;
    min-width: 0;
  }

  /* Page-guide modal: cap height and let the body scroll so the footer
     ("Got it") is never clipped on short / landscape screens. */
  .guide-modal {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 32px);
  }
  .guide-header,
  .guide-footer { flex-shrink: 0; }
  .guide-body { max-height: none; flex: 1 1 auto; }
}

@media (max-width: 430px) {
  /* Small-phone tier: reclaim horizontal space lost to padding. */
  .container { padding: 14px 12px; }
}

/* Admin "view as user" impersonation banner — pinned above everything so the
   admin can always get back to their own account. */
.impersonation-banner {
  position: sticky;
  top: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 8px 16px;
  background: #7b341e;
  color: #fff;
  font-size: 0.82rem;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
}
.impersonation-banner__text {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.impersonation-banner__text strong { font-weight: 700; }
.impersonation-banner__btn {
  background: #fff;
  color: #7b341e;
  border: none;
  border-radius: 6px;
  padding: 4px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}
.impersonation-banner__btn:hover { background: #ffe8d9; }

/* ── Shared Calendar ─────────────────────────────────────────────────────── */

.calendar-toolbar {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-bottom: 20px;
}

.calendar-today-btn { min-height: 32px; padding: 6px 16px; }

.calendar-toolbar-nav { display: flex; gap: 2px; }

.calendar-nav-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: var(--text-secondary);
  font-size: 1.1rem;
  text-decoration: none;
  border: none;
  box-shadow: none;
  background: transparent;
  min-height: 0;
  padding: 0;
}
.calendar-nav-arrow:hover { background: var(--bg-elevated); color: var(--accent); box-shadow: none; transform: none; }

.calendar-toolbar-title {
  font-size: 1.3rem;
  font-weight: 700;
  margin: 0;
  color: var(--text-primary);
}

.calendar-grid {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-surface);

  /* Geometry of the multi-day span band, shared by the overlay, the bars and
     the top margin each day cell reserves for them. Kept as variables so the
     three stay in step — and so the mobile block below can retune all of them
     by overriding two numbers. */
  --cal-lane-h: 18px;
  --cal-lane-gap: 3px;
  --cal-band-top: 36px; /* cell padding-top + day-number head + the 4px gap */
}

/* minmax(0, 1fr), NOT 1fr — every seven-column track in this file is written
   that way and they must stay in step. Bare `1fr` means minmax(AUTO, 1fr), so a
   cell whose content can't shrink drags its own column wider than the other
   six: `.calendar-event-time` and `.calendar-bill-amount` are both
   `flex-shrink: 0; white-space: nowrap`, so a day holding a timed event had a
   larger minimum than an empty day. The visible result was that the columns
   were only as even as the month's contents happened to be — a month with a
   couple of timed events rendered visibly different column widths from the one
   next to it. The floor of 0 makes all seven exactly equal at every width, and
   `.calendar-day`'s min-width below lets the cell actually shrink to it. */
.calendar-week {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  position: relative; /* anchors the multi-day span overlay */
}

.calendar-week--header {
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border);
}

.calendar-dow {
  padding: 10px 12px;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  text-transform: uppercase;
}

.calendar-day {
  min-height: 108px;
  /* Pairs with the minmax(0, …) tracks above: without it the cell keeps a
     content-based minimum width and pushes its column back out again. */
  min-width: 0;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  position: relative;
  cursor: pointer;
}

/* Same reason one level down — the title already ellipsises, so let it take the
   squeeze instead of the row forcing the cell (and so the column) wider.

   `flex-wrap: wrap` is the fix for the actual complaint, though: the trailing
   time / amount is `flex-shrink: 0; white-space: nowrap`, so in a cell narrower
   than about 150px it took its ~50px off the top and the TITLE absorbed the
   whole shortfall — ellipsised down to two or three characters. A month whose
   days all carry an event (e.g. anything with a daily repeat in it) therefore
   rendered as a grid of cut-off words, while an empty month next to it looked
   fine. With wrapping plus a floor on the title, the time drops to its own line
   inside the chip instead of eating the name, and nothing has to guess a
   breakpoint: they sit side by side whenever they both fit. The phone rules
   hide the time outright, so this only ever engages between the two. */
.calendar-event-row,
.calendar-bill-row {
  min-width: 0;
  overflow: hidden;
  flex-wrap: wrap;
}

.calendar-event-row .calendar-event-title,
.calendar-bill-row .calendar-event-title {
  flex: 1 1 auto;
  min-width: 4.5em; /* ~7 characters — below this the time wraps away instead */
}

.calendar-day--muted { color: var(--text-muted); }
.calendar-day--muted .calendar-day-num { color: var(--text-muted); }

/* Today's circle must not make its cell's head taller than every other cell's,
   or today's events sit lower than the same row's neighbours — and the
   absolutely-positioned span band (--cal-band-top) then lines up with only the
   other days. So EVERY day number occupies the same 24px box and today merely
   fills it in. */
.calendar-day--today .calendar-day-num {
  background: var(--text-primary);
  color: var(--bg-surface);
}

.calendar-day-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.calendar-day-num {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-primary);
  /* Fixed box on every day, today or not — see .calendar-day--today below. */
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* Themed, not a fixed pink. #e0507a was hardcoded here and is the one text
   colour in the app that failed in EVERY theme — 3.34:1 on light (a Saturday
   date was the hardest thing on that screen to read) and, being a mid-tone, it
   can't be fixed with one value: anything dark enough for the light theme goes
   under 3:1 on the dark ones, and vice versa. It needs a token per theme. */
.calendar-day-num--weekend { color: var(--calendar-weekend); }
.calendar-day--muted .calendar-day-num--weekend { color: var(--calendar-weekend); opacity: 0.5; }

.calendar-add-btn {
  border: 1px solid var(--border);
  box-shadow: none;
  min-height: 0;
  cursor: pointer;
  color: var(--text-secondary);
  background: var(--bg-surface);
  font-weight: 700;
  font-family: inherit;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  padding: 0;
  flex-shrink: 0;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.calendar-add-btn:hover { background: var(--accent); color: var(--on-accent); border-color: var(--accent); box-shadow: none; transform: none; }

.calendar-day-events {
  display: flex;
  flex-direction: column;
  gap: 3px;
  /* 4px as before, plus one lane pitch for each multi-day bar overlaid on this
     week row, so the day's own chips clear the band. */
  margin-top: calc(4px + var(--span-lanes, 0) * (var(--cal-lane-h) + var(--cal-lane-gap)));
}

/* ══════════════════════════════════════════════════════════════════════════
   MONTH GRID ON PHONES
   The week view had a mobile treatment; the month grid never did, so it kept
   7 columns of min-height:108px / padding:8px 10px at every width. On a 390px
   phone that leaves ~35px of usable width per cell — the day number, the "+"
   button and every event chip all fighting over it, which is the "squished
   together" the grid actually looked like.
   The fix is the standard mobile-calendar shape: a compact grid where an event
   is a coloured stripe rather than unreadable 6px text, and tapping a day
   opens a sheet listing that day in full. Nothing is only reachable by hitting
   a 5px target.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 700px) {
  .calendar-grid {
    --cal-lane-h: 14px;
    --cal-lane-gap: 2px;
    --cal-band-top: 26px; /* 3px cell padding + 19px day-number box + 4px gap */
  }

  .calendar-day { min-height: 62px; padding: 3px 1px; }
  .calendar-dow { padding: 7px 2px; font-size: 0.58rem; text-align: center; letter-spacing: 0.02em; }

  .calendar-day-head { justify-content: center; }
  /* Same fixed box as desktop, just smaller — today included, so its circle
     never shifts its own cell's events out of line with the rest of the row. */
  .calendar-day-num { font-size: 0.7rem; width: 19px; height: 19px; }
  /* Adding is the FAB, or the "Add event" button in the day sheet — a 22px
     control cannot share a 51px-wide cell with the date. */
  .calendar-add-btn { display: none; }

  .calendar-day-events { gap: 2px; }

  /* Every tap in a cell goes to the day sheet: individual chips are far too
     small to aim at, so they stop being tap targets and the whole cell
     becomes one. Clones inside the sheet are unaffected — the rules below are
     scoped to .calendar-day-events, and the sheet is a different container. */
  .calendar-day-events { pointer-events: none; }

  /* Titles stay, just small, with each chip only as tall as its own line of
     text. The time and the bill amount are what actually don't fit in ~51px —
     dropping those buys the room for the name, which is the useful part. */
  .calendar-day-events .calendar-event-time,
  .calendar-day-events .calendar-bill-amount,
  .calendar-day-events .calendar-bill-icon { display: none; }

  .calendar-day-events .calendar-event-row {
    gap: 0;
    padding: 0 1px;
    min-height: 0;
    align-items: center;
  }
  /* The dot plus its gap costs ~10px of a ~55px cell — most of a word. The
     title carries the colour instead, so nothing is lost but the space. */
  .calendar-day-events .calendar-event-dot { display: none; }
  .calendar-day-events .calendar-event-title {
    font-size: 0.55rem;
    line-height: 1.35;
    font-weight: 700; /* heavier than desktop: coloured text at this size needs it */
    color: var(--event-color);
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .calendar-day-events .calendar-event-bar,
  .calendar-day-events .calendar-bill-row {
    display: block;
    min-height: 0;
    height: auto;
    padding: 0 3px;
    margin-bottom: 0;
    font-size: 0.55rem;
    line-height: 1.35;
    border-radius: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Multi-day bars keep their text: spanning several columns they're wide
     enough to read, and they stay tappable since they're big enough to hit. */
  .calendar-span-bar { font-size: 0.6rem; padding: 0 5px; margin: 0 2px; border-radius: 4px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   PHONE: THE MONTH GRID IS THE WHOLE INTERFACE
   A calendar app fills the screen; it doesn't sit in the middle of a scrolling
   page. On a phone the month view locks to the viewport and the grid takes
   every pixel the chrome doesn't need — week rows share the remaining height
   equally, so the month is always fully visible with no page scroll.
   Scoped to .calendar-app--month via the body class: the WEEK view has its own
   internally-scrolling grid and must NOT be locked to the viewport.
   ══════════════════════════════════════════════════════════════════════════ */
.calendar-search-toggle { display: none; }
.calendar-title-short { display: none; }

/* While a swipe is mid-flight the grid is translated sideways, which would
   otherwise widen the page and flash a horizontal scrollbar. (The phone month
   view already clips here; this covers the week view and wider screens.)
   `overflow`, NOT `overflow-x`: setting one axis to hidden while the other
   stays `visible` promotes that other axis to `auto`, quietly turning this into
   a nested vertical scroll container — the same trap documented on
   .wb-table-scroll. The class only exists for the ~250ms of the animation. */
body.cal-swiping main.container { overflow: hidden; }

@media (max-width: 700px) {
  body.calendar-app--month {
    height: 100vh;
    height: 100dvh; /* dvh so iOS' collapsing URL bar doesn't clip the last row */
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  body.calendar-app--month .main-header { flex: 0 0 auto; }
  body.calendar-app--month .nav-container { padding-top: 9px; padding-bottom: 9px; }

  body.calendar-app--month main.container {
    flex: 1 1 auto;
    min-height: 0; /* without this the grid can't shrink and the page overflows */
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* No horizontal padding: the grid runs edge to edge so all seven columns
       get every available pixel. The chrome above it re-adds its own inset
       below. Safe-area insets so a notch can't clip a column in landscape.

       The bottom is the floating buttons' lane. This view is locked to the
       viewport, so without it BOTH the nav button (bottom-left) and the add
       button (bottom-right) sit on top of the last week row — covering four day
       cells that are themselves the only tap target for reading those days. It
       costs the grid ~60px of height, i.e. about 10px off each week row, which
       is the cheaper side of that trade. */
    padding: 10px env(safe-area-inset-right) calc(76px + env(safe-area-inset-bottom)) env(safe-area-inset-left);
  }

  /* Everything above the grid is fixed-height chrome; the grid gets the rest. */
  body.calendar-app--month .calendar-toolbar,
  body.calendar-app--month .calendar-offline-banner,
  body.calendar-app--month .calendar-install-hint,
  body.calendar-app--month .calendar-search-results {
    margin-left: 8px;
    margin-right: 8px;
  }
  body.calendar-app--month .calendar-toolbar {
    flex: 0 0 auto;
    flex-wrap: nowrap;
    gap: 8px;
    margin-bottom: 8px;
  }
  body.calendar-app--month .calendar-toolbar-title {
    font-size: 1rem;
    white-space: nowrap;
    /* Truncates rather than shoving the controls after it off the row. */
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  body.calendar-app--month .calendar-title-long { display: none; }
  body.calendar-app--month .calendar-title-short { display: inline; }
  body.calendar-app--month .calendar-today-btn { min-height: 30px; padding: 5px 11px; font-size: 0.73rem; }
  body.calendar-app--month .calendar-view-btn { padding: 5px 9px; font-size: 0.7rem; }

  /* A horizontal swipe on the grid moves a month/week (see _calendar_scripts),
     which makes the arrows redundant on a phone — and dropping them is what
     frees the room for the offline chip. The chip is last in this nowrap row,
     so it was being pushed past the (overflow:hidden) container edge and was
     invisible on exactly the device it exists for. */
  body.calendar-app .calendar-toolbar-nav { display: none; }

  /* A full-width search box costs an entire row, so it collapses behind the
     magnifier and only takes space while actually in use. */
  body.calendar-app--month .calendar-search-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    min-height: 0;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: var(--bg-elevated);
    box-shadow: none;
    color: var(--text-secondary);
    cursor: pointer;
  }
  body.calendar-app--month .calendar-search { display: none; order: 9; flex: 1 1 100%; }
  body.calendar-app--month.search-open .calendar-toolbar { flex-wrap: wrap; row-gap: 8px; }
  body.calendar-app--month.search-open .calendar-search { display: flex; }

  /* Compact enough to share the single toolbar row, but still showing how many
     files are held — that count matters most on the phone you're travelling
     with, so it survives rather than collapsing to a bare icon. The label text
     is dropped (font-size: 0) and rebuilt from data-count, which
     _offline_scripts keeps in step with the cache. */
  body.calendar-app--month .calendar-offline-chip {
    font-size: 0;
    gap: 3px;
    padding: 0 9px;
    height: 30px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  body.calendar-app--month .calendar-offline-chip::before { content: "\2913"; font-size: 0.9rem; }
  body.calendar-app--month .calendar-offline-chip::after {
    content: attr(data-count);
    font-size: 0.66rem;
    font-weight: 700;
    margin-left: 3px;
  }

  body.calendar-app--month .calendar-search-results {
    flex: 0 0 auto;
    max-height: 38vh;
    overflow-y: auto;
    margin-bottom: 8px;
  }
  body.calendar-app--month .calendar-offline-banner,
  body.calendar-app--month .calendar-install-hint {
    flex: 0 0 auto;
    margin-bottom: 8px;
    padding: 7px 10px;
    font-size: 0.73rem;
  }

  /* The grid itself: a flex column whose week rows divide the leftover height
     evenly, so the whole month fits the screen exactly. */
  body.calendar-app--month .calendar-grid {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* Side borders and the corner radius would each eat into the columns, so
       the grid is flush to the screen edges on a phone. */
    border-left: none;
    border-right: none;
    border-radius: 0;
  }
  body.calendar-app--month .calendar-week--header { flex: 0 0 auto; }
  body.calendar-app--month .calendar-week { flex: 1 1 0; min-height: 0; }
  /* min-height:0 lets a row shrink below 62px on a short phone; the overflow
     clip keeps a busy day from pushing the grid past the viewport, and the day
     sheet is how everything stays reachable. */
  body.calendar-app--month .calendar-day { min-height: 0; overflow: hidden; }

  body.calendar-app--month .calendar-fab {
    width: 48px;
    height: 48px;
    bottom: calc(16px + env(safe-area-inset-bottom));
  }
}

/* ── Day sheet (phones only) ───────────────────────────────────────────────
   Tapping a day opens this. Built by cloning that cell's chips, so it needs no
   new data and each row keeps its data-* payload and click handler — tapping
   one still opens the normal editor.
   Deliberately NOT tagged data-portal-to-body: like the transaction sheet's
   .filter-sheet, it is position:fixed only inside this media query, and
   portaling something that sits inline on desktop is what that trap warns
   about.
   Same open/close mechanism as .filter-sheet and for the same reason: the
   sheet stays in the DOM at all times and .is-open toggles a transform, so
   the open/close can actually transition. The previous version toggled the
   `hidden` attribute directly (display:none <-> block), which has no
   intermediate state to animate through — it just popped open instantly,
   which read as a lag rather than a slide. */
.calendar-day-sheet { display: none; }

@media (max-width: 700px) {
  .calendar-day-sheet { display: block; }

  .calendar-day-sheet-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.42);
    z-index: 900;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.22s ease, visibility 0.22s ease;
  }
  .calendar-day-sheet.is-open .calendar-day-sheet-backdrop { opacity: 1; visibility: visible; }

  .calendar-day-sheet-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 901;
    max-height: 78vh;
    display: flex;
    flex-direction: column;
    background: var(--bg-surface);
    border-radius: 18px 18px 0 0;
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
    box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.22);
    transform: translateY(100%);
    visibility: hidden;
    transition: transform 0.24s ease, visibility 0.24s ease;
  }
  .calendar-day-sheet.is-open .calendar-day-sheet-panel { transform: translateY(0); visibility: visible; }

  .calendar-day-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
  }
  .calendar-day-sheet-head h2 { margin: 0; font-size: 1rem; font-weight: 600; }
  .calendar-day-sheet-head button {
    border: none;
    background: none;
    box-shadow: none;
    min-height: 0;
    padding: 0 4px;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
  }

  .calendar-day-sheet-body {
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .calendar-day-sheet-empty { font-size: 0.85rem; color: var(--text-muted); padding: 10px 2px 14px; }

  /* Cloned rows render at full size here — this is where the titles and times
     the grid had to drop become readable again. */
  .calendar-day-sheet-body .calendar-event-row,
  .calendar-day-sheet-body .calendar-bill-row {
    width: 100%;
    min-height: 42px;
    padding: 8px 12px;
    border-radius: 10px;
    background: var(--bg-elevated);
    font-size: 0.85rem;
  }
  .calendar-day-sheet-body .calendar-event-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
  .calendar-day-sheet-body .calendar-event-bar,
  .calendar-day-sheet-body .calendar-span-bar {
    width: 100%;
    height: auto;
    min-height: 42px;
    margin: 0;
    padding: 8px 12px;
    border-radius: 10px;
    justify-content: flex-start;
    font-size: 0.85rem;
  }

  .calendar-day-sheet-add {
    margin-top: 12px;
    width: 100%;
    min-height: 44px;
    border: none;
    border-radius: 10px;
    background: var(--accent);
    color: var(--on-accent);
    font-family: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    cursor: pointer;
  }
}

/* ── Offline support ──────────────────────────────────────────────────────
   The banner and the write affordances are driven by body.is-offline, which
   _offline_scripts toggles from the online/offline events. */
.calendar-offline-chip {
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  box-shadow: none;
  min-height: 0;
  padding: 6px 12px;
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
}
.calendar-offline-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: none;
  transform: none;
}

.calendar-offline-banner {
  display: none;
  margin: 0 0 14px;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-secondary);
}
body.is-offline .calendar-offline-banner { display: block; }

/* Offline is read-only: hide the things that would need the server rather than
   letting them fail on submit. */
body.is-offline .calendar-add-btn,
body.is-offline .calendar-modal-save,
body.is-offline .calendar-modal-delete,
body.is-offline .calendar-offline-chip { display: none; }

.calendar-install-hint {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0 0 14px;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--accent-dim);
  border: 1px solid var(--border);
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--text-secondary);
}
.calendar-install-hint[hidden] { display: none; }
.calendar-install-hint button {
  border: none;
  background: none;
  box-shadow: none;
  min-height: 0;
  padding: 0 2px;
  margin-left: auto;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
}

.calendar-offline-save {
  border: 1px dashed var(--border);
  background: none;
  box-shadow: none;
  min-height: 0;
  width: 100%;
  margin-top: 8px;
  padding: 8px;
  border-radius: 8px;
  font-family: inherit;
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
}
.calendar-offline-save:hover { border-color: var(--accent); color: var(--accent); box-shadow: none; transform: none; }
.calendar-offline-save[hidden] { display: none; }

/* ── Multi-day event bars ──────────────────────────────────────────────────
   A second 7-column grid laid over the week row, aligned to the same columns
   as the day cells, so one bar can span several days as a single unbroken
   element. Offset 36px = the cell's 8px top padding + the ~24px day-number
   head + the 4px gap, i.e. exactly where the first in-cell chip would sit. */
.calendar-week-spans {
  position: absolute;
  top: var(--cal-band-top);
  left: 0;
  right: 0;
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  grid-auto-rows: var(--cal-lane-h);
  row-gap: var(--cal-lane-gap);
  pointer-events: none; /* the layer is click-through; the bars are not */
  z-index: 2;
}

.calendar-span-bar {
  pointer-events: auto;
  border: none;
  box-shadow: none;
  min-height: 0;
  height: var(--cal-lane-h);
  margin: 0 4px;
  padding: 0 8px;
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center; /* title centred across the whole run */
  font-family: inherit;
  font-size: 0.7rem;
  font-weight: 600;
  color: #fff;
  cursor: pointer;
  overflow: hidden;
}
.calendar-span-bar:hover { filter: brightness(0.95); box-shadow: none; transform: none; }
/* Both min-widths are load-bearing, and they're the same automatic-minimum trap
   as the minmax(0, 1fr) tracks, `.calendar-day` and `.calendar-event-title` —
   this was the one place in the calendar it was missed. `.calendar-span-bar` is
   a GRID item, `.calendar-span-title` a FLEX item, and both default to
   `min-width: auto`, which resolves to the min-content width of nowrap text.
   So a bar could not shrink below its own full title and overflowed its grid
   area sideways over the neighbouring days — `overflow: hidden` on the bar
   clips its text but does nothing about the bar's own width. Invisible on a
   desktop column and unmissable on a ~55px phone one. */
.calendar-span-bar { min-width: 0; }
.calendar-span-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* A run crossing a week boundary has to be two bars — the grid wraps — so the
   continuing ends are squared off and pushed to the very edge of the row.
   That's what makes the two segments read as one band. */
.calendar-span-bar.is-continued {
  margin-left: 0;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.calendar-span-bar.is-continues {
  margin-right: 0;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.calendar-event-row {
  border: none;
  box-shadow: none;
  min-height: 0;
  background: transparent;
  font-family: inherit;
  width: 100%;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 1px 2px;
  border-radius: 4px;
  font-size: 0.74rem;
}
.calendar-event-row:hover { background: var(--bg-elevated); box-shadow: none; transform: none; }

/* ── Bill chips (virtual, read-only — never CalendarEvent rows) ───────────
   Deliberately styled apart from event rows: a bill isn't editable here and
   clicking it leaves for the wealth manager, so it shouldn't look like
   something the shared-calendar edit modal will open. */
.calendar-bill-row {
  display: flex; align-items: center; gap: 5px; width: 100%;
  padding: 2px 5px; margin-bottom: 2px; border-radius: 5px;
  border: 1px dashed rgba(21, 101, 192, 0.45);
  background: rgba(21, 101, 192, 0.07);
  font-size: 0.72rem; font-weight: 600; color: var(--text-secondary);
  text-decoration: none; overflow: hidden; cursor: pointer;
}
.calendar-bill-row:hover { background: rgba(21, 101, 192, 0.15); text-decoration: none; }
.calendar-bill-icon {
  flex-shrink: 0; width: 13px; height: 13px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: #1565C0; color: #fff; font-size: 0.6rem; font-weight: 800; line-height: 1;
}
.calendar-bill-amount { margin-left: auto; flex-shrink: 0; color: var(--text-muted); font-weight: 700; }

.calendar-bill-row.is-overdue { border-color: rgba(198, 40, 40, 0.55); background: rgba(198, 40, 40, 0.09); }
.calendar-bill-row.is-overdue .calendar-bill-icon { background: #C62828; }
/* Paid bills stay on the calendar as a record of what was due, but recede. */
.calendar-bill-row.is-paid { opacity: 0.55; border-style: solid; border-color: var(--border); background: transparent; }
.calendar-bill-row.is-paid .calendar-bill-icon { background: var(--text-muted); }
.calendar-bill-row.is-paid .calendar-event-title { text-decoration: line-through; }

/* A projected repeat: this bill's cycle lands here, but no row exists for it
   yet (it's created when the current occurrence is paid). Reads as a preview —
   lighter than a live bill, and never carries paid/overdue state. */
.calendar-bill-row.is-projected {
  opacity: 0.7;
  border-style: dotted;
  border-color: rgba(21, 101, 192, 0.35);
  background: transparent;
  font-weight: 500;
}
.calendar-bill-row.is-projected .calendar-bill-icon { background: rgba(21, 101, 192, 0.55); }

/* Week view: bills get their own strip above the all-day lanes. */
.cal-week-bills { border-bottom: 1px solid var(--border); }
.cal-week-bills-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; padding: 3px 0; }
.cal-week-bills-cell { min-width: 0; display: flex; flex-direction: column; gap: 2px; }

.calendar-event-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  /* Set per row in _month.html.erb; the phone rules reuse it to tint the title
     once the dot itself is dropped. */
  background: var(--event-color);
}

.calendar-event-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-primary);
  font-weight: 500;
}

.calendar-event-time {
  color: var(--text-muted);
  font-size: 0.68rem;
  margin-left: auto;
  white-space: nowrap;
  flex-shrink: 0;
}

.calendar-event-bar {
  border: none;
  box-shadow: none;
  min-height: 0;
  font-family: inherit;
  width: 100%;
  cursor: pointer;
  display: block;
  padding: 2px 7px;
  border-radius: 4px;
  font-size: 0.74rem;
  font-weight: 600;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}
.calendar-event-bar:hover { filter: brightness(0.95); box-shadow: none; transform: none; }

/* ── Event modal ─────────────────────────────────────────────────────────── */

.calendar-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 9000;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.calendar-modal-overlay--visible { display: flex; }

.calendar-modal {
  background: var(--bg-surface);
  border-radius: 16px;
  width: 100%;
  max-width: 420px;
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.32);
}

.calendar-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg-surface);
}

.calendar-modal-close {
  border: none;
  box-shadow: none;
  min-height: 0;
  background: none;
  color: var(--text-muted);
  font-size: 1.5rem;
  line-height: 1;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 50%;
  cursor: pointer;
  flex-shrink: 0;
}
.calendar-modal-close:hover { background: var(--bg-elevated); color: var(--text-primary); box-shadow: none; transform: none; }

.calendar-modal-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-primary);
  flex: 1;
  text-align: center;
}

.calendar-modal-save {
  border: none;
  box-shadow: none;
  min-height: 0;
  background: var(--bg-elevated);
  color: var(--text-primary);
  font-weight: 700;
  font-size: 0.8rem;
  border-radius: 999px;
  padding: 8px 18px;
  cursor: pointer;
  flex-shrink: 0;
}
.calendar-modal-save:hover { background: var(--accent); color: var(--on-accent); box-shadow: none; transform: none; }

.calendar-modal-body {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.calendar-modal-title-input {
  border: none;
  background: none;
  width: 100%;
  padding: 4px 2px;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--text-primary);
  font-family: var(--font-body);
}
.calendar-modal-title-input:focus { outline: none; }
.calendar-modal-title-input::placeholder { color: var(--text-muted); font-weight: 500; }

.calendar-modal-card {
  background: var(--bg-elevated);
  border-radius: 12px;
  overflow: hidden;
}

.calendar-modal-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-primary);
}
.calendar-modal-row:last-child { border-bottom: none; }

.calendar-field-label {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  color: var(--text-secondary);
}

/* Date and its time share one row, time to the right of the date. The pair
   occupies the slot a single pill input would, so every row still lines up.
   It wraps rather than squeezing both inputs: a native date and a native time
   input have real minimum widths, and clipping either is worse than letting the
   time drop under the date on a narrow phone. */
.calendar-datetime {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-width: 0;
}
.calendar-modal-row .calendar-datetime .calendar-pill-input {
  width: auto;
  max-width: none;
  flex: 0 1 auto;
  min-width: 0;
}
.calendar-time-slot { display: flex; min-width: 0; }
/* toggleTimeRows() hides this for an all-day event; beats the rule above. */
.calendar-time-slot[hidden] { display: none; }

.calendar-pill-input {
  background: var(--bg-surface);
  border: none;
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 0.82rem;
  color: var(--text-primary);
  font-family: inherit;
}

.calendar-modal-note {
  border: none;
  background: none;
  width: 100%;
  padding: 12px 14px;
  font-size: 0.85rem;
  color: var(--text-primary);
  font-family: inherit;
  resize: vertical;
}
.calendar-modal-note:focus { outline: none; }

.calendar-modal-delete {
  border: none;
  box-shadow: none;
  min-height: 0;
  background: none;
  color: var(--danger, #c0392b);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 10px;
  cursor: pointer;
  text-align: center;
}
.calendar-modal-delete:hover { background: var(--bg-elevated); box-shadow: none; transform: none; }

.calendar-toggle {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 24px;
  flex-shrink: 0;
}
.calendar-toggle input { opacity: 0; width: 0; height: 0; }
.calendar-toggle-slider {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 24px;
  cursor: pointer;
  transition: background 0.15s ease;
}
.calendar-toggle-slider::before {
  content: "";
  position: absolute;
  height: 18px;
  width: 18px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.15s ease;
}
.calendar-toggle input:checked + .calendar-toggle-slider { background: var(--accent); }
.calendar-toggle input:checked + .calendar-toggle-slider::before { transform: translateX(16px); }

select.calendar-pill-input { cursor: pointer; }

.calendar-modal-plain-input {
  background: var(--bg-elevated);
  border: none;
  border-radius: 12px;
  padding: 11px 14px;
  font-size: 0.85rem;
  color: var(--text-primary);
  font-family: inherit;
  width: 100%;
}
.calendar-modal-plain-input::placeholder { color: var(--text-muted); }

.calendar-member-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
  color: var(--text-primary);
}
.calendar-member-option:last-child { border-bottom: none; }
.calendar-member-option input { width: 16px; height: 16px; accent-color: var(--accent); }

.calendar-todo-note {
  color: var(--text-muted);
  font-size: 0.72rem;
  padding: 0 14px 10px;
  margin: 0;
}

.calendar-file-list { display: flex; flex-direction: column; gap: 6px; padding: 10px; }
.calendar-file-list:empty { display: none; }

.calendar-file-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: var(--bg-surface);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 0.8rem;
}

.calendar-file-chip a {
  color: var(--text-primary);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.calendar-file-chip a:hover { color: var(--accent); }

.calendar-file-remove {
  border: none;
  box-shadow: none;
  min-height: 0;
  background: none;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
}
.calendar-file-remove:hover { background: var(--bg-elevated); color: var(--danger, #c0392b); box-shadow: none; transform: none; }

.calendar-file-input {
  padding: 10px 14px;
  font-size: 0.78rem;
  color: var(--text-secondary);
}

/* ── Timesheet ────────────────────────────────────────────────────────────── */

.timesheet-container {
  max-width: 520px;
}

@media (min-width: 900px) {
  .timesheet-container {
    max-width: 1080px;
  }

  .timesheet-weeks {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 32px;
    align-items: start;
  }

  .timesheet-week {
    margin-bottom: 0;
  }
}

.timesheet-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
}

.timesheet-nav-arrow {
  min-height: 40px;
  min-width: 40px;
  font-size: 1.2rem;
  padding: 0;
}

.timesheet-nav-title {
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.95rem;
}

.timesheet-status {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.timesheet-status--sent { color: var(--accent); }
.timesheet-status--draft { color: var(--text-muted); }

.timesheet-week {
  margin-bottom: 20px;
}

.timesheet-week-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.timesheet-day {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
  margin-bottom: 10px;
}

.timesheet-day-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 10px;
}

.timesheet-day-name {
  font-weight: 700;
  color: var(--text-primary);
}

.timesheet-day-date {
  color: var(--text-muted);
  font-size: 0.82rem;
}

.timesheet-day-total {
  margin-left: auto;
  font-weight: 600;
  color: var(--accent);
  font-size: 0.85rem;
}

.timesheet-day-fields {
  /* flex-wrap, not a grid column count, is what actually prevents overflow
     here: CSS Grid's "auto" track sizing still yields to a native
     <input type="time">'s intrinsic render width on iOS regardless of
     min-width:0, but a wrapping flex row lets each field claim its
     flex-basis and wrap to a new line the moment there isn't room —
     Start/Finish end up on their own row on a phone, joined by Break/
     Location too once there's enough width (e.g. the desktop 2-up weeks
     layout above), with nothing ever pushed outside the card. */
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.timesheet-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.78rem;
  color: var(--text-secondary);
  flex: 1 1 130px;
  min-width: 0;
}

input[type="time"] {
  min-width: 0;
  max-width: 100%;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 0.92rem;
  padding: 9px 12px;
  width: 100%;
}

input[type="time"]:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
  outline: none;
}

.timesheet-totals {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  background: var(--bg-elevated);
  border-radius: 10px;
  padding: 14px;
  margin-bottom: 16px;
  text-align: center;
}

.timesheet-totals div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.timesheet-totals span {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.timesheet-totals strong {
  font-size: 1.05rem;
  color: var(--text-primary);
}

.timesheet-totals-grand strong { color: var(--accent); }

.timesheet-actions {
  display: flex;
  gap: 10px;
  margin-bottom: 24px;
}

.timesheet-actions .btn,
.timesheet-actions form {
  flex: 1;
}

.timesheet-actions input[type="submit"],
.timesheet-actions .btn {
  width: 100%;
}

.timesheet-settings {
  margin-bottom: 40px;
  color: var(--text-secondary);
}

.timesheet-settings summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--text-muted);
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 12px;
}

.timesheet-settings-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* ══════════════════════════════════════════════════════════════════════════
   KINETIC MOTION LAYER
   ---------------------------------------------------------------------------
   A single, cohesive motion language shared by EVERY page/layout (all of them
   load this stylesheet). The goal is "energy held on a leash": momentum on
   load, weight/direction on interaction, and a consistent rhythm so the whole
   site reads as one continuous performance rather than separate static screens.

   Everything here is additive and appended last so it augments — never fights —
   the existing rules above. All motion is gated behind `prefers-reduced-motion`
   at the very bottom of this section for accessibility.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Motion tokens ──────────────────────────────────────────────────────────
   One timing/easing vocabulary used everywhere. `--ease-out-expo` is the
   decisive "settle into place" curve; `--ease-spring` overshoots for a tactile
   snap; `--ease-inout` is the propulsive page-to-page curve. */
:root {
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring:   cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-inout:    cubic-bezier(0.76, 0, 0.24, 1);
  --dur-fast: 0.14s;
  --dur:      0.28s;
  --dur-slow: 0.52s;
}

/* ── User-selectable accent palettes ────────────────────────────────────────
   Set on <html data-accent="…"> and persisted in localStorage (see the shared
   appearance-boot partial + the account page picker). These override only the
   accent-family variables, so they layer cleanly on top of ANY base theme
   (light / dark / blue) — the user keeps their light/dark choice and colours
   the motion accents to taste. Omitting data-accent (or "blue") keeps each
   theme's own native blue, so no rule is emitted for the default. */
[data-accent="emerald"] {
  --accent: #10B981; --accent-amber: #34D399;
  --accent-dim: rgba(16, 185, 129, 0.13); --border-accent: rgba(16, 185, 129, 0.34);
}
[data-accent="violet"] {
  --accent: #8B5CF6; --accent-amber: #A78BFA;
  --accent-dim: rgba(139, 92, 246, 0.14); --border-accent: rgba(139, 92, 246, 0.36);
}
[data-accent="amber"] {
  --accent: #F59E0B; --accent-amber: #FBBF24;
  --accent-dim: rgba(245, 158, 11, 0.14); --border-accent: rgba(245, 158, 11, 0.38);
}
[data-accent="rose"] {
  --accent: #F43F5E; --accent-amber: #FB7185;
  --accent-dim: rgba(244, 63, 94, 0.13); --border-accent: rgba(244, 63, 94, 0.36);
}
[data-accent="cyan"] {
  --accent: #06B6D4; --accent-amber: #22D3EE;
  --accent-dim: rgba(6, 182, 212, 0.14); --border-accent: rgba(6, 182, 212, 0.36);
}
[data-accent="magenta"] {
  --accent: #D946EF; --accent-amber: #E879F9;
  --accent-dim: rgba(217, 70, 239, 0.14); --border-accent: rgba(217, 70, 239, 0.36);
}
/* Accent changes should glide, not jump, when the user switches palette. */
body, .main-header, .btn, .button, input, select, textarea, .nav-links a, .nav-dropdown-toggle {
  transition-property: background, background-color, border-color, color, box-shadow, transform, filter;
}

/* ── Keyframes ───────────────────────────────────────────────────────────── */

/* Elements arrive from slightly below with a touch of blur, as if decelerating
   into position — weight + direction rather than a plain fade. */
/* The resting state MUST be `none`, not `translateY(0)` / `blur(0)`.
   animation-fill-mode: both (see the page-load orchestration below) leaves the
   final keyframe applied forever, and a transform or filter that isn't `none`
   makes the element BOTH a stacking context and the containing block for any
   position: fixed descendant. Since this animation targets every direct child
   of main.container, that quietly broke overlays app-wide: the transaction
   sheet's filter dropdowns rendered under the ledger table, and the dashboard's
   full-screen drill-down modal (.vd-overlay, `position: fixed; inset: 0`)
   rendered as a box the size of the page content instead of covering the
   viewport. `none` renders identically and interpolates the same. */
@keyframes kinRise {
  from { opacity: 0; transform: translateY(14px); filter: blur(4px); }
  to   { opacity: 1; transform: none;             filter: none; }
}
/* A charged accent line that sweeps across the top of the header on first load
   — the instant "the machine just powered on" signal. */
@keyframes kinCharge {
  from { transform: scaleX(0); opacity: 1; }
  to   { transform: scaleX(1); opacity: 0; }
}
/* Light catching a moving surface — used on primary CTAs. */
@keyframes kinSheen {
  from { transform: translateX(-120%) skewX(-18deg); }
  to   { transform: translateX(220%)  skewX(-18deg); }
}

/* ── Page-load orchestration ────────────────────────────────────────────────
   CSS-only staggered reveal of the main content's direct children. Because it
   keys off the shared container classes, EVERY layout animates on load with no
   per-view markup. Delays are short and capped so the page never feels slow. */
/* fill-mode is `backwards`, NOT `both`, and this matters more than it looks:
   kinRise animates `filter` and `transform`, and an element with either applied
   becomes the containing block for its `position: fixed` descendants. With
   `both` the final keyframe stays applied forever, so every full-screen overlay
   living inside one of these blocks (the dashboard's loading veil, the modals,
   the mobile filter sheet) resolved `inset: 0` against the PAGE CARD instead of
   the viewport — the symptom being a grey panel smaller than the window on
   load. `backwards` still applies the opening keyframe during the stagger delay
   (which is the flash this was guarding against) and leaves nothing behind once
   the animation ends. Overlays are additionally re-parented to <body> at load —
   see data-portal-to-body in the layout — since the trap also exists while the
   animation is genuinely running. */
main.container > *,
.admin-content > *,
.wordle-main > * {
  animation: kinRise var(--dur-slow) var(--ease-out-expo) backwards;
}
main.container > *:nth-child(1), .admin-content > *:nth-child(1), .wordle-main > *:nth-child(1) { animation-delay: 0.02s; }
main.container > *:nth-child(2), .admin-content > *:nth-child(2), .wordle-main > *:nth-child(2) { animation-delay: 0.07s; }
main.container > *:nth-child(3), .admin-content > *:nth-child(3), .wordle-main > *:nth-child(3) { animation-delay: 0.12s; }
main.container > *:nth-child(4), .admin-content > *:nth-child(4), .wordle-main > *:nth-child(4) { animation-delay: 0.17s; }
main.container > *:nth-child(5), .admin-content > *:nth-child(5), .wordle-main > *:nth-child(5) { animation-delay: 0.22s; }
main.container > *:nth-child(n+6), .admin-content > *:nth-child(n+6), .wordle-main > *:nth-child(n+6) { animation-delay: 0.27s; }

/* Header charge-line: a sweeping accent bar under the sticky nav on load. */
.main-header::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  transform-origin: left center;
  animation: kinCharge 0.9s var(--ease-out-expo) 0.1s both;
  pointer-events: none;
}

/* ── Scroll-triggered reveal ────────────────────────────────────────────────
   Applied by the shared kinetic JS (IntersectionObserver) to below-the-fold
   blocks so the page keeps reacting to the visitor as they scroll — momentum
   that builds rather than resets. `.kin-in` is added when the element enters. */
[data-kin-reveal],
.kin-auto {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity var(--dur-slow) var(--ease-out-expo),
              transform var(--dur-slow) var(--ease-out-expo);
  will-change: opacity, transform;
}
[data-kin-reveal].kin-in,
.kin-auto.kin-in { opacity: 1; transform: none; }

/* ── Tactile interactions ───────────────────────────────────────────────────
   Buttons gain a physical press (quick, slightly springy). Combined with the
   existing hover-lift above, actions feel like objects with real inertia. */
.button:active,
.btn:active,
input[type="submit"]:active,
button:active {
  transform: translateY(1px) scale(0.985);
  transition-duration: var(--dur-fast);
}

/* Primary/accent CTAs catch a sheen of light as the pointer arrives. */
.btn-primary,
.button.secondary,
.button.success,
.btn-success,
.cleanup-page .btn-save {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.btn-primary::before,
.button.secondary::before,
.button.success::before,
.btn-success::before,
.cleanup-page .btn-save::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(255,255,255,0.35) 50%, transparent 80%);
  transform: translateX(-120%) skewX(-18deg);
  z-index: -1;
  pointer-events: none;
}
.btn-primary:hover::before,
.button.secondary:hover::before,
.button.success:hover::before,
.btn-success:hover::before,
.cleanup-page .btn-save:hover::before {
  animation: kinSheen 0.7s var(--ease-inout);
}

/* Inputs get an energy focus-ring in the current accent — a clear "this is
   live" signal that reinforces the accent colour as part of the motion. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
  transition: box-shadow var(--dur-fast) var(--ease-out-expo),
              border-color var(--dur-fast) var(--ease-out-expo);
}

/* Anchors that aren't buttons get a quick underline "wipe" from the left. */
main.container a:not(.btn):not(.button):not(.nav-username):not(.admin-nav-link):not([class*="badge"]):not(.role-badge) {
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 1.5px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--dur) var(--ease-out-expo);
}
main.container a:not(.btn):not(.button):not(.nav-username):not(.admin-nav-link):not([class*="badge"]):not(.role-badge):hover {
  background-size: 100% 1.5px;
}

/* ── Nav alignment fix (desktop) ────────────────────────────────────────────
   Bug: the "Mappings" / "Other Apps" <summary> dropdown toggles didn't sit on
   the same baseline as the plain <a> nav links (a flex <summary> centres its
   content while an inline <a> sits on the text baseline, so they drifted a
   couple of px). Give every top-level nav item — links AND dropdown toggles —
   an identical pill box (same height, padding, centring) so they line up
   exactly. Scoped to desktop; the mobile drawer already stacks them full-width. */
@media (min-width: 769px) {
  .nav-links { align-items: center; }
  .nav-links > li { display: flex; align-items: center; }
  .nav-links > li > .nav-dropdown { display: flex; align-items: center; }
  /* Only the TOP-LEVEL row items (plain links + dropdown toggles) get the
     uniform pill box. Scoped with `> li >` so it does NOT hit the links inside
     an open dropdown menu — those must stay full-width blocks (below). */
  .nav-links > li > a,
  .nav-dropdown-toggle {
    display: inline-flex;
    align-items: center;
    height: 30px;
    padding: 0 12px;
    line-height: 1;
  }
  /* keep the caret from adding height / knocking text off-centre */
  .nav-dropdown-toggle::after { position: relative; top: 0.5px; }

  /* Dropdown menu items fill the whole menu width so the entire row is
     hoverable/clickable (not just the text). */
  .nav-dropdown-menu a { display: block; width: 100%; }

  /* Open the dropdown on hover on desktop (JS toggles the <details> `open`
     attribute — see shared/_kinetic_scripts). Remove the 4px gap between the
     toggle and the menu so moving the pointer down into the menu doesn't cross
     empty space and close it. */
  .nav-dropdown-menu { top: 100%; }
}

/* ── Avatars ────────────────────────────────────────────────────────────────
   Shared circular avatar that shows an uploaded profile picture when present
   and falls back to the coloured initial otherwise. `object-fit: cover` crops
   any aspect ratio to the circle so we never need server-side image
   processing (the image_processing gem is intentionally left off this
   memory-constrained VM). */
.fm-avatar {
  position: relative;
  border-radius: 50%;
  background: var(--accent-dim);
  border: 1.5px solid var(--border-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: var(--accent);
  overflow: hidden;
  flex-shrink: 0;
  vertical-align: middle;
}
/* Image overlays the initial; if it fails to load (onerror hides it) the
   initial shows through, so the avatar never collapses to an empty box. */
.fm-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.fm-avatar-fallback { line-height: 1; }

/* ══════════════════════════════════════════════════════════════════════════
   ACCOUNT PAGE  (redesigned — kinetic, mobile-first)
   ══════════════════════════════════════════════════════════════════════════ */

/* Hero banner: big avatar + name, with a soft accent glow that echoes the
   site's accent colour so the page opens with a clear focal point. */
.acct-hero {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 26px 28px;
  border-radius: 16px;
  background:
    radial-gradient(120% 140% at 0% 0%, var(--accent-dim), transparent 60%),
    var(--bg-surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-deep);
  margin-bottom: 26px;
  position: relative;
  overflow: hidden;
}
.acct-hero::before {
  /* thin accent edge on the left — the "charged" marker */
  content: "";
  position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: linear-gradient(var(--accent), var(--accent-amber));
}
.acct-avatar {
  width: 92px; height: 92px;
  font-size: 2rem;
  box-shadow: 0 6px 20px var(--accent-dim);
  transition: transform var(--dur) var(--ease-spring);
}
.acct-hero:hover .acct-avatar { transform: scale(1.04) rotate(-2deg); }
.acct-hero-meta { min-width: 0; }
.acct-hero-meta h1 { margin: 0 0 4px; }
.acct-hero-email {
  margin: 0; color: var(--text-muted);
  font-size: 0.9rem; font-family: "JetBrains Mono", monospace;
  word-break: break-all;
}
.acct-hero-badges { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; }

/* Card grid — auto-fits 1→N columns; each card is a self-contained section. */
.acct-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 22px;
  align-items: start;
}
.acct-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 24px;
  transition: transform var(--dur) var(--ease-out-expo),
              box-shadow var(--dur) var(--ease-out-expo),
              border-color var(--dur) var(--ease-out-expo);
}
.acct-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-deep);
  border-color: var(--border-accent);
}
.acct-card-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 4px;
}
.acct-card-hint { color: var(--text-muted); font-size: 0.82rem; margin: 0 0 18px; }
.acct-card .field { margin-bottom: 14px; }
.acct-card .label-block {
  display: block; margin-bottom: 6px;
  font-size: 0.78rem; font-weight: 600; color: var(--text-secondary);
}
.acct-card input[type="text"],
.acct-card input[type="email"],
.acct-card input[type="password"] {
  width: 100%;
  padding: 11px 14px;
  border-radius: 9px;
}
.acct-card .actions { margin-top: 6px; }
.acct-card .actions input[type="submit"],
.acct-card .actions .btn {
  width: 100%;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 12px;
  border-radius: 9px;
}

/* Avatar-upload card: live preview + file picker + remove. */
.acct-avatar-edit { display: flex; align-items: center; gap: 18px; margin-bottom: 16px; }
.acct-avatar-preview { width: 72px; height: 72px; font-size: 1.5rem; }
.acct-avatar-actions { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 0; }
.acct-file-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  cursor: pointer;
  background: var(--bg-elevated);
  border: 1px dashed var(--border-accent);
  color: var(--text-secondary);
  padding: 9px 14px; border-radius: 9px;
  font-size: 0.8rem; font-weight: 600;
  transition: all var(--dur-fast) var(--ease-out-expo);
}
.acct-file-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }
.acct-file-name { font-size: 0.76rem; color: var(--text-muted); }

/* Appearance picker: base theme buttons + accent swatch row (savable). */
.acct-appearance-group { margin-bottom: 20px; }
.acct-appearance-group > span {
  display: block; margin-bottom: 8px;
  font-size: 0.78rem; font-weight: 600; color: var(--text-secondary);
}
.acct-theme-row { display: flex; gap: 8px; flex-wrap: wrap; }
.acct-theme-row .theme-btn { border: 1px solid var(--border); background: var(--bg-elevated); }
.acct-theme-row .theme-btn.active { background: var(--accent); color: var(--on-accent); }
.acct-swatches { display: flex; gap: 12px; flex-wrap: wrap; }
.acct-swatch {
  width: 34px; height: 34px; border-radius: 50%;
  cursor: pointer; border: 2px solid transparent; padding: 0;
  box-shadow: 0 1px 4px rgba(0,0,0,0.15);
  transition: transform var(--dur-fast) var(--ease-spring), border-color var(--dur-fast) ease;
  min-height: 0;
}
.acct-swatch:hover { transform: scale(1.15) translateY(-2px); }
.acct-swatch.active { border-color: var(--text-primary); transform: scale(1.12); }
.acct-swatch--blue    { background: #1565C0; }
.acct-swatch--emerald { background: #10B981; }
.acct-swatch--violet  { background: #8B5CF6; }
.acct-swatch--amber   { background: #F59E0B; }
.acct-swatch--rose    { background: #F43F5E; }
.acct-swatch--cyan    { background: #06B6D4; }
.acct-swatch--magenta { background: #D946EF; }

@media (max-width: 600px) {
  .acct-hero { flex-direction: column; text-align: center; gap: 14px; padding: 22px 18px; }
  .acct-hero::before { width: 100%; height: 4px; bottom: auto; }
  .acct-hero-badges { justify-content: center; }
  .acct-hero-email { word-break: break-word; }
}

/* ══════════════════════════════════════════════════════════════════════════
   SUGGESTED MAPPINGS  (mobile-friendly cards on top of the existing table)
   The <table> still renders on wide screens; below 640px each row collapses
   into a labelled card so nothing needs horizontal scrolling on a phone.
   ══════════════════════════════════════════════════════════════════════════ */
.sm-section-title {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 14px;
}
.sm-count-pill {
  font-family: var(--font-body);
  font-size: 0.72rem; font-weight: 700;
  background: var(--accent-dim); color: var(--accent);
  border: 1px solid var(--border-accent);
  border-radius: 999px; padding: 2px 10px;
}
.sm-empty {
  text-align: center; color: var(--text-muted);
  padding: 40px 20px; border: 1px dashed var(--border);
  border-radius: 14px; background: var(--bg-surface);
}

@media (max-width: 640px) {
  /* Turn the suggestions data-table into stacked cards on phones. */
  .sm-table, .sm-table thead, .sm-table tbody, .sm-table tr, .sm-table td, .sm-table th { display: block; }
  .sm-table thead { position: absolute; left: -9999px; }         /* visually hide header row */
  .sm-table tr {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 14px 16px;
    margin-bottom: 12px;
  }
  .sm-table td {
    border: none !important;
    padding: 5px 0;
    display: flex; justify-content: space-between; gap: 12px;
    align-items: center;
    text-align: right;
  }
  .sm-table td::before {
    content: attr(data-label);
    font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-muted);
    text-align: left; flex-shrink: 0;
  }
  .sm-table td[data-label="Actions"] { flex-wrap: wrap; justify-content: flex-end; }
}

/* ══════════════════════════════════════════════════════════════════════════
   GLOBAL MOBILE SAFEGUARDS
   Backstops so no page can overflow horizontally on a phone and every tap
   target is comfortable, regardless of per-page markup.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  .container { padding: 18px 14px; }

  /* EVERY page leaves a lane at the bottom for the floating nav button — and for
     the filter button where a page has one — or they sit permanently on top of
     the last row of content: a table's final row, a period's prev/next arrows, a
     form's submit. 78px is the 52px button plus its 18px inset plus air. The
     calendar's locked month view can't use page padding for this and reserves
     the lane inside its own flex column instead (see the month-grid block). */
  main.container,
  .admin-shell,
  .wordle-main { padding-bottom: calc(78px + env(safe-area-inset-bottom)); }

  /* These two are their own `overflow-y: auto` columns, so the page padding
     above can't shrink them — they need the lane on their own scroll content or
     the button covers whatever link is at the bottom of the list. */
  .admin-sidebar,
  .sql-sidebar { padding-bottom: calc(78px + env(safe-area-inset-bottom)); }

  /* Any wide table wrapper scrolls inside itself instead of the page body. */
  .mappings-table-scroll,
  .table-scroll,
  .data-table-wrap { -webkit-overflow-scrolling: touch; }
  /* Forms and control bars stack rather than cramp. */
  .search-form { flex-direction: column; align-items: stretch; }
  .search-form .search-input,
  .search-form .form-control,
  .search-form .form-select { width: 100%; }
}

/* ── Reduced-motion ─────────────────────────────────────────────────────────
   Honour the OS "reduce motion" preference: strip transforms/animations while
   keeping the layout and colour system fully intact. Genuinely useful, not
   just polite — motion sensitivity is real. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  main.container > *, .admin-content > *, .wordle-main > *,
  [data-kin-reveal], .kin-auto { opacity: 1 !important; transform: none !important; filter: none !important; }
  .main-header::after { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   CALENDAR — colours, multi-day, week view, search, FAB
   ══════════════════════════════════════════════════════════════════════════ */

/* Day hover on the month grid — a soft accent wash so the cell you're about to
   click lifts toward you. */
.calendar-day { transition: background 0.15s var(--ease-out-expo); }
.calendar-day:hover { background: var(--accent-dim); }

/* Toolbar: spacer pushes search + view toggle to the right; wraps on mobile. */
.calendar-toolbar-spacer { flex: 1 1 auto; }

.calendar-search { position: relative; display: flex; align-items: center; }
.calendar-search-icon { position: absolute; left: 11px; color: var(--text-muted); pointer-events: none; }
.calendar-search-input {
  padding: 7px 14px 7px 32px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: 0.85rem;
  width: 200px;
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease, width var(--dur) var(--ease-out-expo);
}
.calendar-search-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); width: 240px; }

.calendar-view-toggle {
  display: flex;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
  flex-shrink: 0;
}
.calendar-view-btn {
  padding: 5px 15px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  text-decoration: none;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.calendar-view-btn.active { background: var(--accent); color: var(--on-accent); }

/* Floating add button. */
.calendar-fab {
  position: fixed;
  right: 24px;
  bottom: 24px;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  box-shadow: 0 8px 24px var(--accent-dim), 0 2px 8px rgba(0,0,0,0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 500;
  transition: transform var(--dur) var(--ease-spring), box-shadow var(--dur) ease;
}
.calendar-fab:hover { transform: translateY(-3px) scale(1.05) rotate(90deg); box-shadow: 0 12px 30px var(--accent-dim); }
.calendar-fab:active { transform: translateY(0) scale(0.96); }

/* Search results panel. */
.calendar-search-results {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: 18px;
  overflow: hidden;
}
.calendar-search-results-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 0.8rem;
  color: var(--text-muted);
}
.calendar-search-clear { font-weight: 600; text-decoration: none; }
.calendar-search-list { list-style: none; margin: 0; padding: 0; }
.calendar-search-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  box-shadow: none;
  min-height: 0;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  border-radius: 0;
}
.calendar-search-item:last-child { border-bottom: none; }
.calendar-search-item:hover { background: var(--bg-elevated); transform: none; box-shadow: none; }
.calendar-search-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.calendar-search-item-main { min-width: 0; }
.calendar-search-item-title { display: block; font-weight: 600; color: var(--text-primary); }
.calendar-search-item-meta { display: block; font-size: 0.76rem; color: var(--text-muted); }
.calendar-search-empty { padding: 20px 16px; text-align: center; color: var(--text-muted); font-size: 0.85rem; }

/* Multi-day bands: flatten the corner on the side that continues into the
   adjacent day so the event reads as spanning. */

/* Even-width modal inputs: every pill control is the same width so the Starts /
   Ends / Repeat / Remind rows line up (the reported "different widths" bug). */
.calendar-modal-row .calendar-pill-input { width: 160px; max-width: 58%; box-sizing: border-box; }

/* Weekday filter for a daily repeat. Chips are real checkboxes with the box
   itself hidden, so the whole pill is the hit target on a phone. */
.calendar-modal-row--days { align-items: flex-start; }
.calendar-day-picker { display: flex; flex-direction: column; align-items: flex-end; gap: 7px; }
.calendar-day-chips { display: flex; gap: 5px; flex-wrap: wrap; justify-content: flex-end; }
.calendar-day-chip input { position: absolute; opacity: 0; pointer-events: none; }
.calendar-day-chip span {
  display: inline-block;
  min-width: 34px;
  padding: 5px 7px;
  border-radius: 8px;
  background: var(--bg-surface);
  border: 1px solid transparent;
  font-size: 0.74rem;
  font-weight: 600;
  text-align: center;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.calendar-day-chip span:hover { border-color: var(--border); }
.calendar-day-chip input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.calendar-day-chip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.calendar-day-presets { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.calendar-day-preset {
  background: none;
  border: none;
  padding: 0;
  min-height: 0;
  box-shadow: none;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
}
.calendar-day-preset:hover { text-decoration: underline; box-shadow: none; }

/* Colour picker inside the modal. */
.calendar-modal-row--color { align-items: flex-start; }
.calendar-color-swatches { display: flex; gap: 9px; flex-wrap: wrap; justify-content: flex-end; }
.calendar-color-swatch {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  min-height: 0;
  box-shadow: none;
  transition: transform var(--dur-fast) var(--ease-spring), border-color var(--dur-fast) ease;
}
.calendar-color-swatch:hover { transform: scale(1.18); box-shadow: none; }
.calendar-color-swatch.active { border-color: var(--text-primary); transform: scale(1.12); }
.calendar-color-swatch--default {
  background: var(--bg-surface);
  border: 2px dashed var(--border);
  position: relative;
}
.calendar-color-swatch--default::after {
  content: "";
  position: absolute;
  inset: 3px;
  background: linear-gradient(135deg, transparent 45%, var(--danger) 45%, var(--danger) 55%, transparent 55%);
  opacity: 0.5;
}

/* ── Week view (Google-style time grid) ────────────────────────────────────── */
.cal-week {
  --gutter: 56px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-surface);
}
.cal-week-head {
  display: grid;
  grid-template-columns: var(--gutter) repeat(7, minmax(0, 1fr));
  border-bottom: 1px solid var(--border);
}
.cal-week-dayhead { padding: 8px 4px; text-align: center; border-left: 1px solid var(--border); }
.cal-week-dayhead .dow { display: block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); font-weight: 700; }
.cal-week-dayhead .dnum { display: block; font-size: 1.15rem; font-weight: 700; color: var(--text-primary); line-height: 1.25; }
.cal-week-dayhead.is-today .dnum {
  background: var(--accent);
  color: var(--on-accent);
  border-radius: 50%;
  width: 28px;
  height: 28px;
  line-height: 28px;
  margin: 0 auto;
}

.cal-week-allday {
  display: grid;
  grid-template-columns: var(--gutter) 1fr;
  border-bottom: 1px solid var(--border);
  min-height: 26px;
}
.cal-week-allday-label { font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); padding: 5px 6px; text-align: right; }
.cal-week-allday-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; padding: 3px 0; align-content: start; }
.cal-week-allday-bar {
  border: none;
  box-shadow: none;
  min-height: 0;
  color: #fff;
  font-size: 0.7rem;
  font-weight: 600;
  border-radius: 4px;
  padding: 2px 7px;
  text-align: left;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  cursor: pointer;
}
.cal-week-allday-bar:hover { filter: brightness(0.95); transform: none; box-shadow: none; }

.cal-week-scroll { max-height: calc(100vh - 250px); overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; }
.cal-week-body { display: grid; grid-template-columns: var(--gutter) repeat(7, minmax(0, 1fr)); }
.cal-week-hour { height: 48px; position: relative; }
.cal-week-hour span { position: absolute; top: -7px; right: 6px; font-size: 0.62rem; color: var(--text-muted); white-space: nowrap; }
.cal-week-col { position: relative; border-left: 1px solid var(--border); height: 1152px; cursor: pointer; }
.cal-week-col.is-today { background: var(--accent-dim); }
.cal-week-hourline { height: 48px; border-top: 1px solid var(--border); }
.cal-week-hourline:first-child { border-top: none; }
.cal-week-event {
  position: absolute;
  border: none;
  box-shadow: 0 1px 3px rgba(0,0,0,0.18);
  min-height: 0;
  color: #fff;
  border-radius: 5px;
  padding: 2px 6px;
  font-size: 0.7rem;
  text-align: left;
  overflow: hidden;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}
.cal-week-event:hover { filter: brightness(0.96); transform: none; box-shadow: 0 2px 8px rgba(0,0,0,0.25); z-index: 5; }
.cal-week-event-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-week-event-time { font-size: 0.62rem; opacity: 0.9; }

@media (max-width: 640px) {
  .calendar-toolbar { flex-wrap: wrap; row-gap: 12px; }
  .calendar-search-input, .calendar-search-input:focus { width: 100%; }
  .calendar-search { flex: 1 1 140px; }
  .cal-week { --gutter: 42px; }
  .cal-week-dayhead .dnum { font-size: 0.95rem; }
  .cal-week-dayhead .dow { font-size: 0.6rem; }
  .cal-week-event { font-size: 0.62rem; padding: 1px 3px; }
  .calendar-fab { right: 16px; bottom: 16px; }
}

/* ── Admin users: last-active path + compact row actions ──────────────────── */
.user-last-path {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.68rem;
  color: var(--text-muted);
  word-break: break-all;
}
/* Row action buttons: button_to wraps each control in a <form>; keep them on
   one compact wrapping row and stop the "View as" button stretching full-width
   on mobile (the reported oversized button). */
.user-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.user-actions form { margin: 0; display: inline-flex; }
.user-actions .button.tiny { width: auto; white-space: nowrap; }
@media (max-width: 600px) {
  .user-actions .button.tiny { padding: 6px 9px; font-size: 0.72rem; min-height: 34px; }
}

/* ── Admin users table → stacked cards on phones ──────────────────────────── */
@media (max-width: 700px) {
  .admin-user-table,
  .admin-user-table thead,
  .admin-user-table tbody,
  .admin-user-table tr,
  .admin-user-table td { display: block; width: 100%; }
  .admin-user-table { border: none; box-shadow: none; background: none; }
  .admin-user-table thead { position: absolute; left: -9999px; }   /* hide header row */
  .admin-user-table tr {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-deep);
    padding: 6px 14px 12px;
    margin-bottom: 14px;
  }
  .admin-user-table td {
    border: none !important;
    padding: 7px 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    text-align: right;
  }
  /* Labelled cells show their column name on the left. */
  .admin-user-table td[data-label]::before {
    content: attr(data-label);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
    text-align: left;
    flex-shrink: 0;
  }
  /* User cell is the card header — full width, left aligned, no label. */
  .admin-user-table td.au-cell-user {
    text-align: left;
    border-bottom: 1px solid var(--border) !important;
    padding-bottom: 12px;
    margin-bottom: 4px;
  }
  /* Actions span the full width as a wrapping button row. */
  .admin-user-table td.au-cell-actions { justify-content: flex-start; }
  .admin-user-table td.au-cell-actions .user-actions { width: 100%; }
  .admin-user-table .user-last-path { text-align: right; }
}


/* ── Shared file preview overlay (all four apps) ───────────────────────────
   Rendered by every layout from shared/_file_preview. Fixed to the viewport and
   portalled to <body> (see data-portal-to-body in the layout) so an animating
   page card can never become its containing block — the same trap that made the
   dashboard's loading veil render as a grey panel smaller than the window. */
.fp-overlay {
  display: none; position: fixed; inset: 0; z-index: 3000;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(0, 0, 0, 0.62); backdrop-filter: blur(3px);
}
.fp-overlay.open { display: flex; }

.fp-panel {
  display: flex; flex-direction: column;
  width: min(1080px, 100%); height: min(88vh, 100%);
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.34); overflow: hidden;
}

.fp-head {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  background: var(--bg-elevated); flex-shrink: 0;
}
.fp-head-info { display: flex; align-items: center; gap: 11px; min-width: 0; }
.fp-icon { font-size: 1.3rem; flex-shrink: 0; }
.fp-titles { display: flex; flex-direction: column; min-width: 0; }
.fp-name {
  font-weight: 700; font-size: 0.93rem; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fp-meta { font-size: 0.74rem; color: var(--text-muted); }
.fp-head-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.fp-btn {
  display: inline-flex; align-items: center; padding: 6px 13px; border-radius: 7px;
  border: 1px solid var(--border); background: var(--bg-surface);
  color: var(--text-secondary); font-size: 0.78rem; font-weight: 700;
  text-decoration: none; cursor: pointer; white-space: nowrap;
}
.fp-btn:hover { color: var(--accent); border-color: var(--border-accent); text-decoration: none; }
.fp-btn--primary { background: var(--accent); border-color: var(--accent); color: var(--bg-base); }
.fp-btn--primary:hover { color: var(--bg-base); opacity: 0.9; }

.fp-close {
  background: none; border: none; font-size: 1.7rem; line-height: 1;
  color: var(--text-muted); cursor: pointer; padding: 0 4px;
  min-height: auto; box-shadow: none;
}
.fp-close:hover { color: var(--text-primary); transform: none; box-shadow: none; }

.fp-body {
  flex: 1; min-height: 0; overflow: auto; background: var(--bg-base);
  display: flex; flex-direction: column;
}

/* Media fills the panel without ever forcing the body to scroll sideways. */
.fp-image { margin: auto; max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.fp-frame { flex: 1; width: 100%; border: 0; min-height: 0; }
.fp-media { margin: auto; padding: 28px; width: 100%; max-width: 820px; }
.fp-media audio { width: 100%; }
.fp-video { width: 100%; max-height: 70vh; }

.fp-pre {
  margin: 0; padding: 18px 20px; flex: 1;
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 0.8rem; line-height: 1.55; color: var(--text-primary);
  white-space: pre-wrap; word-break: break-word;
}

.fp-doc { padding: 30px 38px; max-width: 860px; margin: 0 auto; color: var(--text-primary); line-height: 1.65; }
.fp-doc img { max-width: 100%; height: auto; }
.fp-doc table { border-collapse: collapse; width: 100%; margin: 12px 0; }
.fp-doc td, .fp-doc th { border: 1px solid var(--border); padding: 6px 9px; font-size: 0.85rem; }

/* Wide tables scroll inside their own box — the panel itself never does. */
.fp-table-wrap { overflow: auto; padding: 14px; }
.fp-table { border-collapse: collapse; font-size: 0.78rem; }
.fp-table th, .fp-table td {
  border: 1px solid var(--border); padding: 5px 9px; white-space: nowrap;
  color: var(--text-primary);
}
.fp-table th { background: var(--bg-elevated); font-weight: 700; position: sticky; top: 0; }
.fp-truncated { margin: 0; padding: 9px 14px; font-size: 0.76rem; color: var(--text-muted); }

.fp-note {
  margin: auto; padding: 40px 28px; text-align: center;
  color: var(--text-secondary); font-size: 0.88rem;
}
.fp-fallback-icon { font-size: 2.6rem; margin: 0 0 10px; }
.fp-fallback-name { font-weight: 700; color: var(--text-primary); margin: 0 0 6px; word-break: break-all; }
.fp-fallback-reason { color: var(--text-muted); font-size: 0.83rem; margin: 0 0 18px; }

.fp-spinner {
  display: inline-block; width: 15px; height: 15px; margin-right: 9px;
  vertical-align: -2px; border: 2px solid var(--border);
  border-top-color: var(--accent); border-radius: 50%;
  animation: fpSpin 0.7s linear infinite;
}
@keyframes fpSpin { to { transform: rotate(360deg); } }

/* ── File chips (the clickable thing that opens a preview) ─────────────── */
.file-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg-elevated);
  color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;
  text-decoration: none; cursor: pointer; max-width: 16rem;
}
.file-chip:hover { color: var(--accent); border-color: var(--border-accent); text-decoration: none; }
.file-chip-icon { flex-shrink: 0; }
.file-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 700px) {
  .fp-overlay { padding: 0; }
  .fp-panel { width: 100%; height: 100%; border-radius: 0; border: 0; }
  .fp-doc { padding: 20px 16px; }
  /* Keep the file name and the close button; the rest would wrap awkwardly. */
  .fp-head-actions .fp-btn { padding: 6px 10px; font-size: 0.72rem; }
}
