/* ── Ember design system ───────────────────────────────────────────────────
   Refined Wealthview successor. Geist sans + Fira Code mono, dense terminal aesthetic.
   Three palettes (ink default, ember, mono), three densities (compact default,
   roomy, print), three chromes (frameless default, standard, terminal).
   Toggled per-user via [data-palette]/[data-density]/[data-chrome] on <html>.

   Existing template class names (.card, .data-table, .kpi-*, .pill, .form-*,
   .txn-*, .plan-*) get compatibility aliases at the bottom so the secondary
   pages render coherently without per-template rewrites.
   ─────────────────────────────────────────────────────────────────────── */

/* ── Tokens — defaults are the user's chosen ink palette ─────────────── */
:root {
  --font-sans: 'Geist', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: 'Fira Code', ui-monospace, 'JetBrains Mono', Menlo, monospace;
}

/* Fira Code's programming ligatures (calt) are off everywhere — Ember reads
   composed, not clever. `font-variant-ligatures: no-contextual` kills the
   programming ligatures without disabling standard ligatures (fi, fl) in
   the sans family. See docs/EMBER-DESIGN.md → Type. */
* { font-variant-ligatures: no-contextual; }

:root {
  --row-h: 30px;
  --radius: 6px;
  --radius-lg: 10px;

  /* Ink palette (default) */
  --bg: #F7F8FA;
  --bg-2: #EEF1F5;
  --surface: #FFFFFF;
  --surface-2: #F8F9FB;
  --line: #E2E6EC;
  --line-2: #EDF0F4;
  --ink-1: #0B1220;
  --ink-2: #3E4A5E;
  --ink-3: #6E7A8E;
  --ink-4: #A6AEBC;

  /* Keycaps (.kbd-hint) — the printed shortcut badges on buttons, menu
     rows and the selection pill. They used to be --ink-4 at 0.7 opacity
     over a hairline --line-2 border, which reads fine on white and lands
     around 1.7:1 on the dark surface: enough to see that something is
     printed there, not enough to read WHICH key, which is the entire
     job. A hint you have to lean in for is a hint that doesn't work.
     So the chip gets a filled body and ink that clears AA in both
     themes — --kbd-ink rides --ink-2 (~9:1 light, ~9.6:1 dark). Only
     the fill and edge need per-theme values, since a dark chip lifts
     off its surface with white at low alpha and a light one sinks in
     with ink at low alpha. */
  --kbd-ink: var(--ink-2);
  --kbd-bg: rgba(11, 18, 32, 0.05);
  --kbd-line: rgba(11, 18, 32, 0.16);

  --accent: #1E40AF;
  --accent-soft: #DEE5F4;
  --accent-ink: #1E3A8A;

  /* Signal — the "hot core" accent. Two-color system: --accent (sapphire)
     is the chrome where you ARE; --signal (plasma) is the heat of what
     just CHANGED. Used for focus halos, live pulse dots, NEW badges,
     chart leading-edge cursor. Same blue family as --accent — chrome
     pulls from the rim, signal from the core. Never use signal for
     selected/chosen state (that's chrome). See docs/EMBER-DESIGN.md. */
  --signal:      #0EA5E9;
  --signal-soft: #E0F2FE;
  --signal-ink:  #0369A1;
  --signal-glow: rgba(14, 165, 233, 0.70);

  /* Focus rings — focused inputs RADIATE signal rather than just outline.
     Border-color stays chrome; the halo is signal. Polish layer redefines
     these identically so the base sheet stands on its own. */
  --ring-focus:        0 0 0 2px var(--signal-soft), 0 0 14px -2px var(--signal-glow);
  --ring-focus-strong: 0 0 0 1px var(--signal), 0 0 0 3px var(--signal-soft), 0 0 18px -3px var(--signal-glow);
  --ring-danger:       0 0 0 2px var(--neg-soft);

  /* Emerald-600 retune (was emerald-700 #047857). The previous value
     desaturated at the small mono sizes where positive amounts most
     commonly appear (12.5px ledger numbers, 10.5px stat-strip
     percentages). Same hue family, one luminance step brighter. */
  --pos: #059669;
  --pos-soft: #D1FAE5;
  --neg: #BE123C;
  --neg-soft: #F4DBE2;
  --warn: #B45309;
  --warn-soft: #F6EAC9;

  --sidebar-w: 232px;
  --sidebar-w-collapsed: 64px;

  /* Spacing scale (4px base). Several SPA inline styles pad with
     var(--s-N); leaving these undefined made the declarations resolve
     to nothing, so loading/empty/error cards rendered unpadded. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-7: 28px;
  --s-8: 32px;
}

/* Aliases for older Wealthview variable names referenced in inline styles. */
:root {
  --text-primary: var(--ink-1);
  --text-secondary: var(--ink-3);
  --text-muted: var(--ink-4);
  --border: var(--line);
  --border-soft: var(--line-2);
  --card-bg: var(--surface);
  --content-bg: var(--bg);
  --row-hover: var(--bg-2);
  --accent-light: var(--accent-soft);
  --green: var(--pos);
  --green-light: var(--pos-soft);
  --red: var(--neg);
  --red-light: var(--neg-soft);
  --gold: var(--warn);
  /* --ink-line appears in a handful of SPA inline borders; without this
     alias the border-color resolved to nothing and the border vanished. */
  --ink-line: var(--line);
}

/* Dark mode override — applied to <html data-theme="dark">. Palette stays
   ink; chrome stays frameless; this just shifts the surface/ink/accent
   variables to a cool dark scheme. ``data-theme="auto"`` is resolved
   client-side by base.html (reads prefers-color-scheme). */
[data-theme="dark"] {
  --bg: #0A0F1A;
  --bg-2: #111827;
  --surface: #0F1622;
  --surface-2: #131C2B;
  --line: rgba(255,255,255,0.07);
  --line-2: rgba(255,255,255,0.04);
  --ink-1: #E8ECF3;
  --ink-2: #B5BECC;
  --ink-3: #7C8699;
  --ink-4: #4A5365;
  /* --kbd-ink inherits var(--ink-2) from :root and re-resolves here. */
  --kbd-bg: rgba(255, 255, 255, 0.09);
  --kbd-line: rgba(255, 255, 255, 0.20);
  --accent: #60A5FA;
  --accent-soft: rgba(96,165,250,0.16);
  --accent-ink: #93C5FD;
  --signal:      #38BDF8;
  --signal-soft: rgba(56, 189, 248, 0.18);
  --signal-ink:  #7DD3FC;
  --signal-glow: rgba(56, 189, 248, 0.90);
  --pos: #4ADE80;
  --pos-soft: rgba(74,222,128,0.16);
  --neg: #F87171;
  --neg-soft: rgba(248,113,113,0.14);
  --warn: #FBBF24;
  --warn-soft: rgba(251,191,36,0.14);
}

/* ── Density variants ──────────────────────────────────────────────── */
[data-density="roomy"] {
  --row-h: 38px;
  font-size: 14.5px;
}
[data-density="roomy"] .body { padding: 32px 40px 48px; }
[data-density="roomy"] .stat { padding: 22px 26px; }
[data-density="roomy"] .stat-val { font-size: 24px; }
[data-density="roomy"] .stat-val.lg { font-size: 34px; }
[data-density="roomy"] .stat-label { font-size: 11px; letter-spacing: 0.10em; }
[data-density="roomy"] .stat-meta { font-size: 12px; }
[data-density="roomy"] .panel-head { padding: 16px 20px; }
[data-density="roomy"] .panel-title { font-size: 13px; }
[data-density="roomy"] .panel-sub { font-size: 12px; }
[data-density="roomy"] .tbl { font-size: 13.5px; }
[data-density="roomy"] .tbl thead th { padding: 14px 16px; font-size: 11px; }
[data-density="roomy"] .tbl tbody td { padding: 12px 16px; font-size: 13.5px; }
[data-density="roomy"] .h1 { font-size: 30px; }
[data-density="roomy"] .h1-sub { font-size: 13.5px; }
[data-density="roomy"] .btn,
[data-density="roomy"] .input,
[data-density="roomy"] .select { height: 32px; font-size: 13px; }
[data-density="roomy"] .top { height: 52px; font-size: 12.5px; }
[data-density="roomy"] .crumb { font-size: 12.5px; }
[data-density="roomy"] .side { font-size: 15.5px; }
[data-density="roomy"] .side-link { font-size: 15.5px; padding: 9px 10px; }
[data-density="roomy"] .side-link .badge { font-size: 12.5px; }
[data-density="roomy"] .side-section { font-size: 11.5px; padding: 18px 16px 8px; }
[data-density="roomy"] .side-wordmark { font-size: 18px; }
[data-density="roomy"] .side-foot { padding: 14px 12px; }
html.sidebar-collapsed[data-density="roomy"] .side-foot { padding: 14px 8px; }
[data-density="roomy"] .side-foot-name { font-size: 14px; }
[data-density="roomy"] .side-foot-sub { font-size: 12.5px; }
[data-density="roomy"] .side-avatar { width: 30px; height: 30px; font-size: 12px; }
[data-density="roomy"] .side-foot-btn { width: 30px; height: 30px; }
[data-density="roomy"] .side-mark { width: 26px; height: 26px; }
[data-density="roomy"] .acc-name { font-size: 14px; }
[data-density="roomy"] .acc-sub { font-size: 12px; }
[data-density="roomy"] .acc-bal { font-size: 14px; }
[data-density="roomy"] .top-meta { font-size: 12.5px; gap: 16px; }
[data-density="roomy"] .top-btn { height: 30px; font-size: 12.5px; }
[data-density="roomy"] .chip { font-size: 11px; padding: 3px 8px; }

/* Chrome is locked to "frameless" by design — no toggle. The "frameless"
   look (light sidebar, panel-less, type-led) was previously gated behind
   [data-chrome="frameless"]; those overrides now live on the bare elements
   below (.side, .panel, .top, etc.) so there's no extra selector cost. */

/* ── Reset + base ───────────────────────────────────────────────────── */
* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  font-family: var(--font-sans);
  font-size: 13px;
  font-feature-settings: 'cv11', 'ss01', 'ss03';
  line-height: 1.45;
  color: var(--ink-1);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
body { overflow: hidden; }

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

button { font-family: inherit; cursor: pointer; }

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }

.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* ── App shell ──────────────────────────────────────────────────────── */
/* Effective sidebar width: the remembered drag width (--sidebar-w, up to
   420px) is additionally capped to a third of the viewport, so a width
   saved on a big monitor doesn't squeeze the content column when the
   window is later split-screen. Both the grid and the resize handle key
   off this derived value; the raw --sidebar-w stays untouched, so the
   full remembered width comes back the moment the window widens. */
:root { --sidebar-w-eff: min(var(--sidebar-w), 33vw); }
.app {
  display: grid;
  grid-template-columns: var(--sidebar-w-eff) 1fr;
  height: 100vh;
  background: var(--bg);
  position: relative; /* positioning context for .side-resize */
}

/* ── Sidebar (frameless: transparent over page bg, light text) ──────── */
.side {
  background: transparent;
  color: var(--ink-2);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  font-size: 12.5px;
  overflow-y: auto;
  transition: width 0.18s ease;
}

/* ── Sidebar resize handle ──────────────────────────────────────────────
   Draggable separator pinned to the sidebar's right edge. A wide hit
   target centered on the .side hairline; the visible cue is a 2px accent
   line shown on hover / keyboard focus / active drag. Parented to .app
   (not .side, which is overflow-y:auto) so it spans full height and
   ignores nav scroll. Follows the width via left: var(--sidebar-w). */
.side-resize {
  position: absolute;
  top: 0; bottom: 0;
  left: var(--sidebar-w-eff);
  width: 9px;
  transform: translateX(-5px); /* center the hit area on the 1px hairline */
  z-index: 5;
  cursor: col-resize;
  background: transparent;
  touch-action: none; /* pointer events drive the drag on touch devices */
}
.side-resize::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 4px; width: 2px; /* straddles the .side right hairline */
  background: transparent;
  transition: background var(--t-fast) var(--ease);
}
.side-resize:hover::after,
.side-resize:focus-visible::after,
html.sidebar-resizing .side-resize::after { background: var(--accent); }
/* The accent line is the focus indicator — it replaces the default ring. */
.side-resize:focus-visible { outline: none; }

/* The collapsed icon rail has a fixed width — no handle there. */
html.sidebar-collapsed .side-resize { display: none; }

/* Active-drag globals: a consistent col-resize cursor and no text
   selection while the pointer ranges off the thin handle, plus freeze the
   .side width ease so the edge tracks the pointer 1:1 instead of chasing
   through the 0.18s transition. */
html.sidebar-resizing { cursor: col-resize; user-select: none; }
html.sidebar-resizing .side { transition: none; }
.side-brand {
  display: flex; align-items: center; gap: 8px;
  padding: 16px 16px 14px;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}
.side-mark {
  width: 22px; height: 22px; border-radius: 50%;
  background: radial-gradient(circle at 38% 38%, #EAF6FF 0%, #7DD3FC 18%, #38BDF8 36%, #2563EB 62%, #1E1B4B 100%);
  position: relative; flex-shrink: 0;
  box-shadow: 0 0 14px rgba(56,189,248,0.45), inset 0 0 6px rgba(255,255,255,0.25);
}
.side-mark::before {
  content: ''; position: absolute; inset: 3px;
  background: radial-gradient(circle at 32% 30%, rgba(255,255,255,0.70), transparent 55%);
  border-radius: 50%;
}
.side-wordmark {
  font-weight: 600; font-size: 16px; color: var(--ink-1);
  letter-spacing: -0.02em;
}
.side-wordmark sup {
  font-weight: 400; color: var(--ink-3); font-size: 10px;
  margin-left: 4px; vertical-align: super;
}
.side-beta {
  margin-left: auto; flex-shrink: 0;
  font-size: 9.5px; font-weight: 600; line-height: 1;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--signal-ink);
  background: var(--signal-soft);
  border: 1px solid var(--signal);
  padding: 3px 6px; border-radius: 999px;
}
.side-section {
  font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-3);
  padding: 14px 16px 6px;
}
.side-nav { display: flex; flex-direction: column; padding: 0 8px; gap: 1px; }
.side-link {
  display: flex; align-items: center; gap: 9px; white-space: nowrap;
  padding: 6px 8px; border-radius: 5px;
  color: var(--ink-2); text-decoration: none;
  font-size: 14px; line-height: 1.2;
  background: none; border: none; width: 100%; text-align: left;
  font-family: inherit; cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.side-link:hover { background: var(--bg-2); color: var(--ink-1); text-decoration: none; }
.side-link.active { background: var(--accent-soft); color: var(--accent-ink); }
/* Dark mode: push resting nav-link text to pure white for stronger contrast
   against the dark sidebar surface. Scoped to the sidebar so the rest of the
   app's secondary ink (--ink-2) is untouched. The active rule is re-stated
   here (equal specificity, later source order) so the accent color still
   wins for the selected link. */
[data-theme="dark"] .side-link { color: #FFFFFF; }
[data-theme="dark"] .side-link.active { color: var(--accent-ink); }
.side-link.active .ico { color: var(--accent); }
.side-link .ico { color: var(--ink-3); flex-shrink: 0; }
.side-link .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.side-link .badge {
  margin-left: auto; font-family: var(--font-mono);
  font-size: 11px; color: var(--ink-3); padding-left: 6px;
}
/* Dark mode: link count badges go white too, matching the account numbers. */
[data-theme="dark"] .side-link .badge { color: #FFFFFF; }
.side-link .chev {
  margin-left: auto; opacity: 0.5;
  transition: transform 0.18s; flex-shrink: 0;
}
.side-link .chev.open { transform: rotate(180deg); }

.nav-sub { display: flex; flex-direction: column; gap: 1px; padding: 2px 0 6px; }
.nav-sub-item {
  position: relative;
  display: flex; align-items: center;
  padding: 5px 10px 5px 33px;
  border-radius: 5px; border: none; background: none;
  cursor: pointer; width: 100%; text-decoration: none;
  font-family: inherit;
  transition: background 0.12s;
}
.nav-sub-item:hover { background: var(--bg-2); text-decoration: none; }
.nav-sub-item.active { background: var(--accent-soft); }
.nav-sub-name {
  flex: 1; min-width: 0; font-size: 13.5px;
  color: var(--ink-3); font-weight: 400;
  text-align: left; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.nav-sub-item.active .nav-sub-name { color: var(--accent-ink); font-weight: 500; }
/* Dark mode: account names go pure white, matching the top-level .side-link
   text, for stronger contrast against the dark sidebar. Active rows keep the
   accent color (re-stated for equal-specificity/later-source-order win). */
[data-theme="dark"] .nav-sub-name { color: #FFFFFF; }
[data-theme="dark"] .nav-sub-item.active .nav-sub-name { color: var(--accent-ink); }
/* Deleted accounts folder — archival, display-only rows. Dimmed and
   non-interactive (no navigation, no drag); the user restores an account
   from the /accounts page to bring it back. The italic + reduced opacity
   read as "put away" without a badge or status dot. */
.nav-sub-item-deleted { cursor: default; opacity: 0.55; }
.nav-sub-item-deleted:hover { background: none; }
.nav-sub-item-deleted .nav-sub-name { font-style: italic; }
/* Closed accounts folder — LIVE rows: still navigable, still carrying a real
   balance badge and status dots, just lightly dimmed so they read as "put
   away" against the daily list. Full opacity on hover/active keeps them
   feeling interactive. Contrast with .nav-sub-item-deleted above, which is
   inert. */
.nav-sub-item-closed { opacity: 0.7; }
.nav-sub-item-closed:hover, .nav-sub-item-closed.active { opacity: 1; }
.nav-sub-dot {
  position: absolute; left: 17px; top: 50%;
  transform: translateY(-50%);
  width: 8px; height: 8px; border-radius: 50%;
}
.nav-sub-dot[hidden] { display: none; }
.nav-sub-dot.unapproved { background: var(--warn); }
.nav-sub-dot.unreconciled { background: var(--signal); }
.nav-sub-amt {
  flex-shrink: 0; margin-left: 6px;
  /* Sans (not mono) at a slightly larger size: Fira Code at 11px read
     cramped/hard to scan in the narrow sidebar. Geist is far more legible
     small; tabular-nums keeps the digits column-aligned down the list. */
  font-family: var(--font-sans); font-size: 14px; font-weight: 500;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.nav-sub-amt.neg { color: var(--neg); }
.nav-sub-item.active .nav-sub-amt { color: var(--accent-ink); opacity: 0.75; }
.nav-sub-item.active .nav-sub-amt.neg { color: var(--neg); opacity: 1; }
/* Dark mode: net-balance numbers go pure white for contrast, matching the
   account names. Negative balances stay red — that's a signal, not chrome —
   but use a brighter, more saturated red (#FF6B6B) than the global --neg
   (#F87171), which read too muddy against the dark sidebar. Active rows keep
   their accent color. */
[data-theme="dark"] .nav-sub-amt { color: #FFFFFF; }
[data-theme="dark"] .nav-sub-amt.neg { color: #FF6B6B; }
[data-theme="dark"] .nav-sub-item.active .nav-sub-amt { color: var(--accent-ink); }
[data-theme="dark"] .nav-sub-item.active .nav-sub-amt.neg { color: #FF6B6B; }
/* Non-100 ownership-percent chip. Renders only when the account contributes
   a partial share to net worth (ownership_percent != 100). See
   docs/database/account.md "Ownership percent (net-worth share)". */
.nav-sub-share {
  flex-shrink: 0;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 4px;
  background: var(--bg-2);
  color: var(--ink-3);
  font-size: 11px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 16px;
}
.nav-sub-item.active .nav-sub-share { color: var(--accent-ink); opacity: 0.85; }

/* Section headers (Operating / Tracking) inside the Transactions
   sub-menu. Heavier than .nav-sub-item, lighter than .side-section, so
   the hierarchy reads as:
       Accounts                (.side-section)
         Operating             (.nav-sub-section-label — this)
           Chase Checking      (.nav-sub-item)
   See docs/database/account.md "Sidebar grouping".
   Rendered as a <button> so the header is a click target that toggles
   the whole section open/closed. */
.nav-sub-section-label {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
  padding: 10px 16px 4px 12px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  user-select: none;
  transition: color 0.12s;
}
/* Right-pad matches .nav-sub-item (10px) so the section total's right edge
   lines up with the per-account balance column below it. */
.nav-sub-section-label { padding-right: 10px; }
/* Label text takes the row so the total is pushed to the right edge. */
.nav-section-label-text { flex: 1; min-width: 0; }
/* Section total (sum of the section's account balances). Sits in the
   header's number column; letter-spacing/transform reset so the digits
   read as a number, not spaced-out uppercase text. Slightly dimmer than
   the per-account badge — it's a summary, not a row. */
.nav-section-total {
  flex-shrink: 0;
  margin-left: 6px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  letter-spacing: normal;
  text-transform: none;
}
.nav-section-total.neg { color: var(--neg); }
[data-theme="dark"] .nav-section-total { color: #FFFFFF; }
[data-theme="dark"] .nav-section-total.neg { color: #FF6B6B; }
/* "All Accounts" net-worth total — the grand sum across every section.
   Heavier than a per-account badge so the top line reads as the total. */
.nav-sub-amt-total { font-weight: 600; }
.nav-sub-section-label:hover { color: var(--ink-1); }
.nav-sub-section-label:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: 3px;
}
.nav-section-chevron {
  display: inline-block;
  width: 10px;
  font-size: 10px;
  line-height: 1;
  transition: transform 0.15s;
}
.nav-sub-section-label[aria-expanded="false"] .nav-section-chevron {
  transform: rotate(-90deg);
}
.nav-sub-section { display: flex; flex-direction: column; gap: 1px; }
.nav-sub-section[hidden] { display: none; }
html.sidebar-collapsed .nav-sub-section-label { display: none; }

/* Drag-and-drop affordances for sidebar reorder. The dragged row dims;
   the drop target gets a 2px accent bar above or below to preview where
   the row will land. See base.html's drag handlers. */
.nav-sub-item.nav-dragging { opacity: 0.4; cursor: grabbing; }
.nav-sub-item.nav-drop-above { box-shadow: inset 0 2px 0 0 var(--accent); }
.nav-sub-item.nav-drop-below { box-shadow: inset 0 -2px 0 0 var(--accent); }
/* Drag handle — fades in on row hover, grab cursor contained to the handle */
.nav-drag-handle {
  position: absolute;
  left: 2px; top: 50%;
  transform: translateY(-50%);
  width: 12px; height: 14px;
  cursor: grab;
  opacity: 0;
  transition: opacity 0.12s;
}
.nav-drag-handle::before {
  content: '';
  display: block;
  position: absolute;
  left: 1px; top: 0;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--ink-3);
  box-shadow:
    5px 0 0 var(--ink-3),
    0 5px 0 var(--ink-3), 5px 5px 0 var(--ink-3),
    0 10px 0 var(--ink-3), 5px 10px 0 var(--ink-3);
}
.nav-sub-item:hover .nav-drag-handle { opacity: 0.35; }
.nav-drag-handle:hover { opacity: 0.65 !important; }

/* Outer shell: the part of the footer group that's pinned to the bottom of
   the sidebar. margin-top:auto pushes it down when the nav is short, sticky
   bottom:0 keeps it visible when the nav overflows and scrolls. The bg is
   needed so scrolled content doesn't bleed through (the sidebar itself is
   transparent over the page bg). Holds .side-foot-beta (collapsed-only,
   sits above the divider line) plus .side-foot (the bordered identity box). */
.side-foot-wrap {
  margin-top: auto;
  position: sticky;
  bottom: 0;
  background: var(--bg);
}
.side-foot {
  display: flex; align-items: center; gap: 4px;
  padding: 10px 10px;
  border-top: 1px solid var(--line);
}
/* Identity block (avatar + name + sub-line) is the settings link. flex:1 so it
   eats the row; the avatar's left edge lines up with the nav-item icons above
   (footer padding 10 + link padding 6 = 16px, matching .side-nav). */
.side-foot-link {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 9px;
  padding: 5px 6px;
  border-radius: var(--radius);
  color: inherit; text-decoration: none;
  transition: background 0.12s;
}
.side-foot-link:hover { background: var(--bg-2); text-decoration: none; }
.side-foot-link.active { background: var(--accent-soft); }
.side-foot-link.active .side-foot-name { color: var(--accent-ink); }
.side-avatar {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-ink);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 600;
  flex-shrink: 0;
}
.side-foot-link.active .side-avatar { background: var(--accent); color: var(--bg); }
.side-foot-id { flex: 1; min-width: 0; }
.side-foot-name {
  color: var(--ink-1); font-size: 13.5px; line-height: 1.25; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.side-foot-sub {
  color: var(--ink-3); font-size: 11px; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Trailing utility cluster: mobile-view + collapse, as matching ghost icon
   buttons. Grouped tight (gap 2) and set apart from the identity block. */
.side-foot-actions {
  flex-shrink: 0;
  display: flex; align-items: center; gap: 2px;
}
.side-foot-btn {
  background: transparent; border: none;
  color: var(--ink-3);
  width: 28px; height: 28px;
  border-radius: var(--radius);
  display: grid; place-items: center;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.12s, background 0.12s;
}
.side-foot-btn:hover { color: var(--ink-1); background: var(--bg-2); }
.side-foot-btn:focus-visible { outline: none; box-shadow: var(--ring-focus); }

/* Sidebar collapsed */
html.sidebar-collapsed .app { grid-template-columns: var(--sidebar-w-collapsed) 1fr; }
html.sidebar-collapsed .side-brand { padding: 14px 12px; justify-content: center; }
html.sidebar-collapsed .side-wordmark,
html.sidebar-collapsed .side-beta,
html.sidebar-collapsed .side-section,
html.sidebar-collapsed .side-link .label,
html.sidebar-collapsed .side-link .badge,
html.sidebar-collapsed .side-link .chev,
html.sidebar-collapsed .nav-sub,
html.sidebar-collapsed .side-foot-id,
html.sidebar-collapsed .side-mobile-link { display: none !important; }
html.sidebar-collapsed .side-link { justify-content: center; padding: 8px 0; }
html.sidebar-collapsed .side-foot-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding-top: 8px;
}
html.sidebar-collapsed .side-foot {
  flex-direction: column;
  gap: 6px;
  padding: 10px 6px;
  justify-content: center;
  width: 100%;
}
html.sidebar-collapsed .side-foot-link {
  flex: 0 0 auto;
  padding: 0;
  justify-content: center;
}
html.sidebar-collapsed .side-toggle svg path:last-child {
  transform: translateX(-2px) scaleX(-1);
  transform-origin: center;
}
html.sidebar-collapsed .side-nav { padding: 0 4px; }
html.sidebar-collapsed .side-section { padding: 10px 0 4px; }
/* Collapsed-rail ledger entry. The Accounts subsection (.nav-sub) holds the
   "All Accounts" / ledger link but is hidden when collapsed, leaving no way
   to reach the ledger. This single icon takes its place — hidden when the
   sidebar is expanded (the full subsection covers it then). */
.nav-collapsed-ledger { display: none; }
html.sidebar-collapsed .nav-collapsed-ledger { display: block; }

/* Collapsed-rail beta badge. The brand-row .side-beta pill is hidden when
   collapsed (no room next to an icon-only mark) — this compact stand-in
   takes its place at the bottom of the rail, above the .side-foot divider
   line (a sibling of .side-foot inside .side-foot-wrap, not nested inside
   it), so Beta stays visible in both states without crowding the identity
   block below the line. Hidden when expanded (the brand pill covers it
   then). Same visual tokens as .side-beta, just recentered. */
.side-foot-beta { display: none; }
html.sidebar-collapsed .side-foot-beta {
  display: inline-flex; align-items: center; justify-content: center;
  align-self: center;
  font-size: 9.5px; font-weight: 600; line-height: 1;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--signal-ink);
  background: var(--signal-soft);
  border: 1px solid var(--signal);
  padding: 3px 6px; border-radius: 999px;
}

/* ── Main + topbar ──────────────────────────────────────────────────── */
.main { display: flex; flex-direction: column; min-width: 0; overflow: hidden; }

.top {
  display: flex; align-items: center;
  height: 44px; padding: 0 20px;
  background: var(--bg);
  gap: 16px; flex-shrink: 0; white-space: nowrap; overflow: hidden;
}
.crumb {
  font-size: 11px; color: var(--ink-3);
  display: flex; gap: 6px; align-items: center;
}
.crumb .sep { color: var(--ink-4); }
.crumb .now { color: var(--ink-1); font-weight: 500; display: inline-flex; align-items: center; gap: 6px; }
.crumb a { color: inherit; text-decoration: none; }
.crumb a:hover { color: var(--ink-1); }
.crumb .crumb-edit { display: inline-flex; align-items: center; color: var(--ink-3); opacity: 0.7; background: none; border: 0; padding: 0; cursor: pointer; }
.crumb .crumb-edit:hover { color: var(--ink-1); opacity: 1; }
/* External-link affordance next to the account name — sits beside the edit
   pencil, so it shares the muted-chrome resting state and brightens to the
   accent (link) color on hover to signal it opens the account's website. */
.crumb .crumb-link { display: inline-flex; align-items: center; color: var(--ink-3); opacity: 0.7; text-decoration: none; }
.crumb .crumb-link:hover { color: var(--accent); opacity: 1; }

/* Account-scoped actions in the breadcrumb — currently Mark to Market on
   brokerage / retirement accounts, portalled in from the register. Same
   muted-chrome resting state as .crumb-edit / .crumb-link beside it, but
   labelled rather than icon-only: this one is a real, irreversible-ish
   write (it appends today's market-change rows), not a navigation, so it
   says what it does. Collapses when empty so the flex gap leaves no
   phantom notch after the account name. */
.crumb-actions { display: inline-flex; align-items: center; gap: 6px; }
.crumb-actions:empty { display: none; }
.crumb .crumb-action {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--ink-3); opacity: 0.75;
  background: none; border: 0; padding: 0 2px;
  font: inherit; font-size: 11.5px; font-weight: 400;
  cursor: pointer; white-space: nowrap;
  border-radius: 3px;
}
.crumb .crumb-action:hover:not(:disabled) { color: var(--accent); opacity: 1; }
.crumb .crumb-action:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.crumb .crumb-action:disabled { cursor: default; opacity: 0.5; }
.crumb .crumb-action-ico { display: inline-block; line-height: 1; }

/* Mount point for browser-extension chrome at the end of the breadcrumb (see
   docs/frontend/extension-bridge.md). The app renders the node empty and never
   owns its children — an extension appends here. Styling the injected controls
   from our sheet is the point: the extension ships semantic markup and inherits
   light/dark and both densities, instead of hardcoding colors we'd never see.
   Collapses when nothing has mounted, so the flex gap doesn't leave a phantom
   notch after the account name for the (usual) no-extension case. */
.crumb-ext { display: inline-flex; align-items: center; gap: 6px; }
.crumb-ext:empty { display: none; }
/* The affordance itself — same resting/hover contract as .crumb-link beside it. */
.crumb-ext .crumb-ext-item {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--ink-3); opacity: 0.7;
  background: none; border: 0; padding: 0;
  font: inherit; cursor: pointer; text-decoration: none;
}
.crumb-ext .crumb-ext-item:hover { color: var(--accent); opacity: 1; }
/* Needs-action variant — amber, per the status vocabulary in EMBER-DESIGN.md. */
.crumb-ext .crumb-ext-item.warn,
.crumb-ext .crumb-ext-item.warn:hover { color: var(--warn); opacity: 1; }
/* Secondary control (e.g. an opt-out): present but silent until the group is
   hovered or focused, so the resting header stays calm. Hidden via opacity
   rather than visibility so it stays tab-reachable and :focus-within can bring
   it back for keyboard users; reserving the space also means no layout shift. */
.crumb-ext .crumb-ext-aside {
  color: var(--ink-4); background: none; border: 0; padding: 0;
  font: inherit; cursor: pointer; text-decoration: underline;
  opacity: 0; transition: opacity var(--t-fast) var(--ease);
}
.crumb-ext:hover .crumb-ext-aside,
.crumb-ext:focus-within .crumb-ext-aside { opacity: 1; }
.crumb-ext .crumb-ext-aside:hover { color: var(--ink-2); }

.top-meta { margin-left: auto; display: flex; align-items: center; gap: 14px; font-size: 11px; color: var(--ink-3); }
.top-meta .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--pos); display: inline-block; margin-right: 5px; }
/* Amber variant for the sync-status pill: a Plaid item needs attention
   (error / reauth) or has never synced. Green stays "synced OK". */
.top-meta .dot.warn { background: var(--warn); }
.top-btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 26px; padding: 0 9px; border-radius: 5px;
  border: 1px solid var(--line); background: var(--surface);
  font: inherit; font-size: 11px; color: var(--ink-2); cursor: pointer;
  text-decoration: none;
}
.top-btn:hover { border-color: var(--ink-3); color: var(--ink-1); text-decoration: none; }
.top-btn.primary { background: var(--ink-1); color: var(--bg); border-color: var(--ink-1); }

.body { flex: 1; overflow-y: auto; padding: 20px 24px 32px; min-width: 0; }

/* ── Page header ────────────────────────────────────────────────────── */
.h1 { font-size: 22px; font-weight: 500; letter-spacing: -0.02em; color: var(--ink-1); }
.h1-row { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.h1-sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.h2 {
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px;
}
.h2::after { content: ''; flex: 1; height: 1px; background: var(--line); }

/* ── Stats strip (frameless: no outer border, lighter divider rules) ── */
.stats {
  display: grid;
  grid-auto-flow: column;
  /* min-content (not max-content) floors: a tile can shrink to its longest
     unbreakable run (the dollar figure) and let its meta line wrap, so the
     strip fits the page instead of growing a sideways scrollbar. The
     overflow-x stays as a last-resort guard for pathological widths. */
  grid-auto-columns: minmax(min-content, 1fr);
  overflow-x: auto;
  background: transparent;
  margin-bottom: 22px;
}
/* Split-screen: wrap the strip into a 2-up grid rather than crushing four
   tiles side by side. */
@media (max-width: 1100px) {
  .stats {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-columns: unset;
  }
  .stats .stat:nth-child(even) { border-right: none; }
}
.stat {
  padding: 14px 18px;
  border-right: 1px solid var(--line-2);
  min-width: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.stat:last-child { border-right: none; }
.stat-label {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}
.stat-val {
  font-family: var(--font-mono); font-weight: 500;
  font-size: 18px; color: var(--ink-1);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.stat-val.lg { font-size: 22px; font-weight: 500; }
.stat-meta {
  font-family: var(--font-mono);
  font-size: 11px; color: var(--ink-3);
  display: flex; align-items: center; gap: 4px;
}
.pos { color: var(--pos); }
.neg { color: var(--neg); }
.warn { color: var(--warn); }
.muted { color: var(--ink-3); }
.dim { color: var(--ink-4); }

/* ── Panel (frameless: no border, transparent bg, light divider rules) ── */
.panel {
  background: transparent;
  border: none;
  overflow-x: auto;
  position: relative;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  border-radius: var(--radius);
  margin-bottom: 18px;
}
.panel-head {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line-2);
  background: transparent;
}
.panel-title { font-size: 12px; font-weight: 500; color: var(--ink-1); }
.panel-sub { font-size: 11px; color: var(--ink-3); margin-left: auto; }
.panel-body { padding: 14px 16px; }

/* ── Table ──────────────────────────────────────────────────────────── */
.tbl { width: 100%; border-collapse: collapse; font-size: 12.5px; min-width: max-content; }
.tbl thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface);
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-3);
  text-align: left; padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.tbl thead th.r { text-align: right; }
.tbl tbody td {
  padding: 6px 12px;
  border-bottom: 1px solid var(--line-2);
  vertical-align: middle;
  height: var(--row-h);
  white-space: nowrap;
  color: var(--ink-1);
}
.tbl tbody tr:hover td { background: var(--bg-2); }
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl td.r { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.tbl td.tick { font-family: var(--font-mono); font-weight: 500; letter-spacing: -0.01em; }
.tbl td.dim { color: var(--ink-3); }
.tbl tfoot td {
  padding: 8px 12px; border-top: 1px solid var(--line);
  font-size: 11px; color: var(--ink-2);
  background: var(--surface-2);
}
.tbl tfoot td.r { text-align: right; font-family: var(--font-mono); font-weight: 500; color: var(--ink-1); }

/* Fit-to-container modifier — the default .tbl uses `min-width: max-content`
   so it can grow as wide as its content (and rely on its parent to scroll).
   Inside a roomy dashboard `.panel` that has `overflow: hidden`, that means
   long text cells push the right-aligned amount column out of view. `tbl-fit`
   pairs `table-layout: fixed` with `min-width: 0` so the table honors its
   parent width; the existing `white-space: nowrap` on tbody td combined with
   `overflow: hidden` here turns long cells into a clean ellipsis. */
.tbl.tbl-fit { min-width: 0; table-layout: fixed; }
.tbl.tbl-fit tbody td { overflow: hidden; text-overflow: ellipsis; }
/* Headers get the same treatment: with table-layout: fixed a narrow column
   would otherwise let a long uppercase label (e.g. "MARKET VALUE") bleed
   into the neighbor cell instead of ellipsizing. */
.tbl.tbl-fit thead th { overflow: hidden; text-overflow: ellipsis; }
/* Chips inside a fixed-layout cell must truncate like text, not clip
   mid-glyph: cap them to the cell and ellipsize their label. Pair the
   cell with a title="" tooltip so the full value stays reachable. */
.tbl.tbl-fit .chip {
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

/* Dense-fit modifier for wide report tables (holdings, tax lots): 10-11
   columns must fit the panel with no horizontal scrollbar (see
   docs/EMBER-DESIGN.md § Tables — "tables fit, they don't scroll"), and
   the default 12px cell side-padding alone eats ~250px across a row.
   8px keeps the scan comfortable while buying back roughly a column. */
.tbl.tbl-tight thead th,
.tbl.tbl-tight tbody td,
.tbl.tbl-tight tfoot td { padding-left: 8px; padding-right: 8px; }

/* Active (selected/cursor) row — softer tint with accent stripes on both edges */
.tbl tbody tr.is-selected td {
  background: color-mix(in oklab, var(--accent-soft) 45%, var(--surface));
  color: var(--ink-1);
}
.tbl tbody tr.is-selected td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
.tbl tbody tr.is-selected td:last-child  { box-shadow: inset -2px 0 0 var(--accent); }

/* Trailing actions column: separate sibling controls so adjacent buttons
   ("Edit" + "Mark inactive", "Restore" + "Permanently delete", etc.) don't
   read as one glued-together word. 6px matches the gap used by
   .account-capsule-foot-actions and .banner .actions. */
.tbl td.actions > * + * { margin-left: 6px; }

/* Custom checkbox treatment — used inside .tbl rows */
.tbl input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 13px; height: 13px;
  border: 1.25px solid var(--ink-4);
  border-radius: 3px;
  background: var(--surface);
  cursor: pointer;
  position: relative;
  vertical-align: middle;
  transition: border-color 0.12s, background 0.12s;
  margin: 0;
}
.tbl input[type="checkbox"]:hover { border-color: var(--ink-3); }
.tbl input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
}
.tbl input[type="checkbox"]:checked::after {
  content: ''; position: absolute;
  left: 3px; top: 0;
  width: 5px; height: 9px;
  border: solid white;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(42deg);
}
/* Checked row — full accent-soft background to mark bulk selection */
.tbl tbody tr:has(input[type="checkbox"]:checked) td { background: var(--accent-soft); }

/* ── Chips ──────────────────────────────────────────────────────────── */
.chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 6px; border-radius: 3px;
  font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.01em;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-2);
  white-space: nowrap;
}
.chip.equity, .chip.etf { color: var(--accent-ink); border-color: var(--accent-soft); background: var(--accent-soft); }
.chip.bond, .chip.fixed-income { color: #92400E; border-color: #FCE5C8; background: #FDF1DE; }
.chip.cash { color: #475569; border-color: #DBE2EC; background: #EEF2F7; }
.chip.intl { color: #6B21A8; border-color: #ECDAF5; background: #F3E5FA; }
.chip.real, .chip.real-estate { color: #166534; border-color: #D6E8D2; background: #E5F1E0; }
.chip.crypto { color: #9D174D; border-color: #F9D5E5; background: #FCEBF1; }
.chip.taxable { color: var(--accent-ink); border-color: var(--accent-soft); background: var(--accent-soft); }
.chip.tax-deferred { color: #475569; border-color: #DBE2EC; background: #EEF2F7; }
.chip.tax-free { color: #166534; border-color: #D6E8D2; background: #E5F1E0; }
.chip.operating { color: var(--accent-ink); border-color: var(--accent-soft); background: var(--accent-soft); }
.chip.tracking { color: #475569; border-color: #DBE2EC; background: #EEF2F7; }
.chip.lt { color: var(--pos); border-color: var(--pos-soft); background: var(--pos-soft); }
.chip.st { color: #92400E; border-color: #FCE5C8; background: #FDF1DE; }
.chip.warn { color: var(--warn); border-color: var(--warn-soft); background: var(--warn-soft); }
.chip.accent { color: #fff; border-color: var(--accent); background: var(--accent); }

/* ── Inputs ─────────────────────────────────────────────────────────── */
.field { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-2); }
.field-col { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; }
.field-label { font-size: 10px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); }

.input, .select {
  height: 28px; padding: 0 8px;
  white-space: nowrap; flex-shrink: 0;
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--surface);
  font-family: var(--font-sans); font-size: 12.5px;
  color: var(--ink-1); outline: none;
  font-variant-numeric: tabular-nums;
}
.input:focus-visible, .select:focus-visible { border-color: var(--accent); box-shadow: var(--ring-focus); }
.input.num, .select.num { font-family: var(--font-mono); }
.input.full { width: 100%; }
.select.full { width: 100%; }
textarea.input { white-space: pre-wrap; height: auto; padding: 6px 8px; min-height: 60px; line-height: 1.4; }

/* ── Buttons ────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 28px; padding: 0 11px;
  white-space: nowrap; flex-shrink: 0;
  border-radius: 4px; border: 1px solid var(--line);
  background: var(--surface); color: var(--ink-1);
  font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
  text-decoration: none;
}
.btn:hover { border-color: var(--ink-3); text-decoration: none; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); color: #fff; }
.btn.ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn.ghost:hover { color: var(--ink-1); background: var(--bg-2); border-color: transparent; }
.btn.danger { color: var(--neg); border-color: var(--neg-soft); background: var(--neg-soft); }
.btn.danger:hover { color: #fff; background: var(--neg); border-color: var(--neg); }
.btn.warn { color: var(--warn); border-color: var(--warn-soft); background: var(--warn-soft); }
.btn.warn:hover { color: var(--warn); border-color: var(--warn); }
.btn.lg { height: 34px; padding: 0 14px; font-size: 13px; }
.btn.sm { height: 24px; padding: 0 8px; font-size: 11px; }

/* ── Charts ─────────────────────────────────────────────────────────── */
.spark { display: block; }
.spark-pos { stroke: var(--pos); }
.spark-neg { stroke: var(--neg); }
.trend-line { stroke: var(--accent); stroke-width: 1.5; fill: none; }
.trend-area { fill: var(--accent); opacity: 0.06; }
.trend-baseline { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 3; fill: none; }
.trend-cursor { stroke: var(--ink-3); stroke-dasharray: 2 3; }
/* Leading-edge cursor on a LIVE-updating series — signal-glow dot at the
   right edge. Historical (no live tick) charts keep the chrome treatment. */
.trend-cursor.live {
  fill: var(--signal);
  stroke: var(--surface);
  stroke-width: 2;
  filter: drop-shadow(0 0 6px var(--signal-glow));
}

/* ── Allocation bars ────────────────────────────────────────────────── */
.alloc-row { display: grid; grid-template-columns: 90px 1fr 70px; gap: 12px; align-items: center; padding: 5px 0; font-size: 12px; }
.alloc-bar { height: 6px; background: var(--line-2); border-radius: 2px; position: relative; overflow: hidden; }
.alloc-fill { position: absolute; left: 0; top: 0; bottom: 0; background: var(--accent); border-radius: 2px; }

/* ── Account row ────────────────────────────────────────────────────── */
.acc-row {
  display: grid;
  grid-template-columns: 32px 1fr auto 80px;
  gap: 12px; align-items: center;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-2);
}
.acc-row:last-child { border-bottom: none; }
.acc-ico {
  width: 28px; height: 28px; border-radius: 5px;
  display: grid; place-items: center;
  background: var(--bg-2);
  color: var(--ink-2);
  flex-shrink: 0;
}
.acc-name { font-size: 12.5px; color: var(--ink-1); font-weight: 500; }
.acc-sub { font-size: 11px; color: var(--ink-3); display: flex; align-items: center; gap: 6px; margin-top: 1px; }
.acc-bal { font-family: var(--font-mono); font-size: 13px; color: var(--ink-1); text-align: right; font-variant-numeric: tabular-nums; }
.acc-meta { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); text-align: right; margin-top: 1px; }

/* ── Misc ───────────────────────────────────────────────────────────── */
.kbd {
  display: inline-block; padding: 1px 5px;
  font-family: var(--font-mono); font-size: 10px;
  color: var(--ink-3); border: 1px solid var(--line);
  border-radius: 3px; background: var(--surface);
  line-height: 1.4;
}
.divider { height: 1px; background: var(--line); margin: 24px 0; border: none; }

/* Status dot for transactions */
.cleared {
  width: 12px; height: 12px; border-radius: 50%;
  border: 1.5px solid var(--ink-4); display: inline-block;
  background: var(--bg-2); flex-shrink: 0;
}
.cleared.is-cleared { background: var(--pos); border-color: var(--pos); }
.cleared.is-rec { background: var(--accent); border-color: var(--accent); }

/* Empty state */
.empty {
  background: var(--surface); border: 1px dashed var(--line);
  border-radius: var(--radius); padding: 40px 24px;
  text-align: center; color: var(--ink-3); font-size: 13px;
}
.empty p { margin: 0; }

/* ── Welcome takeover (zero-account dashboard) ──────────────────────
   Rendered by the dashboard SPA instead of the empty dashboard when
   the user has no accounts at all. Minimal utility tone: one line on
   what Ember is + three entry cards (connect / import / manual). The
   cards are the multi-field-creation exception to frameless — they're
   navigation targets on an otherwise blank page, so they get a
   hairline border (no shadow: shadows stay reserved for floating
   surfaces). See docs/frontend/onboarding.md. */
.welcome { max-width: 860px; margin: 6vh auto 0; }
.welcome-sub {
  margin: 10px 0 0;
  max-width: 560px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-3);
}
.welcome-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-top: 28px;
}
.welcome-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--t-fast) var(--ease);
}
.welcome-card:hover { border-color: var(--ink-4); }
.welcome-card.is-primary { border-color: var(--accent); }
.welcome-card.is-primary .welcome-card-title { color: var(--accent-ink); }
.welcome-card-title { font-size: 14px; font-weight: 600; color: var(--ink-1); }
.welcome-card-body { font-size: 12px; line-height: 1.5; color: var(--ink-3); }
.welcome-card-cta { margin-top: auto; padding-top: 8px; font-size: 12px; font-weight: 500; color: var(--accent); }

/* Flash messages */
.flash { padding: 10px 14px; border-radius: var(--radius); margin-bottom: 14px; font-size: 12.5px; border: 1px solid transparent; }
.flash.ok { background: var(--pos-soft); color: var(--pos); border-color: var(--pos-soft); }
.flash.err { background: var(--neg-soft); color: var(--neg); border-color: var(--neg-soft); }
.flash pre { font-family: var(--font-mono); font-size: 11px; margin-top: 6px; padding: 6px 8px; background: rgba(0,0,0,0.04); border-radius: 4px; overflow-x: auto; }

/* ── Toolbar (search row) ───────────────────────────────────────────── */
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.toolbar .grow { flex: 1; min-width: 0; }
.toolbar .right { margin-left: auto; }
.search-wrap { position: relative; flex: 1; max-width: 360px; min-width: 200px; }
.search-wrap .ico { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); color: var(--ink-3); pointer-events: none; }
.search-wrap input { width: 100%; padding-left: 30px; padding-right: 26px; }
.search-wrap .kbd { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); }

/* ── Settings page (refined Ember look) ────────────────────────────── */
.settings-page {
  max-width: 1180px;
  margin: 0 auto;
  padding: 8px 0 64px;
}

/* Hero ─────────────────────────────────────────────────────────────── */
.settings-hero {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: 40px;
  align-items: stretch;
  padding: 8px 0 28px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 36px;
}
.settings-hero-head { display: flex; flex-direction: column; }
.settings-hero-kicker {
  font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 10px;
}
.settings-hero-title {
  font-size: 38px; font-weight: 500;
  letter-spacing: -0.025em;
  color: var(--ink-1);
  line-height: 1;
  margin-bottom: 14px;
}
.settings-hero-sub {
  font-size: 13.5px; color: var(--ink-2);
  line-height: 1.55; max-width: 48ch;
}
.settings-hero-note {
  margin-top: auto;
  padding-top: 18px;
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 11.5px; color: var(--ink-3);
}
.settings-hero-note svg { color: var(--ink-4); flex-shrink: 0; }

/* Account capsule (top-right) ────────────────────────────────────── */
.account-capsule {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 10px);
  display: flex; flex-direction: column;
  overflow: hidden;
}
.account-capsule .profile-avatar {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-ink) 100%);
  color: #fff;
  display: grid; place-items: center;
  font-size: 12.5px; font-weight: 500;
  letter-spacing: 0.02em;
  flex-shrink: 0;
}
.account-capsule-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line-2);
}
.account-capsule-id { flex: 1; min-width: 0; }
.account-capsule-name {
  display: flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: 500; color: var(--ink-1);
  letter-spacing: -0.01em;
}
.account-capsule-name-text {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.account-capsule-email {
  font-size: 11.5px; color: var(--ink-3);
  margin-top: 2px;
  font-family: var(--font-mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.account-capsule-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  margin: 0;
}
.account-capsule-meta > div {
  padding: 10px 16px;
  border-bottom: 1px solid var(--line-2);
}
.account-capsule-meta > div:nth-child(odd) { border-right: 1px solid var(--line-2); }
.account-capsule-meta > div:nth-last-child(-n+2) { border-bottom: none; }
.account-capsule-meta dt {
  font-size: 9.5px; font-weight: 500;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 3px;
}
.account-capsule-meta dd {
  font-size: 12px; color: var(--ink-1);
  margin: 0; line-height: 1.3;
}
.account-capsule-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 10px 12px 10px 16px;
  background: var(--bg-2);
}
.account-capsule-session {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 10.5px; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.account-capsule-session svg { flex-shrink: 0; color: var(--ink-4); }
.account-capsule-foot-actions { display: flex; gap: 6px; flex-shrink: 0; }

/* Small button modifier — used in capsule foot */
.btn.btn-sm { height: 24px; padding: 0 9px; font-size: 11px; }

/* Quiet "danger" button — not a giant red rectangle */
.btn.danger-ghost {
  color: var(--neg);
  background: transparent;
  border-color: var(--line);
}
.btn.danger-ghost:hover {
  color: var(--neg);
  background: var(--neg-soft);
  border-color: var(--neg-soft);
}

.settings-stack { display: flex; flex-direction: column; }

/* Section ──────────────────────────────────────────────────────────── */
.set-section {
  margin-bottom: 40px;
  scroll-margin-top: 60px;
}
.set-section-head {
  display: flex; align-items: baseline; gap: 12px;
  margin-bottom: 16px;
}
.set-section-kicker {
  font-family: var(--font-mono);
  font-size: 11px; color: var(--ink-4);
  letter-spacing: 0.04em;
}
.set-section-title {
  font-size: 18px; font-weight: 500;
  letter-spacing: -0.015em;
  color: var(--ink-1);
}
.set-section-body { display: flex; flex-direction: column; gap: 14px; }

/* Card ─────────────────────────────────────────────────────────────── */
.set-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 10px);
  overflow: hidden;
}

/* 2-col label/control row ──────────────────────────────────────────── */
.set-row {
  display: grid;
  grid-template-columns: minmax(160px, 220px) 1fr;
  gap: 28px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line-2);
  align-items: center;
}
.set-row.set-row--top { align-items: flex-start; }
.set-card > .set-row:last-child { border-bottom: none; }
.set-row-label { min-width: 0; }
.set-row-title {
  font-size: 13px; font-weight: 500;
  color: var(--ink-1);
  margin-bottom: 4px;
}
.set-row-sub {
  font-size: 11.5px; color: var(--ink-3);
  line-height: 1.5; max-width: 38ch;
}
.set-row-ctrl { min-width: 0; }

/* Segmented control — scoped to settings so it doesn't clobber the
   payees filter pills which use the same .seg / .seg-btn class names
   but a different visual style. */
.set-seg {
  display: inline-flex;
  padding: 3px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  gap: 2px;
  height: auto;
  overflow: visible;
}
.set-seg .seg-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 14px;
  height: auto;
  border: none;
  background: transparent;
  color: var(--ink-3);
  font: inherit; font-size: 12px; font-weight: 500;
  font-family: var(--font-sans);
  border-radius: 5px;
  cursor: pointer;
  transition: color 0.12s, background 0.12s;
  white-space: nowrap;
}
.set-seg .seg-btn:hover { color: var(--ink-1); background: transparent; }
.set-seg .seg-btn.is-active {
  background: var(--surface);
  color: var(--ink-1);
  box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 0 0 1px var(--line);
}

/* Theme grid ───────────────────────────────────────────────────────── */
.theme-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  max-width: 540px;
}
.theme-card {
  position: relative;
  display: flex; flex-direction: column;
  gap: 10px;
  padding: 10px 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  cursor: pointer;
  text-align: left;
  font: inherit;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.theme-card:hover { border-color: var(--ink-4); }
.theme-card.is-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 1px 2px rgba(0,0,0,0.04);
}
.theme-card-meta {
  display: flex; flex-direction: column; gap: 1px;
  padding: 0 2px;
}
.theme-card-name {
  font-size: 12.5px; font-weight: 500; color: var(--ink-1);
}
.theme-card-hint {
  font-size: 11px; color: var(--ink-3);
}
.theme-card-check {
  position: absolute; top: 10px; right: 10px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  display: none;
  place-items: center;
}
.theme-card.is-active .theme-card-check { display: grid; }
.theme-card-check svg { stroke: #fff; }

/* Theme swatch (mini preview). Light & dark colours are baked into the
   swatch so previews show the actual mode regardless of which theme the
   user is currently viewing the page in. */
.theme-swatch {
  height: 60px;
  border-radius: 5px;
  border: 1px solid var(--line);
  overflow: hidden;
  display: flex;
  position: relative;
}
.theme-swatch-row {
  flex: 1;
  display: flex; flex-direction: column;
  justify-content: center; gap: 5px;
  padding: 0 12px;
}
.theme-swatch-bar {
  display: block;
  height: 4px; width: 70%;
  border-radius: 2px;
}
.theme-swatch-bar.short { width: 40%; }
.theme-swatch-dot {
  display: block;
  width: 10px; height: 10px;
  border-radius: 50%;
  position: absolute;
  right: 10px; bottom: 10px;
}
.theme-swatch--light { background: #FFFFFF; }
.theme-swatch--light .theme-swatch-bar { background: #E2E6EC; }
.theme-swatch--light .theme-swatch-dot { background: #1E40AF; }
.theme-swatch--dark { background: #0F1622; }
.theme-swatch--dark .theme-swatch-bar { background: rgba(255,255,255,0.10); }
.theme-swatch--dark .theme-swatch-dot { background: #60A5FA; }
.theme-swatch--split { padding: 0; }
.theme-swatch-half {
  flex: 1; position: relative;
  display: flex; flex-direction: column;
  justify-content: center; gap: 5px;
  padding: 0 10px;
}
.theme-swatch-half:first-child { border-right: 1px solid var(--line); }
.theme-swatch-half--light { background: #FFFFFF; }
.theme-swatch-half--light .theme-swatch-bar { background: #E2E6EC; }
.theme-swatch-half--light .theme-swatch-dot { background: #1E40AF; right: 8px; bottom: 8px; }
.theme-swatch-half--dark { background: #0F1622; }
.theme-swatch-half--dark .theme-swatch-bar { background: rgba(255,255,255,0.10); }
.theme-swatch-half--dark .theme-swatch-dot { background: #60A5FA; right: 8px; bottom: 8px; }
.theme-swatch--split .theme-swatch-dot { position: absolute; }

/* Mark-to-market card ──────────────────────────────────────────────── */
.mtm-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line-2);
}
.mtm-body {
  padding: 6px 22px 4px;
  display: flex; flex-direction: column;
}
.mtm-step {
  display: grid;
  grid-template-columns: 28px 1fr;
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px dashed var(--line-2);
}
.mtm-step:last-child { border-bottom: none; }
.mtm-step-n {
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 1px solid var(--line);
  display: grid; place-items: center;
  font-family: var(--font-mono);
  font-size: 11px; color: var(--ink-2);
  background: var(--bg-2);
}
.mtm-step-title {
  font-size: 12.5px; font-weight: 500; color: var(--ink-1);
  margin-bottom: 4px;
}
.mtm-step-body {
  font-size: 12px; color: var(--ink-2);
  line-height: 1.55;
}
.code-pill {
  display: inline-block;
  padding: 1px 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-radius: 3px;
  white-space: nowrap;
}
.mtm-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: 14px 22px;
  background: var(--bg-2);
  border-top: 1px solid var(--line-2);
}
.mtm-foot-hint {
  font-size: 11.5px; color: var(--ink-3);
}
.mtm-foot-actions {
  display: flex; gap: 8px;
}

/* Status pill ──────────────────────────────────────────────────────── */
.status-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px 3px 8px;
  border-radius: 99px;
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid;
  flex-shrink: 0;
}
.status-pill-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.status-pill.is-ok    { color: var(--pos);   background: var(--pos-soft); border-color: transparent; }
.status-pill.is-empty { color: var(--ink-3); background: var(--bg-2);     border-color: var(--line); }

/* Foot ────────────────────────────────────────────────────────────── */
.settings-foot {
  display: flex; align-items: center; gap: 14px;
  margin-top: 32px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  color: var(--ink-3);
}
.settings-foot-saved { color: var(--pos); font-weight: 500; }

/* Responsive ─────────────────────────────────────────────────────── */
@media (max-width: 1080px) {
  .settings-hero { grid-template-columns: 1fr; gap: 24px; }
  .account-capsule { max-width: 480px; }
}

@media (max-width: 720px) {
  .set-row { grid-template-columns: 1fr; gap: 12px; align-items: stretch; }
  .account-capsule-meta { grid-template-columns: 1fr; }
  .account-capsule-meta > div:nth-child(odd) { border-right: none; }
}

@media (max-width: 560px) {
  .settings-hero-title { font-size: 30px; }
  .theme-grid { grid-template-columns: 1fr; }
  .mtm-foot { flex-direction: column; align-items: stretch; }
  .mtm-foot-actions { justify-content: stretch; }
  .mtm-foot-actions form { flex: 1; }
  .mtm-foot-actions .btn { width: 100%; justify-content: center; }
  .account-capsule-foot { flex-direction: column; align-items: stretch; gap: 8px; }
  .account-capsule-foot-actions { width: 100%; }
  .account-capsule-foot-actions .btn { flex: 1; justify-content: center; }
}

/* ── Sortable table headers ────────────────────────────────────────── */
.tbl thead th.sortable { cursor: pointer; user-select: none; }
.tbl thead th.sortable:hover { color: var(--ink-1); }
.tbl thead th.sortable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tbl thead th.sort-asc::after { content: ' ▲'; font-size: 8px; color: var(--accent); }
.tbl thead th.sort-desc::after { content: ' ▼'; font-size: 8px; color: var(--accent); }

/* ──────────────────────────────────────────────────────────────────────
   Compatibility aliases for older template class names
   These let secondary pages render coherently with minimal template edits.
   ────────────────────────────────────────────────────────────────────── */

/* Old layout names */
.sidebar { /* fallback for any leftover refs */ display: contents; }
.topbar {
  display: flex; align-items: center;
  height: 44px; padding: 0 20px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
  gap: 16px; flex-shrink: 0;
}
.topbar-title { font-size: 13px; font-weight: 500; color: var(--ink-1); flex: 1; }
.topbar-date { font-size: 11px; color: var(--ink-3); }
.topbar-badge {
  background: var(--accent-soft); color: var(--accent-ink);
  font-size: 10.5px; font-weight: 500; padding: 2px 8px; border-radius: 3px;
}
.content { flex: 1; overflow-y: auto; padding: 20px 24px 32px; min-width: 0; }

/* Cards (frameless by default, matching .panel) */
.card {
  background: transparent;
  border: none;
  border-radius: var(--radius);
  margin-bottom: 18px;
  overflow: hidden;
}
.card-header {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line-2);
  flex-wrap: wrap;
}
.card-title {
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-3);
}
.card-sub { font-size: 11px; color: var(--ink-3); margin-left: auto; }
.card-body { padding: 14px 16px; }
.card-body.tight { padding: 8px 14px; }

/* KPI grid (frameless by default) */
.kpi-grid {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(max-content, 1fr);
  overflow-x: auto;
  background: transparent;
  margin-bottom: 18px;
  border-radius: 0;
}
.kpi-grid.three { grid-auto-columns: minmax(max-content, 1fr); }
.kpi-card {
  padding: 14px 18px;
  border-right: 1px solid var(--line-2);
  display: flex; flex-direction: column; gap: 4px;
  min-width: 0; background: transparent;
}
.kpi-card:last-child { border-right: none; }
.kpi-label {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 2px;
}
.kpi-value {
  font-family: var(--font-mono); font-weight: 500;
  font-size: 22px; color: var(--ink-1);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em; line-height: 1.1;
}
.kpi-value.gain { color: var(--pos); }
.kpi-value.loss { color: var(--neg); }
.kpi-change { font-size: 11px; font-family: var(--font-mono); color: var(--ink-3); display: flex; align-items: center; gap: 4px; margin-top: 2px; }
.kpi-change.up { color: var(--pos); }
.kpi-change.down { color: var(--neg); }
.kpi-change.muted { color: var(--ink-3); }

/* Grid layouts */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 18px; align-items: start; }
.grid-3-1 { display: grid; grid-template-columns: 3fr 1fr; gap: 16px; margin-bottom: 18px; align-items: start; }
.grid-2-1 { display: grid; grid-template-columns: 1.6fr 1fr; gap: 16px; margin-bottom: 18px; align-items: start; }

/* Data table (alias of .tbl) */
.data-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.data-table thead th {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-3); text-align: left; padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap; background: var(--surface);
}
.data-table thead th.num { text-align: right; }
.data-table tbody td {
  padding: 8px 12px; border-bottom: 1px solid var(--line-2);
  font-size: 12.5px; color: var(--ink-1); vertical-align: middle;
}
.data-table tbody td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover td { background: var(--bg-2); }
.data-table tfoot td, .data-table tfoot th {
  font-size: 11px; font-weight: 500; color: var(--ink-1);
  border-top: 1px solid var(--line); border-bottom: none;
  padding: 8px 12px; background: var(--surface-2);
}
.data-table tfoot td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.data-table.compact thead th { padding: 6px 8px; }
.data-table.compact tbody td { padding: 6px 8px; }
.data-table thead th.sortable { cursor: pointer; user-select: none; }
.data-table thead th.sortable:hover { color: var(--ink-1); }
/* Keyboard focus ring for the sortable data-table headers (role="button" +
   tabindex, wired in accounts.html). Matches the treatment the ledger,
   Holdings, Balances, and Tax-lots headers use so a keyboard user can see
   which column Enter / Space will sort. outline-offset pulls the ring inside
   the header edge. */
.data-table thead th.sortable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  color: var(--ink-1);
}
.data-table thead th.sort-asc, .data-table thead th.sort-desc { color: var(--accent); }
.data-table thead th.sort-asc::after { content: ' ▲'; font-size: 8px; }
.data-table thead th.sort-desc::after { content: ' ▼'; font-size: 8px; }

/* Pill rendered as a button (operating/tracking toggle in the accounts table). */
.pill-btn {
  cursor: pointer; font-family: inherit;
}
.pill-btn:hover { filter: brightness(1.05); }

/* Inline expand caret for the "show linked source" disclosure. */
.linked-toggle {
  background: none; border: none; cursor: pointer;
  color: var(--ink-3); font-size: 10px; padding: 0 2px;
  margin-left: 4px; vertical-align: middle;
}
.linked-toggle:hover { color: var(--ink-1); }

/* Linked-source detail row in the accounts table. */
.linked-detail {
  padding: 10px 14px; background: var(--bg-2);
  border-radius: var(--radius); border: 1px solid var(--line-2);
  margin: 4px 0;
}
.kv-grid {
  display: grid; grid-template-columns: max-content 1fr;
  column-gap: 14px; row-gap: 4px; margin: 0;
}
.kv-grid dt { font-size: 11px; color: var(--ink-3); white-space: nowrap; }
.kv-grid dd { font-size: 12px; color: var(--ink-1); margin: 0; font-family: var(--font-mono); }

.gain { color: var(--pos); }
.loss { color: var(--neg); }
.ticker { font-family: var(--font-mono); font-weight: 500; letter-spacing: -0.01em; }

/* Pills (alias of chip) */
.pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 6px; border-radius: 3px;
  font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.01em;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--ink-2); white-space: nowrap;
}
.pill.equity, .pill.etf { color: var(--accent-ink); border-color: var(--accent-soft); background: var(--accent-soft); }
.pill.bond, .pill.fixed-income { color: #92400E; border-color: #FCE5C8; background: #FDF1DE; }
.pill.cash { color: #475569; border-color: #DBE2EC; background: #EEF2F7; }
.pill.intl { color: #6B21A8; border-color: #ECDAF5; background: #F3E5FA; }
.pill.real-estate { color: #166534; border-color: #D6E8D2; background: #E5F1E0; }
.pill.crypto { color: #9D174D; border-color: #F9D5E5; background: #FCEBF1; }
.pill.taxable { color: var(--accent-ink); border-color: var(--accent-soft); background: var(--accent-soft); }
.pill.tax-deferred { color: #475569; border-color: #DBE2EC; background: #EEF2F7; }
.pill.tax-free { color: #166534; border-color: #D6E8D2; background: #E5F1E0; }

/* Section label */
.section-label {
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 10px;
}

/* Account list (dashboard old style) */
.account-list { display: flex; flex-direction: column; gap: 0; }
.account-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line-2);
}
.account-row:last-child { border-bottom: none; }
.account-icon {
  width: 32px; height: 32px; border-radius: 5px;
  display: grid; place-items: center;
  background: var(--bg-2); color: var(--ink-2);
  flex-shrink: 0; font-size: 14px;
}
.account-name { font-size: 12.5px; color: var(--ink-1); font-weight: 500; }
.account-type { font-size: 11px; color: var(--ink-3); margin-top: 2px; display: flex; align-items: center; gap: 6px; }
.account-bal { font-family: var(--font-mono); font-size: 13px; color: var(--ink-1); text-align: right; font-variant-numeric: tabular-nums; }
.account-grow { flex: 1; min-width: 0; }
.account-meta { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); margin-top: 1px; font-variant-numeric: tabular-nums; }

/* Asset mix list (dashboard old style) */
.alloc-list { display: flex; flex-direction: column; gap: 8px; }
.alloc-item { display: flex; align-items: center; gap: 10px; }
.alloc-dot { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
.alloc-label { font-size: 12px; color: var(--ink-1); flex: 1; }
.alloc-pct { font-family: var(--font-mono); font-size: 12px; font-weight: 500; color: var(--ink-1); font-variant-numeric: tabular-nums; }
.alloc-val { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.alloc-bar-wrap { flex: 1; height: 6px; background: var(--line-2); border-radius: 2px; overflow: hidden; margin: 2px 0; }
.alloc-bar { height: 100%; border-radius: 2px; }

/* Forms — alias old class names to new tokens */
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; align-items: end; }
.form-field { display: flex; flex-direction: column; gap: 5px; }
.form-label {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}
.form-input, .form-select {
  height: 30px; padding: 0 8px;
  border: 1px solid var(--line); border-radius: 4px;
  font-family: var(--font-sans); font-size: 12.5px;
  color: var(--ink-1); background: var(--surface);
  outline: none; transition: border 0.15s;
  font-variant-numeric: tabular-nums;
  width: 100%;
}
.form-input:focus-visible, .form-select:focus-visible { border-color: var(--accent); box-shadow: var(--ring-focus); }
.form-input[type="file"] { padding: 4px 8px; height: auto; }
textarea.form-input { white-space: pre-wrap; height: auto; padding: 6px 8px; min-height: 60px; line-height: 1.4; }

/* Styled file picker — a .btn-styled <label> fronting a visually-hidden
   <input type="file">. The native control renders the chosen filename inline
   and grows to fit it, so a long name bloats the control (the browser then
   truncates it mid-name) and can run past a narrow container. Fronting it
   with a fixed-width label keeps the picker compact; show the filename
   (wrapping) on its own line instead. The label carries the focus ring on
   behalf of the hidden — but still keyboard-focusable — input. */
.file-picker { cursor: pointer; }
.file-picker:focus-within { border-color: var(--accent); box-shadow: var(--ring-focus); }
.file-picker > input[type="file"] {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── Transactions page (preserved, frameless-styled by default) ─────── */
.txn-summary {
  display: grid; grid-auto-flow: column;
  grid-auto-columns: minmax(max-content, 1fr);
  overflow-x: auto;
  background: transparent;
  margin-bottom: 12px;
}
.txn-sum-card {
  padding: 14px 18px; border-right: 1px solid var(--line-2);
  display: flex; flex-direction: column; gap: 4px; min-width: 0;
}
.txn-sum-card:last-child { border-right: none; }
.txn-sum-label {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 2px;
}
.txn-sum-val {
  font-family: var(--font-mono); font-weight: 500;
  font-size: 18px; color: var(--ink-1);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.txn-sum-val.green { color: var(--pos); }
.txn-sum-val.red { color: var(--neg); }
.txn-sum-val.gold { color: var(--warn); }

.recon-strip {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 12px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.recon-strip.recon-ready { border-color: var(--pos); background: var(--pos-soft); }
.recon-strip.recon-delta { border-color: var(--warn); background: var(--warn-soft); }
.recon-strip.recon-unapproved { border-color: var(--warn-soft); background: var(--surface-2); }
.recon-info { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; min-width: 0; }
.recon-meta { display: flex; flex-direction: column; gap: 2px; }
.recon-meta-label {
  font-size: 10px; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
}
.recon-meta-asof { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.recon-numbers { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.recon-num { display: flex; flex-direction: column; gap: 2px; }
.recon-num-label {
  font-size: 10px; font-weight: 500; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-3);
}
.recon-num-val {
  font-family: var(--font-mono); font-weight: 500; font-size: 14px;
  color: var(--ink-1); font-variant-numeric: tabular-nums;
}
.recon-num-val.green { color: var(--pos); }
.recon-num-val.red { color: var(--neg); }
.recon-num-val.gold { color: var(--warn); }
.recon-action { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.recon-msg { font-size: 12.5px; color: var(--ink-1); }
.recon-msg.green { color: var(--pos); font-weight: 500; }
.recon-msg.muted { color: var(--ink-3); }

/* SPA stat strip — Working balance math on the left, Review /
   Reconciliation on the right. The math box reads as
   "Working = Uncleared + Cleared", with a label under each term so the
   user can see how the running total decomposes.

   BOTH CELLS ALWAYS RENDER. The right cell holds everything that used to
   be a conditionally-mounted banner above the strip; keeping it in the
   grid is what stops the ledger from jumping as counts hit zero. The
   `:has()` variant is gone with it — the template is unconditional. */
.txn-stats {
  display: grid;
  /* Three cells: the balance math, Entry review, Reconciliation. All
     three always render — Entry review doesn't depend on a bank
     connection, and Reconciliation keeps its place (with a hint instead
     of content) in All-accounts scope so the strip's shape is the same
     everywhere. */
  /* Measured at a 1440px window (1180px of strip). What each cell wants
     in order not to wrap:
       balance math   ~460px cash / ~590px brokerage (four terms)
       entry review   ~430px (three counts + "Show all N")
       reconciliation ~250px (a balance over a fixed-width button)
     Brokerage + three counts is 1270px, so at this width they cannot all
     win. The split below keeps the review counts on one line — they're
     the thing being read at a glance — which means a BROKERAGE-shape
     balance (four terms, not the usual three) wraps to two lines. That's
     cosmetic: the strip's height is pinned, so nothing below it moves. */
  grid-template-columns: minmax(0, 1.92fr) minmax(0, 1.6fr) minmax(0, 0.96fr);
  gap: 0;
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  background: var(--surface);
  margin-bottom: 0;
  overflow: hidden;
  /* FIXED HEIGHT ACROSS EVERY ACCOUNT AND EVERY STATE.
     Measured as the tallest configuration at desktop widths: a cash
     account carrying a bank comparison plus the three-line case
     (needs-review + needs-category + pending-matches). Everything else
     — a quiet account, All-accounts scope, a brokerage — comes in
     shorter and the Review bar stretches to absorb the difference, so
     the slack lands inside the block rather than as a gap under it.
     Without this the strip ran 101px on All-accounts and 143px on
     Checking, and flipping between accounts jolted the ledger.

     101 is MEASURED, not chosen — re-measure it rather than nudging it
     by eye. It is the natural height of the tallest configuration: the
     All-accounts view, where six-figure review counts push the Entry
     review line onto two rows. Every single account comes in at 80–86
     and carries the difference as cell padding.

     Re-measure whenever a cell's content changes shape, by overriding
     `min-height: 0` and sweeping the sidebar. This number has gone stale
     twice: 164 while the review counts stacked three deep, then 133
     while the four-term balance math still wrapped. Both times the
     surplus showed up as the things it was reported as — a void between
     the bank figure and the button under it, and an Entry-review block
     several times the height of its own text.

     It's a floor, not a cap: on a narrow window cells wrap and the strip
     grows, which is correct — better a taller header than clipped math. */
  min-height: 101px;
}
/* The loan strip below reuses .txn-stats for its own flex row of terms
   and sets its own display, so it must not inherit the two-column
   template. */
.txn-stats.txn-loan-stats { grid-template-columns: none; }
.txn-stat {
  padding: 12px 16px;
  border-right: 1px solid var(--line-2);
  display: flex; flex-direction: column; gap: 6px; min-width: 0;
}
.txn-stat:last-child { border-right: none; }
.txn-stat-label {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}
/* The "Reconciliation" label doubles as the disclosure for the bank-statement
   panel. It keeps the label's exact type so the strip's three headings still
   read as one row — only the caret and the hover/open colour say it's a
   control. A button reset is needed because the label was a <div>: without it
   the UA's button font and padding would make this heading taller than its
   two siblings and shove the cell's contents down. */
button.txn-stat-label-toggle {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; font-size: 10px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3); cursor: pointer;
}
button.txn-stat-label-toggle:hover,
button.txn-stat-label-toggle.is-open { color: var(--accent-ink); }
button.txn-stat-label-toggle:focus-visible {
  outline: none; box-shadow: var(--ring-focus); border-radius: 3px;
}
.txn-stat-label-caret { font-size: 8px; line-height: 1; }
.txn-stat-val {
  font-family: var(--font-mono); font-weight: 500;
  font-size: 18px; color: var(--ink-1);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  line-height: 1.2;
}
.txn-stat-val.neg { color: var(--neg); }
.txn-stat-val.warn { color: var(--warn); }
/* A stat value that carries a hover-tooltip breakdown (e.g. the
   brokerage Uncleared term, which decomposes into cash + positions)
   advertises it with the universal "there's more on hover" affordance:
   a dotted underline + help cursor. Without this the title tooltip was
   undiscoverable, unlike the Net-position headline beside it, which
   uses the `?` info glyph. */
.txn-stat-val.has-tip {
  cursor: help;
  text-decoration: underline dotted var(--line);
  text-underline-offset: 4px;
}

/* Working-balance math expression — Working = Uncleared + Cleared */
.txn-stat-math-row {
  display: flex; align-items: flex-start; gap: 10px;
  flex-wrap: wrap;
}
/* Brokerage shape: FOUR terms (Net = Cash + Booked + Uncleared) instead of
   the cash shape's three, in the same third of the strip. At the standard
   18px the identity needed ~574px against ~470px of cell on an account
   with large balances and wrapped to two lines — which made this the
   tallest cell in the strip and, because the strip is pinned to its
   tallest configuration, padded out every other account's cells to match.
   Stepping the figures down keeps it on one line. Labels and operators
   are unchanged; only the numerals shrink. */
.txn-stat-math-row.is-wide { gap: 8px; }
.txn-stat-math-row.is-wide .txn-stat-val { font-size: 14px; }
.txn-stat-math-row.is-wide .txn-stat-op { font-size: 14px; }
/* Pair each operator with its following term so they wrap together —
   otherwise a narrow container (e.g. brokerage view with the recon
   panel) leaves a trailing "+" stranded at the end of a line while
   the term it belongs to drops to the next line on its own. */
.txn-stat-pair {
  display: flex; align-items: flex-start; gap: 10px;
}
.txn-stat-term {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.txn-stat-term-label {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}
.txn-stat-op {
  font-family: var(--font-mono); font-weight: 400;
  font-size: 18px; color: var(--ink-3);
  line-height: 1.2;
  align-self: flex-start;
  padding: 0 2px;
}

/* Reconciliation cell — bank comparison on top, one call-to-action
   underneath. The whole cell greens once the ledger agrees with the
   bank; that tint is the glanceable "this account is settled" signal,
   which is why it's on the cell and not just the button. */
.txn-stat-recon {
  flex-direction: column; align-items: stretch; gap: 6px;
}
.txn-stat-recon.is-ready {
  background: color-mix(in oklab, var(--pos-soft) 50%, var(--surface));
}
/* Bank figure and CTA sit TOGETHER, top-aligned. They were previously
   pushed apart with space-between, which handed every pixel of the
   cell's spare height to the gap between them — so a reader saw an
   unexplained void between "Bank · as of …" and the button that refers
   to it. Any slack now falls at the bottom edge, where it reads as
   padding rather than as a separator. */
/* Bank figure and CTA sit SIDE BY SIDE, wrapping to two rows only when
   the cell is too narrow (a brokerage, where Mark to Market joins them).
   They were previously stacked and pushed apart with space-between,
   which handed every pixel of the cell's spare height to the gap between
   them — a reader saw an unexplained void between "Bank · as of …" and
   the button that refers to it. Side by side, this cell stops being the
   tallest thing in the strip, which is what lets the pinned height come
   down for everyone. */
.txn-recon-body {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  column-gap: 10px;
  row-gap: 6px;
  min-width: 0;
}

/* The bank comparison, as a button: clicking it shows the cleared rows
   behind the number. That used to be the standalone difference pill's
   job, which the CTA below absorbed when the two merged. */
.txn-recon-bank {
  background: none; border: 0; padding: 0;
  font: inherit; cursor: pointer;
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 1px; text-align: right; min-width: 0;
  border-radius: 3px;
}
.txn-recon-bank:focus-visible { outline: none; box-shadow: var(--ring-focus); }
/* 13px, not the strip's headline 15/18px: this is the BANK's number, a
   reference point for the working balance on the left rather than a
   headline of its own. It's also the widest thing competing with the CTA
   for this cell — at 15px a six-figure balance pushed the CTA onto a
   second row, which made that one account 31px taller than the rest. */
.txn-recon-bank-val {
  font-family: var(--font-mono); font-weight: 500;
  font-size: 13px; color: var(--ink-1);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.txn-recon-bank:hover .txn-recon-bank-val { color: var(--accent-ink); }
.txn-recon-bank-meta {
  font-size: 10.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.txn-recon-actions {
  display: flex; flex-wrap: wrap; justify-content: flex-end;
  align-items: center; gap: 4px;
}
/* One button, two labels — "−$2,790.19 off" or "Reconcile →". The
   min-width holds the slot steady so swapping between them (or between
   difference magnitudes) doesn't resize the cell. Kept to 104 so it fits
   BESIDE the bank figure on one row even when that figure runs to seven
   digits — wrapped, a single account was 31px taller than the rest and
   dragged the strip's pinned height up with it. 104 still covers both
   labels ("Reconcile →" ~85px, "−$2,790.19 off" ~100px), so the swap
   between them doesn't resize anything. */
.txn-recon-cta {
  min-width: 104px;
  justify-content: center;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Off-the-bank state: amber like every other "needs your attention"
   surface, rather than the primary blue it wears when the books agree
   and the action is simply "lock it". */
.txn-recon-cta.is-off {
  background: var(--warn-soft);
  border-color: var(--warn-soft);
  color: var(--warn);
  font-weight: 600;
}
.txn-recon-cta.is-off:hover { border-color: var(--warn); }
.txn-recon-cta-suffix {
  font-weight: 500;
  font-family: var(--font-sans);
  letter-spacing: 0;
}
.txn-recon-match-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 9px;
  background: var(--pos); color: #fff;
  font-size: 11px; font-weight: 700;
}
/* All-accounts scope: the cell holds its place in the grid but has
   nothing to reconcile against, so it says so instead of sitting blank. */
.txn-recon-scope-hint {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  font-size: 11.5px;
  color: var(--ink-4);
  text-align: right;
}

/* ── Entry review cell ────────────────────────────────────────────────
   One amber block holding the middot-separated counts and the "Show all
   N" button. Amber matches the per-row "This needs a category" chip and
   the "i" approval badge, so the aggregate and the rows it points at
   read as one signal.

   The bar is sized to its CONTENT and centred in the slot, not stretched
   to fill it. It used to stretch, on the theory that slack inside the
   block beats a gap under it — but the strip's pinned height is set by a
   neighbour (a brokerage's balance math, which wraps), so on a simple
   account that handed the bar ~47px of surplus and made a single line of
   text sit in an 88px slab. Centred, the block always matches its
   content and the surplus reads as cell padding. */
.txn-stat-review { flex-direction: column; align-items: stretch; gap: 6px; }
.txn-review-slot {
  flex: 1;
  display: flex;
  align-items: center;
  min-width: 0;
  font-size: 11.5px;
}
.txn-review-bar {
  flex: 1;
  display: grid;
  grid-template-columns: 12px minmax(0, 1fr) auto;
  gap: 8px;
  /* Centered, not top-aligned: the block stretches to the cell's full
     height, so content pinned to the top would look stranded. */
  align-items: center;
  padding: 5px 7px 5px 9px;
  border-radius: var(--radius);
  background: var(--warn-soft);
  border: 1px solid var(--warn-soft);
  min-width: 0;
}
.txn-review-bar-ico {
  color: var(--warn);
  font-weight: 700;
  line-height: 1.7;
  text-align: center;
}
/* The counts run INLINE, middot-separated, wrapping only when they must.
   Stacked they were three short strings in a tall narrow column that
   drove the strip's fixed height up and left the cell mostly
   whitespace. */
.txn-review-bar-lines {
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 5px;
}
.txn-review-sep { color: var(--warn); opacity: 0.5; }

/* Clickable counts. Padded well past the text so each is a real hit
   target rather than the dotted-underline text link they replaced — an
   11.5px string is a poor thing to aim at. */
.txn-review-line {
  background: none;
  border: 0;
  padding: 1px 4px;
  margin: 0 -2px;
  font: inherit;
  line-height: 1.7;
  color: var(--warn);
  text-align: left;
  cursor: pointer;
  border-radius: 3px;
  white-space: nowrap;
}
.txn-review-line:hover {
  background: color-mix(in oklab, var(--warn) 16%, transparent);
}
.txn-review-line:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
}

/* "Show all N" — the escape hatch to the whole backlog. Amber-outlined
   rather than filled so it stays secondary to the lines beside it; the
   lines are the specific, actionable thing. */
.txn-review-showall {
  flex: none;
  white-space: nowrap;
  border-color: color-mix(in oklab, var(--warn) 45%, transparent);
  color: var(--warn);
  background: transparent;
}
.txn-review-showall:hover {
  border-color: var(--warn);
  background: color-mix(in oklab, var(--warn) 12%, transparent);
}
/* All-clear state — the same block shape and footprint, drained of the
   amber. Still a block rather than bare text because the slot stretches
   to the row's full height, and a lone line of text floating in it reads
   as exactly the dead space this layout removed. Kept nearly
   transparent (hairline + muted text, no fill): at the strip's fixed
   height this block is often much taller than its one line of content,
   and a solid grey slab that size dominates a header whose whole message
   is "nothing to do here". */
.txn-review-bar.is-clear {
  grid-template-columns: 12px minmax(0, 1fr);
  background: transparent;
  border-color: var(--line-2);
  border-style: dashed;
}
.txn-review-bar.is-clear .txn-review-bar-ico { color: var(--pos); }
.txn-review-clear { color: var(--ink-4); line-height: 1.7; }

/* Reconcile popover — anchored to the "Reconcile" button in the
   recon strip (top-right). Opens downward-leftward from the button,
   compact card with optional diff + chooser. */
.recon-popover {
  width: 360px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: 0 10px 30px rgba(0,0,0,0.22);
  z-index: 1500;
  display: flex; flex-direction: column;
}
.recon-popover-header {
  padding: 10px 14px; border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
}
.recon-popover-title { font-size: 13px; font-weight: 600; color: var(--ink-1); }
.recon-popover-close {
  background: none; border: none; color: var(--ink-3);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.recon-popover-close:hover { color: var(--ink-1); }
.recon-popover-body {
  padding: 12px 14px; display: flex; flex-direction: column; gap: 10px;
}
.recon-popover-prompt {
  margin: 4px 0 0; font-size: 12px; color: var(--ink-2); font-weight: 500;
}
.recon-popover-diff {
  background: var(--warn-soft); border: 1px solid var(--warn-soft);
  border-radius: 4px; padding: 8px 10px;
  display: flex; flex-direction: column; gap: 6px;
}
.recon-popover-diff-header {
  display: flex; justify-content: space-between; align-items: baseline;
}
.recon-popover-diff-label {
  font-size: 11px; font-weight: 600; color: var(--warn);
  letter-spacing: 0.06em; text-transform: uppercase;
}
.recon-popover-diff-amount {
  font-family: var(--font-mono); font-weight: 600; font-size: 15px;
  color: var(--warn); font-variant-numeric: tabular-nums;
}
.recon-popover-diff-rows {
  display: flex; flex-direction: column; gap: 2px;
  font-size: 11.5px; color: var(--ink-2);
}
.recon-popover-diff-rows > div {
  display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
.recon-popover-choices {
  display: flex; flex-direction: column; gap: 6px;
  margin-top: 4px;
}
.recon-popover-choice {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 12px; color: var(--ink-1); cursor: pointer;
  line-height: 1.4;
}
.recon-popover-choice input[type="radio"] {
  margin-top: 2px; flex-shrink: 0;
}
.recon-popover-choice-hint {
  color: var(--ink-3); font-size: 11px; font-style: italic;
}
.recon-popover-cat {
  height: 28px; padding: 0 8px;
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--surface); color: var(--ink-1);
  font: inherit; font-size: 12px; outline: none;
  margin-left: 22px;
}
.recon-popover-cat:focus-visible {
  border-color: var(--accent); box-shadow: var(--ring-focus);
}
.recon-popover-warnings {
  font-size: 11px; color: var(--ink-3); line-height: 1.5;
}
.recon-popover-footer {
  padding: 10px 14px; border-top: 1px solid var(--line);
  display: flex; gap: 8px; justify-content: flex-end;
  background: var(--surface-2);
}


/* Active client-side review filter chip in the toolbar — appears when
   the user clicks a reconcile blocker. Click the × (or the chip) to
   clear the filter. */
.txn-review-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 6px 0 10px;
  border-radius: 4px;
  border: 1px solid var(--warn-soft);
  background: var(--warn-soft); color: var(--warn);
  font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer; white-space: nowrap;
}
.txn-review-chip:hover { border-color: var(--warn); }
/* Enter/Space on a focused chip is the only mouse-free way to clear the
   filter, so it needs a visible keyboard focus ring like the rest of the
   toolbar (it doesn't carry the .chip class that the universal rule hits). */
.txn-review-chip:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.txn-review-chip-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 8px;
  background: rgba(180,83,9,0.14); color: var(--warn);
  font-size: 14px; line-height: 1;
}

.txn-toolbar { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.txn-search-wrap { position: relative; flex: 1; min-width: 200px; max-width: 400px; }
.txn-search {
  width: 100%; height: 30px; padding: 0 12px 0 32px;
  border: 1px solid var(--line); border-radius: 4px;
  font-family: var(--font-sans); font-size: 12.5px;
  color: var(--ink-1); background: var(--surface); outline: none;
}
.txn-search:focus-visible { border-color: var(--accent); box-shadow: var(--ring-focus); }
.txn-search-icon {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  color: var(--ink-3); font-size: 12px; pointer-events: none;
}
.txn-filter-btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 30px; padding: 0 10px; border-radius: 4px;
  border: 1px solid var(--line); background: var(--surface);
  font: inherit; font-size: 12px; color: var(--ink-2); cursor: pointer;
  white-space: nowrap; text-decoration: none;
}
.txn-filter-btn:hover { border-color: var(--ink-3); color: var(--ink-1); text-decoration: none; }
.txn-filter-btn.active {
  background: var(--accent-soft); border-color: var(--accent-soft);
  color: var(--accent-ink); font-weight: 500;
}
.txn-filter-btn.gold {
  color: var(--warn); border-color: var(--warn-soft); background: var(--warn-soft);
}
.txn-filter-badge {
  background: var(--warn-soft); color: var(--warn);
  border-radius: 8px; padding: 0 5px; font-size: 10px;
  font-variant-numeric: tabular-nums; font-family: var(--font-mono);
}
.txn-add-btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 30px; padding: 0 12px; border-radius: 4px;
  border: 1px solid var(--accent); background: var(--accent); color: #fff;
  font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
  text-decoration: none;
}
.txn-add-btn:hover { background: var(--accent-ink); border-color: var(--accent-ink); text-decoration: none; }

/* Segmented Undo / Redo control. Two adjacent icon buttons sharing one
   border, sized to match .txn-filter-btn so it sits flush with the
   filter dropdowns in the toolbar. */
.txn-undo-group {
  display: inline-flex;
  height: 30px; border-radius: 4px;
  border: 1px solid var(--line); background: var(--surface);
  overflow: hidden;
}
.txn-undo-btn {
  width: 30px; height: 100%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; padding: 0; margin: 0;
  color: var(--ink-2); cursor: pointer;
}
.txn-undo-btn + .txn-undo-btn { border-left: 1px solid var(--line); }
.txn-undo-btn:hover:not(:disabled) {
  background: var(--surface-2); color: var(--ink-1);
}
.txn-undo-btn:disabled { color: var(--ink-4); cursor: default; }
.txn-undo-btn svg { width: 14px; height: 14px; display: block; }

/* Edit-history timeline panel (UndoHistoryPanel). A `.pop` floating
   surface anchored under the history button: undone (redoable) steps
   grey on top, a "current state" pointer rule, then the active steps.
   Clicking a step jumps the server-side timeline to it. */
.undo-history-pop { width: 260px; padding-bottom: 0; }
.undo-history-list { max-height: 320px; overflow-y: auto; }
.undo-history-row {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 5px 10px;
  background: transparent; border: none; text-align: left;
  font: inherit; font-size: 12.5px; color: var(--ink-1);
  cursor: pointer;
}
.undo-history-row:hover:not(:disabled) { background: var(--surface-2); }
/* Keyboard focus gets the same menu-item treatment as .txn-ctx-item, so
   arrow-key travel through the timeline is as visible as the mouse cue. */
.undo-history-row:focus:not(:disabled) {
  background: var(--accent-soft); color: var(--accent); outline: none;
}
.undo-history-row:disabled { cursor: default; opacity: 0.6; }
.undo-history-row.is-undone .undo-history-label {
  color: var(--ink-4); font-style: italic;
}
.undo-history-label {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.undo-history-time { flex: none; font-size: 11px; color: var(--ink-4); }
.undo-history-pointer {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 10px; color: var(--accent);
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
}
.undo-history-pointer::before,
.undo-history-pointer::after {
  content: ""; flex: 1; height: 1px; background: var(--accent);
  opacity: 0.5;
}
.undo-history-empty {
  padding: 10px; font-size: 12px; color: var(--ink-4);
}

.content:has(.txn-page) { padding: 0; overflow: hidden; }
/* The ledger is edge-to-edge in every density. Roomy's `.body` gutter
   (set in ember-polish.css) ties on specificity with the reset above and
   would otherwise win by source order, re-inflating the gutter only in
   roomy — bump specificity here so the ledger stays flush. */
[data-density="roomy"] .content:has(.txn-page) { padding: 0; }
/* The SPA mounts into #spa-root; bind its height to the .content viewport
   so .txn-page's `height: 100%` resolves to the visible area instead of
   inflating to fit children. Without this, the virtualized .txn-table-wrap
   has no bounded scroll container and the virtualizer can't tell which
   rows are off-screen. */
#spa-root { height: 100%; display: flex; flex-direction: column; min-height: 0; }
.txn-page { display: flex; flex-direction: column; height: 100%; padding: 0; min-height: 0; gap: 0; flex: 1; }
.txn-page > form { display: flex; flex-direction: column; flex: 1; min-height: 0; }

/* Tight, frameless ledger: any remaining page band is a full-bleed row
   divided by a hairline rather than a floating rounded card with gutters
   around it — see EMBER-DESIGN "section breaks are hairlines, not gaps."
   Strip the radius + side/top borders + bottom margin so each band sits
   flush against the next and the ledger reaches the viewport edges.

   The register deliberately has almost nothing left in this list: bands
   that mount and unmount while the user works were re-laying out the whole
   ledger, so the attention banner moved into the stat strip's Review box
   and the match-fix guidance became a floating overlay. */
.txn-page > .flash,
.txn-page > .txn-notice {
  margin-bottom: 0;
  border-radius: 0;
  border-top: 0;
  border-left: 0;
  border-right: 0;
  border-bottom: 1px solid var(--line);
}

.txn-table-wrap {
  flex: 1; min-height: 0; overflow: auto;
  background: transparent;
  border-radius: 0; margin-bottom: 0;
}
/* The ledger's <thead> is position:sticky and pins over the top of this
   scroll area (~31px tall). scroll-padding-top keeps keyboard
   scroll-into-view (scrollRowIntoView in TransactionsTable.tsx) from
   landing the cursor row tucked under the pinned header; the small bottom
   pad leaves the row a hair off the bottom edge on ArrowDown instead of
   flush against it. Scoped to .txn-page so the shared .txn-table-wrap on
   the Balances grid is unaffected. */
.txn-page .txn-table-wrap {
  scroll-padding-top: 36px;
  scroll-padding-bottom: 8px;
}
/* The global ::-webkit-scrollbar-thumb is painted var(--line) — a near-white
   hairline that is effectively invisible on the ledger's tall scroll area, so
   the register reads as "no scrollbar." Give this one scroll container a
   clearly visible thumb (var(--ink-4), the color the global rule only reaches
   on hover). Blink/WebKit keep their always-visible, space-consuming classic
   bar from the global ::-webkit-scrollbar rule. */
.txn-page .txn-table-wrap::-webkit-scrollbar-thumb {
  background: var(--ink-4);
}
.txn-page .txn-table-wrap::-webkit-scrollbar-thumb:hover {
  background: var(--ink-3);
}
/* Firefox ignores ::-webkit-scrollbar, so color the bar via the standard
   properties there instead. Guarded to Firefox only: setting scrollbar-width
   / scrollbar-color in Blink/WebKit silently disables the ::-webkit classic
   bar and reverts it to a macOS overlay bar that fades out when idle — the
   very "invisible scrollbar" we're fixing. */
@supports not selector(::-webkit-scrollbar) {
  .txn-page .txn-table-wrap {
    scrollbar-width: thin;
    scrollbar-color: var(--ink-4) transparent;
  }
}
/* While a selection exists, the floating .selection-pill (position:fixed,
   bottom:22px, up to ~72px tall when it wraps on narrow viewports) hangs
   over the bottom of this scroll area. Without extra padding, ArrowDown's
   scrollRowIntoView lands the cursor row at the bottom edge — directly
   under the pill, where it can't be read. Reserve clearance matching the
   104px spacer row rendered at the end of <tbody> so the focused row
   always parks above the pill. Toggled by .has-selection on the wrap
   (TransactionsTable.tsx) so an unselected ledger keeps the tighter 8px. */
.txn-page .txn-table-wrap.has-selection {
  scroll-padding-bottom: 104px;
}
.txn-table { width: 100%; border-collapse: collapse; }

/* ── Search "potential matches" ──────────────────────────────────────────
   Rows matching the search that the current account / state filters exclude,
   shown in groups below the in-scope results so the user doesn't have to
   switch to "All" to find them. See docs/frontend/transactions-search.md.

   The visual job is one thing only: say "these are outside what you filtered
   to" without competing with the real results. So — a band header (the same
   idiom as the Scheduled / Pending groups), muted ink, and a left rule in a
   quiet hairline. Deliberately NOT --accent: chrome means "chosen / where you
   are", and these rows are precisely what the user did NOT choose. */
.txn-scope-toggle {
  cursor: pointer;
  user-select: none;
  background: var(--surface-2);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.txn-scope-toggle > td { padding: 6px 10px; }
.txn-scope-head { display: flex; align-items: center; gap: 6px; }
.txn-scope-label {
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-3);
}
.txn-scope-count {
  font-size: 10px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.txn-scope-hint { font-size: 10px; color: var(--ink-4); }
/* Muted, but never illegible — the row still has to be readable and editable.
   `:not(.is-editing)` keeps a promoted row at full strength: once you're
   editing it, it's the thing you're looking at, not background. */
.txn-scope-group .txn-row:not(.is-editing) > td { color: var(--ink-3); }
/* The left rule. On the first cell (not the <tr>) so it survives the sticky /
   virtualized column treatment. */
.txn-scope-group .txn-row > td:first-child {
  box-shadow: inset 2px 0 0 var(--line-2);
}
.txn-scope-loading > td,
.txn-scope-empty > td {
  padding: 6px 10px;
  font-size: 10px;
  color: var(--ink-4);
}
.txn-scope-empty > td { padding: 12px 12px 8px; }
/* "Showing 20 of 137 — show all 137". The footer of a paged group: quiet like
   the header it belongs to, but with a real click target, so a long group
   reads as paged rather than truncated. Carries the same left rule as the
   rows above it so the band still reads as one block. */
.txn-scope-more {
  cursor: pointer;
  user-select: none;
}
.txn-scope-more > td {
  padding: 6px 10px;
  font-size: 10px;
  color: var(--ink-4);
  box-shadow: inset 2px 0 0 var(--line-2);
}
.txn-scope-more-cta {
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.txn-scope-more:hover > td,
.txn-scope-more:focus-visible > td { background: var(--bg-2); }
.txn-scope-more:focus-visible,
.txn-scope-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.txn-table thead tr {
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.txn-table thead th {
  position: sticky; top: 0; z-index: 2;
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-3); padding: 8px 12px; text-align: left;
  user-select: none; white-space: nowrap;
  background: var(--bg);
  box-shadow: inset 0 -1px 0 var(--line);
}
/* Icon-style column headers are narrow (24-48px) and the default
 * 12px horizontal padding on <th> eats more than the column width,
 * clipping the checkbox / icon. Reduce padding and center-align
 * for those headers. */
.txn-table thead th[data-col-key="checkbox"],
.txn-table thead th[data-col-key="approval"],
.txn-table thead th[data-col-key="tags"],
.txn-table thead th[data-col-key="cleared"],
.txn-table thead th[data-col-key="arrow"] {
  padding: 8px 4px;
  text-align: center;
}
/* Tags body cell: the inline-flex button defaults to left-aligned
 * content, but the column is icon-style now — center the chip so
 * it lines up with the icon in the header. */
.txn-table td[data-cell="tags"] .inline-trigger {
  justify-content: center;
}
.txn-table th.sortable { cursor: pointer; }
.txn-table th.sortable:hover { color: var(--ink-1); }
/* Keyboard focus ring for the sortable ledger headers (role="button" +
   tabIndex). Matches the treatment the Balances / Holdings / Tax-lots
   headers use so a keyboard user can see which column Enter / Space will
   sort. outline-offset pulls the ring inside the sticky header edge. */
.txn-table thead th[role="button"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  color: var(--ink-1);
}
.txn-table th.sort-active, .txn-table th.sort-asc, .txn-table th.sort-desc { color: var(--accent); }
.txn-table th.sort-asc::after { content: ' ▲'; font-size: 8px; }
.txn-table th.sort-desc::after { content: ' ▼'; font-size: 8px; }
.txn-table td {
  padding: 0; border-bottom: 1px solid var(--line-2); vertical-align: middle;
}
.txn-table tr:last-child td { border-bottom: none; }
.txn-row:hover td { background: var(--bg-2); }
.txn-cell {
  /* Legibility bump matching the sidebar upgrade: row text runs a notch
     larger (14px, was 12.5px) at font-weight 500 so the ledger reads as
     clearly as the nav. Sub-text sizes below were bumped in step. See
     docs/frontend/transactions.md "Row typography". */
  padding: 6px 12px; font-size: 14px; font-weight: 500; color: var(--ink-1);
  height: var(--row-h);
}
.txn-cell.center { text-align: center; }
.txn-cell.right { text-align: right; }
.txn-cell.muted { color: var(--ink-3); font-size: 12.5px; }
.txn-cell.tabular { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* Dates use TABULAR figures — same face, equal-width digits.
   The date column is content-fit and measured ONCE, so it ends up sized to
   whichever dates were rendered at measure time. In the proportional face the
   digits differ enough that "YYYY-MM-DD" spans ~73.6px to ~83.0px depending on
   which digits it contains, so a column fitted to a narrow date ("2026-07-11")
   clips a wider one ("2026-08-28" / "2026-05-11") to "2026-08-…". Split legs
   made that reproducible on demand — they render *after* the measurement, so
   their own dates never get a vote — but any date scrolling into the
   virtualized viewport later had the same exposure. Tabular figures make every
   date exactly one width, which is the property measure-once needs to be
   correct, and align the column digit-for-digit the way a ledger should.

   Bank date is the same kind of column with the same measure-once exposure,
   and it additionally mixes two faces in one column — italic while it agrees
   with Date, upright while it drifts — so the widest rendered cell at measure
   time decides the width for both. Equal-width digits keep that honest, and
   keep the two date columns aligned with each other. */
.txn-cell[data-cell="date"],
.txn-cell[data-cell="bankDate"] { font-variant-numeric: tabular-nums; }
.txn-cell.payee { font-weight: 500; white-space: nowrap; }
.txn-cell.account-name {
  font-size: 12.5px; max-width: 140px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink-3);
}

/* ── Investment ledger rows ──────────────────────────────────────
 * Cash and investment rows share the same 11-column table layout.
 * Investment rows render their own cell content (security symbol +
 * subtitle in the payee column, TxnType label in the category column,
 * qty × price in the memo column, position-unit POS pill on the
 * amount). Pickers do not open on investment rows — the cells render
 * as plain text. See .1-tasks/unified-ledger-design.md for the
 * design hand-off this implements. */
.txn-row.inv-row .inv-security-sym {
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--ink-1);
  letter-spacing: -0.01em;
  /* The ticker symbol sits inline on the first line of the cell; the
   * long-form security name (.inv-security-sub, below) is a block on the
   * second line. Symbols are short, so this stays one line in practice —
   * overflow is left visible rather than clipped. The single-line
   * nowrap+ellipsis treatment lives on .inv-security-sub. */
  display: inline;
  white-space: normal;
  overflow: visible;
}
/* Clickable ticker — filters the ledger to this security's activity.
 * Reset the native <button> chrome (at zero specificity via :where so
 * the .inv-security-sym / auto-row rules keep owning font + color), then
 * add a hover/focus affordance so the filter interaction is
 * discoverable instead of looking like static text. */
.txn-row.inv-row button:where(.inv-security-link) {
  background: none;
  border: none;
  margin: 0;
  padding: 0;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease);
}
.txn-row.inv-row button.inv-security-link:hover,
.txn-row.inv-row button.inv-security-link:focus-visible {
  outline: none;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* Keyboard focus additionally gets the register's standard focus ring, so a
 * Tab-only user can tell focus from a mouse hover — the two were otherwise
 * pixel-identical (accent + underline), leaving no cue which ticker was the
 * keyboard target while navigating the investment ledger. This also brings
 * the ticker link in line with the other keyboard-operable read-view
 * controls (the approval badge, cleared dot, and MTM-override cell all draw
 * a ring on focus) and satisfies EMBER-DESIGN's "visible focus ring on every
 * interactive element."
 *
 * The ring is INSET. The payee cell used to be overflow:visible, so an
 * outset --ring-focus glow rendered in full; now that the cell clips its
 * line to hold the row to one line (see the payee/category/memo rule
 * below), an outset glow would be sheared off at the cell edge — the
 * ticker sits at the very left of the cell, so the left half of the glow
 * would vanish. This is the same trade the MTM-override cell already
 * makes for the same reason, and it uses the same inset --signal ring so
 * the two clipped-column focus cues match. */
.txn-row.inv-row button.inv-security-link:focus-visible {
  border-radius: 3px;
  box-shadow: inset 0 0 0 2px var(--signal);
}
.txn-row.inv-row .inv-security-sub {
  font-size: 12px;
  color: var(--ink-3);
  /* The long-form name sits INLINE, after the ticker on the same line —
   * it used to be a `display: block` second line, which gave every
   * investment row two lines of text where a cash row has one. Same
   * pixel height either way (the 42px row absorbed it), but the register
   * read as two different kinds of row stacked together, and an
   * investment row is not supposed to announce itself as one.
   *
   * Truncation is the CELL's job now (see the payee/category/memo rule
   * below): this is one inline run in a nowrap line that the cell
   * ellipsises as a whole, so the name gives way at the column edge and
   * the ticker — the identifying half — is never the part that's lost. */
  display: inline;
  margin-left: 6px;
  white-space: nowrap;
  line-height: 1.25;
}
.txn-row.inv-row .inv-txn-type {
  color: var(--ink-2);
  /* One line, like every other cell on the row — "Dividend · reinvested"
   * used to wrap and break mid-word, which was the third source of a
   * taller-than-cash investment row. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Click-to-edit affordance on the investment Type label. Clicking it
 * promotes the row and opens the TxnType picker (mouse parity with the `k`
 * shortcut). Without a cue it read as static text, leaving the interaction
 * undiscoverable — the same problem .inv-security-link solves for the
 * ticker just above. Mirror that treatment: pointer cursor + an accent
 * underline on hover. Only editable types carry the --editable modifier;
 * engine-managed MARKET_CHANGE / OPENING_POSITION rows keep the inert
 * plain label. */
.txn-row.inv-row .inv-txn-type--editable { cursor: pointer; }
.txn-row.inv-row .inv-txn-type--editable:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* Click-to-edit affordance on the computed Amount figure. A click on the
 * dollar figure of a non-auto investment row promotes the row and lands on
 * its leading value input (qty on a trade row) — the same edit path the `$`
 * shortcut and the qty × price detail click resolve to. Without a cue the
 * most prominent number on the row read as inert static text, the same gap
 * the Type label and ticker link solve above. Pointer + an underline on
 * hover; unlike the Type label this keeps the figure's own pos/neg colour
 * (the sign is a signal — EMBER-DESIGN "color is never the only signal"), so
 * the hover adds only the underline, not an accent recolour. Auto rows get
 * the .mtm-override-btn treatment instead; locked rows carry no modifier. */
.txn-row.inv-row .inv-amount--editable { cursor: pointer; }
.txn-row.inv-row .inv-amount--editable:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* Click-to-edit affordance on the investment Category "—". Investment rows
 * classify by Type, not category, so the cell shows a muted dash — but it's
 * clickable: the row onClick remaps a category-cell click to the Type cell
 * and opens the TxnType picker (mouse parity with `k`). Give editable rows
 * the same pointer + accent-underline-on-hover cue the Type label, qty ×
 * price detail, and ticker link carry, so the last clickable investment cell
 * stops reading as inert text. Engine-managed MARKET_CHANGE / OPENING_POSITION
 * rows open no picker, so they keep the inert dash (no --editable modifier). */
.txn-row.inv-row .inv-category-dash { color: var(--text-muted, #9ca3af); }
.txn-row.inv-row .inv-category-dash--editable { cursor: pointer; }
.txn-row.inv-row .inv-category-dash--editable:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* An investment row is the SAME HEIGHT as a cash row, and reads as one
 * line of text, not two.
 *
 * These cells used to be `white-space: normal` so the txn-type and memo
 * could wrap to a second line rather than run past the column edge. Two
 * things went wrong with that. The payee cell stacked the ticker over
 * the long-form security name, so every investment row showed two lines
 * of text against a cash row's one — the register read as two different
 * kinds of row interleaved, when the whole point of the unified ledger
 * is that they're one queue. And a trade row's memo (`80 sh @ $478.00 ·
 * Demo BUY VOO`) genuinely wrapped at the memo column's natural width,
 * pushing that row to ~47px against its neighbours' 42px — a visibly
 * taller row on exactly the entries the user adds by hand.
 *
 * So: one line, truncate with an ellipsis at the column edge, the same
 * contract cash-row .txn-cell-text has always had. The register freezes
 * its layout over a measured <colgroup>, so a clipped tail can't push
 * the table into horizontal scroll; every truncated element carries a
 * `title` with the full text. */
.txn-row.inv-row .txn-cell {
  white-space: nowrap;
  vertical-align: middle;
}
/* Each cell keeps its children in ONE inline flow and truncates the
 * whole line at the cell edge. `text-overflow: ellipsis` on the cell
 * (not on the children) is what makes a multi-element flow — ticker +
 * name, or qty×price + separator + note — truncate as a single line.
 *
 * Do NOT reach for flex here. `display: flex` on a <td> takes the cell
 * out of the table layout algorithm, and in this fixed-layout table the
 * row box then sizes off its remaining table-cell siblings: the flexed
 * cells measured a correct 42px while every sibling grew to 70px,
 * making the row taller than before rather than shorter. */
.txn-row.inv-row .txn-cell[data-cell="payee"],
.txn-row.inv-row .txn-cell[data-cell="category"],
.txn-row.inv-row .txn-cell[data-cell="memo"] {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.txn-row.inv-row .inv-txn-type-sep {
  color: var(--ink-4);
  margin: 0 6px;
}
.txn-row.inv-row .inv-txn-type-user {
  color: var(--ink-2);
}
.txn-row.inv-row .inv-detail {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  font-size: 12.5px;
  white-space: nowrap;
}
/* Click-to-edit affordance on the qty × price detail ("100 sh @ $50.00").
 * Clicking it promotes the row and lands focus on the shares input — the
 * same routing the `$` shortcut and an Amount-cell click use (the onClick
 * remaps a click inside .inv-detail to the amount cell). Without a cue it
 * read as static text, leaving the most-edited figures on a trade row
 * undiscoverable — the same gap .inv-txn-type--editable and
 * .inv-security-link already close for the Type label and ticker on the
 * same row. Mirror that treatment: pointer cursor + an accent underline on
 * hover. Only BUY / SELL / REINVEST rows render .inv-detail, and those
 * types are always user-editable, so no modifier guard is needed. */
.txn-row.inv-row .inv-detail { cursor: pointer; }
.txn-row.inv-row .inv-detail:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.txn-row.inv-row .inv-detail-sep {
  font-family: var(--font-sans);
  font-style: italic;
  color: var(--ink-4);
  font-size: 12px;
  margin: 0 4px;
}
/* MARKET_CHANGE rows: system-generated, dimmed; the user still
 * reviews + approves them via the existing cleared-state controls. */
.txn-row.inv-row.auto-row td {
  background: color-mix(in oklab, var(--bg-2) 50%, var(--surface, white));
}
.txn-row.inv-row.auto-row .inv-security-sym {
  font-style: italic;
  font-weight: 400;
  color: var(--ink-3);
}
.txn-row.inv-row.auto-row .inv-security-sub {
  color: var(--ink-4);
}
/* "auto" pill on MARKET_CHANGE rows — sits next to the TxnType
 * label in the class column. */
.auto-chip {
  display: inline-block;
  font-size: 9.5px;
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  padding: 1px 5px;
  margin-left: 6px;
  background: var(--warn-soft, #fef3c7);
  color: var(--warn, #b45309);
  border-radius: 8px;
  vertical-align: middle;
  /* The chip carries an explanatory tooltip (what "auto" means for the
   * row); the help cursor advertises that hovering it tells you more. */
  cursor: help;
}
/* POS pill — flags an amount cell whose value is a position-leg
 * change rather than a cash movement (MARKET_CHANGE, REINVEST,
 * reinvested DIVIDEND). Sits to the LEFT of the number so the
 * tabular alignment of the digits is preserved. */
.pos-pill {
  display: inline-block;
  font-size: 9px;
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.10em;
  color: var(--ink-4);
  background: var(--bg-2);
  border-radius: 3px;
  padding: 1px 4px;
  margin-right: 6px;
  vertical-align: middle;
}
/* Amount cell on an investment row whose value is the position leg —
 * the number itself stays in ink-1 (neutral) for negative or zero,
 * and the existing color rules (pos green) apply for positive. */
.txn-cell.amt-position { font-variant-numeric: tabular-nums; }

/* Visible focus ring on the row-editor's plain <input> cells (qty, price,
 * position amount, dividend/fee amount, memo, the amount input).
 * These are styled inline by editorInputStyle() with a static
 * --accent border and outline:none, so a keyboard-focused field looked
 * identical to its idle neighbours — most confusing in the investment
 * compound cell, where qty + price + memo sit side by side and Tab gave
 * no cue which one was live. EMBER-DESIGN mandates a visible focus ring on
 * every interactive element; --ring-focus-strong is the token reserved for
 * inline-edit. We scope to :not([class]) so this hits only the bare editor
 * inputs and leaves the pickers' own classed search inputs (which already
 * carry a ring) untouched. The inset --signal hairline reads even inside
 * the compound cell's overflow:hidden, which would otherwise clip the
 * outset glow. */
.txn-row.is-editing input:not([class]):focus-visible {
  box-shadow: inset 0 0 0 1.5px var(--signal), var(--ring-focus-strong);
}

/* Hover affordance on the read-view MTM-override amount cell of an `auto`
 * investment row (MARKET_CHANGE / OPENING_POSITION). The cell is click-to-
 * override (opens the inline editor), carries a title tooltip, and draws the
 * focus ring below — but it's a bare <span role="button">, so the global
 * `button { cursor: pointer }` rule never reached it and it gave no pointer
 * cursor and no hover cue. That left it the lone editable investment read-view
 * cell without one: the ticker link, Type label, computed amount, qty × price
 * detail, and category dash all brighten or underline on hover. Mirror
 * .inv-amount--editable: a pointer cursor plus an underline on hover, keeping
 * the figure's own pos/neg colour (the sign is a signal — EMBER-DESIGN "color
 * is never the only signal"), so the affordance adds only the underline rather
 * than an accent recolour. */
.txn-row.inv-row .mtm-override-btn { cursor: pointer; }
.txn-row.inv-row .mtm-override-btn:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Visible focus ring on the read-view MTM-override amount cell of an
 * `auto` investment row (MARKET_CHANGE / OPENING_POSITION). The cell is a
 * real keyboard button — role="button", tabIndex=0 — so Tab reaches it and
 * Enter/Space opens the override input, but it carried no focus ring and
 * fell back to the browser default outline: inconsistent with the register's
 * other keyboard-operable read-view elements (the approval badge, cleared
 * dot, and ticker link all use --ring-focus). An INSET ring (like the
 * inline editor inputs above) sits cleanly inside the tabular amount cell;
 * the small radius keeps the glow off the tabular digits. */
.txn-row.inv-row .mtm-override-btn:focus-visible {
  outline: none;
  border-radius: 3px;
  box-shadow: inset 0 0 0 1.5px var(--signal);
}

/* The MTM-override edit input that the button above swaps in on click /
 * Enter. It seeds + selects its value so it's focused the instant it opens,
 * yet it was the one inline-edit field in the register still falling back to
 * the browser's default outline — every other edit field (the row-editor
 * inputs, the InlineEditCell inputs, and the override BUTTON above) carries
 * the `--ring-focus` / inset `--signal` treatment. Worse, in the wide split
 * Outflow / Inflow layout the amount columns clip overflow, so the default
 * outline gets sheared away there, leaving a keyboard user who Tabs back into
 * an open override with no focus cue. Mirror the button's INSET `--signal`
 * ring (inset for the same clip reason) so both states of the override read
 * the same, and `outline: none` since the inline style sets neither. */
.txn-row.inv-row .mtm-override-input:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 1.5px var(--signal);
}

/* "× revert to cash" button in the row editor's security cell, shown only
 * while a new cash row has been flipped to investment shape (row.kind ===
 * "cash"). It's a real Tab stop — the PayeePicker's onTab lands forward Tab
 * on it so a keyboard user who flipped the row by mistake can undo it — but
 * it shipped as a bare inline-styled glyph: cursor:pointer the only hint, no
 * hover state, and just the browser-default outline on focus, out of step
 * with every other keyboard-operable control in the register (the security
 * picker beside it, the cleared dot, the ticker link all give hover feedback
 * and carry --ring-focus). Reverting is a NEUTRAL utility — it preserves the
 * date/account picks and discards nothing the server has saved — so it
 * brightens to the chrome ink on hover rather than the danger tone a
 * destructive control uses, and carries the register's standard --ring-focus
 * on keyboard focus so a Tab-only user can see where focus has landed. */
.txn-row.is-editing .inv-revert-cash {
  display: inline-flex; align-items: center; justify-content: center;
  /* 24px min hit area (WCAG 2.5.5 AA), matching the register's other
   * small icon controls (.txn-post-now / .approval-badge). The glyph
   * still reads at 12px; min-width/min-height only pad the *target* so
   * this undo-the-flip control isn't an easy mis-click on trackpad or
   * touch. The taller security picker beside it already sets the
   * compound cell's height, so this floor never grows the row. */
  min-width: 24px; min-height: 24px;
  background: none; border: none; cursor: pointer;
  padding: 0 4px; margin-left: 2px;
  font-size: 12px; line-height: 1; border-radius: 4px;
  color: var(--ink-4);
  transition: color var(--t-fast) var(--ease);
}
.txn-row.is-editing .inv-revert-cash:hover { color: var(--ink-1); }
.txn-row.is-editing .inv-revert-cash:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
}

/* Column resizing on the SPA transactions table.
 *
 * The handle is an 8px-wide hit zone *inside* each resizable <th>,
 * flush against the column edge it controls (right-edge for most
 * columns; left-edge for Amount, whose right side is crowded by
 * the cleared/arrow icon columns). Keeping the handle fully inside
 * the column means Memo's right handle and Amount's left handle —
 * which both target the Memo|Amount boundary — sit *adjacent* (16px
 * total grab zone) rather than overlapping in the same 8px space.
 * Drag works identically from either side; double-click auto-fits
 * whichever column owns the handle clicked. */
.txn-col-resizer {
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  user-select: none;
  z-index: 3;
}
.txn-col-resizer-left {
  left: 0;
  right: auto;
}
.txn-col-resizer::after {
  content: "";
  position: absolute;
  top: 20%;
  right: 0;
  width: 1px;
  height: 60%;
  background: var(--ink-3, #888);
  opacity: 0.6;
  transition: top 120ms, height 120ms, background 120ms, opacity 120ms;
}
.txn-col-resizer-left::after {
  right: auto;
  left: 0;
}
.txn-col-resizer:hover::after,
.txn-col-resizer:active::after {
  top: 0;
  height: 100%;
  background: var(--accent, #2563eb);
  opacity: 1;
}

.approval-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--warn); color: white;
  font-size: 9px; font-weight: 700; font-style: italic;
  font-family: 'Times New Roman', Georgia, serif;
  line-height: 1; cursor: pointer;
  user-select: none; padding: 0; border: none;
  transition: transform 0.1s, background 0.15s;
}
.approval-badge:hover { background: var(--warn); transform: scale(1.15); }
.approval-badge:focus-visible { outline: none; box-shadow: var(--ring-focus); }

.approve-popover {
  position: fixed; z-index: 1000; display: none;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 5px; box-shadow: 0 4px 14px rgba(0,0,0,0.16);
  padding: 3px; gap: 3px;
}
.approve-popover.visible { display: inline-flex; }
.approve-popover-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 4px 10px; border-radius: 4px; font-size: 12px; line-height: 1;
  font-family: inherit; cursor: pointer; border: none;
  background: transparent; color: var(--ink-1); text-decoration: none;
}
.approve-popover-btn.primary { background: var(--accent); color: #fff; font-weight: 500; }
.approve-popover-btn.primary:hover { opacity: 0.9; color: #fff; }
.approve-popover-btn.edit { color: var(--ink-3); padding: 4px 8px; }
.approve-popover-btn.edit:hover { color: var(--accent); background: var(--accent-soft); }
.approve-popover-btn:focus-visible { outline: none; box-shadow: var(--ring-focus); }

/* Link-approval popover buttons set color/border inline but no box-shadow, so
   this layers a focus ring on top — they auto-focus Approve on open and a
   keyboard user confirming/rejecting an auto-link needs to see which control
   holds focus. Mirrors .approve-popover-btn / .cleared-btn. */
.link-approval-popover-btn:focus-visible { outline: none; box-shadow: var(--ring-focus); }

.cleared-btn {
  background: none; border: none; cursor: pointer; padding: 0;
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
}
.cleared-btn:focus-visible { outline: none; box-shadow: var(--ring-focus); border-radius: 50%; }

.txn-ctx-menu {
  position: fixed; z-index: 1100; display: none;
  min-width: 180px; padding: 4px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 6px; box-shadow: 0 6px 20px rgba(0,0,0,0.18);
  font-size: 12.5px;
}
.txn-ctx-menu.visible { display: block; }
.txn-ctx-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 6px 10px; border-radius: 4px;
  background: transparent; border: none; cursor: pointer;
  color: var(--ink-1); font-family: inherit; font-size: 12.5px;
  text-align: left; text-decoration: none;
}
.txn-ctx-item:hover, .txn-ctx-item:focus {
  background: var(--accent-soft); color: var(--accent); outline: none;
}
.txn-ctx-item.danger { color: var(--red); }
.txn-ctx-item.danger:hover, .txn-ctx-item.danger:focus {
  background: var(--red-light); color: var(--red);
}
.cleared-dot {
  width: 12px; height: 12px; border-radius: 50%;
  border: 1.5px solid var(--ink-4); background: var(--bg-2);
  position: relative;
}
/* Cleared — a soft tonal green disc carrying a monoline "c". This is the
   ledger convention from Quicken/QuickBooks, where a lowercase c marks a
   cleared transaction. The glyph is a CSS mask of a stroked arc tinted
   --pos, so it stays crisp at 12px and never depends on a font's ✓ shape
   the way the old 8px text glyph did. White strokes in the mask make it
   work under both alpha and luminance mask modes. */
.cleared-dot.cleared { background: var(--pos-soft); border-color: var(--pos); }
.cleared-dot.cleared::after {
  content: ''; position: absolute; inset: 0;
  background-color: var(--pos);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round'%3E%3Cpath d='M16.2 8 A5.6 5.6 0 1 0 16.2 16'/%3E%3C/svg%3E") center / 7.5px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round'%3E%3Cpath d='M16.2 8 A5.6 5.6 0 1 0 16.2 16'/%3E%3C/svg%3E") center / 7.5px no-repeat;
}
/* Reconciled — the same tonal treatment in chrome blue, carrying a
   monoline "R". Quicken/QuickBooks flip the cleared "c" to an "R" once a
   statement is reconciled, so the c → R pair reads as one system. This
   replaces the former padlock mask (the lock-out is still signalled by
   the row being non-editable; the dot now says "reconciled" the same way
   the cleared dot says "cleared"). Heavy round-stroke R drawn to match
   the c's weight. */
.cleared-dot.reconciled { background: var(--accent-soft); border-color: var(--accent); }
.cleared-dot.reconciled::after {
  content: ''; position: absolute; inset: 0;
  background-color: var(--accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 4.5 V19.5 M8 4.5 H12.8 A3.5 3.5 0 0 1 12.8 11.5 H8 M10.4 11.5 L16.6 19.5'/%3E%3C/svg%3E") center / 8px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 4.5 V19.5 M8 4.5 H12.8 A3.5 3.5 0 0 1 12.8 11.5 H8 M10.4 11.5 L16.6 19.5'/%3E%3C/svg%3E") center / 8px no-repeat;
}
/* Pending — bank-confirmed but not yet posted. A solid gold dot, so it
   reads distinctly from a green cleared dot and a plain grey-outline
   uncleared dot. (Single source of truth for both the posted-list and
   the pending-group render paths — previously only the posted list
   carried this as an inline style, so pending rows in the pending group
   looked identical to uncleared.) */
.cleared-dot.pending {
  background: var(--gold, #d97706); border-color: var(--gold, #d97706);
}
/* Removed — the bank revoked a charge it had previously reported. A red
   dashed ring reads as "was here, now gone". */
.cleared-dot.removed {
  background: transparent; border: 2px dashed var(--red);
}

.cat-text { font-size: 12.5px; color: var(--ink-1); }
.cat-text.uncategorized { color: var(--warn); font-style: italic; }
.cat-text.split { color: var(--ink-3); font-style: italic; }

/* SPA category picker: flag rows whose category is missing but
   expected (i.e. not a split or transfer). Same warn colour as the
   legacy .cat-text.uncategorized so the cue is consistent across
   the two table renderers. */
.inline-trigger.cat-uncategorized { color: var(--warn); font-style: italic; }
.inline-trigger.cat-uncategorized:hover { color: var(--warn); opacity: 0.8; }

.inline-trigger {
  display: inline-block; max-width: 100%;
  /* padding-right reserves a few pixels between the ellipsis and the
   * cell border so a truncated value doesn't sit flush against the
   * next column's content. */
  padding: 2px 6px 2px 0; font-size: 12.5px; font-weight: inherit;
  cursor: pointer; border: none; background: none; color: inherit;
  font-family: inherit; text-align: left;
  /* Truncate with ellipsis so cell content can't bleed into the
   * column to its right when the user resizes a column narrower
   * than its content. The button is the clipping container, not
   * the surrounding <td>, so picker popovers (which live in the
   * <td> as siblings) still render outside the cell bounds. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color 0.15s;
}

/* Plain-text cell content that needs the same truncation treatment as
 * the inline-trigger button (e.g. the Account column, which doesn't
 * use a click-to-edit shell). */
.txn-cell-text {
  display: inline-block; max-width: 100%;
  padding-right: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inline-trigger:hover { color: var(--accent); }
.inline-trigger[disabled] { cursor: not-allowed; opacity: 0.6; }
/* Visible focus ring on every click-to-edit trigger button, per the
 * EMBER-DESIGN "focus ring on every interactive element" rule. The
 * trigger carries no border or outline of its own (the read view stays
 * calm), so a keyboard-focused-but-closed trigger looked identical to its
 * idle neighbours. This bites hardest after a keyboard user commits an
 * inline edit (Enter / Esc) on a scheduled / future-dated cash row's
 * InlineEditCell: focus returns to the now-closed trigger (it deliberately
 * doesn't reopen — see skipNextFocusOpenRef in InlineEditCell.tsx) and the
 * user lost any cue to where they are. The 3px radius tracks the rounded
 * accent wrap the row editor draws around its picker triggers. Subsumes the
 * earlier editor-scoped (.txn-row.is-editing) and footer-scoped
 * (.txn-row-edit-actions) copies of this same ring. */
.inline-trigger:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-radius: 3px;
}

/* Account modal → Import transactions → "Recent imports". The list is a
 * `.tbl` (docs/EMBER-DESIGN.md § Repeated items are tables), so these rules
 * only have to make the name cell behave: a full-bleed trigger that drills
 * into the import's rows, ellipsizing before the "Reverted" chip beside it
 * rather than shoving it out of the cell.
 *
 * The name brightens to the accent on hover/focus and shows the standard
 * focus ring — the register's click-to-edit affordances. The colour lives
 * here (not inline) so hover can override it; a reverted batch stays dimmed
 * until pointed at. */
.import-history-open {
  display: inline-flex; align-items: center;
  max-width: 100%; height: var(--import-row-content);
  appearance: none; background: none; border: none;
  padding: 0; margin: 0;
  font: inherit; text-align: left; color: inherit;
  cursor: pointer;
}
.import-history-name {
  color: var(--ink-1);
  min-width: 0; flex: 0 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The chip never gives way — the name ellipsizes around it, because a status
 * you can lose to a long filename isn't a status. Height is pinned for the
 * same reason the controls are: one pixel of extra chip is one pixel of extra
 * row, on the one row that has a chip. */
.import-history-open .chip {
  flex: 0 0 auto;
  display: inline-flex; align-items: center;
  height: 20px; padding: 0 7px;
}
.import-history-name.is-reverted { color: var(--ink-3); }
.import-history-open:hover .import-history-name,
.import-history-open:focus-visible .import-history-name { color: var(--accent); }
.import-history-open:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-radius: 4px;
}

/* Dates in the "Imported" column are digits the user scans down — mono +
 * tabular, like every other number column. `td.r` would give both, but this
 * column is left-aligned (it's a date, not a quantity), so it takes the
 * metrics without the alignment. */
.import-history-when {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Row controls (Re-apply, and the rename editor's Save / Cancel) take the row
 * ceiling too. Centre them while we're here: they're inline-level, so left to
 * the baseline they'd sit with descender space below them. */
.import-history-tbl td.actions > * { vertical-align: middle; }
.import-history-tbl td.actions .btn {
  display: inline-flex; align-items: center;
  height: var(--import-row-content);
  padding: 0 9px; font-size: 12px;
}

/* `.tbl-fit` clips every cell so text ellipsizes at the column edge — but
 * these two cells hold focusable controls whose focus ring would be clipped
 * with them, and both already truncate their own contents (the name via
 * .import-history-name, the actions not at all). Let the ring out. */
.import-history-tbl tbody td:first-child,
.import-history-tbl tbody td.actions { overflow: visible; }

/* The import *preview* step rides in the same shell as the upload step, whose
 * width is set by the history table. Preview is a short form, though, so
 * every block in it takes a readable measure rather than the full 720-800px:
 * a name input stretched to a filename's worth of empty space reads as a
 * mistake. Applied to the children rather than the body itself so the body
 * keeps owning the scroll and its scrollbar stays on the dialog's edge. */
.import-preview-body > * { max-width: 560px; }

/* Import-detail header: the import's name IS the rename trigger. Unlike the
 * history row — where clicking the name is already spoken for (it drills into
 * the import) and rename needs its own pencil — nothing else competes for the
 * click here, so a separate control would be one affordance too many. Mirrors
 * the click-to-rename primitive the accounts table uses (.vb-inline-name):
 * invisible at rest, a soft plate on hover, accent border on keyboard focus,
 * and a text cursor announcing "this is editable". */
.import-detail-name {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  padding: 2px 6px;
  margin: -2px -6px;
  border-radius: 4px;
  font: inherit;
  text-align: left;
  color: var(--ink-1);
  cursor: text;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.import-detail-name:hover {
  background: var(--surface-2);
  border-color: var(--line);
}
.import-detail-name:focus-visible {
  outline: none;
  border-color: var(--accent);
}

/* Every control a history row can hold is pinned to this height, so a row
 * with a "Reverted" chip and a Re-apply button is exactly as tall as a row
 * with neither. Left to their natural heights the chip (23.8px) and the
 * button (34px) made the one reverted row 8px taller than its neighbours —
 * a visible step in the middle of a column of rows. The cell padding that
 * turns this into a full row height lives in ember-polish.css beside the
 * width plan, because roomy's `.tbl` padding has to be overridden there. */
:root { --import-row-content: 24px; }

/* Rename trigger on each history row. Always rendered (never hover-only —
 * that reads as absent to a keyboard user) but dimmed to --ink-3 so ten rows
 * of pencils don't compete with the names; it comes forward on hover/focus
 * and whenever its own row is pointed at. */
.import-history-rename {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--import-row-content); height: var(--import-row-content);
  border: none; background: none; padding: 0;
  border-radius: 4px; cursor: pointer;
  color: var(--ink-3);
  transition: color 120ms ease, background 120ms ease;
}
.import-history-rename:hover {
  color: var(--accent);
  background: var(--accent-soft);
}
.import-history-rename:focus-visible {
  outline: none;
  color: var(--accent);
  box-shadow: var(--ring-focus);
}

.inline-input {
  width: 100%; min-width: 60px;
  border: none; border-bottom: 1px solid var(--accent);
  outline: none; background: transparent;
  font-family: var(--font-sans); font-size: 12px;
  color: var(--ink-1); padding: 1px 0;
}
.inline-input.inline-date {
  width: 100%; min-width: 110px;
  border: 1px solid var(--accent); border-radius: 3px;
  padding: 2px 4px; font-size: 11.5px;
  background: var(--surface); color: var(--ink-1);
}
.inline-input.inline-amount {
  width: 100%; text-align: right;
  border: 1px solid var(--accent); border-radius: 3px;
  padding: 2px 6px; font-size: 12.5px; font-weight: 500;
  background: var(--surface); color: var(--ink-1);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}

.cat-dropdown-wrap { position: relative; display: inline-block; }
.cat-dropdown {
  position: fixed; z-index: 1000;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  min-width: 220px; max-height: 300px; overflow-y: auto; padding: 5px;
}
.cat-dropdown-search {
  width: 100%; padding: 6px 9px; border: 1px solid var(--line);
  border-radius: 4px; font-size: 12px; color: var(--ink-1);
  background: var(--bg); outline: none; margin-bottom: 5px;
  font-family: inherit;
}
.cat-dropdown-search:focus-visible { border-color: var(--accent); box-shadow: var(--ring-focus); }
.cat-group-label {
  font-size: 9.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3);
  padding: 6px 8px 3px;
}
.cat-option {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 4px; cursor: pointer;
  font-size: 12.5px; color: var(--ink-1); user-select: none;
  transition: background 0.1s;
}
.cat-option:hover, .cat-option.active { background: var(--accent-soft); color: var(--accent-ink); }
.cat-option.selected { background: var(--accent-soft); color: var(--accent-ink); font-weight: 500; }
.cat-option .cat-clear { color: var(--ink-3); font-style: italic; }
.cat-option-empty {
  padding: 12px 10px; font-size: 12px; color: var(--ink-3);
  font-style: italic; text-align: center;
}
.cat-option-create { border-top: 1px dashed var(--line); margin-top: 4px; }
.cat-option-create:hover { background: var(--accent-soft); color: var(--accent-ink); }

.txn-footer {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px; border-top: 1px solid var(--line);
  background: var(--surface-2);
  font-size: 11px; color: var(--ink-3);
  position: sticky; bottom: 0; z-index: 1;
}
.txn-footer-stat { display: inline-flex; align-items: center; gap: 6px; margin-right: 18px; }
.txn-footer-val {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  color: var(--ink-1); font-variant-numeric: tabular-nums;
}
.txn-footer-val.green { color: var(--pos); }
.txn-footer-val.red { color: var(--neg); }

/* Active row (keyboard cursor / clicked highlight) — softer tint plus a
   left accent stripe. The right edge is the sign-accent channel below, so
   selection no longer claims it. */
.txn-row.focused td, .txn-row.is-selected td {
  background: color-mix(in oklab, var(--accent-soft) 45%, var(--surface));
  color: var(--ink-1);
}
.txn-row.focused td:first-child, .txn-row.is-selected td:first-child {
  box-shadow: inset 2px 0 0 var(--accent);
}

/* Inflow / outflow sign accent — a 3px bar on the row's right edge.
   Replaces the old Outflow/Inflow split columns: the amount stays a
   single accounting value (parens = negative) and direction reads off
   this colored edge. Drawn on the last cell so it sits flush at the
   table's right edge. */
.txn-row[data-direction="inflow"] td:last-child {
  box-shadow: inset -3px 0 0 var(--pos);
}
.txn-row[data-direction="outflow"] td:last-child {
  box-shadow: inset -3px 0 0 var(--neg);
}
/* The cursor row is a real focus target now (roving tabindex), and a
   focused row always carries .focused, whose accent background + inset
   bars above are the cursor indicator — so suppress the default outline
   that would otherwise double up on the <tr>. */
.txn-row:focus { outline: none; }

/* Bulk-selected row (checkbox checked) — full accent-soft, no stripes. */
.txn-table input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 13px; height: 13px;
  border: 1.25px solid var(--ink-4);
  border-radius: 3px;
  background: var(--surface);
  cursor: pointer;
  position: relative;
  vertical-align: middle;
  transition: border-color 0.12s, background 0.12s;
  margin: 0;
}
.txn-table input[type="checkbox"]:hover { border-color: var(--ink-3); }
/* appearance:none strips the native focus outline, so a keyboard user
   tabbing onto a row's selection checkbox (or the select-all header box)
   would otherwise get no visible cursor. Restore the standard ring —
   matching .cleared-btn — per EMBER-DESIGN "every
   interactive element has a visible focus ring." */
.txn-table input[type="checkbox"]:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--ring-focus);
}
.txn-table input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
}
.txn-table input[type="checkbox"]:checked::after {
  content: ''; position: absolute;
  left: 3px; top: 0;
  width: 5px; height: 9px;
  border: solid white;
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(42deg);
}
.txn-table tbody tr:has(input[type="checkbox"]:checked) td { background: var(--accent-soft); }

/* A printed key. Three things were wrong with these and they compounded:
   the ink (--ink-4 at 0.7 opacity — about 1.8:1 on the dark surface), the
   size (9px), and the typeface.

   The typeface is the one worth explaining, because "kbd is mono" is the
   house rule everywhere else (see docs/EMBER-DESIGN.md → Type) and this
   is the deliberate exception. Fira Code is drawn for reading *lines of
   code* at 12-14px: low contrast, open counters, generous side bearings,
   letterforms tuned so `l` `1` `I` stay distinguishable in a column of
   text. Almost none of that pays off on a single glyph in a 16px chip —
   and its light strokes, which read as elegant in a code block, just
   thin out. A keycap isn't code; it's one letter that has to be legible
   at a glance and nothing else. The sans face at 11px/600 has heavier
   stems, tighter fitting and a larger apparent x-height in the same box,
   which is exactly what a one-glyph label needs. It's also what macOS
   and Windows put in their own menu keycaps.

   `tabular-nums` keeps digit chips the same width as letter chips, and
   no `opacity` at all: dimming the whole chip is what made these
   unreadable, and a keycap is either worth printing at full strength or
   not worth printing. */
.kbd-hint {
  display: inline-block; margin-left: 4px;
  font-family: var(--font-sans); font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  color: var(--kbd-ink); background: var(--kbd-bg);
  border: 1px solid var(--kbd-line); border-radius: 3px;
  padding: 0 5px; line-height: 1.45;
  min-width: 1.1em; text-align: center;
  vertical-align: middle; user-select: none;
}
.txn-search-wrap .search-hint {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%); margin: 0;
}

.kbd-help-overlay {
  display: none; position: fixed; inset: 0;
  background: rgba(15, 18, 24, 0.55); z-index: 2000;
  align-items: center; justify-content: center; padding: 24px;
}
.kbd-help-overlay.visible { display: flex; }
.kbd-help-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: 0 12px 36px rgba(0,0,0,0.25);
  width: 100%; max-width: 720px; max-height: 90vh;
  display: flex; flex-direction: column; overflow: hidden;
}
.kbd-help-header {
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
}
.kbd-help-header h2 { font-size: 14px; font-weight: 600; color: var(--ink-1); margin: 0; }
.kbd-help-close {
  background: none; border: none; color: var(--ink-3);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 0 6px;
}
.kbd-help-close:hover { color: var(--ink-1); }
.kbd-help-body {
  padding: 14px 18px; overflow-y: auto;
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px 28px;
}
.kbd-help-body section h3 {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); margin: 0 0 8px;
}
.kbd-help-body dl {
  display: grid; grid-template-columns: max-content 1fr;
  column-gap: 14px; row-gap: 6px; margin: 0;
}
.kbd-help-body dt { font-size: 11px; white-space: nowrap; }
.kbd-help-body dd { font-size: 11px; color: var(--ink-3); margin: 0; }
.kbd-help-body kbd {
  display: inline-block;
  font-family: var(--font-mono); font-size: 10px;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: 3px; padding: 1px 5px;
  color: var(--ink-1);
}
.kbd-help-footer {
  padding: 8px 18px; border-top: 1px solid var(--line);
  font-size: 10.5px; color: var(--ink-3); background: var(--bg);
}
/* Search input row — sits between header and body so it scrolls
   independently of the shortcut list. */
.kbd-help-search {
  padding: 10px 18px; border-bottom: 1px solid var(--line);
}
.kbd-help-search input {
  width: 100%; padding: 6px 10px; font: inherit; font-size: 12.5px;
  background: var(--bg); color: var(--ink-1);
  border: 1px solid var(--line); border-radius: 4px;
}
.kbd-help-search input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
/* Section caption — short explanation under the h3 (e.g. "skipped
   while typing in a field"). */
.kbd-help-detail {
  grid-column: 1 / -1;
  font-size: 10.5px; color: var(--ink-3);
  margin: -4px 0 6px;
}
/* Empty state when no shortcut matches the search query. */
.kbd-help-empty {
  grid-column: 1 / -1;
  font-size: 11px; color: var(--ink-3);
  padding: 16px 0; text-align: center;
}
/* "then" separator between keys in a sequence (g, then d). */
.kbd-then {
  display: inline-block;
  font-size: 9.5px;
  color: var(--ink-4);
  letter-spacing: 0.04em;
  margin: 0 4px;
}
.kbd-help-body dt .kbd-then { vertical-align: middle; }
/* Row-level secondary detail (rare — used for "confirms before
   deleting" on bulk actions). */
.kbd-row-detail {
  display: block;
  font-size: 10px; color: var(--ink-4);
  margin-top: 2px;
}
@media (max-width: 600px) {
  .kbd-help-body { grid-template-columns: 1fr; }
}

/* ── Generic modal ────────────────────────────────────────────────── */
.modal-overlay {
  display: none; position: fixed; inset: 0;
  background: rgba(15, 18, 24, 0.55); z-index: 2000;
  align-items: center; justify-content: center; padding: 24px;
}
.modal-overlay.visible { display: flex; }
.modal-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: 0 12px 36px rgba(0,0,0,0.25);
  width: 100%; max-width: 560px; max-height: 90vh;
  display: flex; flex-direction: column; overflow: hidden;
}
.modal-header {
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
}
.modal-header h2 { font-size: 14px; font-weight: 600; color: var(--ink-1); margin: 0; }
.modal-close {
  background: none; border: none; color: var(--ink-3);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 0 6px;
}
.modal-close:hover { color: var(--ink-1); }
.modal-body { padding: 14px 18px; overflow-y: auto; }

/* ── Sell planner table (preserved) ────────────────────────────────── */
.plan-card { padding: 0; }
.plan-card .card-header { padding: 12px 16px; }
.plan-table-wrap {
  overflow: auto; max-height: calc(100vh - 460px); min-height: 240px;
  border-top: 1px solid var(--line);
}
.plan-table { width: 100%; border-collapse: collapse; }
.plan-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface);
  font-size: 10px; font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-3);
  padding: 8px 12px; text-align: left; white-space: nowrap;
  box-shadow: inset 0 -1px 0 var(--line);
  user-select: none;
}
.plan-table th.num, .plan-table td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.plan-table td {
  padding: 8px 12px; font-size: 12.5px; color: var(--ink-1);
  border-bottom: 1px solid var(--line-2); vertical-align: middle;
}
.plan-table tr:last-child td { border-bottom: none; }
.plan-table .exp-col { width: 28px; padding: 6px 8px; text-align: center; }

.grp-row { cursor: pointer; }
.grp-row > td { font-weight: 500; background: var(--surface-2); }
.grp-row:hover > td { background: var(--bg-2); }
.grp-row.has-selection > td.grp-proceeds,
.grp-row.has-selection > td.grp-tax,
.grp-row.has-selection > td.grp-sell-qty { color: var(--accent); font-weight: 600; }
.grp-account { font-size: 11.5px; color: var(--ink-3); }

.grp-toggle {
  background: none; border: none; cursor: pointer; padding: 0;
  width: 18px; height: 18px; border-radius: 3px;
  font-size: 10px; color: var(--ink-3); line-height: 1;
}
.grp-toggle:hover { background: var(--accent-soft); color: var(--accent); }

.rec-badge {
  display: inline-block; min-width: 16px; padding: 1px 5px;
  background: var(--accent); color: #fff; border-radius: 3px;
  font-size: 9.5px; font-weight: 600; text-align: center;
  font-variant-numeric: tabular-nums; font-family: var(--font-mono);
}

.lot-row > td { background: var(--surface); }
.lot-row:hover > td { background: var(--bg-2); }
.lot-row.recommended > td { background: var(--accent-soft); }
.lot-row.recommended:hover > td { background: var(--accent-soft); opacity: 0.85; }
.lot-row:not(.checked) > td.lot-sell-qty,
.lot-row:not(.checked) > td.lot-proceeds,
.lot-row:not(.checked) > td.lot-tax { color: var(--ink-4); }
.lot-row.checked > td.lot-proceeds,
.lot-row.checked > td.lot-tax { color: var(--ink-1); font-weight: 500; }

.lot-cb { cursor: pointer; }
.lot-acquired { font-size: 11.5px; }
.lot-indent { color: var(--ink-4); margin-right: 4px; }

.lot-actions {
  display: inline-flex; align-items: center; gap: 6px;
  margin-left: 8px; opacity: 0; transition: opacity 0.15s ease;
}
.lot-row:hover .lot-actions { opacity: 1; }
.lot-actions a, .lot-actions button {
  color: var(--ink-3); font-size: 12px; line-height: 1;
  padding: 0 4px; border-radius: 3px; cursor: pointer;
  background: none; border: none; text-decoration: none;
}
.lot-actions a:hover { color: var(--accent); }
.lot-actions button:hover { color: var(--neg); }

.link-btn {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent); font-size: 11.5px; font-family: inherit;
  text-decoration: underline; text-decoration-style: dotted;
}
.link-btn:hover { opacity: 0.85; }

/* Hide old floating theme toggle (replaced by /settings page) */
.theme-toggle { display: none !important; }

/* Auth pages — center a panel on a clean backdrop */
.auth-page {
  display: grid; place-items: center;
  min-height: 100vh; padding: 24px;
  background: var(--bg);
}
.auth-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 28px 28px 24px;
  width: 100%; max-width: 380px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.04);
}
.auth-brand {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 20px;
}
.auth-brand .side-mark { width: 28px; height: 28px; }
.auth-brand .auth-wordmark {
  font-weight: 600; font-size: 18px; color: var(--ink-1);
  letter-spacing: -0.02em;
}
.auth-brand .auth-wordmark sup {
  font-weight: 400; color: var(--ink-3); font-size: 10px;
  margin-left: 4px; vertical-align: super;
}
.auth-card h1 {
  font-size: 18px; font-weight: 500; color: var(--ink-1);
  margin-bottom: 18px; letter-spacing: -0.015em;
}
.auth-card .form-row { display: block; }
.auth-card .form-field { margin-bottom: 12px; }
.auth-foot {
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line);
  font-size: 12px; color: var(--ink-3); text-align: center;
}
.auth-foot a { color: var(--accent); }

/* Settings choice cards (for theme + density picker) — base rules live
   above (around line 652 with .choice). Keep the two-column variant and
   the narrow-viewport collapse here. */
.choice-grid.two { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 720px) {
  .choice-grid, .choice-grid.two { grid-template-columns: repeat(2, 1fr); }
  /* On narrow viewports, drop the label-left layout — stack label above cards. */
  .appearance-field { flex-direction: column; align-items: stretch; gap: 4px; }
  .appearance-field .field-label { flex: none; }
}

/* ── Payees page (master/detail split) ─────────────────────────────── */
.payee-split {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(360px, 1fr);
  gap: 18px;
  align-items: start;
}
@media (max-width: 1180px) { .payee-split { grid-template-columns: 1fr; } }

.payee-tbl {
  table-layout: fixed;
  min-width: 0;
  width: 100%;
}
.payee-tbl thead th { background: var(--surface); }
.payee-tbl tbody td {
  height: 44px;
  padding: 6px 10px;
  overflow: hidden;
}
.payee-tbl tbody tr { cursor: pointer; }
.payee-tbl tbody tr:hover:not(.is-selected) td { background: var(--bg-2); }

/* Tag manager (/app/tags). Same fixed-layout contract as .payee-tbl: without
 * it the table sizes to its content and bleeds past the panel's right edge,
 * clipping the row actions. The Tag column is the only flexible one — every
 * other width is declared inline in src/tags/App.tsx and sized so the two
 * action buttons ("Merge…" + "Archive", "Restore" + "Delete…") always fit. */
.tag-tbl {
  table-layout: fixed;
  min-width: 0;
  width: 100%;
}
.tag-tbl thead th { background: var(--surface); }
.tag-tbl tbody td {
  height: 44px;
  padding: 6px 10px;
  overflow: hidden;
}
.tag-tbl tbody tr { cursor: pointer; }
.tag-tbl tbody tr:hover:not(.is-selected) td { background: var(--bg-2); }
/* While a row is being renamed, its cells must not clip: the name-conflict
 * error floats below the input and is wider than the Tag column, and it
 * carries the "Merge instead" button that is the whole point of the 409.
 * The row also lifts above its neighbours so the popover isn't painted over. */
.tag-tbl tbody tr.is-editing td { overflow: visible; }
.tag-tbl tbody tr.is-editing { position: relative; z-index: 5; }

/* The name cell holds a chip that must be allowed to ellipsize rather than
 * force the column wider. */
.tag-tbl .tag-name-btn { max-width: 100%; overflow: hidden; }
.tag-tbl .tag-name-btn .tag-chip {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.payee-name {
  font-weight: 500; color: var(--ink-1);
  overflow: hidden; text-overflow: ellipsis;
}
.payee-alias-preview {
  color: var(--ink-4);
  font-family: var(--font-mono); font-size: 10.5px;
  margin-top: 1px; overflow: hidden; text-overflow: ellipsis;
}

.payee-detail .panel-head {
  background: var(--surface-2);
  gap: 12px;
  align-items: flex-start;
  padding: 14px 16px;
}

.payee-section {
  padding: 14px 16px;
  border-bottom: 1px solid var(--line-2);
}
.payee-section:last-child { border-bottom: none; }
.payee-section-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 8px;
}
.payee-hint {
  font-size: 11px;
  color: var(--ink-3);
  margin-top: 8px;
  line-height: 1.45;
}

.alias-list { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.alias-chip {
  display: inline-flex; align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 7px;
  border-radius: 3px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  color: var(--ink-2);
  line-height: 1.2;
  font-family: var(--font-mono);
  font-size: 11px;
}
.alias-x {
  display: inline-grid; place-items: center;
  width: 14px; height: 14px;
  border: none; background: transparent;
  border-radius: 2px;
  color: var(--ink-3);
  cursor: pointer;
}
.alias-x:hover { background: var(--neg-soft); color: var(--neg); }
.alias-empty {
  color: var(--ink-4);
  font-size: 11.5px;
  font-style: italic;
}

.alias-add { display: flex; gap: 6px; margin-top: 8px; }
.alias-add .input { flex: 1; font-family: var(--font-mono); font-size: 11px; }

/* Segmented filter pills */
.seg {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface);
  overflow: hidden;
  height: 28px;
}
.seg-btn {
  font: inherit;
  font-size: 11.5px; font-weight: 500;
  color: var(--ink-2);
  background: transparent;
  border: none;
  padding: 0 12px;
  height: 26px;
  cursor: pointer;
  border-right: 1px solid var(--line);
  white-space: nowrap;
}
.seg-btn:last-child { border-right: none; }
.seg-btn:hover { background: var(--bg-2); color: var(--ink-1); }
.seg-btn.is-on { background: var(--accent-soft); color: var(--accent-ink); }

.mini-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--line-2);
  border: 1px solid var(--line-2);
  border-radius: 4px;
  overflow: hidden;
}
.mini-stat { background: var(--surface); padding: 8px 10px; }
.mini-stat-label {
  font-size: 9.5px; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}
.mini-stat-val {
  font-size: 14px; font-weight: 500; color: var(--ink-1);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.mini-stat-val.mono { font-family: var(--font-mono); }

.payee-foot {
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
}
.payee-foot .grow { flex: 1; min-width: 200px; font-size: 11px; color: var(--ink-4); }

/* Inline "set category" warn pill */
.cat-warn {
  color: var(--warn);
  font-style: italic;
  font-size: 12px;
}

/* ── SPA helpers (shared across balances/transactions/categories SPAs) ─── */
.spa-loading {
  padding: 24px; color: var(--ink-3); font-size: 13px;
}
.spa-loading-inline { font-size: 11px; color: var(--ink-4); }
.spa-error {
  padding: 18px 20px; background: var(--neg-soft); color: var(--neg);
  border: 1px solid var(--neg-soft); border-radius: var(--radius);
  margin: 12px 0; display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.spa-error button {
  background: var(--surface); color: var(--ink-1); border: 1px solid var(--line);
  border-radius: 4px; padding: 6px 12px; font-size: 12px; cursor: pointer;
}

/* ── Balances SPA ──────────────────────────────────────────────────── */
.bal-toolbar { padding-top: 16px; }
.bal-period-toggle {
  display: inline-flex; border: 1px solid var(--line); border-radius: 4px;
  overflow: hidden; height: 30px;
}
.bal-period-btn {
  background: var(--surface); border: none; color: var(--ink-2);
  font: inherit; font-size: 12px; padding: 0 14px; cursor: pointer;
  white-space: nowrap;
}
.bal-period-btn:hover { color: var(--ink-1); }
.bal-period-btn.active {
  background: var(--accent-soft); color: var(--accent-ink); font-weight: 500;
}
.bal-period-btn + .bal-period-btn { border-left: 1px solid var(--line); }

.bal-table thead th[role="button"]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}
.bal-table tbody tr.bal-row:hover td {
  background: var(--accent-soft);
}
.bal-table tbody tr.bal-row:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}
.bal-table tbody tr.bal-row:focus-visible td {
  background: var(--accent-soft);
}
.bal-row:focus-visible .bal-row-cta { color: var(--accent); }
.bal-date { font-variant-numeric: tabular-nums; }
.bal-monthend-hint {
  font-size: 10px; color: var(--ink-4); text-transform: uppercase;
  letter-spacing: 0.06em; margin-top: 2px;
}
.bal-account-name { font-weight: 500; color: var(--ink-1); }
.bal-institution { font-size: 11px; color: var(--ink-4); }
.bal-amount {
  text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono);
}
.bal-delta {
  text-align: right; font-variant-numeric: tabular-nums;
  font-family: var(--font-mono); font-weight: 500;
}
.bal-delta-zero { color: var(--pos); }
.bal-delta-nonzero { color: var(--neg); }
.bal-delta-none { color: var(--ink-4); font-weight: normal; }
.bal-row-cta { color: var(--ink-4); font-size: 14px; }
.bal-row:hover .bal-row-cta { color: var(--accent); }

/* Add-snapshot dialog */
.bal-dialog-backdrop {
  position: fixed; inset: 0; background: rgba(15, 18, 24, 0.55);
  z-index: 2000; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.bal-dialog {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: 0 12px 36px rgba(0,0,0,0.25);
  width: 100%; max-width: 480px; display: flex; flex-direction: column;
  overflow: hidden;
}
.bal-dialog-header {
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
}
.bal-dialog-title { font-size: 14px; font-weight: 600; color: var(--ink-1); }
.bal-dialog-close {
  background: none; border: none; color: var(--ink-3);
  font-size: 20px; line-height: 1; cursor: pointer; padding: 0 6px;
}
.bal-dialog-close:hover { color: var(--ink-1); }
.bal-dialog-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
.bal-dialog-blurb { margin: 0 0 4px; font-size: 12px; }
.bal-dialog-hint { margin: 4px 0 0; font-size: 11px; }
.bal-dialog-footer {
  padding: 12px 18px; border-top: 1px solid var(--line);
  display: flex; gap: 10px; justify-content: flex-end; background: var(--surface-2);
}

/* ── Drawer ───────────────────────────────────────────────────────────
   Right-side slide-out panel. The modal family (.dialog) interrupts you for
   a decision; a drawer lets you inspect something while the surface behind
   it stays put — used by the register's entry-detail inspector.
   The .bal-rec-* block below predates this and should migrate onto it. */
.drawer-backdrop {
  position: fixed; inset: 0; background: rgba(15, 18, 24, 0.45);
  z-index: 2050;
}
.drawer-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(560px, 94vw);
  background: var(--surface); border-left: 1px solid var(--line);
  box-shadow: -12px 0 36px rgba(0, 0, 0, 0.25);
  z-index: 2051; display: flex; flex-direction: column;
  animation: drawer-slide-in 160ms ease-out;
}
@keyframes drawer-slide-in {
  from { transform: translateX(24px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
/* Honour reduced-motion: the panel still appears, it just doesn't travel. */
@media (prefers-reduced-motion: reduce) {
  .drawer-panel { animation: none; }
}
.drawer-header {
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  flex-shrink: 0;
}
.drawer-title { font-size: 15px; font-weight: 600; color: var(--ink-1); }
.drawer-subtitle { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.drawer-close {
  background: none; border: none; color: var(--ink-3);
  font-size: 20px; line-height: 1; cursor: pointer; padding: 0 6px;
}
.drawer-close:hover { color: var(--ink-1); }
.drawer-body {
  overflow-y: auto; flex: 1; padding: 14px 18px 24px;
  display: flex; flex-direction: column; gap: 18px;
}
.drawer-footer {
  padding: 10px 18px; border-top: 1px solid var(--line);
  background: var(--surface-2); flex-shrink: 0;
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
}

/* ── Statement mode on the register ───────────────────────────────────
   The strip above the ledger when ?bank_period is active, plus the Bank
   date column inside it. Frameless per the house rules: one hairline
   under the strip, no card. Semantic colour only on the verdict. */
.stmt-strip {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 0 12px; margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.stmt-strip-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.stmt-strip-period { display: flex; align-items: center; gap: 6px; }
.stmt-strip-label {
  font-size: 13px; font-weight: 600; color: var(--ink-1);
  min-width: 9.5em; text-align: center;
}
.stmt-strip-tag {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-3);
}
/* Period picker — the trigger sits where the plain label used to, and keeps
   its width so stepping months doesn't shuffle the arrows either side. */
.stmt-period-picker { position: relative; display: flex; }
.stmt-period-trigger {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  min-width: 9.5em;
  background: none; border: 1px solid transparent; border-radius: var(--radius);
  padding: 3px 8px; cursor: pointer; line-height: 1.3;
  font-size: 13px; font-weight: 600; color: var(--ink-1);
}
.stmt-period-trigger:hover { background: var(--surface-2); border-color: var(--line); }
.stmt-period-trigger[aria-expanded="true"] {
  background: var(--surface-2); border-color: var(--line);
}
.stmt-period-trigger:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.stmt-period-caret { font-size: 9px; color: var(--ink-3); }

.stmt-strip-actions { display: flex; align-items: center; gap: 6px; }
.stmt-strip-err { font-size: 12px; color: var(--neg); }
.stmt-step {
  background: none; border: 1px solid var(--line); border-radius: var(--radius);
  color: var(--ink-2); cursor: pointer; line-height: 1;
  padding: 4px 8px; font-size: 11px;
}
.stmt-step:hover:not(:disabled) { background: var(--surface-2); color: var(--ink-1); }
.stmt-step:disabled { color: var(--ink-4); cursor: default; }
.stmt-step:focus-visible { outline: none; box-shadow: var(--ring-focus); }

.stmt-strip-figures {
  display: flex; align-items: flex-start; gap: 22px; flex-wrap: wrap;
}
.stmt-figure { display: flex; flex-direction: column; gap: 1px; min-width: 6.5em; }
/* Marks a figure whose derivation is worth reading — hover the label or the
   number for it. Quiet by default; the strip is a place to read numbers, not
   a place to be nagged by glyphs. */
.stmt-figure-info {
  margin-left: 3px; font-size: 9px; color: var(--ink-4);
  vertical-align: 1px;
}
.stmt-figure:hover .stmt-figure-info { color: var(--ink-3); }
/* The hint fires from the label AND the number, so both carry the cursor —
   a help cursor on the glyph alone would advertise a smaller target than
   the one that actually works. */
.stmt-figure.has-hint .stmt-figure-label,
.stmt-figure.has-hint .stmt-figure-val { cursor: help; }
.stmt-figure-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-3);
}
.stmt-figure-val { font-size: 15px; font-weight: 600; color: var(--ink-1); }
.stmt-figure.strong .stmt-figure-val { font-size: 17px; }
.stmt-figure-val.pos { color: var(--pos); }
.stmt-figure-val.neg { color: var(--neg); }
.stmt-figure-sub { font-size: 10.5px; color: var(--ink-3); }
/* The "bank said …" line, when it's also the way to correct that figure. */
.stmt-figure-sub-edit {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; text-align: left;
}
.stmt-figure-sub-edit:hover { color: var(--ink-1); }
.stmt-figure-sub-edit:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.stmt-figure-sub-pencil { color: var(--ink-4); }
.stmt-figure-sub-edit:hover .stmt-figure-sub-pencil { color: var(--accent); }

/* Inline statement-balance entry, sitting where the verdict normally is.
   The verdict slot is the narrow tail of a flex row, so the entry claims a
   full-width line of its own while open — otherwise Cancel wraps below Save
   and the form reads as broken. */
.stmt-strip-verdict.editing {
  flex-basis: 100%; max-width: none; margin-left: 0;
}
.stmt-balance-entry {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.stmt-balance-label { font-size: 12.5px; color: var(--ink-2); }
.stmt-balance-input { width: 9em; }
/* The verdict takes the slack so it sits away from the figures rather than
   crowding the closing balance it comments on. */
.stmt-strip-verdict {
  margin-left: auto; display: flex; align-items: center; gap: 10px;
  font-size: 12.5px; color: var(--ink-2); max-width: 34em;
}
.stmt-strip-verdict-text.ok { color: var(--pos); }
.stmt-strip-verdict-text.off { color: var(--neg); }
/* "This month ties, the opening doesn't" — a real problem, but a prior
   period's, so amber rather than the closing mismatch's red. */
.stmt-strip-verdict-text.warn { color: var(--warn); }
.stmt-strip-inline-link {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; color: var(--accent-ink); text-decoration: underline;
}
.stmt-strip-inline-link:focus-visible { outline: none; box-shadow: var(--ring-focus); }

.stmt-strip-bridge-toggle {
  align-self: flex-start;
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 11.5px; color: var(--ink-3);
}
.stmt-strip-bridge-toggle:hover { color: var(--ink-1); }
.stmt-strip-bridge-toggle:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.stmt-strip-bridge { max-width: 44em; }
.stmt-strip-bridge-note {
  font-size: 11px; color: var(--ink-3); margin: 6px 0 0; font-style: italic;
}
.stmt-walk-count {
  margin-left: 6px; background: var(--line); color: var(--ink-2);
  border-radius: 8px; padding: 0 5px; font-size: 10px;
}

/* The "This needs a category" chip lives in a flex column that shrinks under
   pressure, and `.chip` is nowrap with no max-width — so below ~138px of
   column it used to spill over the Memo cell next door. Cap it to its cell and
   ellipsize; the title attribute carries the full text. */
.txn-needs-category {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline-block;
}

/* Bank date cell in the register. The column exists to answer one question —
   which rows settled on a day the register doesn't know about — so it's styled
   as a short list of exceptions rather than a full column of dates.

   A bank date that AGREES with the Date column carries no information, whatever
   produced it (the bank's own posted date, a hand-set override, or the register
   date standing in). It recedes: muted and italic, so the eye reads the column
   as texture and stops only on the rows that break it. The moment it disagrees,
   that gap is the drift being hunted — upright, at the row's own full strength.

   The drift case deliberately sets NO color of its own. It inherits `.txn-cell`
   (--ink-1), which is also what makes a dimmed scope group dim its bank date
   along with the rest of the row instead of leaving one cell shouting. */
.txn-bankdate:not(.drifts) { color: var(--ink-3); font-style: italic; }
/* Hasn't settled anywhere. The placeholder is an absence, not a value, so it
   goes fainter still — and it can never carry `.drifts` (there's no date to
   disagree with anything), so the italic above still applies. */
.txn-bankdate.src-unsettled { color: var(--ink-4); }

/* ── Bank statement view (/app/statement) ─────────────────────────────
   Frameless per the house rules: hairlines separate the sections, no card
   chrome. The only filled surfaces are the verdict strip (which has to be
   the first thing read) and the summary rail. */
.stmt-page { display: flex; flex-direction: column; gap: 16px; }

/* Header: account picker on the left, month stepper on the right — the
   position the account pages will eventually host this control in. */
.stmt-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.stmt-head-left { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.stmt-back { font-size: 12.5px; white-space: nowrap; }
.stmt-inst { font-size: 12px; color: var(--ink-3); }
.stmt-period { display: flex; align-items: center; gap: 8px; }
.stmt-period-label {
  font-size: 13px; font-weight: 600; color: var(--ink-1);
  min-width: 9.5em; text-align: center;
}
.stmt-step {
  background: none; border: 1px solid var(--line); border-radius: var(--radius);
  color: var(--ink-2); cursor: pointer; line-height: 1;
  padding: 5px 9px; font-size: 11px;
}
.stmt-step:hover:not(:disabled) { background: var(--surface-2); color: var(--ink-1); }
.stmt-step:disabled { color: var(--ink-4); cursor: default; }
.stmt-step:focus-visible { outline: none; box-shadow: var(--ring-focus); }

/* Verdict strip — the answer before the evidence. Semantic colour only. */
.stmt-verdict {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; border-radius: var(--radius);
  font-size: 13px; color: var(--ink-2);
  border: 1px solid var(--line); background: var(--surface-2);
}
.stmt-verdict-mark {
  font-size: 14px; line-height: 1; font-weight: 700;
  width: 20px; text-align: center; flex-shrink: 0;
}
.stmt-verdict-text { flex: 1; min-width: 14em; }
.stmt-verdict.ok { border-color: var(--pos); background: var(--pos-soft); }
.stmt-verdict.ok .stmt-verdict-mark { color: var(--pos); }
.stmt-verdict.off { border-color: var(--neg); background: var(--neg-soft); }
.stmt-verdict.off .stmt-verdict-mark { color: var(--neg); }
.stmt-verdict.unknown .stmt-verdict-mark { color: var(--ink-3); }

.stmt-section { display: flex; flex-direction: column; gap: 8px; }
/* Everything outside the table gets the table's own inline gutter (8px, from
   `.tbl-tight`), so the summary's "Closing" figure lands on the same right
   edge as the Balance column beneath it and the labels line up with the
   column headers. Without it the rail ran flush to the window edge while the
   table sat inset. */
.stmt-h2,
.stmt-empty,
.stmt-summary,
.stmt-walk-line,
.stmt-sub-head,
.stmt-sub-hint { padding-inline: 8px; }
.stmt-h2 { margin: 0; }

/* Opening / in / out / closing rail. */
.stmt-summary {
  display: flex; gap: 24px; flex-wrap: wrap;
  padding-block: 10px; border-bottom: 1px solid var(--line);
}
.stmt-stat { display: flex; flex-direction: column; gap: 2px; min-width: 8em; }
.stmt-stat-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-3);
}
.stmt-stat-val { font-size: 18px; font-weight: 600; color: var(--ink-1); }
.stmt-stat-val.pos { color: var(--pos); }
.stmt-stat-val.neg { color: var(--neg); }
.stmt-stat-sub { font-size: 11px; color: var(--ink-3); }
.stmt-stat-closing { margin-left: auto; text-align: right; }

/* padding-BLOCK, not the `padding` shorthand: the grouped gutter rule above
   sets padding-inline, and a shorthand here would silently reset it back to 0
   (it is later in the file and same specificity). Same for .stmt-sub-head and
   .stmt-walk-line below. */
.stmt-empty { font-size: 12.5px; color: var(--ink-3); padding-block: 12px; margin: 0; }

/* Statement table. The house rule is that a table FITS — no horizontal
   scroll, no auto-hidden columns. `table-layout: fixed` plus the <colgroup>
   in App.tsx pins the four predictable columns (dates, money) and hands the
   remaining width to payee + memo, which truncate.

   `min-width: 0` is load-bearing: the base `.tbl` sets `min-width:
   max-content`, which overrides `width: 100%` the moment content is wider
   than the container — that (with the memo cell's `width: 100%`) is how this
   table first blew a 1280px viewport out to a million pixels wide.

   The gutters come from `.tbl-tight` (in the polish layer), which the markup
   opts into: two date columns plus two money columns is a dense report table,
   and roomy's 18px side gutters clipped an ISO date to "2023-0…". Don't
   hand-roll a padding override here — the roomy rule lives in the later sheet
   and wins on both specificity and source order, which is exactly how the
   first attempt at this silently did nothing. */
.stmt-table { width: 100%; table-layout: fixed; min-width: 0; }
.stmt-table th,
.stmt-table td {
  vertical-align: middle;
  overflow: hidden; text-overflow: ellipsis;
}
.stmt-td-date { white-space: nowrap; }
.stmt-td-ledger { white-space: nowrap; color: var(--ink-3); }
/* A ledger date that disagrees with the bank date is the whole point of the
   column — lift it out of the muted default so the drift is visible. */
.stmt-td-ledger.drift { color: var(--ink-2); }
.stmt-td-payee { white-space: nowrap; }
.stmt-td-memo { white-space: nowrap; color: var(--ink-3); }
.stmt-td-amt, .stmt-td-bal { text-align: right; white-space: nowrap; }
.stmt-td-amt.pos { color: var(--pos); }
.stmt-td-amt.neg { color: var(--neg); }
.stmt-td-bal { color: var(--ink-1); font-weight: 500; }
.stmt-row.is-busy { opacity: 0.55; }
.stmt-payee { color: var(--ink-1); }
.stmt-nopayee { color: var(--ink-4); font-style: italic; }
.stmt-tag {
  margin-left: 6px; background: var(--line); color: var(--ink-2);
  border-radius: 2px; padding: 1px 5px;
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;
}
.stmt-tag-quiet { background: none; border: 1px solid var(--line); color: var(--ink-3); }

/* Bank-date cell: a button that reads as a date until you reach for it. */
.stmt-bankdate { display: inline-flex; align-items: center; gap: 2px; }
.stmt-bankdate-btn {
  background: none; border: 1px solid transparent; border-radius: var(--radius);
  color: var(--ink-1); cursor: pointer; padding: 2px 5px;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: inherit; line-height: inherit;
}
.stmt-bankdate-btn:hover:not(:disabled) {
  border-color: var(--line); background: var(--surface-2);
}
.stmt-bankdate-btn:focus-visible { outline: none; box-shadow: var(--ring-focus-strong); }
.stmt-bankdate-btn:disabled { cursor: default; color: var(--ink-3); }
/* An overridden date is a *chosen* state, so it takes chrome accent. */
.stmt-bankdate-btn.src-override { color: var(--accent-ink); }
.stmt-bankdate-mark { margin-left: 4px; font-size: 10px; }
/* No bank record behind the date — it's the register date standing in. */
.stmt-bankdate-btn.src-ledger { color: var(--ink-2); }
.stmt-bankdate-reset {
  background: none; border: none; color: var(--ink-3);
  cursor: pointer; font-size: 11px; line-height: 1; padding: 2px 3px;
  border-radius: var(--radius);
}
.stmt-bankdate-reset:hover { color: var(--neg); background: var(--neg-soft); }
.stmt-bankdate-reset:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.stmt-bankdate-input {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-1);
  padding: 1px 4px; font-size: inherit;
}
.stmt-bankdate-input:focus { outline: none; box-shadow: var(--ring-focus-strong); }

/* Collapsible sub-lists (timing buckets, pull-in trays). */
.stmt-sub { border-top: 1px solid var(--line-2); }
.stmt-sub-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: none; cursor: pointer;
  padding-block: 8px; text-align: left;
  font-size: 12.5px; color: var(--ink-2);
}
.stmt-sub-head:hover:not(:disabled) { color: var(--ink-1); }
.stmt-sub-head:disabled { cursor: default; }
.stmt-sub-head:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.stmt-sub-caret { width: 10px; color: var(--ink-4); flex-shrink: 0; }
.stmt-sub-title { flex: 1; }
.stmt-sub-count { color: var(--ink-3); font-size: 11.5px; }
.stmt-sub-total { color: var(--ink-1); font-weight: 500; min-width: 6em; text-align: right; }
.stmt-sub-hint {
  font-size: 11.5px; color: var(--ink-3);
  margin: 0 0 8px; padding-left: 28px;
}
.stmt-table-sub { margin-bottom: 12px; }

/* The bridge walk. Same line grammar as a printed reconciliation. */
.stmt-walk { display: flex; flex-direction: column; }
.stmt-walk-line {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  font-size: 13px; color: var(--ink-2); padding-block: 5px;
}
.stmt-walk-line .num { font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
.stmt-walk-total {
  font-weight: 600; color: var(--ink-1);
  border-top: 1px solid var(--line); padding-top: 8px; margin-top: 2px;
}

/* ── Entry detail drawer ──────────────────────────────────────────────
   Content styles for the register's inspector (EntryDetailDrawer.tsx); the
   panel chrome itself comes from .drawer-* above. Read-only throughout —
   every editable field is edited inline in the row behind the drawer. */
/* Value on one row, pills on the next. Sharing a flex row meant a wide
   amount pushed the pills into a ragged wrap that depended on the number. */
.ed-hero { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.ed-hero-amount {
  font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums;
  font-family: var(--font-mono); color: var(--ink-1);
  flex: 0 0 auto; white-space: nowrap;
}
.ed-hero-amount.pos { color: var(--pos); }
.ed-hero-amount.neg { color: var(--neg); }
/* The header's editable amount sizes to its own text. Without this it
   inherits the field-row rule (full width of the value column), which in the
   narrower header box wraps the minus sign onto its own line. */
.ed-hero-amount .ed-editable {
  width: auto; margin: 0; padding: 2px 6px;
  white-space: nowrap; font-size: inherit; color: inherit;
}
.ed-section { display: flex; flex-direction: column; gap: 2px; }
.ed-section-title {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-3); margin-bottom: 6px;
  display: flex; align-items: center; gap: 8px;
}
/* Label/value rows. The label column is fixed so values line up down the
   panel; long values wrap rather than pushing the panel wider. */
.ed-field {
  display: grid; grid-template-columns: 116px 1fr; gap: 10px;
  font-size: 13px; padding: 4px 0; align-items: baseline;
}
.ed-field-label { color: var(--ink-3); font-size: 12px; }
.ed-field-value { color: var(--ink-1); min-width: 0; overflow-wrap: anywhere; }
.ed-field-value.num {
  font-variant-numeric: tabular-nums; font-family: var(--font-mono);
}
.ed-field-value.muted { color: var(--ink-4); }
.ed-mono { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }
.ed-chip {
  font-size: 10px; padding: 2px 8px; border-radius: 4px;
  background: var(--surface-2); color: var(--ink-3);
  border: 1px solid var(--line); text-transform: uppercase;
  letter-spacing: 0.04em; white-space: nowrap;
}
.ed-chip.pos { background: var(--surface-2); color: var(--pos); border-color: var(--pos); }
.ed-chip.warn { background: var(--surface-2); color: var(--warn); border-color: var(--warn); }
.ed-chip.neg { background: var(--surface-2); color: var(--neg); border-color: var(--neg); }
.ed-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.ed-note {
  font-size: 12px; padding: 8px 10px; border-radius: 6px;
  background: var(--surface-2); color: var(--ink-2); border: 1px solid var(--line);
}
.ed-note.warn { border-color: var(--warn); background: var(--warn-soft); }
.ed-empty { font-size: 12px; color: var(--ink-4); }
/* Tabs across an entry's bank records. The page-level `.tab` family would
   work, but at 12.5px with a full-width rule under it it reads as page chrome
   that wandered into a panel — so this is the same idea (muted resting, accent
   underline when selected) rebuilt at drawer scale.

   The strip is drawn for a single record too, where it labels the panel with
   that record's source and date. There it's a `<span>`, not a `<button>`, so
   the interactive affordances — pointer cursor, hover lift, focus ring — are
   scoped to the button form. Same look; nothing that implies a choice which
   isn't there. */
.ed-bank-tabs {
  display: flex; gap: 0; flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin: 0 0 8px;
}
.ed-bank-tab {
  padding: 5px 10px; margin-bottom: -1px;
  font-family: inherit; font-size: 11.5px; color: var(--ink-3);
  background: transparent; border: none;
  border-bottom: 1.5px solid transparent;
  transition: color var(--t-fast), border-color var(--t-fast);
}
button.ed-bank-tab { cursor: pointer; }
button.ed-bank-tab:hover { color: var(--ink-1); }
.ed-bank-tab.is-active {
  color: var(--ink-1); border-bottom-color: var(--accent); font-weight: 500;
}
.ed-bank-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ed-merchant { display: flex; align-items: flex-start; gap: 10px; }
.ed-merchant-logo {
  width: 32px; height: 32px; border-radius: 6px; flex-shrink: 0;
  background: var(--surface-2); object-fit: contain;
}
.ed-merchant-name { font-weight: 600; font-size: 14px; line-height: 1.3; color: var(--ink-1); }
.ed-split {
  display: grid; grid-template-columns: 1fr auto; gap: 8px;
  font-size: 13px; padding: 6px 0; border-bottom: 1px solid var(--line);
}
.ed-split:last-child { border-bottom: none; }
.ed-split-amount {
  font-variant-numeric: tabular-nums; font-family: var(--font-mono); color: var(--ink-1);
}
.ed-split-memo { font-size: 11px; color: var(--ink-4); font-style: italic; margin-top: 2px; }
.ed-disclosure > summary {
  font-size: 11px; color: var(--ink-3); cursor: pointer; padding: 4px 0;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.ed-disclosure > summary:hover { color: var(--ink-1); }
.ed-pre {
  font-family: var(--font-mono); font-size: 11px; background: var(--surface-2);
  padding: 8px; border-radius: 4px; margin: 6px 0 0;
  overflow-x: auto; max-height: 300px; color: var(--ink-2);
}
/* The diagnostics dump is long by design — give it more room before it scrolls. */
.ed-pre.tall { max-height: 460px; }
.ed-actions { display: flex; flex-direction: column; gap: 8px; }
.ed-hint { font-size: 11px; color: var(--ink-4); margin-top: 3px; }
.ed-sub {
  font-size: 11px; color: var(--ink-3); margin: 8px 0 4px;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.ed-check { display: flex; align-items: center; gap: 7px; font-size: 13px; cursor: pointer; }
.ed-check input:disabled + span { color: var(--ink-4); cursor: not-allowed; }
/* Click-to-edit value. Reads as plain text so it sits flush with the picker
   triggers — the whole panel looks uniformly non-boxy, and uniformly
   editable. The hover tint is the only affordance, matching .inline-trigger. */
/* No negative margin: it pulled the text 6px left of the picker triggers
   beside it, so the memo row read as misaligned against everything else in
   the value column. Padding matches .ed-leg-input for the same reason. */
.ed-editable {
  background: none; border: 1px solid transparent; border-radius: 4px;
  color: var(--ink-1); font: inherit; font-size: 13px; text-align: inherit;
  padding: 3px 6px; margin: -3px 0; cursor: text; width: 100%;
}
.ed-editable:hover:not(:disabled) { background: var(--surface-2); border-color: var(--line); }
.ed-editable:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.ed-editable:disabled { color: var(--ink-4); cursor: not-allowed; }
.ed-editable.empty { color: var(--ink-4); font-style: italic; }
/* The one chip that's also a control (review state). */
/* A pill whose width is reserved for its longest possible label, so toggling
   never resizes it and the row never reflows. The sizer holds the box open;
   the live label is laid over it. */
.ed-chip-toggle { cursor: pointer; font-family: inherit; position: relative; }
.ed-chip-toggle.static { cursor: default; }
.ed-chip-sizer { visibility: hidden; }
.ed-chip-label {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
}
.ed-chip-toggle:hover:not(:disabled) { filter: brightness(1.15); }
.ed-chip-toggle:disabled { cursor: not-allowed; opacity: 0.6; }
/* Two-state segmented control (inflow / outflow). */
.ed-toggle { display: inline-flex; border: 1px solid var(--line); border-radius: 5px; overflow: hidden; }
.ed-toggle button {
  background: var(--surface-2); color: var(--ink-3); border: none;
  padding: 5px 12px; font-size: 12px; cursor: pointer; font-family: inherit;
}
.ed-toggle button + button { border-left: 1px solid var(--line); }
.ed-toggle button.active { background: var(--accent); color: #fff; }
.ed-toggle button:disabled { cursor: not-allowed; opacity: 0.6; }
/* Compact variant for the header chip row. */
.ed-toggle.sm button { padding: 2px 8px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; }
/* A related entry — clickable, re-targets the drawer. */
.ed-ref {
  display: flex; width: 100%; gap: 10px; align-items: flex-start;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px;
  padding: 8px 10px; margin-bottom: 6px; cursor: pointer;
  text-align: left; font-family: inherit; color: var(--ink-1);
}
.ed-ref:hover { border-color: var(--accent); }
.ed-ref-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ed-ref-payee { font-size: 13px; display: flex; align-items: center; gap: 6px; }
.ed-ref-meta { font-size: 11px; color: var(--ink-4); }
.ed-ref-memo { font-size: 11px; color: var(--ink-3); font-style: italic; }
.ed-split-amount.pos { color: var(--pos); }
/* Split legs — a slimmed-down ledger: date | category | memo | amount |
   remove. Real inputs, because this is an explicit sub-form with its own Save
   and the running total has to track what you're typing.
   The date column is fixed at the mono width of "YYYY-MM-DD" and the amount
   holds a bare magnitude ("1234.56", no sign or currency), so 84px covers it
   — that trim is what buys the date its column without squeezing category and
   memo below a readable width in a 560px drawer. */
.ed-legs { margin-bottom: 8px; }
.ed-legs-head,
.ed-legs-row {
  display: grid; grid-template-columns: 96px 1fr 1fr 84px 22px;
  gap: 8px; align-items: center;
}
.ed-legs-head {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-4); padding-bottom: 4px; border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}
.ed-legs-head .num { text-align: right; }
.ed-legs-row { padding: 3px 0; }
.ed-leg-input {
  background: none; border: 1px solid transparent; border-radius: 4px;
  color: var(--ink-1); font: inherit; font-size: 13px;
  padding: 3px 6px; width: 100%; min-width: 0;
}
.ed-leg-input:hover:not(:disabled) { border-color: var(--line); }
.ed-leg-input:focus { outline: none; border-color: var(--accent); background: var(--surface-2); }
.ed-leg-input::placeholder { color: var(--ink-4); }
.ed-leg-input:disabled { color: var(--ink-4); cursor: not-allowed; }
.ed-leg-input.num {
  text-align: right; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
/* Mono so "YYYY-MM-DD" fits its fixed column exactly, and so a column of leg
   dates aligns digit-for-digit — which is what makes a cross-month split's
   spread readable at a glance. */
.ed-leg-input.date {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 12px;
}
.ed-leg-remove {
  background: none; border: none; color: var(--ink-4);
  cursor: pointer; padding: 2px; font-size: 12px; line-height: 1;
}
.ed-leg-remove:hover:not(:disabled) { color: var(--neg); }
.ed-leg-remove:disabled { opacity: 0.3; cursor: not-allowed; }
/* Footer: add · running total · status · remove · save. */
.ed-leg-foot {
  display: flex; align-items: baseline; gap: 10px; margin: 8px 0 2px;
  flex-wrap: wrap;
}
.ed-leg-add {
  background: none; border: none; color: var(--accent);
  font: inherit; font-size: 12px; cursor: pointer; padding: 0;
}
.ed-leg-add:hover:not(:disabled) { text-decoration: underline; }
.ed-leg-add:disabled { color: var(--ink-4); cursor: not-allowed; }
/* Empty state: the call to action, nothing else. */
.ed-legs-empty { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ed-leg-total {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 12px; color: var(--ink-1);
  display: inline-flex; align-items: baseline; gap: 6px;
}
/* How far the legs sit from the transaction amount. Only rendered when
   they're out of balance — a zero delta is just "Balanced". */
.ed-leg-delta { color: var(--warn); }
.ed-leg-sum { flex: 1; font-size: 11px; }
.ed-leg-sum.ok { color: var(--ink-4); }
.ed-leg-sum.bad { color: var(--warn); }
.ed-actions-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
/* Troubleshooting: expandable dump on the left, Copy pinned right on the
   same line so the section stays one row tall until you open it. */
.ed-diag-row { display: flex; align-items: flex-start; gap: 12px; }
.ed-diag-row > .ed-disclosure { flex: 1; min-width: 0; }
.ed-diag-copy { flex-shrink: 0; }
.ed-danger { color: var(--neg); border-color: var(--neg); }
/* Troubleshooting key/value dump. */
.ed-raw { margin: 6px 0 0; font-size: 11px; font-family: var(--font-mono); }
.ed-raw-row {
  display: grid; grid-template-columns: 150px 1fr; gap: 8px;
  padding: 3px 0; border-bottom: 1px solid var(--line);
}
.ed-raw-row:last-child { border-bottom: none; }
.ed-raw dt { color: var(--ink-4); }
.ed-raw dd { margin: 0; color: var(--ink-2); overflow-wrap: anywhere; }
.ed-raw dd.muted { color: var(--ink-4); font-style: italic; }

/* Keyboard shortcut cheat-sheet — additions on top of the base
   .kbd-help-* rules above (migrated from wealthview.css). The base
   rules style the card, body grid, and footer; these add what the
   transactions cheat-sheet specifically needs: spacing between
   adjacent keys in multi-key combos, and a single-column fallback
   on narrow viewports. */
.kbd-help-body kbd + kbd { margin-left: 4px; }
@media (max-width: 600px) {
  .kbd-help-body { grid-template-columns: 1fr; }
}

/* `?` affordance button in the transactions toolbar.
   Visually similar to the undo/redo icon buttons so the toolbar
   stays balanced; opens the cheat-sheet on click. */
.txn-help-btn {
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--ink-3); border-radius: 4px;
  width: 28px; height: 28px; padding: 0;
  font-size: 14px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.txn-help-btn:hover { color: var(--ink-1); border-color: var(--ink-4); }
/* When the help button sits inside the undo/redo pill, drop its own
   border/fill/radius and adopt the in-pill shape (full height, a single
   shared divider on the left) so it reads as the third segment of one
   control instead of a separate boxed button crammed in and off-center. */
.txn-undo-group .txn-help-btn {
  width: 30px; height: 100%;
  border: none; border-left: 1px solid var(--line);
  background: transparent; border-radius: 0;
  color: var(--ink-2); font-size: 13px;
}
.txn-undo-group .txn-help-btn:hover { background: var(--surface-2); color: var(--ink-1); }

/* First-run nudge that tells new users the cheat sheet exists.
   Pinned bottom-right; floats above the table. The toast self-dismisses
   once the user opens the dialog by any route, and the dismissal is
   persisted in localStorage. */
.shortcut-nudge-toast {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 1100;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px 8px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  font-size: 12.5px;
  color: var(--ink-1);
  max-width: 360px;
}
/* …but it yields entirely while a bulk selection is up. The selection pill
   is centered at the bottom and, since it stopped wrapping, its rail runs
   wide enough to reach this corner and sit UNDER the toast (z-index 100 vs
   1100) — which left the toast silently swallowing clicks on the pill's
   rightmost control. Stepping the toast up instead just moves the problem:
   the slot above is the onboarding tab's (bottom: 84px). So the ambient
   first-run hint gets out of the way of the surface the user just summoned,
   and comes back when the selection clears. Visibility, not display, so
   nothing about its dismissal state changes. */
body:has(.selection-pill) .shortcut-nudge-toast { visibility: hidden; }
.shortcut-nudge-text { display: inline-flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.shortcut-nudge-text kbd {
  font-family: inherit;
  font-size: 11px;
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 3px;
  background: var(--surface-1);
  color: var(--ink-1);
  margin: 0 1px;
}
.shortcut-nudge-open {
  background: var(--surface-1); border: 1px solid var(--line);
  color: var(--ink-1); border-radius: 4px;
  padding: 4px 10px; font-size: 12px; cursor: pointer;
}
.shortcut-nudge-open:hover { border-color: var(--ink-4); }
.shortcut-nudge-close {
  background: transparent; border: none;
  color: var(--ink-3); font-size: 16px; line-height: 1;
  padding: 0 4px; cursor: pointer;
}
.shortcut-nudge-close:hover { color: var(--ink-1); }

/* ═══════════════════════════════════════════════════════════════════
   Touch sizing — tablets & touch devices (iPad, Android tablets)
   ═══════════════════════════════════════════════════════════════════
   Scoped by `pointer: coarse`, NOT by width. A mouse-driven desktop
   always reports `pointer: fine`, so none of these rules can ever apply
   to it — no matter how narrow the window is dragged. Touch devices
   (iPad included, despite its Mac-like User-Agent) report
   `pointer: coarse` natively, so the browser opts them in with no
   server-side or JS device detection at all. See docs/frontend/touch.md.

   `min-width: 768px` keeps phones out: they're served the dedicated
   /mobile card-swipe app, and bumping control sizes at phone widths
   would break that already-narrow layout. This block covers the gap
   the mobile bundle doesn't — tablets on the full desktop UI.

   Targets aim for comfortable ~40px finger hits without ballooning the
   data-dense ledger the way a strict 44px-everywhere rule would. Input
   and select font-size is lifted to 16px specifically so iOS Safari
   doesn't auto-zoom the viewport when a field gains focus. */
@media (pointer: coarse) and (min-width: 768px) {
  :root {
    /* Register/table row height. Default 30px, roomy 38px — touch sits
       just above roomy so row-level tap targets clear the finger. */
    --row-h: 40px;
  }

  /* Core control vocabulary — buttons, inputs, selects. */
  .btn { height: 38px; padding: 0 14px; font-size: 13px; }
  .btn.sm { height: 32px; padding: 0 11px; font-size: 12px; }
  .btn.lg { height: 44px; padding: 0 18px; font-size: 14px; }

  .input, .select {
    height: 38px; padding: 0 10px;
    /* 16px kills iOS Safari's focus-zoom (it zooms any field < 16px). */
    font-size: 16px;
  }

  /* Sidebar nav — heavily used, so give links a taller hit area. */
  .side-link { padding: 10px 10px; font-size: 15px; }

  /* Top bar + its pill buttons. Grow the bar so the taller buttons fit. */
  .top { height: 52px; }
  .top-btn { height: 36px; padding: 0 12px; font-size: 13px; }

  /* Filter/asset chips double as tappable pills in several toolbars. */
  .chip { padding: 5px 10px; font-size: 12px; }

  /* Selection checkboxes are the smallest — and hardest — touch target
     in the ledger. Grow the box and re-center the checkmark glyph for
     both the legacy .tbl and the SPA .txn-table. */
  .tbl input[type="checkbox"],
  .txn-table input[type="checkbox"] { width: 18px; height: 18px; }
  .tbl input[type="checkbox"]:checked::after,
  .txn-table input[type="checkbox"]:checked::after {
    left: 5px; top: 1px; width: 6px; height: 11px; border-width: 0 2px 2px 0;
  }
}
