/* ===========================================================================
 *  CMW DESIGN SYSTEM — "PennMarc"
 *  Light is the default theme (:root). Dark overrides on [data-theme="dark"].
 *  The topbar theme toggle flips data-theme on <html> and sets the
 *  cmw_theme cookie so the server can render the right theme on next load
 *  with no flash. See templates/partials/_topbar.html.
 *
 *  Ported from the Streamlit-era utils/ui_state.py CMW_CSS block. Streamlit
 *  chrome selectors (.stApp, .stButton, [data-testid=...], etc.) have been
 *  dropped; everything below targets plain HTML rendered by Jinja.
 * ======================================================================== */
:root{
    /* surfaces & text (light default) */
    --bg:#eef2f7;
    --surface-1:#f4f8fc;
    --surface-2:#ffffff;
    --border:#dde6f0;
    --text-primary:#1c2b3a;
    --text-secondary:#5d6e83;
    --text-muted:#8696aa;
    /* brand / accent */
    --cmw-accent:    #1773b8;
    --cmw-accent-bg: #eaf1f8;
    --cmw-accent-txt:#0f5a93;
    /* status */
    --cmw-pos:  #2e9e6b;
    --cmw-neg:  #d2493e;
    --cmw-warn: #856404;
    --cmw-warn-bg:#faeeda;
    /* form */
    --cmw-shadow:0 4px 16px rgba(28,43,61,.08);
    --cmw-radius:13px; --cmw-radius-sm:8px;
    --cmw-serif:Georgia,'Times New Roman',serif;
    --cmw-sans:'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
}
:root[data-theme="dark"]{
    --bg:#0f1620;
    --surface-1:#1e2c3d;
    --surface-2:#172230;
    --border:#2c3e54;
    --text-primary:#e7eef6;
    --text-secondary:#9fb1c6;
    --text-muted:#7e90a8;
    --cmw-accent:    #3a98dd;
    --cmw-accent-bg: #22344a;
    --cmw-accent-txt:#7cc0f0;
    --cmw-pos:  #2e9e6b;
    --cmw-neg:  #d2493e;
    --cmw-warn: #d6a93e;
    --cmw-warn-bg:#3a3320;
    --cmw-shadow:0 6px 22px rgba(0,0,0,.40);
}

*{ box-sizing:border-box; }
html, body{ margin:0; padding:0; }
body{
    background:var(--bg); color:var(--text-primary);
    font-family:var(--cmw-sans); -webkit-font-smoothing:antialiased;
    min-height:100vh;
}
p, span, label, li{ color:inherit; }
a{ color:var(--cmw-accent); text-decoration:none; }
a:hover{ text-decoration:underline; }

/* Headings — serif brand voice */
h1, h2{ font-family:var(--cmw-serif); color:var(--cmw-accent-txt);
    letter-spacing:.3px; font-weight:700; margin:0 0 8px; }
h3, h4{ font-family:var(--cmw-sans); color:var(--text-primary);
    font-weight:650; margin:0 0 6px; }

/* Rounded cards w/ soft shadow */
.cmw-card{ background:var(--surface-2); border:1px solid var(--border);
    border-radius:var(--cmw-radius); padding:16px 17px; box-shadow:var(--cmw-shadow);
    margin-bottom:14px; }

/* Buttons */
button, .cmw-btn{
    background:var(--surface-2); border:1px solid var(--border); color:var(--text-secondary);
    border-radius:var(--cmw-radius-sm); padding:8px 12px; font-size:12.5px; font-weight:550;
    font-family:var(--cmw-sans); cursor:pointer; }
button:hover, .cmw-btn:hover{
    color:var(--cmw-accent); border-color:var(--cmw-accent); }
button.primary, .cmw-btn.primary{
    background:var(--cmw-accent); border-color:var(--cmw-accent); color:#fff; }
button.primary:hover, .cmw-btn.primary:hover{ filter:brightness(1.08); }
button:disabled{ opacity:.5; cursor:default; }

/* Inputs / selects */
input[type="text"], input[type="password"], input[type="date"],
input[type="number"], input[type="search"], select, textarea{
    background:var(--surface-2); color:var(--text-primary);
    border:1px solid var(--border); border-radius:var(--cmw-radius-sm);
    font-family:var(--cmw-sans); font-size:13px; padding:7px 10px; }
input:focus, select:focus, textarea:focus{
    border-color:var(--cmw-accent); outline:none; }

/* Flat-underline tabs */
.cmw-tabs{ display:flex; gap:4px; border-bottom:1px solid var(--border); margin-bottom:16px; }
.cmw-tabs a{ color:var(--text-secondary); text-decoration:none;
    font-size:13.5px; font-weight:550; padding:10px 15px; border-bottom:2px solid transparent; }
.cmw-tabs a:hover{ color:var(--cmw-accent); }
.cmw-tabs a.active{ color:var(--cmw-accent); border-bottom-color:var(--cmw-accent); }

/* Main top-level tab bar (Dashboard/Bills & Income/Planning/Investing/
 * Property & Debt/Accounts) — sits below the sticky topbar as header
 * chrome, distinct from in-page .cmw-tabs sub-tab rows (Accounts'
 * Linked/Connect/Import, Cash Flow's Forecast/Scenarios, etc.) which keep
 * their own look. Reuses .cmw-tabs link styling via a combined class. */
.cmw-main-tabs{
    margin:0 auto; max-width:none; padding:0 16px;
    background:var(--surface-2); border-bottom:1px solid var(--border);
    position:sticky; top:46px; z-index:90; overflow-x:auto;
}

/* Top nav bar */
.cmw-topbar{
    display:flex; align-items:center; gap:10px;
    padding:0 16px; height:46px;
    background:var(--surface-2);
    border-bottom:1px solid var(--border);
    position:sticky; top:0; z-index:100;
}
.cmw-logo{ font-size:15px; font-weight:600; color:var(--cmw-accent-txt); font-family:var(--cmw-serif); letter-spacing:.01em; white-space:nowrap; }
.cmw-logo span{ font-size:11px; color:var(--text-muted); margin-left:4px; font-weight:400; font-family:var(--cmw-sans); }
/* Hamburger menu */
.cmw-hamburger-btn{
    width:32px; height:32px; border-radius:6px; border:1px solid var(--border);
    background:var(--surface-1); display:inline-flex; flex-direction:column;
    align-items:center; justify-content:center; gap:4px; cursor:pointer; padding:0;
}
.cmw-hamburger-btn:hover{ border-color:var(--cmw-accent); }
.cmw-hamburger-btn span{ display:block; width:16px; height:2px; background:var(--text-secondary); border-radius:1px; }
.cmw-hamburger-btn:hover span{ background:var(--cmw-accent); }
.cmw-nav-dropdown{
    position:absolute; top:46px; left:16px; z-index:200;
    display:none; flex-direction:column; min-width:190px;
    background:var(--surface-2); border:1px solid var(--border);
    border-radius:var(--cmw-radius-sm); box-shadow:var(--cmw-shadow);
    padding:6px; gap:1px;
}
.cmw-nav-dropdown.open{ display:flex; }
.cmw-nav-dropdown a{
    padding:9px 12px; border-radius:6px; font-size:13px;
    color:var(--text-secondary); text-decoration:none;
    white-space:nowrap;
}
.cmw-nav-dropdown a:hover{ background:var(--surface-1); color:var(--text-primary); }
.cmw-nav-dropdown a.active{ background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); font-weight:500; }
.cmw-topbar-right{ margin-left:auto; display:flex; align-items:center; gap:6px; }
.cmw-icon-btn{
    width:30px; height:30px; border-radius:6px;
    border:1px solid var(--border);
    background:var(--surface-1);
    display:inline-flex; align-items:center; justify-content:center;
    cursor:pointer; color:var(--text-secondary);
    font-size:14px; text-decoration:none;
}
.cmw-icon-btn:hover{ color:var(--cmw-accent); border-color:var(--cmw-accent); }
/* 2026-08-06: "show inactive accounts" toggle's on-state — same accent
   treatment as hover above, but persistent (not just while the pointer is
   over it) so the button visibly communicates the toggle is currently on. */
.cmw-icon-btn-active{ color:var(--cmw-accent); border-color:var(--cmw-accent); background:var(--cmw-accent-bg); }
/* Logout's power icon reads as a distinct, slightly alarming action —
   colored with the neg/red accent instead of the standard gray, per the
   reference screenshot (2026-07-06). */
.cmw-icon-btn-danger{ color:var(--cmw-neg); }
.cmw-icon-btn-danger:hover{ color:var(--cmw-neg); border-color:var(--cmw-neg); filter:brightness(1.15); }
/* 2026-07-17 ("the action buttons can be a little bigger so I can see the
   icons") — Review Queue's Uncategorized action row (Sweep/Skip-Quicken/
   Expand-all/Process) uses this size modifier instead of resizing the base
   .cmw-icon-btn, so the topbar's icon buttons (which share that base class)
   don't grow too. */
.cmw-icon-btn-lg{ width:38px; height:38px; }
.cmw-icon-btn-lg .cmw-icon{ width:20px; height:20px; }
/* Topbar icon set switched from emoji to inline outline SVGs (2026-07-06,
   per reference screenshot) — stroke="currentColor" in the markup means
   these automatically follow .cmw-icon-btn's color/hover states and both
   themes with no extra CSS. */
.cmw-icon{ width:17px; height:17px; display:block; }
.cmw-notif-badge{
    position:absolute; top:-3px; right:-3px; min-width:15px; height:15px;
    border-radius:999px; background:var(--cmw-neg); color:#fff;
    font-size:9px; font-weight:700; line-height:15px; text-align:center;
    padding:0 3px; display:none; align-items:center; justify-content:center;
}
/* Receipts button count chips (2026-07-08) — "pending" (actionable, needs a
   look) uses the same red as .cmw-notif-badge; "matched" (informational
   only, just a running total) uses a muted neutral chip instead so it
   doesn't visually compete with pending for attention. Inline pills next to
   the button label rather than .cmw-notif-badge's corner-dot positioning,
   since a plain-text button can carry two counts side by side more legibly
   than two overlapping absolute-positioned dots would. Hidden on "0" by the
   same JS pattern as the notif badge (see transactions.html). */
.cmw-rx-count-chip{
    display:none; align-items:center; justify-content:center;
    min-width:16px; height:16px; margin-left:5px; padding:0 5px;
    border-radius:999px; font-size:9.5px; font-weight:700; line-height:16px;
}
.cmw-rx-count-chip.pending{ background:var(--cmw-neg); color:#fff; }
.cmw-rx-count-chip.matched{ background:var(--surface-1); color:var(--text-muted); border:1px solid var(--border); }
/* Username display (2026-07-06) — plain text per reference screenshot
   ("Daniel Clem (admin)"), replacing the earlier avatar-circle pill. */
.cmw-username-plain{ font-size:12.5px; font-weight:500; color:var(--text-primary); white-space:nowrap; }
.cmw-username-plain .cmw-role-muted{ color:var(--text-muted); font-weight:400; }

/* KPI strip */
.cmw-kpi-strip{
    display:grid; grid-template-columns:repeat(auto-fit, minmax(160px, 1fr));
    gap:12px; padding:14px 16px 0;
}
.cmw-kpi{ background:var(--surface-2); border:1px solid var(--border);
    border-left:3px solid var(--cmw-accent); border-radius:11px;
    padding:13px 15px; box-shadow:var(--cmw-shadow); }
.cmw-kpi-label{ font-size:11px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.6px; margin-bottom:6px; }
.cmw-kpi-val{ font-size:23px; font-weight:700; line-height:1.05; color:var(--cmw-accent-txt); }
.cmw-kpi-val.pos{ color:var(--cmw-pos); }
.cmw-kpi-val.neg{ color:var(--cmw-neg); }
.cmw-kpi.warn{ border-left-color:var(--cmw-neg); }
.cmw-kpi.warn .cmw-kpi-val{ color:var(--cmw-neg); }

/* 2026-08-13 ("the kpi cards on each accounts register... should fit in
   the account header row with the online balance") — compact inline
   version of the KPI strip above, used only in Account Detail's header now
   (partials/_account_kpi_compact.html) in place of that page's own
   full-width .cmw-kpi-strip band. No card chrome (background/border/
   shadow/radius) since it sits inline in a page-header row alongside the
   title and balance rather than as its own block — thin vertical dividers
   between items stand in for the old cards' separation instead. Lives
   INSIDE #account-detail-header's own single sticky band on purpose — see
   that partial's comment and .cmw-acct-top's history below for why a
   SEPARATE band sharing the header's row broke this page's sticky math
   before. */
.cmw-kpi-compact{ display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.cmw-kpi-compact-item{
    display:flex; flex-direction:column; align-items:center; gap:2px;
    padding-right:16px; border-right:1px solid var(--border);
}
.cmw-kpi-compact-item:last-child{ padding-right:0; border-right:none; }
.cmw-kpi-compact-val{ font-size:15px; font-weight:700; line-height:1; color:var(--cmw-accent-txt); white-space:nowrap; }
.cmw-kpi-compact-val.pos{ color:var(--cmw-pos); }
.cmw-kpi-compact-val.neg{ color:var(--cmw-neg); }
.cmw-kpi-compact-label{ font-size:9.5px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.5px; white-space:nowrap; }

/* Dashboard "needs review" banner (2026-07-14, dashboard.html) — full-width,
   above the KPI strip, shown only when there's something to review. Uses
   --cmw-warn/--cmw-warn-bg (amber "needs your attention") rather than
   --cmw-neg (reserved for negative dollar amounts/over-budget) or
   --cmw-accent (reserved for neutral/informational) — this is neither, it's
   an outstanding task. */
.cmw-dash-review-banner{
    display:flex; align-items:center; gap:10px;
    background:var(--cmw-warn-bg); color:var(--cmw-warn);
    border:1px solid var(--cmw-warn); border-radius:var(--cmw-radius-sm);
    padding:11px 16px; margin:14px 0 0; text-decoration:none;
    font-size:13px; font-weight:600;
}
.cmw-dash-review-banner:hover{ filter:brightness(1.04); }
.cmw-dash-review-banner-icon{ font-size:15px; flex-shrink:0; }
.cmw-dash-review-banner-text{ flex:1; }
.cmw-dash-review-banner-text strong{ font-weight:700; }
.cmw-dash-review-banner-cta{ flex-shrink:0; white-space:nowrap; text-decoration:underline; }

/* Page wrapper */
.cmw-main{ padding:16px; }
.cmw-page-header{ padding:14px 2px 10px; }
.cmw-page-title{ font-size:22px; font-weight:700; font-family:var(--cmw-serif); color:var(--cmw-accent-txt); display:inline-block; }
.cmw-page-subtitle{ font-size:13px; color:var(--text-muted); margin-top:2px; }
/* 2026-07-17 ("it just takes up valuable space") — a subtitle that
 * immediately follows a page title (the standard .cmw-page-header pattern
 * every tab uses) now sits on the same line instead of stacking below it,
 * freeing a full row of vertical space on every page. Scoped with the
 * adjacent-sibling combinator so it ONLY affects a subtitle directly after
 * a title — the many OTHER .cmw-page-subtitle uses sprinkled through
 * individual pages (empty-state notes, secondary explainer lines further
 * down the page, etc.) are untouched, since none of those immediately
 * follow a .cmw-page-title element. */
.cmw-page-title + .cmw-page-subtitle{ display:inline-block; margin-top:0; margin-left:10px; }

/* App shell — global left account panel + main content, both scroll under
 * the sticky topbar. The panel is loaded lazily via HTMX (see base.html:
 * hx-get="/app/accounts/panel") so no route needs to thread account data
 * through its own context. */
.cmw-app-shell{ display:flex; align-items:flex-start; max-width:none; margin:0 auto; }
/* Narrow/wide topbar toggle (2026-07-06, base.html's #cmw-width-toggle) —
   the [data-width="narrow"] attribute lives on <html>, set client-side from
   the cmw_width cookie before first paint (see base.html's head script).
   Feedback 2026-07-06: wide should mean edge-to-edge full width, and narrow
   should be what used to be the only (1600px-capped) option — so "wide" is
   now the uncapped default below, and [data-width="narrow"] is the 1600px
   cap. Narrows both the content column and the main tab bar above it
   together so the tabs stay aligned with the content instead of spanning
   wider than what's under them. */
[data-width="narrow"] .cmw-app-shell,
[data-width="narrow"] .cmw-main-tabs{ max-width:1600px; }
.cmw-shell-main{ flex:1; min-width:0; }
.cmw-account-panel{
    width:310px; flex-shrink:0; padding:14px 10px 14px 16px;
    /* Was hardcoded to top:46px (topbar height only) — but .cmw-main-tabs
       just above it in the DOM is ALSO sticky at top:46px, so once both
       were stuck the panel silently scrolled in underneath the (higher
       z-index) main tab bar for the first ~40px of its sticky region.
       --cmw-stack-2 (topbar + main tabs, computed in base.html) is the
       correct clearance regardless of main-tabs' actual rendered height. */
    position:sticky; top:var(--cmw-stack-2, 90px); max-height:calc(100vh - var(--cmw-stack-2, 90px));
    display:flex; flex-direction:column;
}
.cmw-account-panel-head{
    display:flex; justify-content:space-between; align-items:center;
    margin-bottom:8px; padding:0 4px; flex-shrink:0;
}
.cmw-account-panel-head-actions{ display:flex; align-items:center; gap:6px; }
/* 2026-08-04/2026-08-06 ("collapse/expand accounts button icon needs to be
   larger", bumped again 08-06): in the expanded/default state this was
   still the base 30px/17px .cmw-icon-btn size and easy to miss next to the
   bolder Sync pill; now sized well above every other topbar icon on
   purpose. Scoped to this button's id (same precedent as the
   collapsed-state override below and .cmw-icon-btn-lg above) so Sync/
   topbar icons aren't inflated too. */
#cmw-sidebar-toggle{ width:40px; height:40px; }
#cmw-sidebar-toggle svg{ width:26px; height:26px; }
.cmw-account-panel-title{ font-family:var(--cmw-serif); font-size:15px; font-weight:700; color:var(--cmw-accent-txt); }
.cmw-account-panel-list{ flex:1; min-height:0; overflow-y:auto; }
/* Collapsed sidebar (2026-07-14, #cmw-sidebar-toggle in base.html) — shrink
   to a thin rail showing only the collapse/expand button so the user can
   reclaim the width (e.g. for a wide transactions table) without losing the
   sidebar entirely or hunting for how to bring it back. Transition matches
   the panel's own width so the collapse reads as a slide, not a jump. */
.cmw-account-panel{ transition:width .15s ease; }
[data-sidebar="collapsed"] .cmw-account-panel{ width:38px; padding:14px 4px; overflow:visible; }
[data-sidebar="collapsed"] .cmw-account-panel-title,
[data-sidebar="collapsed"] .cmw-sync-btn,
[data-sidebar="collapsed"] .cmw-acct-subgroup-select,
[data-sidebar="collapsed"] .cmw-account-panel-list{ display:none; }
[data-sidebar="collapsed"] .cmw-account-panel-head{ justify-content:center; }
[data-sidebar="collapsed"] #cmw-sidebar-toggle svg{ transform:rotate(180deg); }
/* 2026-08-06 ("make the button the same for each condition: 40x40px and
   26x26px arrow") — collapsed state now matches the expanded-state button
   above exactly (40px/40px box, 26px/26px arrow via the shared svg rule),
   rather than the taller/narrower 30x50px box used previously. No override
   needed here anymore; kept as an explicit rule (repeating the base value)
   so it's obvious this state was deliberately matched, not just inherited. */
[data-sidebar="collapsed"] #cmw-sidebar-toggle{ width:40px; height:40px; }
.cmw-acct-panel-total{
    display:flex; justify-content:space-between; align-items:center;
    font-family:var(--cmw-serif); font-size:15px; font-weight:700;
    padding:8px 10px; margin:0 2px 8px; border-radius:var(--cmw-radius-sm);
    background:var(--cmw-accent-bg); color:var(--cmw-accent-txt);
}
.cmw-acct-panel-total .neg{ color:var(--cmw-neg); }
.cmw-sync-btn{
    display:inline-flex; align-items:center; gap:4px; padding:4px 10px; border-radius:20px;
    border:1px solid var(--border); background:var(--surface-1); color:var(--text-secondary);
    font-size:11px; font-weight:600; cursor:pointer;
}
.cmw-sync-btn:hover{ color:var(--cmw-accent); border-color:var(--cmw-accent); }
/* 2026-07-11: sub-grouping selector for the account sidebar ("custom
   collapsible subgroups" — institution/owner, within each top-level
   Banking/Investing/etc. bucket). Styled like a slim version of the filter
   bar's <select>s rather than cmw-sync-btn's pill shape, since this is a
   persistent per-panel setting rather than a one-off action button. */
.cmw-acct-subgroup-select{
    width:100%; margin:0 0 8px; padding:5px 8px; font-size:12px;
    border:1px solid var(--border); border-radius:var(--cmw-radius-sm);
    background:var(--surface-1); color:var(--text-secondary); flex-shrink:0;
}
/* Nested one level inside .cmw-group-body, so it reads as "inside Banking"
   rather than another top-level frame — no card border/shadow of its own
   (that's reserved for the top-level .cmw-acct-group), just a thin rule
   above each subgroup after the first to separate them, and a smaller
   header than .cmw-group-header. */
.cmw-acct-subgroup{ margin-top:2px; }
.cmw-acct-subgroup + .cmw-acct-subgroup{ border-top:1px solid var(--border); padding-top:2px; }
.cmw-subgroup-header{
    display:flex; justify-content:space-between; align-items:center; gap:6px;
    padding:6px 4px; cursor:pointer; width:100%; background:none; border:none;
    font-family:inherit; text-align:left;
}
.cmw-subgroup-header:hover{ background:var(--surface-1); }
.cmw-subgroup-label{ font-size:11px; font-weight:600; color:var(--text-secondary); }
.cmw-acct-subgroup.collapsed .cmw-group-chevron{ transform:rotate(-90deg); }
.cmw-acct-subgroup.collapsed .cmw-subgroup-body{ display:none; }
.cmw-subgroup-body{ padding:0 0 4px 6px; }
.cmw-subgroup-body .cmw-acct-card{ margin-bottom:5px; }
@media (max-width: 960px){
    .cmw-app-shell{ flex-direction:column; }
    .cmw-account-panel{ width:100%; position:static; max-height:none; padding:14px 16px 0; }
    .cmw-account-panel-list{ max-height:340px; }
}

/* Framed like a .cmw-card (border + radius + shadow) so each group visually
   reads as one collapsible unit — header + its cards enclosed together —
   instead of the header floating separately above unrelated-looking cards.
   Feedback 2026-07-05: "account cards aren't in the account type frame" /
   "account types aren't collapsible" — the collapse JS/CSS (.collapsed
   .cmw-group-body{display:none}, below) always worked, but with no visible
   boundary around the group it just looked broken. */
.cmw-acct-group{
    margin-bottom:10px; background:var(--surface-2); border:1px solid var(--border);
    border-radius:var(--cmw-radius-sm); box-shadow:var(--cmw-shadow); overflow:hidden;
}
.cmw-acct-group .cmw-group-body{ padding:0 8px 8px; }
.cmw-acct-card{
    display:block; background:var(--surface-1); border:1px solid var(--border); border-radius:var(--cmw-radius-sm);
    padding:9px 11px; margin-bottom:6px; text-decoration:none;
}
.cmw-acct-card:last-child{ margin-bottom:0; }
.cmw-acct-card:hover{ border-color:var(--cmw-accent); text-decoration:none; }
.cmw-acct-card-top{ display:flex; justify-content:space-between; align-items:baseline; gap:8px; }
/* 2026-08-06 ("bump the font size for the account bar") — every text size in
   the left account panel bumped ~1px: card name/balance, subtitle, group
   label/total, subgroup select/label. Card sizing (padding, gaps) untouched
   — this is a legibility pass, not a layout redo. */
.cmw-acct-card-name{ font-size:13.5px; font-weight:650; color:var(--text-primary);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cmw-acct-card-bal{ font-size:13.5px; font-weight:700; color:var(--text-primary); white-space:nowrap; flex-shrink:0; }
.cmw-acct-card-sub{ font-size:11.5px; color:var(--text-muted); margin-top:3px; line-height:1.4; }

/* Account sidebar (legacy Streamlit-ported classes — group header/label/total
 * are reused by the new panel above; .cmw-acct-item/.cmw-acct-icon were the
 * old single-line filterable-list style and are unused by the new card
 * layout but kept in case a compact-mode toggle is added later) */
.cmw-acct-sidebar{ font-size:13px; }
.cmw-group-header{
    display:flex; justify-content:space-between; align-items:center; gap:6px;
    padding:10px 8px; cursor:pointer; width:100%; background:none; border:none;
    font-family:inherit; text-align:left;
}
.cmw-group-header:hover{ background:var(--surface-1); }
.cmw-group-header-left{ display:flex; align-items:center; gap:5px; min-width:0; }
/* 2026-08-06 ("these icons need to be larger... asked several times"):
   this is a DIFFERENT icon than #cmw-sidebar-toggle above (the whole-panel
   collapse button) — this is the small per-group expand/collapse caret in
   front of each Banking/Investing/Property & Debt/etc. row (and each
   institution/owner subgroup row nested inside one, via the same class —
   see .cmw-acct-subgroup.collapsed below). It was still 9px, untouched by
   every prior pass at this file that only ever resized #cmw-sidebar-toggle
   — that's the actual bug behind "I've asked several times and it's not
   changing." Bumped to 16px, well above every other icon in the panel. */
.cmw-group-chevron{
    font-size:20px; color:var(--text-muted); transition:transform .15s ease; flex-shrink:0;
    display:inline-block; line-height:1;
}
.cmw-acct-group.collapsed .cmw-group-chevron{ transform:rotate(-90deg); }
.cmw-acct-group.collapsed .cmw-group-body{ display:none; }
.cmw-group-label{ font-size:11px; font-weight:700; color:var(--cmw-accent-txt); text-transform:uppercase; letter-spacing:.07em; }
.cmw-group-total{ font-size:11px; font-weight:600; color:var(--text-muted); }
.cmw-acct-all{ display:flex; align-items:center; gap:7px; padding:6px 8px; margin:2px 0; border-radius:6px; background:var(--cmw-accent-bg); cursor:pointer; }
.cmw-acct-item{ display:flex; align-items:center; gap:7px; padding:5px 8px; margin:1px 0; border-radius:6px; cursor:pointer; text-decoration:none; }
.cmw-acct-item:hover{ background:var(--surface-1); }
.cmw-acct-item.selected{ background:var(--cmw-accent-bg); }
/* 2026-08-06: .cmw-acct-icon (the colored dep/cc/inv/loan account-type
   badge) removed — confirmed zero references left in templates/, it was
   dead code from the pre-card single-line list style (see the comment
   above .cmw-acct-sidebar). */
.cmw-acct-name{ flex:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:12px; font-weight:500; color:var(--text-primary); }

/* Feedback — floating button + modal, global on every /app/* page */
.cmw-feedback-fab{
    position:fixed; right:20px; bottom:20px; z-index:200;
    background:var(--cmw-accent); color:#fff; border:none;
    border-radius:999px; padding:10px 16px; font-size:12.5px; font-weight:600;
    box-shadow:var(--cmw-shadow); cursor:pointer; display:flex; align-items:center; gap:6px;
}
.cmw-feedback-fab:hover{ filter:brightness(1.08); color:#fff; }
.cmw-modal-overlay{
    position:fixed; inset:0; background:rgba(15,22,32,.45);
    display:none; align-items:center; justify-content:center; z-index:300;
}
.cmw-modal-overlay.open{ display:flex; }
.cmw-modal{
    background:var(--surface-2); border:1px solid var(--border); border-radius:var(--cmw-radius);
    box-shadow:var(--cmw-shadow); width:100%; max-width:420px; padding:18px 18px 16px; margin:16px;
    /* 2026-07-17 ("the income card is too tall and can't fit on a laptop
       screen") — .cmw-modal never had a height cap; it just grew to fit
       content, and .cmw-modal-overlay centers it (align-items:center) with
       no scroll of its own, so a tall form (Income's five sections is the
       worst case, but Split/Vault/etc. can hit this too) simply ran off
       both edges of a shorter laptop viewport with no way to reach the rest.
       Caps at the viewport height minus this element's own 16px top+bottom
       margin, and scrolls its own content instead — the sticky modal-head
       stays visible while the form scrolls underneath it. */
    max-height:calc(100vh - 32px); overflow-y:auto;
}
/* 2026-07-20 ("the split card needs to be wider, I couldn't read the cat/sub
   cat inputs") — the bill Split flyout's .cmw-split-line grid packs TWO
   comboboxes (Category + Subcategory, via catsub_pair) into one 2fr column,
   same shape the Transactions Split modal has always used, but .cmw-modal's
   shared 420px max-width (set 2026-07-17 for the Income modal, see above)
   left almost no room for either combo's typed text to be readable. Scoped
   modifier rather than widening .cmw-modal globally, which would also
   stretch every simple confirm/single-field modal in the app that's fine at
   420px. */
/* 2026-07-20 follow-up: computed size was still coming out 420x352 (the
   plain .cmw-modal default) even with this class confirmed present on the
   element in DevTools — normal cascade/source-order reasoning says this
   rule should already win, so brute-forcing it with a higher-specificity
   combined selector + !important rather than keep guessing at what's
   actually overriding it. */
.cmw-modal.cmw-modal-wide{ width:90vw !important; max-width:820px !important; }
.cmw-modal-head{
    display:flex; align-items:center; justify-content:space-between; margin-bottom:10px;
    position:sticky; top:0; background:var(--surface-2); z-index:1;
}
.cmw-modal-close{ background:none; border:none; color:var(--text-muted); font-size:18px; cursor:pointer; padding:0; line-height:1; }
.cmw-modal-close:hover{ color:var(--cmw-accent); }
.cmw-modal textarea{ width:100%; min-height:90px; resize:vertical; }
.cmw-modal-actions{ display:flex; justify-content:flex-end; gap:8px; margin-top:10px; }
.cmw-acct-bal{ font-size:12px; font-weight:500; flex-shrink:0; }
.cmw-acct-bal.pos{ color:var(--cmw-pos); }
.cmw-acct-bal.neg{ color:var(--cmw-neg); }
.cmw-divider{ height:1px; background:var(--border); margin:6px 0; }

/* Flyout drawer (Receipts / Reconcile, off the Transactions page) */
.cmw-flyout-overlay{
    position:fixed; inset:0; background:rgba(15,22,32,.45);
    display:none; z-index:300;
}
.cmw-flyout-overlay.open{ display:block; }
.cmw-flyout{
    position:fixed; top:0; right:0; height:100%; width:100%; max-width:640px;
    background:var(--surface-2); box-shadow:-6px 0 24px rgba(0,0,0,.18);
    transform:translateX(100%); transition:transform .22s ease;
    display:flex; flex-direction:column; overflow:hidden;
}
.cmw-flyout-overlay.open .cmw-flyout{ transform:translateX(0); }
/* 2026-07-08 fix: #cmw-flyout-content (the page-shell target div, see
   transactions.html) and #cmw-rx-flyout-inner (the Receipts partial's own
   wrapper, see _rx_flyout_inner.html) both sit between .cmw-flyout's flex
   column and .cmw-flyout-body — as plain unstyled divs, neither is a flex
   item/container, so .cmw-flyout-body's flex:1+overflow-y:auto never gets a
   bounded height to scroll within. Content just grew past the drawer and
   got clipped by .cmw-flyout's overflow:hidden with no way to reach it
   (reported: "I can't scroll to see all the pending transactions" after a
   Gmail import backlog filled the Pending tab past one screen).
   display:contents removes a div from the box tree entirely while keeping
   it in the DOM (so htmx's id-based hx-target/hx-swap keeps working
   unchanged) — its children become direct flex items of .cmw-flyout, same
   as if these wrapper divs weren't there. */
#cmw-flyout-content, #cmw-rx-flyout-inner{ display:contents; }
.cmw-flyout-head{
    display:flex; align-items:center; justify-content:space-between;
    padding:16px 20px; border-bottom:1px solid var(--border); flex-shrink:0;
}
.cmw-flyout-head h3{ margin:0; font-family:var(--cmw-serif); color:var(--cmw-accent-txt); }
.cmw-flyout-body{ padding:18px 20px; overflow-y:auto; flex:1; }
.cmw-flyout-tabs{ display:flex; gap:14px; padding:0 20px; border-bottom:1px solid var(--border); flex-shrink:0; }
.cmw-flyout-tabs a{
    padding:10px 2px; font-size:12.5px; font-weight:600; color:var(--text-muted);
    text-decoration:none; border-bottom:2px solid transparent; cursor:pointer;
}
.cmw-flyout-tabs a.active{ color:var(--cmw-accent); border-bottom-color:var(--cmw-accent); }

/* 2026-07-22 ("I like the floating window") — Reconcile moves from the
   side-drawer above to a draggable/resizable floating window instead, the
   first UI in this app to work that way. Kept as its own class set rather
   than a .cmw-flyout variant — that class is depended on by Receipts/
   Transactions exactly as it is (fixed side-drawer, slide-in transition),
   and bending it to also support free positioning risked breaking those. */
.cmw-float-overlay{
    position:fixed; inset:0; background:rgba(15,22,32,.45);
    display:none; z-index:300;
}
.cmw-float-overlay.open{ display:block; }
.cmw-float-win{
    /* top/left/width/height are set inline by JS (account_detail.html) —
       centered with a sensible default size on first open, then whatever
       the user last dragged/resized to (remembered via localStorage) on
       every open after that. */
    position:fixed; display:none; flex-direction:column;
    background:var(--surface-2); border:1px solid var(--border); border-radius:var(--cmw-radius);
    box-shadow:0 16px 56px rgba(0,0,0,.4);
    /* Native CSS resize (browser-drawn bottom-right grip) rather than
       hand-rolled JS drag handles on every edge/corner — far less code, and
       every browser already knows how to clamp/cursor this correctly.
       Requires overflow other than visible, which this needs anyway for
       the rounded corners to actually clip .cmw-float-win-head's square
       corners. */
    resize:both; overflow:hidden;
    min-width:460px; min-height:340px;
}
.cmw-float-overlay.open .cmw-float-win{ display:flex; }
.cmw-float-win-head{
    display:flex; align-items:center; justify-content:space-between;
    padding:14px 18px; border-bottom:1px solid var(--border); flex-shrink:0;
    background:var(--surface-1); border-radius:var(--cmw-radius) var(--cmw-radius) 0 0;
    cursor:move; user-select:none;
}
.cmw-float-win-head h3{ margin:0; font-family:var(--cmw-serif); color:var(--cmw-accent-txt); font-size:15px; }
.cmw-float-win-body{ padding:18px 20px; overflow:auto; flex:1; min-height:0; }
/* Same fix .cmw-flyout-content needed above (see that history) — a plain
   wrapper div between .cmw-float-win's flex column and .cmw-float-win-body
   would strand flex:1/overflow:auto without a bounded height to scroll
   within, otherwise. */
#cmw-reconcile-flyout-content{ display:contents; }

/* Native transaction table (2026-07-11 full rewrite)
   Replaces the old div+CSS-Grid .cmw-txn-table system, which broke
   repeatedly once embedded in more complex parent layouts — CSS Grid
   items + position:sticky interactions on Account Detail's 2-column
   layout produced misaligned sticky headers and rows rendering outside
   the card frame, on top of the documented 2026-07-05 overflow-x/sticky
   bug this same system had before that. A real <table>/<thead>/<tbody>
   is what browsers are actually built to lay out predictably: sticky
   header cells and colspan row-spanning are native, spec-guaranteed
   behaviors instead of reimplementations.
   Used by Transactions (data-table-key="txn"), Account Detail cash-flow
   ("acct") and investment activity ("inv"), and Pending Receipts ("rx").
   See static/js/txn-table.js for resize/sort/reorder/show-hide.

   2026-07-11 third pass: dropped <colgroup>/<col> — column widths now
   live directly on each <th data-col> (set server-side, updated by JS on
   resize/prefs-apply), not on a separate <col> element JS had to keep in
   sync with the real cells. That syncing layer was the source of repeated
   column-rendering bugs (header fine, body cells collapsed to near-zero
   width) that no amount of sticky-positioning or layout fixes touched,
   because the actual bug was one level down from where those fixes were
   aimed. table-layout:fixed reads column widths from the first row's
   cells per spec — a <thead> row qualifies exactly as validly as a
   <colgroup> would, so nothing about the rendering model changed, just
   which element owns the width.

   2026-07-11 second rebuild: every .cmw-ntable now lives inside a
   .cmw-table-scroll wrapper with its own bounded height and its own
   overflow-y:auto — the sticky header sticks to THIS box (top:0), not to
   a JS-computed offset summing up everything else on the page. That JS
   stacking approach (topbar + main tabs + page header + filter bar + ...)
   broke every time the page composition changed underneath it: 2-column
   layouts it couldn't model, stale timing on first paint, sticky
   containing-block confusion whenever an ancestor picked up overflow
   other than visible. A bounded local scroll box has no such dependency —
   it's the table's only possible containing block, full stop, regardless
   of what else is on the page or how tall it is. This is also just a
   better fit for dense/spreadsheet-style tables generally: the page
   chrome stays put and only the rows scroll, the same as a real
   spreadsheet, instead of the whole page scrolling underneath a header
   that has to track it.

   2026-07-12 fix ("the column names should not move when i scroll through
   the table"): that bounded local scroll box was only ever half the story
   — see the 2026-07-20 reversal below for why this two-scroll-region setup
   (page scroll + the table's own independent scroll) was replaced. This
   history is kept for context: thead th's position:sticky;top:0 pinned the
   header to the top of .cmw-table-scroll itself, but nothing stopped the
   page around that box from also scrolling at the body level and carrying
   the whole box — header included — off screen with it, so
   .cmw-table-scroll was made position:sticky too, at the same
   top:var(--cmw-stack-2) offset the account sidebar uses, to lock the box
   (and therefore its header) in place once scrolled that far.

   2026-07-20 ("why is there a separate window level scroll?"): reverted.
   Two independent scroll regions on one page (page scroll vs. this box's
   own overflow-y:auto) meant the mouse wheel did different things
   depending on cursor position — confusing, not a deliberate feature.
   .cmw-table-scroll is now a plain block in normal page flow again — no
   height cap, no internal vertical scrollbar, no position:sticky of its
   own — so there's exactly one scroll region (the page) for every table.
   The header still stays visible while scrolling: .cmw-ntable thead th
   below now sticks to var(--cmw-stack-top) instead of top:0, the same
   viewport-relative slot Review Queue's column heads (.cmw-review-col-head)
   and the Vault's category heads (.cmw-vault-cat-head) already stick to —
   this was always the app's existing pattern for "stays visible against
   the PAGE scroll," .cmw-table-scroll's bounded-box approach was the odd
   one out, not the norm. overflow-x:auto is kept (a wide table still needs
   to scroll horizontally on a narrow viewport — an unrelated axis from the
   vertical dual-scroll problem this reversal fixes).

   Kanban-style multi-column sections (Review Queue's four columns, Bills/
   Income/Receipts' board columns) are NOT affected by any of this — they
   were never built on .cmw-table-scroll/.cmw-ntable in the first place.
   Each of those columns already uses the split .cmw-review-col-head
   (sticky to the page, var(--cmw-stack-top)) + .cmw-review-col-body (its
   own bounded overflow-y:auto, scrolling independently of its neighbors)
   pattern — a deliberate, different design for "several columns side by
   side, each scrollable on its own so you can compare them at once,"
   unlike a single long table where one page-level scroll is all that's
   needed. That pattern is untouched. */
/* 2026-07-20 follow-up ("only tags, source/status, and the far-left column
   are sticky" -> traced further after the th.sortable specificity fix
   didn't fully resolve it): overflow-x:auto here was ALSO breaking every
   sticky header cell, for a completely different, spec-level reason —
   position:sticky resolves against the nearest ANCESTOR with a "scrolling
   mechanism" (any overflow value other than visible on EITHER axis
   qualifies, per the CSS spec — not just an axis that's actually
   overflowing), not necessarily the viewport. With overflow-x:auto set
   here (and per the spec, an explicit overflow-x other than visible with
   no explicit overflow-y computes overflow-y as auto too, not visible),
   .cmw-table-scroll itself became that nearest scrolling ancestor — so
   thead th's top:var(--cmw-stack-top) was being measured from the top of
   THIS box (which sits partway down the page, below the header/KPI/filter
   bar), not from the viewport, pushing the header visibly down into the
   middle of the table's own rows instead of pinning it near the top of the
   screen. Dropping overflow-x here (no horizontal scroll box of its own)
   means .cmw-table-scroll no longer has any non-visible overflow on either
   axis, so it's no longer a scrolling ancestor at all — sticky headers
   resolve against the real viewport again. A very wide table now just
   relies on the page's own horizontal scrollbar if it doesn't fit,
   consistent with this same day's "one scroll" simplification generally. */
.cmw-table-scroll{
    min-height:220px;
    border:1px solid var(--border); border-radius:var(--cmw-radius); box-shadow:var(--cmw-shadow);
    background:var(--surface-2);
}
.cmw-ntable{
    width:100%; border-collapse:separate; border-spacing:0; table-layout:fixed;
    background:var(--surface-2);
}
.cmw-ntable thead th{
    position:sticky; top:var(--cmw-stack-top, 140px); z-index:5;
    background:var(--surface-1); border-bottom:1px solid var(--border);
    padding:8px 8px 8px 4px; font-size:10px; font-weight:600; color:var(--text-muted);
    text-align:left; text-transform:uppercase; letter-spacing:.04em;
    white-space:nowrap; user-select:none;
}
/* 2026-07-20 ("only tags, source/status, and the far-left [icon] column
   are sticky with the table frame") — position:relative here was only ever
   meant to give .cmw-col-resize-handle (position:absolute, right below) a
   positioning context; a plain <th> with no resize handle (icon/status/
   tags — the only three columns NOT built via the sort_header macro, see
   templates/partials/_macros.html) never got this rule at all, so they
   correctly kept .cmw-ntable thead th's position:sticky above. But
   `.cmw-ntable th.sortable` has higher specificity (two classes) than
   `.cmw-ntable thead th` (one class + two type selectors) regardless of
   source order, so position:relative was silently WINNING over
   position:sticky for every sortable column — i.e. every column except
   those three. position:sticky is just as valid a containing block for an
   absolutely-positioned child as position:relative is (any position value
   other than static works), so switching to it here fixes the sticky
   header for every column without touching the resize handle at all. */
.cmw-ntable th.sortable{ cursor:pointer; position:sticky; top:var(--cmw-stack-top, 140px); }
.cmw-ntable th.sortable:hover{ color:var(--text-primary); }
.cmw-ntable th.sorted{ color:var(--cmw-accent); }
.cmw-ntable th.align-right, .cmw-ntable td.align-right{ text-align:right; }
.cmw-ntable .cmw-sort-arrow{ font-size:8px; margin-left:2px; }
.cmw-ntable .cmw-col-resize-handle{
    position:absolute; right:-5px; top:0; bottom:0; width:10px; cursor:col-resize; z-index:3;
}
.cmw-ntable .cmw-col-resize-handle:hover, .cmw-ntable .cmw-col-resize-handle.resizing{
    background:var(--cmw-accent); opacity:.35;
}
.cmw-ntable tbody td{
    padding:6px 8px 6px 4px; font-size:11.5px; color:var(--text-secondary);
    border-bottom:1px solid var(--border); vertical-align:middle;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.cmw-ntable tbody tr:last-child td{ border-bottom:none; }
.cmw-ntable tbody tr:hover td{ background:var(--surface-1); }
.cmw-ntable-icon-cell{ width:22px; text-align:center; padding-left:8px; }
.cmw-txn-icon{ width:22px; height:22px; border-radius:5px; background:var(--surface-1); border:1px solid var(--border);
    display:inline-flex; align-items:center; justify-content:center; font-size:11px; }
.cmw-txn-name{ font-size:12.5px; font-weight:500; color:var(--text-primary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cmw-txn-sub{ font-size:10.5px; color:var(--text-muted); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cmw-ntable td select{ width:100%; font-size:11px; padding:4px 6px; }
.cmw-ntable td input[type="text"], .cmw-ntable td input[type="number"], .cmw-ntable td input[type="date"]{
    width:100%; font-size:11px; padding:4px 6px;
}
.cmw-tags-input{ width:100%; font-size:11px; padding:4px 6px; }
.cmw-txn-amt{ font-size:12.5px; font-weight:600; white-space:nowrap; }
.cmw-txn-amt.deb{ color:var(--cmw-neg); }
.cmw-txn-amt.crd{ color:var(--cmw-pos); }
/* 2026-07-12: generic pos/neg cell coloring for .cmw-ntable (Live Holdings'
   Gain/Loss + Gain/Loss % columns) — .cmw-txn-amt.deb/.crd above is scoped
   to the amount cell's own wrapper class, not reusable for an arbitrary
   plain <td>, and .cmw-data-table td.pos/.neg (below) is scoped to that
   other table class, not this one. */
.cmw-ntable td.pos{ color:var(--cmw-pos); }
.cmw-ntable td.neg{ color:var(--cmw-neg); }
.cmw-txn-status{ display:flex; align-items:center; justify-content:flex-end; gap:6px; }
/* 2026-07-13 "i need to be able to delete transactions" — small, muted
   icon button so it doesn't visually compete with the cleared checkbox
   it sits next to; hover makes the delete intent obvious without being
   loud at rest (this sits in every row of a dense table). */
.cmw-txn-delete-btn{
    background:none; border:none; padding:0 2px; font-size:13px; line-height:1;
    color:var(--text-muted); cursor:pointer; opacity:.55;
}
.cmw-txn-delete-btn:hover{ opacity:1; color:var(--cmw-neg); }
/* 2026-07-13 "I need to be able to have no tag" — replaces the old
   read-only .cmw-entity-badge pill (still used elsewhere) with a small
   inline select; sized down from the browser default so it doesn't
   dominate the muted account/spender sub-line it now lives in. */
.cmw-txn-tag-select{ font-size:10px; padding:1px 4px; width:auto; height:auto; }
.cmw-ntable-empty{ padding:40px 16px; text-align:center; color:var(--text-muted); font-size:13px; }
.cmw-cleared-check{ width:auto; }

/* Pending Receipts' always-visible edit panel below each row (2026-07-08) —
   a real second <tr><td colspan="N"> now, instead of the old
   display:contents/grid-column:1/-1 trick that relied on CSS Grid
   semantics a table doesn't have. colspan is set server-side to the
   column count so it always spans everything regardless of hide/reorder
   state (native tables don't shrink colspan automatically when columns
   are hidden via visibility:collapse the way a grid's 1/-1 span did, but
   the panel is meant to always read as "spanning the whole row" so this
   is the correct behavior, not a gap to fix). */
.cmw-rx-edit-row td{ padding:0 8px 12px; border-bottom:1px solid var(--border); }
.cmw-rx-edit-row:last-child td{ border-bottom:none; }
.cmw-rx-edit-panel{
    margin:2px 0 0; padding:12px 14px;
    background:var(--surface-1); border:1px solid var(--border); border-radius:var(--cmw-radius);
}

/* Customize Columns modal (2026-07-06, roadmap #4) — draggable checklist,
   see #cmw-colcustom-overlay in base.html + static/js/txn-table.js. */
.cmw-colcustom-row{ display:flex; align-items:center; gap:8px; padding:7px 4px; border-bottom:1px solid var(--border); cursor:grab; }
.cmw-colcustom-row:last-child{ border-bottom:none; }
.cmw-colcustom-row.dragging{ opacity:.4; }
.cmw-colcustom-handle{ color:var(--text-muted); font-size:14px; user-select:none; line-height:1; }

/* Ghost-text + dropdown combobox (category/transfer picker, payee, tags —
   see static/js/combobox.js). The hidden field (when present) carries the
   real submitted value; the visible input is display + typing only. */
.cmw-combo{ position:relative; min-width:0; }
.cmw-combo-input{ position:relative; z-index:2; background:transparent; width:100%; }
.cmw-combo-ghost{
    position:absolute; inset:0; z-index:1; pointer-events:none;
    display:flex; align-items:center; overflow:hidden; white-space:pre;
    border:1px solid transparent;
}
.cmw-combo-ghost .typed{ visibility:hidden; }
.cmw-combo-ghost .suggest{ color:var(--text-muted); opacity:.8; }
.cmw-combo-list{
    position:absolute; top:calc(100% + 3px); left:0; right:0; z-index:40;
    background:var(--surface-2); border:1px solid var(--border); border-radius:var(--cmw-radius-sm);
    box-shadow:var(--cmw-shadow); max-height:220px; overflow-y:auto; display:none;
}
.cmw-combo-list.open{ display:block; }
.cmw-combo-group{
    font-size:10px; text-transform:uppercase; letter-spacing:.04em; color:var(--text-muted);
    padding:6px 10px 2px; user-select:none;
}
.cmw-combo-item{ padding:6px 10px; font-size:12.5px; color:var(--text-primary); cursor:pointer; }
.cmw-combo-item.sub{ padding-left:20px; }
.cmw-combo-item.active, .cmw-combo-item:hover{ background:var(--surface-1); }
.cmw-txn-cell .cmw-combo, .cmw-review-row-actions .cmw-combo{ min-width:200px; }

/* Paired Category/Transfer + Subcategory comboboxes (catsub_pair macro) —
   two side-by-side fields instead of one merged "Category > Sub" field, so
   ghost-text on each doesn't require knowing the other first. */
.cmw-catsub-group{ display:flex; gap:6px; align-items:stretch; }
.cmw-catsub-group .cmw-combo{ min-width:0; }
.cmw-catsub-group .cmw-catsub-cat{ flex:1.3; }
.cmw-catsub-group .cmw-catsub-sub{ flex:1; }
/* 2026-07-11: the native table's Category/Subcategory cells put
   .cmw-catsub-cat/.cmw-catsub-sub directly in their own <td>s (no wrapping
   .cmw-catsub-group div — a table row's children must be actual <td>/<th>
   cells, so there's no display:contents-style escape hatch here the way
   the old CSS Grid version had). Category/Subcategory cross-wiring
   (static/js/combobox.js's initCatSubGroup, which needs one shared
   ancestor carrying data-subcats to find both fields) instead uses the
   <tr> itself as that ancestor — see partials/_txn_row.html. querySelector
   searches all descendants regardless of how deep, so this works exactly
   the same as before, just scoped to the whole row instead of a group div.

   FOUND 2026-07-11 (this is the "banking table still broken, investment
   fine" bug): the rule above — display:flex — applies to ANY element
   carrying .cmw-catsub-group, including that <tr>. Putting display:flex on
   a table row overrides its default display:table-row entirely, which
   turns every <td> child into a flex item sized by flexbox rules (shrink
   to content) instead of a table cell sized by the column's width. The
   <thead> row doesn't carry this class, so it rendered fine; every <tbody>
   row on Transactions/Account Detail's cash-flow table does, so every one
   of them collapsed. Investment-account rows have no category/subcategory
   fields at all, so they never got this class and never showed the bug —
   that's the tell that pointed here. Restoring table-row display only for
   the <tr> case leaves the actual div-wrapper usage (Review Queue, Split
   modal, Paycheck lines) completely untouched. */
tr.cmw-catsub-group{ display:table-row; }
.cmw-review-row-actions .cmw-catsub-group{ min-width:280px; }

/* Review queue — one card per transaction needing attention, newest first */
.cmw-review-row{
    background:var(--surface-2); border:1px solid var(--border); border-left:3px solid var(--cmw-accent);
    border-radius:var(--cmw-radius-sm); padding:12px 14px; margin-bottom:8px;
}
.cmw-review-row-main{ display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:10px; }
.cmw-review-row-info{ min-width:0; }
.cmw-review-row-actions{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; }
.cmw-review-row-actions select{ min-width:200px; }
.cmw-review-suggest{ border-color:var(--cmw-accent); color:var(--cmw-accent-txt); }

/* 2026-07-05 reconciliation redesign — four review-queue sections sharing
   the .cmw-card look, each with its own left accent stripe so they're
   visually distinct from one another and from the uncategorized rows above. */
.cmw-review-section-title{
    font-family:var(--cmw-serif); font-size:15px; font-weight:700;
    color:var(--cmw-accent-txt); margin:22px 0 2px;
}
.cmw-review-section-title:first-of-type{ margin-top:4px; }
.cmw-overlap-row{ border-left:3px solid #c9962a; }
.cmw-gap-row{ border-left:3px solid var(--text-muted); }
.cmw-unexplained-row{ border-left:3px solid var(--cmw-accent); }

/* Kanban board (2026-07-06) — replaces the four stacked full-width
   .cmw-review-section blocks above with side-by-side columns. Falls back
   to horizontal scroll between ~960-1500px (same idea as .cmw-main-tabs'
   overflow-x:auto) and stacks to one column under 960px, matching the
   account panel's existing mobile breakpoint. Column accent colors mirror
   each card type's own border-left color (see .cmw-overlap-row etc. above)
   so the column header and its cards read as the same group at a glance. */
.cmw-review-board{
    display:grid; grid-template-columns:repeat(4, minmax(310px, 1fr));
    gap:14px; align-items:start; padding-bottom:6px;
    /* No overflow-x:auto here (deliberately) — this is the exact combination
       (overflow-x:auto + position:sticky descendants) that broke the
       transaction table's sticky header earlier this session: any non-
       visible overflow on an ancestor makes IT the sticky containing block
       instead of the page, so the column heads below would stick to (and
       scroll away with) this board instead of the viewport. The sticky
       column headers the user asked for take priority over a horizontal-
       scroll fallback; below 960px the board stacks to one column instead
       (see the media query below), so this only affects the 960-~1300px
       range, where columns just shrink toward their 310px floor. */
}
/* 2026-07-16 "receipts with matching bank transaction need to be in its own
   column" — templates/partials/_review_receipts_wrap.html bundles TWO
   .cmw-review-column divs ("Receipts with no match" and "Receipts with
   matching bank transaction") inside one #review-receipts-wrap swap
   target, included as a single node between the Uncategorized and Bank
   Transaction columns in review.html's 4-column .cmw-review-board grid.
   Without this, #review-receipts-wrap is itself one grid item, so both
   its columns were stacking vertically inside a single ~310px cell instead
   of landing in their own separate grid columns — same display:contents
   escape hatch #acct-results-wrap above uses for the exact same reason
   (one HTMX swap unit whose children need to land in separate grid areas). */
#review-receipts-wrap{ display:contents; }
.cmw-review-column{ min-width:0; }
.cmw-review-col-head{
    border-left:3px solid var(--cmw-accent); padding:8px 0 8px 9px; margin-bottom:10px;
    /* Sticky (2026-07-06 feedback) — reuses the same --cmw-stack-top the
       transaction table's .cmw-col-header sticks to (topbar + main tabs +
       this page's own .cmw-page-header + .cmw-tabs), so all four column
       heads stay pinned in a row as their cards scroll underneath, instead
       of scrolling away and losing which column is which. */
    position:sticky; top:var(--cmw-stack-top, 140px); z-index:60;
    background:var(--bg);
}
.cmw-review-col-head.overlaps{ border-left-color:#c9962a; }
.cmw-review-col-head.gaps{ border-left-color:var(--text-muted); }
.cmw-review-col-head.unexplained{ border-left-color:var(--cmw-accent); }
.cmw-review-col-title{
    font-family:var(--cmw-serif); font-size:14px; font-weight:700;
    color:var(--cmw-accent-txt); display:flex; align-items:center; gap:7px;
}
.cmw-review-col-count{
    font-size:10.5px; font-weight:700; color:var(--text-muted);
    background:var(--surface-1); border:1px solid var(--border);
    border-radius:999px; padding:1px 7px;
}
.cmw-review-col-sub{ font-size:11px; color:var(--text-muted); margin-top:3px; line-height:1.45; }
/* 2026-07-17 ("remove the subtitles, put them in a tooltip on the column
 * title instead") — the four column heads' old always-visible explainer
 * lines (.cmw-review-col-sub above, now unused by review.html/
 * _review_receipts_wrap.html but left defined in case anything else ever
 * wants it) took a full row of vertical space per column, permanently,
 * whether or not anyone needed the explanation. This is that same text,
 * available on demand instead: a small "i" glyph next to the column title,
 * shown on hover OR keyboard focus (:focus-within, so it's reachable
 * without a mouse) rather than a click, since there's nothing to toggle —
 * just a fact to glance at. Positioned absolutely against .cmw-info-tip so
 * it doesn't affect the title row's own height/wrapping. */
.cmw-info-tip{ position:relative; display:inline-flex; }
.cmw-info-icon{
    width:14px; height:14px; border-radius:50%; flex-shrink:0;
    background:var(--surface-1); border:1px solid var(--border);
    color:var(--text-muted); font-size:10px; font-weight:700;
    line-height:12px; text-align:center; cursor:help;
}
.cmw-info-tip-text{
    /* 2026-07-17 fix ("tooltip shows under the column to the right or off
       the screen"): was left:0/width:220px, i.e. anchored to the icon's
       LEFT edge and growing rightward a fixed 220px regardless of where in
       the row the icon sat — in the Review Queue's 4-narrow-column layout
       that routinely pushed the box into the next column, or past the
       browser's right edge entirely for the last column, since nothing
       clamped it back onscreen. Centered on the icon instead (worst-case
       overflow is now half the width in either direction, not the full
       width in one direction), and narrowed so that worst case fits inside
       one column's width in practice. */
    display:none; position:absolute; left:50%; transform:translateX(-50%); top:20px; z-index:80;
    width:190px; box-sizing:border-box; background:var(--surface-2); border:1px solid var(--border);
    border-radius:var(--cmw-radius-sm); box-shadow:var(--cmw-shadow);
    padding:8px 10px; font-size:11px; font-weight:400; line-height:1.45;
    color:var(--text-secondary); white-space:normal;
}
.cmw-info-tip:hover .cmw-info-tip-text,
.cmw-info-tip:focus-within .cmw-info-tip-text{ display:block; }
/* 2026-07-16 "the review columns should scroll independantly" — each
   column now caps its own height and scrolls its own contents, same idea
   as .cmw-bill-column-body on the Bills Kanban board, instead of every
   column growing to the height of the longest one and the whole page
   scrolling together (so working through a long Uncategorized list no
   longer drags the other four columns' headers out of view with it).
   Safe to add overflow-y here (unlike overflow-x on .cmw-review-board
   itself, see that rule's comment) because col-head is a SIBLING of
   col-body, not its ancestor, so col-head's own position:sticky against
   the viewport is unaffected. */
.cmw-review-col-body{
    display:flex; flex-direction:column; gap:8px;
    max-height:calc(100vh - var(--cmw-stack-top, 140px) - 130px);
    overflow-y:auto; padding-right:4px;
}
@media (max-width: 960px){
    .cmw-review-board{ grid-template-columns:1fr; }
    .cmw-review-col-body{ max-height:none; overflow-y:visible; }
}

/* Sync modal progress bar (2026-07-05, deferred item from the earlier
   feedback pass) — one institution at a time via web_sync_one, so the modal
   shows real incremental progress instead of freezing until every
   institution finishes. See partials/_sync_modal.html. */
.cmw-sync-item-status{ width:18px; text-align:center; font-size:13px; }
.cmw-sync-progress-wrap{ margin-top:6px; }
.cmw-sync-progress-label{ font-size:11.5px; color:var(--text-muted); margin-bottom:6px; }
.cmw-sync-progress-bar{
    height:6px; border-radius:3px; background:var(--surface-1); border:1px solid var(--border);
    overflow:hidden; margin-bottom:8px;
}
.cmw-sync-progress-fill{
    height:100%; background:var(--cmw-accent); border-radius:3px;
    transition:width .25s ease; width:0%;
}

/* Split / transfer picker (Transactions row + Add modal + split modal) */
.cmw-split-btn{ flex-shrink:0; padding:3px 6px; font-size:11px; line-height:1; }
/* 2026-07-06: Transactions table's split button, passed into catsub_pair's
   new extra_sub_html slot — sits inside the Subcategory cell (which is
   already position:relative via .cmw-combo) rather than taking its own grid
   column. Small + corner-anchored so it doesn't cover typed text/ghost-text,
   which stays left-aligned in the same box. */
.cmw-catsub-sub .cmw-split-btn{
    position:absolute; right:1px; top:1px; z-index:3;
    padding:1px 4px; font-size:10px; line-height:1.4;
}
.cmw-split-badge{
    background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); border:1px solid var(--cmw-accent);
    border-radius:999px; padding:2px 10px; font-size:10.5px; font-weight:600; cursor:pointer;
}
.cmw-split-line{ display:grid; grid-template-columns:2fr 90px 1fr 26px; gap:6px; align-items:center; margin-bottom:6px; }
.cmw-split-line select, .cmw-split-line input{ font-size:12px; padding:6px 8px; }
.cmw-split-line .cmw-catsub-group{ min-width:0; }
.cmw-split-remove{ padding:4px 6px; font-size:11px; }

/* Enter Paycheck form (2026-07-06, Quicken paycheck-wizard reference) —
   same "line grid + remove button" shape as .cmw-split-line above: Name /
   Category-or-Transfer+Subcategory (a catsub_pair, same as the split
   modal's own 2nd column) / Amount / Remove. Widened the 2nd column vs. the
   split modal's 2fr since it's now genuinely two comboboxes side by side
   (2026-07-06 feedback: "subcategory should be an option too" — was a bare
   combo_encoded with nowhere to pick one). */
.cmw-paycheck-line{ display:grid; grid-template-columns:1fr 2.1fr 90px 26px; gap:6px; align-items:center; margin-bottom:6px; }
.cmw-paycheck-line input{ font-size:12px; padding:6px 8px; }
.cmw-paycheck-line .cmw-catsub-group{ min-width:0; }
.cmw-paycheck-section{ margin-top:16px; padding-top:12px; border-top:1px solid var(--border); }
.cmw-paycheck-section-title{ font-family:var(--cmw-serif); font-weight:700; font-size:13px; color:var(--cmw-accent-txt); margin-bottom:8px; }
.cmw-paycheck-section-total{ display:flex; justify-content:space-between; font-size:12px; color:var(--text-muted); margin-top:6px; }
.cmw-paycheck-summary{ margin-top:16px; padding-top:12px; border-top:2px solid var(--border); display:flex; flex-direction:column; gap:4px; }
.cmw-paycheck-summary div{ display:flex; justify-content:space-between; font-size:13.5px; }
.cmw-paycheck-summary strong{ font-size:15px; }
.cmw-paycheck-netpay-val{ color:var(--cmw-pos); }

/* Nested category manager rows */
.cmw-cat-row{ display:flex; gap:6px; align-items:center; padding:4px 0; }
.cmw-cat-row.sub{ padding-left:20px; }
.cmw-cat-add-sub{ font-size:11px; padding:3px 7px; flex-shrink:0; }

/* Tags */
.tag{ display:inline-block; padding:1px 6px; border-radius:20px; font-size:10px; font-weight:500; }
.tag-cleared{ background:rgba(46,158,107,.14); color:var(--cmw-pos); }
.tag-pending{ background:var(--cmw-warn-bg); color:var(--cmw-warn); }
.tag-receipt{ background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); }
.tag-none{ background:var(--surface-1); color:var(--text-muted); border:1px solid var(--border); }

/* 2026-07-13 Review Queue status badges (macros.txn_status_badges, see
   _macros.html) — "bank-sync status (cleared, pending, etc), Receipt Badge
   (Gmail, Quicken Backfill, Manual, etc)... color coded". tag-source-bank-sync
   (renamed 2026-08-07 from tag-source-plaid as part of the transactions.
   source='plaid'->'bank_sync' rename — see utils/txn_matching.py's module
   docstring) reuses the accent blue (same family as tag-receipt, since both
   describe "where this row came from") but is visually distinct from a
   receipt so the two never get confused. tag-source-quicken gets its own
   purple — deliberately not reusing any existing var() so a big one-time
   historical backfill batch reads as clearly separate from anything
   live/ongoing. tag-paper-receipt (a physical receipt on file, not
   digitized — see migrations/add_paper_receipt_flag.py) is intentionally
   outline-only (no fill) to distinguish "documented but not scanned in"
   from a real attached receipt (solid tag-receipt fill). */
.tag-source-bank-sync{ background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); }
.tag-source-quicken{ background:rgba(124,58,237,.14); color:#8b5cf6; }
.tag-paper-receipt{ background:transparent; border:1px dashed var(--cmw-accent); color:var(--cmw-accent-txt); }
/* 2026-07-22 — same solid accent look partials/_reconcile_section.html's own
   🔒 Reconciled badge already uses for a locked row, now shown in the main
   register too (see _macros.html's txn_status_badges). */
.tag-reconciled{ background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); }
.cmw-txn-badges{ display:flex; flex-wrap:wrap; gap:4px; margin-top:4px; }

/* Filter bar — a transparent flex row (Transactions, Account Detail, and
   anywhere else that needs search/date/category filters above a table).
   2026-07-11: dropped the old .cmw-sticky-band variant that gave Account
   Detail's filter bar its own opaque background/seam-matching with a
   header card above it — that whole two-piece sticky-card arrangement
   went away with the .cmw-acct-top-grid rebuild (see the Account Detail
   comment above); every page's filter bar is just this one plain style now. */
.cmw-filter-bar{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:12px; }
.cmw-filter-bar input, .cmw-filter-bar select{ font-size:12px; padding:6px 9px; }
.cmw-filter-bar .grow{ flex:1; min-width:160px; }
/* 2026-07-20 ("the page header (yellow box) should be fixed") — Transactions'
   and Account Detail's filter bars opt into the app-wide sticky-band stack
   (base.html's updateStickyStack(), same mechanism Review Queue/Vault's
   section headers already use) so the whole header region — page title,
   KPI strip, filters — stays visible together while the rows below it
   scroll, instead of only the page title/KPI strip sticking and the filter
   controls disappearing. Sticky-band elements supply their own opaque
   background (see that JS's own comment: it only auto-applies var(--bg) to
   .cmw-page-header/.cmw-tabs, not .cmw-sticky-band, since some sticky-band
   elements — cards, for instance — already have their own). */
.cmw-filter-bar.cmw-sticky-band{ background:var(--bg); padding:8px 0; }

/* 2026-08-07: Investments account filter — checkbox pills, not a <select>,
   since "filter by selected accounts" means picking any subset at once
   (compare one brokerage against another) rather than one-at-a-time like
   Transactions' single account_id dropdown. */
.cmw-check-pills{ display:flex; flex-wrap:wrap; gap:6px; }
.cmw-check-pill{
    display:flex; align-items:center; gap:5px; font-size:12px; font-weight:400;
    padding:5px 10px; border:1px solid var(--border); border-radius:999px;
    background:var(--surface-2); color:var(--text-secondary); cursor:pointer;
    user-select:none; margin:0;
}
.cmw-check-pill input{ width:auto; accent-color:var(--cmw-accent); }
.cmw-check-pill:has(input:checked){
    border-color:var(--cmw-accent); color:var(--cmw-accent-txt); background:var(--surface-1);
}
/* 2026-08-07: Investments Sankey level pills — same pill as above, plus
   draggable (reorder Total>Institution>Owner>Account Type>Account>Holding)
   using the same handle/dragging look as the table Customize Columns list
   (.cmw-colcustom-handle/.cmw-colcustom-row.dragging above). */
.cmw-check-pill[draggable]{ cursor:grab; }
.cmw-check-pill[draggable].dragging{ opacity:.4; }
.cmw-drag-handle{ color:var(--text-muted); font-size:11px; user-select:none; line-height:1; letter-spacing:-2px; }
.cmw-kpi-strip.cmw-sticky-band{ background:var(--bg); }

/* Account Detail — history: 2026-07-11 first tried a narrow header+filter
   column running parallel to a sticky KPI/table column, sticky against a
   JS-computed offset that only ever measured the left column's height.
   Every layout bug this page had (rows rendering outside the table frame,
   misaligned sticky header, KPI cards the wrong size) traced back to that
   one structural choice: a parallel sticky column base.html's
   updateStickyStack() has no way to account for (it only sums bands that
   stack vertically, one after another). Replaced same day with a narrower
   grid — just the page header paired with the KPI strip on one row, filter
   bar and table full-width below — believed safe at the time because
   nothing on this page was sticky yet, so there was no offset for the
   grid's uneven row heights to throw off.

   2026-07-20 ("the page header (yellow box) should be fixed" + tracing why
   the table's own sticky header, now page-level per var(--cmw-stack-top),
   rendered in the wrong place here specifically): that belief didn't
   survive making header/KPI/filter/table sticky, which is exactly what
   this page needed once .cmw-table-scroll stopped managing its own
   independent scroll (see that rule's history). base.html's
   updateStickyStack() sums each sticky band's height SEQUENTIALLY,
   assuming plain vertical stacking — it has no concept of "these two
   elements share a grid row," so pairing .cmw-page-header beside
   .cmw-kpi-strip in one row (while only the header was ever a recognized
   band) made the JS undercount that row's true height by however much
   taller the KPI strip was, throwing off every sticky offset below it —
   compounding, not just coincidental with, the .cmw-table-scroll bug this
   same investigation found. Simplest correct fix: stop pairing them.
   Plain vertical stack — header, KPI strip (full width, its own default
   .cmw-kpi-strip layout, same as every other page), filter bar, table —
   so every piece is a normal sequential band the existing generic
   mechanism already handles correctly, no special-casing needed. #acct-
   results-wrap (the filter's hx-swap target, bundling the KPI strip +
   table so both refresh together on a filter change) keeps display:contents
   so swapping it doesn't introduce an extra wrapper level into the stack.

   2026-07-20 ("I can see ledger rows behind the fixed header cards") — this
   used to space the four bands (header/filter/KPI-strip/table — #acct-
   results-wrap's display:contents means its KPI-strip+table children count
   as direct flex items here too) with `gap:12px`. Looks identical at rest,
   but base.html's updateStickyStack()/bandHeight() only ever measured
   getBoundingClientRect().height + marginBottom for each band — flex `gap`
   isn't on either of those, so it's invisible to that math. Every band's
   computed `top` came out 12px short per gap already passed (short by 12px
   for the filter bar, 24px for the KPI strip), leaving an uncovered sliver
   the width of each missed gap where the scrolled table rows underneath
   showed through — worse the further down the stack. Switched the spacing
   to margin-bottom on each band instead (same convention the rest of the
   app's sticky bands already use — see .cmw-filter-bar's own
   margin-bottom:12px below, previously zeroed out here in favor of the gap)
   so bandHeight() measures the real spacing and every band's sticky
   background actually covers the seam above it. */
.cmw-acct-top{ display:flex; flex-direction:column; }
.cmw-acct-top > .cmw-page-header{ padding:6px 2px 0; margin-bottom:12px; }
.cmw-acct-top .cmw-acct-columns-row{ margin:0; }
/* 2026-08-13: .cmw-kpi-strip no longer renders on this page at all — moved
   into .cmw-page-header itself as .cmw-kpi-compact (see that class's own
   comment above and partials/_account_kpi_compact.html), specifically
   BECAUSE pairing it as a second sticky band alongside the header is what
   the whole history comment above this block is about. Rule kept (matches
   nothing here now, harmless) rather than deleted, in case a plain
   .cmw-kpi-strip ever gets reintroduced on this page for something else. */
.cmw-acct-top .cmw-kpi-strip{ margin-bottom:12px; }
#acct-results-wrap{ display:contents; }

/* Dashboard (2026-07-12 rebuild) — two summary cards side by side, each a
   short read-only preview (top 5) of a list that has its own full page
   elsewhere (Bills, Budgets); no action buttons here on purpose, since
   _bill_card.html's Mark Paid button hx-targets #bills-list-wrap, which
   doesn't exist on this page — "View all" links to the real page instead
   of trying to make every action work from a preview card. */
.cmw-dash-grid{
    display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); gap:16px;
}
.cmw-dash-card-head{
    display:flex; justify-content:space-between; align-items:baseline; gap:10px; margin-bottom:10px;
}
.cmw-dash-card-head h3{ margin:0; }
.cmw-dash-card-link{ font-size:11.5px; font-weight:600; color:var(--cmw-accent); text-decoration:none; white-space:nowrap; }
.cmw-dash-card-link:hover{ text-decoration:underline; }
.cmw-dash-mini-row{
    display:flex; justify-content:space-between; align-items:center; gap:10px;
    padding:8px 0; border-bottom:1px solid var(--border);
}
.cmw-dash-mini-row:last-child{ border-bottom:none; padding-bottom:0; }

/* Load more */
.cmw-load-more{ text-align:center; padding:14px; }

/* Entity badge */
.cmw-entity-badge{
    display:inline-flex; align-items:center; gap:4px;
    padding:3px 10px; border-radius:20px; font-size:11px; font-weight:500;
    background:var(--cmw-accent-bg); color:var(--cmw-accent-txt);
    cursor:pointer; border:1px solid var(--border);
}

/* Receipt-attached indicator (2026-07-07) — small icon shown in the
   Transactions/Account Detail row next to the payee name when a receipt
   has been matched to this transaction (receipts.transaction_id), so the
   receipt/matching relationship is visible from both directions instead
   of only from the Receipts flyout's Matched tab. */
.cmw-receipt-badge{
    display:inline-flex; align-items:center; justify-content:center;
    font-size:11px; margin-left:5px; opacity:0.85;
}

/* Bills */
.cmw-bill-section{ margin-bottom:18px; }
.cmw-bill-section-title{ font-size:13px; font-weight:650; color:var(--text-primary); margin:0 0 8px; }
.cmw-bill-list{ display:flex; flex-direction:column; gap:6px; }
.cmw-bill-card{
    display:flex; justify-content:space-between; align-items:center; gap:10px;
    background:var(--surface-2); border:1px solid var(--border); border-left:3px solid var(--text-muted);
    border-radius:var(--cmw-radius-sm); padding:10px 13px;
}
.cmw-bill-card.overdue{ border-left-color:var(--cmw-neg); }
.cmw-bill-card.due_soon{ border-left-color:var(--cmw-warn); }
.cmw-bill-card.paid{ border-left-color:var(--cmw-pos); }
.cmw-bill-card.upcoming{ border-left-color:var(--cmw-accent); }
.cmw-bill-main{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.cmw-bill-name{ font-size:13px; font-weight:600; color:var(--text-primary); display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
.cmw-bill-date{ font-size:11px; color:var(--text-muted); }
.cmw-bill-right{ display:flex; align-items:center; gap:10px; flex-shrink:0; position:relative; }
.cmw-bill-amt{ font-size:13px; font-weight:650; color:var(--text-primary); white-space:nowrap; }
.cmw-bill-flag{ color:var(--cmw-neg); font-size:11px; font-weight:600; }
/* 2026-07-19: the plain "SimpleFIN" source label on institution cards used
   to reuse .cmw-bill-flag (var(--cmw-neg), the same red as overdue-bill and
   sync-error flags), which read as an error/warning even though it's just
   naming the sync source -- same neutral accent pair already used for
   .tag-source-bank-sync below, so the two aggregator labels look consistent
   with each other instead of one looking alarmed. Actual "Sync error" /
   "Disconnected" badges on the same card still use .cmw-bill-flag on
   purpose -- those really are errors. */
.cmw-simplefin-badge{ background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); font-size:11px; font-weight:600; padding:1px 7px; border-radius:999px; }
.cmw-bill-empty{ padding:30px; text-align:center; color:var(--text-muted); font-size:13px; }
/* details.cmw-bill-paid (Recently Paid collapse) removed 2026-07-14 — the
   status view is a Kanban board now (see .cmw-bill-board below), each
   bucket including Paid is its own always-visible column, so there's
   nothing left to collapse. .cmw-bill-section/-list/-card/-main/-name/
   -right above stay — still used by partials/_paycheck_list.html and, for
   -card/-main/-name/-right, by the Payee/Date/Tag grouped Bills view
   (partials/_bill_group_card.html), which keeps the wider row layout on
   purpose (a Kanban lane doesn't make sense for those groupings). */

/* Bills & Income Kanban boards (2026-07-14, status view only — see
   partials/_bills_list.html/_paycheck_list.html +
   partials/_bill_card_kanban.html/_paycheck_card_kanban.html).

   2026-07-18 ("the column widths of the income and bills tabs should be 4
   equal column widths that fit"): was a flex row of fixed 250px columns
   that overflowed into horizontal scroll any time the 4 columns' combined
   width exceeded the container — never actually "4 equal columns that
   fit", just 4 fixed-width ones that may or may not add up to the
   available space. Switched to a 4-track grid so all four always divide
   the container width evenly with no scrollbar, down to tablet width;
   below that (four columns really don't fit no matter how they're sized on
   a phone) it falls back to the original horizontal-scroll-per-column
   behavior. min-width:0 on the column is required with grid — without it,
   a column containing something wide/unbreakable (a long merchant name, a
   row of tag badges) would force its grid track wider than its equal
   share, breaking the "equal width" part again. */
/* auto-fit + minmax(0, 1fr), not a hardcoded repeat(4, ...) — this same
   board scaffolding is also reused verbatim by the Receipts board
   (partials/_rx_board.html), which only has 3 columns, not 4; auto-fit
   sizes however many .cmw-bill-column children actually exist into that
   many equal tracks (Bills/Income: 4, Receipts: 3) rather than hardcoding
   one board's column count onto all of them. */
.cmw-bill-board{ display:grid; grid-template-columns:repeat(auto-fit, minmax(0, 1fr)); gap:14px; padding:2px 2px 10px; }
.cmw-bill-column{
    min-width:0; background:var(--surface-1); border-radius:var(--cmw-radius);
    padding:10px; display:flex; flex-direction:column;
}
.cmw-bill-column-head{ display:flex; align-items:center; justify-content:space-between; padding:2px 4px 10px; flex-shrink:0; }
.cmw-bill-column-title{ font-family:var(--cmw-serif); font-weight:700; font-size:13px; color:var(--cmw-accent-txt); }
.cmw-bill-column-badge{ font-size:11px; font-weight:700; padding:1px 7px; border-radius:999px; }
.cmw-bill-column-badge.neg{ background:rgba(210,73,62,.14); color:var(--cmw-neg); }
.cmw-bill-column-badge.warn{ background:var(--cmw-warn-bg); color:var(--cmw-warn); }
.cmw-bill-column-badge.info{ background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); }
.cmw-bill-column-badge.pos{ background:rgba(46,158,107,.14); color:var(--cmw-pos); }
/* 2026-07-18 ("a filter to the right of 'Upcoming' like 'Next # months'") —
   sits between the column title and its count badge (see
   _bills_list.html/_paycheck_list.html); kept small/borderless so it reads
   like a label, not a full form control, in the tight column head. */
.cmw-bill-upcoming-select{
    width:auto; min-width:0; font-size:10px; font-weight:600; padding:1px 4px;
    color:var(--text-muted); border:1px solid var(--border); border-radius:999px;
    background:var(--surface-2); flex-shrink:0;
}
/* Each column scrolls its own contents independently instead of the whole
   page growing — same "cap it, don't let one bucket dominate" idea the old
   Recently Paid <details> collapse used to handle, just via scroll instead
   of collapse now that every bucket is always visible as its own column. */
.cmw-bill-column-body{ display:flex; flex-direction:column; gap:8px; max-height:560px; overflow-y:auto; padding-right:2px; }
.cmw-bill-column-empty{ color:var(--text-muted); font-size:12px; padding:8px 2px; }
@media (max-width: 900px){
    .cmw-bill-board{ display:flex; overflow-x:auto; }
    .cmw-bill-column{ flex:0 0 82vw; }
}

.cmw-bill-kcard{
    position:relative; background:var(--surface-2); border:1px solid var(--border);
    border-left:3px solid var(--text-muted); border-radius:var(--cmw-radius-sm); padding:10px 11px;
}
.cmw-bill-kcard.overdue{ border-left-color:var(--cmw-neg); }
.cmw-bill-kcard.due_soon{ border-left-color:var(--cmw-warn); }
.cmw-bill-kcard.upcoming{ border-left-color:var(--cmw-accent); }
.cmw-bill-kcard.paid{ border-left-color:var(--cmw-pos); opacity:.8; }
.cmw-bill-kcard-name{ font-size:12.5px; font-weight:600; color:var(--text-primary); display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
.cmw-bill-kcard-bottom{ display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:8px; }
.cmw-bill-kcard-actions{ display:flex; align-items:center; gap:4px; position:relative; }

/* "..." row-action menu (2026-07-14) — first use of this pattern in the
   app; kept generic (cmw-bill- prefixed for now since Bills is the only
   caller) but styled plainly enough to lift into a shared component later
   if another dense list (Transactions, Vault) wants the same treatment.
   Toggled open/closed via event delegation in bills.html's extra_scripts
   block, NOT a direct listener — this markup lives inside #bills-list-wrap,
   which every bill action replaces wholesale (hx-swap="outerHTML"), so any
   listener bound directly to a menu button would be destroyed on the very
   next Enter/Mark Paid/Edit/Delete click. */
.cmw-bill-menu-btn{
    background:none; border:1px solid var(--border); border-radius:6px;
    width:26px; height:26px; padding:0; color:var(--text-secondary);
    display:flex; align-items:center; justify-content:center; flex-shrink:0;
}
.cmw-bill-menu-btn:hover{ color:var(--cmw-accent); border-color:var(--cmw-accent); }
.cmw-bill-menu{
    /* 2026-08-03 ("I don't even see the edit/delete window now"): was
       position:absolute (against .cmw-bill-kcard-actions, position:relative)
       with a static right:0/top:32px. base.html's positionBillMenu() now
       computes viewport-relative coordinates (getBoundingClientRect) and
       writes them to inline top/left specifically so the menu can escape
       .cmw-bill-column-body's overflow-y:auto clipping — but with the menu
       still position:absolute, those same px values were being placed
       relative to its offset parent instead of the viewport, landing the
       menu off-screen or in the wrong spot every time. position:fixed is
       what actually makes viewport-relative coordinates correct; z-index
       bumped above the sticky header (100) since a fixed element now
       renders in a different stacking context than before. */
    display:none; position:fixed; z-index:150;
    background:var(--surface-2); border:1px solid var(--border); border-radius:var(--cmw-radius-sm);
    box-shadow:var(--cmw-shadow); min-width:130px; overflow:hidden;
}
.cmw-bill-menu.open{ display:block; }
.cmw-bill-menu-item{
    display:block; width:100%; text-align:left; background:none; border:none;
    border-top:1px solid var(--border); border-radius:0;
    padding:8px 11px; font-size:12px; color:var(--text-primary); cursor:pointer;
}
.cmw-bill-menu-item:first-child{ border-top:none; }
.cmw-bill-menu-item:hover{ background:var(--surface-1); color:var(--cmw-accent); }
.cmw-bill-menu-item.danger{ color:var(--cmw-neg); }
.cmw-bill-menu-item.danger:hover{ background:var(--surface-1); color:var(--cmw-neg); }

/* Receipts Kanban board (2026-07-15, replaces the old always-editable
   Pending Receipts table + the separate Matched flyout tab — see
   templates/receipts_pending.html, partials/_rx_board.html). The board
   scaffolding itself (.cmw-bill-board/-column/-column-head/-title/-badge/
   -body/-empty, above) is reused verbatim rather than duplicated — that
   CSS was already written generic on purpose ("kept cmw-bill- prefixed for
   now... styled plainly enough to lift into a shared component later if
   another dense list wants the same treatment", 2026-07-14) and there's
   nothing bill-specific about column layout/spacing. Only the card needs
   new rules — a receipt card's shape (source badge, match badge, a
   candidate/bank-transaction mini preview) doesn't map onto
   .cmw-bill-kcard's bucket-color-left-border idea, so no menu component
   either: these cards only ever have 1-2 actions, no need for Bills'
   "..." overflow menu. */
.cmw-rx-kcard{
    background:var(--surface-2); border:1px solid var(--border);
    border-radius:var(--cmw-radius-sm); padding:10px 11px;
}
.cmw-rx-kcard-top{ display:flex; gap:5px; flex-wrap:wrap; margin-bottom:5px; }
.cmw-rx-kcard-name{ font-size:12.5px; font-weight:600; color:var(--text-primary); }
.cmw-rx-kcard-sub{ font-size:11px; color:var(--text-muted); margin-top:2px; }
.cmw-rx-kcard-candidate{
    margin-top:8px; padding:7px 8px; background:var(--surface-1);
    border-radius:var(--cmw-radius-sm); border:1px dashed var(--border);
}
.cmw-rx-kcard-candidate-name{ font-size:12px; font-weight:600; color:var(--text-primary); }
.cmw-rx-kcard-bottom{ display:flex; align-items:center; gap:6px; margin-top:8px; flex-wrap:wrap; }
.tag-rx-match{ background:rgba(46,158,107,.14); color:var(--cmw-pos); }

/* Institution-first account cards (2026-07-06 feedback) — replaces the old
   one-card-per-account vertical stack on the Linked Financial Institutions
   tab. Grid defaults to 4 wide (user asked for "3 or 4"), steps down to 3
   then 1, matching the review board's breakpoint convention. */
.cmw-institution-grid{
    display:grid; grid-template-columns:repeat(4, minmax(260px, 1fr));
    gap:14px; align-items:start;
}
@media (max-width: 1400px){
    .cmw-institution-grid{ grid-template-columns:repeat(3, minmax(240px, 1fr)); }
}
@media (max-width: 960px){
    .cmw-institution-grid{ grid-template-columns:1fr; }
}
/* 2026-07-19 ("I want all the review queue cards to be minimized but
   expandable, similar to the uncategorized") — generic collapse chrome for
   the per-card <details> now wrapping every Review Queue card (receipts
   no-match/with-match, manual entries no-match/with-match overlaps,
   bank-no-receipt), same "collapsed by default, click to expand the full
   edit form" shape _review_group_row.html's own "Handle individually"
   disclosure already established for Uncategorized's grouped cards. One
   shared class instead of repeating this per card template. */
.cmw-review-card-details{ margin-top:2px; }
.cmw-review-card-details > summary{ cursor:pointer; list-style:none; }
.cmw-review-card-details > summary::-webkit-details-marker{ display:none; }
.cmw-review-card-details > summary .cmw-review-card-chevron{
    color:var(--text-muted); font-size:10px; display:inline-block; transition:transform .15s ease;
}
.cmw-review-card-details[open] > summary .cmw-review-card-chevron{ transform:rotate(180deg); }
.cmw-review-card-details > summary .cmw-review-card-summary-sub{ margin-top:2px; }
/* 2026-07-19 ("I need action button visible without expanding... smaller
   with an icon when not expanded is fine") — compact one-click action
   buttons living inside a card's <summary> (or, for cards whose header
   already sits outside <details> entirely, right next to it) so the
   primary action (Confirm match, Same purchase, Save & Accept, etc.) never
   requires opening the card first.

   2026-07-19 follow-up #1 ("the icons need to be the same size as the
   other buttons ('X', check, etc)") — first pass gave these their own
   fixed width:24px/height:24px box, visibly smaller than the plain "✕"
   delete buttons next to them. Tried matching manual_nomatch_row's compact
   padding:2px 7px convention instead — turned out that read as TOO small
   once every icon (not just that one delete button) used it (follow-up
   #2, "those are the ones that are too small").

   2026-07-19 follow-up #2 — simplest fix, no more guessing at a size:
   this is now just a flex-shrink guard, nothing else. Every icon button
   plain-inherits `button, .cmw-btn`'s own default padding:8px 12px /
   font-size:12.5px box — the SAME default sizing already used everywhere
   else in this app (including the receipt card's own "✕" delete button,
   which was never touched and was never flagged as wrong), so "same size
   as the other buttons" now holds by definition rather than by a hand-
   picked number. */
.cmw-review-card-icon-btn{ flex-shrink:0; }

.cmw-institution-card{ padding:12px 14px; min-width:0; }
.cmw-institution-card details summary{ cursor:pointer; list-style:none; }
.cmw-institution-card details summary::-webkit-details-marker{ display:none; }
.cmw-institution-head{ display:flex; justify-content:space-between; align-items:flex-start; gap:10px; min-width:0; }
.cmw-institution-head > div:first-child{ min-width:0; }
.cmw-institution-title{ font-family:var(--cmw-serif); font-weight:700; font-size:14px; color:var(--cmw-accent-txt); }
/* 2026-07-19: institution-header link (Chase, Strata Trust, etc.) can be a
   long unbroken URL string. Grid/flex items default to min-width:auto, so
   without the min-width:0 rules above + this wrap rule, that string forces
   the card wider than its grid track and bleeds into the neighboring card. */
.cmw-institution-head a{ overflow-wrap:anywhere; word-break:break-word; }
.cmw-institution-accounts{ margin-top:10px; padding-top:10px; border-top:1px solid var(--border); display:flex; flex-direction:column; gap:8px; }
.cmw-institution-account-row{ display:flex; justify-content:space-between; align-items:center; gap:10px; }

/* Generic data table (Cash Flow, Net Worth, Reports, ...) */
.cmw-data-table{ width:100%; border-collapse:collapse; background:var(--surface-2); border:1px solid var(--border);
    border-radius:var(--cmw-radius); overflow:hidden; box-shadow:var(--cmw-shadow); font-size:12.5px; margin-bottom:14px; }
.cmw-data-table th{ text-align:right; padding:8px 10px; font-size:10px; font-weight:600; color:var(--text-muted);
    text-transform:uppercase; letter-spacing:.04em; background:var(--surface-1); border-bottom:1px solid var(--border); }
.cmw-data-table th:first-child, .cmw-data-table td:first-child{ text-align:left; }
.cmw-data-table td{ padding:7px 10px; text-align:right; border-bottom:1px solid var(--border); color:var(--text-secondary); }
.cmw-data-table tr:last-child td{ border-bottom:none; }
.cmw-data-table td.pos{ color:var(--cmw-pos); }
.cmw-data-table td.neg{ color:var(--cmw-neg); }

/* 2026-08-12 ("the payment schedule header should be fixed") — dedicated
   modifier for the Mortgage Payment Schedule table (up to 360 rows for a
   30-year loan) rather than changing base .cmw-data-table, which several
   short, non-scrolling summary tables (Cash Flow, Net Worth, Reports) also
   use and don't want a sticky header on. Mirrors .cmw-ntable's own already-
   proven sticky approach (see that class's history comment above,
   ~line 599) instead of reinventing it: .cmw-data-table's overflow:hidden
   (there for its rounded corners) makes the <table> itself a
   clipping/scrolling ancestor, which silently breaks position:sticky on
   descendants — same root cause .cmw-table-scroll/.cmw-ntable already hit
   and fixed by dropping overflow entirely and letting the header stick to
   the PAGE (var(--cmw-stack-top)) instead of a local scroll box. */
.cmw-sched-table{ overflow:visible; }
.cmw-sched-table thead th{
    position:sticky; top:var(--cmw-stack-top, 140px); z-index:5;
    background:var(--surface-1);
}

/* Vault cards (2026-07-06 redesign, roadmap #19) — replaces the original
   table layout per feedback: "I like the idea of cards layout similar to
   the review queue." One .cmw-vault-group per category (or one flat group
   for non-category sorts), each with a sticky header, holding a responsive
   grid of .cmw-vault-card. */
.cmw-vault-group{ margin-bottom:18px; }
.cmw-vault-cat-head{
    display:flex; align-items:center; gap:8px; border-left:3px solid var(--cmw-accent);
    padding:8px 0 8px 9px; margin-bottom:10px;
    /* Sticky — same --cmw-stack-top slot the Review Queue's column heads use
       (see .cmw-review-col-head), just one at a time since these stack
       vertically instead of side-by-side: each group's header takes over
       the same sticky spot as its cards scroll past, "iOS section list"
       style. */
    position:sticky; top:var(--cmw-stack-top, 140px); z-index:55;
    background:var(--bg);
}
.cmw-vault-flat-head{ border-left-color:var(--text-muted); }
.cmw-vault-cat-title{ font-family:var(--cmw-serif); font-size:14px; font-weight:700; color:var(--cmw-accent-txt); }
.cmw-vault-cat-count{ font-size:10.5px; font-weight:700; color:var(--text-muted);
    background:var(--surface-1); border:1px solid var(--border); border-radius:999px; padding:1px 7px; }
.cmw-vault-empty{ color:var(--text-muted); font-style:italic; font-size:12.5px; padding:2px 0 2px 12px; }

.cmw-vault-card-grid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr)); gap:10px; }
.cmw-vault-card{
    background:var(--surface-2); border:1px solid var(--border); border-left:3px solid var(--cmw-accent);
    border-radius:var(--cmw-radius-sm); padding:11px 13px; box-shadow:var(--cmw-shadow);
}
.cmw-vault-card.expiring{ border-left-color:var(--cmw-neg); background:var(--cmw-warn-bg); }
.cmw-vault-card-head{ display:flex; justify-content:space-between; align-items:flex-start; gap:8px; }
.cmw-vault-card-title{ font-weight:600; font-size:13px; line-height:1.35; }
.cmw-vault-card-title a{ color:var(--text-primary); }
.cmw-vault-card-title a:hover{ color:var(--cmw-accent); }
.cmw-vault-card-actions{ display:flex; gap:4px; flex-shrink:0; }
.cmw-vault-card-btn{
    background:none; border:1px solid var(--border); border-radius:var(--cmw-radius-sm);
    color:var(--text-muted); font-size:11px; line-height:1; padding:3px 6px; cursor:pointer;
}
.cmw-vault-card-btn:hover{ color:var(--cmw-accent); border-color:var(--cmw-accent); }
.cmw-vault-card-badges{ display:flex; flex-wrap:wrap; gap:5px; margin-top:7px; }
.cmw-vault-badge{ font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.03em;
    padding:2px 7px; border-radius:999px; background:var(--surface-1); color:var(--text-muted); border:1px solid var(--border); }
.cmw-vault-badge-cat{ color:var(--cmw-accent-txt); border-color:var(--cmw-accent); }
.cmw-vault-badge-person{ background:var(--cmw-accent-bg); color:var(--cmw-accent-txt); border-color:var(--cmw-accent); }
.cmw-vault-badge-date{ }
.cmw-vault-badge-date.soon{ background:var(--cmw-neg); color:#fff; border-color:var(--cmw-neg); }
.cmw-vault-card-notes{ color:var(--text-muted); font-size:11.5px; margin-top:7px; line-height:1.4; }
.cmw-vault-card-fields{ display:flex; flex-wrap:wrap; gap:5px; margin-top:8px; padding-top:8px; border-top:1px solid var(--border); }
.cmw-vault-chip{ font-size:10.5px; color:var(--text-secondary); background:var(--surface-1);
    border:1px solid var(--border); border-radius:6px; padding:2px 7px; }
.cmw-vault-chip strong{ color:var(--text-muted); font-weight:600; }

/* Vault edit modal — repeatable custom-field Name/Value rows, same idea as
   .cmw-split-line's clone-a-row pattern in _split_modal.html. */
.cmw-vault-fields-section{ margin-top:12px; }
.cmw-vault-fields-section > label{ display:block; margin-bottom:6px; font-size:11.5px; font-weight:600; }
.cmw-vault-field-row{ display:flex; gap:6px; margin-bottom:6px; align-items:center; }
.cmw-vault-field-row input{ flex:1; min-width:0; }
.cmw-vault-field-remove{
    flex-shrink:0; background:none; border:1px solid var(--border); border-radius:var(--cmw-radius-sm);
    color:var(--text-muted); font-size:11px; line-height:1; padding:5px 8px; cursor:pointer;
}
.cmw-vault-field-remove:hover{ color:var(--cmw-neg); border-color:var(--cmw-neg); }

/* Chart container */
.cmw-chart-card{ background:var(--surface-2); border:1px solid var(--border); border-radius:var(--cmw-radius);
    box-shadow:var(--cmw-shadow); padding:14px; margin-bottom:14px; }

/* Generic form grid (Cash Flow scenarios, future CRUD forms) */
.cmw-form-grid{ display:grid; grid-template-columns:1fr 1fr; gap:10px 14px; }
.cmw-form-grid label{ display:block; font-size:11.5px; color:var(--text-secondary); margin-bottom:3px; }
.cmw-form-grid input, .cmw-form-grid select{ width:100%; }
.cmw-form-grid .full{ grid-column:1 / -1; }
.cmw-form-actions{ margin-top:10px; }

/* Auth / login screen */
.cmw-auth-wrap{ min-height:100vh; display:flex; align-items:center; justify-content:center; }
.cmw-auth-card{ width:100%; max-width:380px; padding:32px 28px; }
.cmw-auth-logo{ text-align:center; margin-bottom:28px; }
.cmw-auth-logo .glyph{ font-size:34px; margin-bottom:6px; }
.cmw-auth-logo .name{ font-size:22px; font-weight:600; font-family:var(--cmw-serif); color:var(--cmw-accent-txt); }
.cmw-auth-logo .tag{ font-size:13px; color:var(--text-muted); margin-top:2px; }
.cmw-auth-field{ margin-bottom:14px; }
.cmw-auth-field label{ display:block; font-size:12px; color:var(--text-secondary); margin-bottom:4px; }
.cmw-auth-field input{ width:100%; }
.cmw-auth-error{ background:var(--cmw-warn-bg); color:var(--cmw-warn); border-radius:var(--cmw-radius-sm);
    padding:8px 10px; font-size:12.5px; margin-bottom:14px; }
.cmw-auth-hint{ text-align:center; font-size:12.5px; color:var(--text-muted); margin-bottom:16px; }
.cmw-auth-card button.primary{ width:100%; padding:10px; font-size:13px; }
.cmw-auth-back{ display:block; text-align:center; margin-top:14px; font-size:12.5px; }
.cmw-auth-qr{ text-align:center; margin-bottom:16px; }
.cmw-auth-qr img{ border-radius:8px; border:1px solid var(--border); }
.cmw-auth-secret{ font-family:monospace; font-size:11px; background:var(--surface-1); border:1px solid var(--border);
    border-radius:6px; padding:6px 8px; text-align:center; margin-top:8px; word-break:break-all; }

/* ===========================================================================
 * Global HTMX in-flight indicator (2026-07-16, "so i know something is
 * happening") — htmx automatically adds an .htmx-request class to whatever
 * element triggered a request (the field/button itself, unless hx-indicator
 * points elsewhere) for exactly as long as that request is in flight.
 *
 * 2026-07-16 follow-up ("the dashboard and category windows is perpetually
 * dimmer") — the original rule below was a blanket `.htmx-request{opacity}`
 * that matched ANY element carrying the class, not just buttons/inputs a
 * user just clicked. The account sidebar (#cmw-account-panel, present on
 * every non-hide_sidebar page — Dashboard and Categories included) is
 * reloaded via htmx.ajax() targeting that whole container on every page
 * load AND on every `accountsChanged` body event (base.html), so it was
 * picking up the class — and the opacity dim — constantly, with no spinner
 * to explain why, reading as a permanently duller page rather than a
 * momentary loading state. Scoped down to just the interactive controls
 * (buttons/inputs/selects a person actually clicked or typed into) so
 * background/container-level refreshes no longer dim large chunks of the
 * page; those still swap their label for a small spinner, inputs/selects
 * still dim+cursor:wait, so a slow request on an actual user action still
 * doesn't silently look identical to a frozen page.
 * ======================================================================== */
button.htmx-request, .cmw-btn.htmx-request, .primary.htmx-request,
input.htmx-request, select.htmx-request, textarea.htmx-request{
    opacity:.55; cursor:wait; transition:opacity .1s ease;
}
button.htmx-request, .cmw-btn.htmx-request, .primary.htmx-request{
    position:relative; color:transparent !important; pointer-events:none;
}
button.htmx-request::after, .cmw-btn.htmx-request::after, .primary.htmx-request::after{
    content:""; position:absolute; top:50%; left:50%; width:14px; height:14px;
    margin:-7px 0 0 -7px; border-radius:50%;
    border:2px solid var(--border); border-top-color:var(--cmw-accent);
    animation:cmw-spin .6s linear infinite;
}
@keyframes cmw-spin{ to{ transform:rotate(360deg); } }
