/* ============================================================================
   Obsidian design system — core component vocabulary
   Web port of the QPainter widgets in apps/budget/views/obsidian/.
   Requires tokens.css. Flat, single-class selectors throughout (BEM-ish) so
   specificity never fights page CSS.
   ========================================================================== */

/* ── Typography roles — fonts.py _ROLE_SPECS ──────────────────────────────── */
.t-title  { font: var(--weight-semibold) var(--font-title)/1.25  var(--font-sans); color: var(--text-main); }
.t-hero   { font: var(--weight-semibold) var(--font-hero)/1.15   var(--font-sans); color: var(--text-main); }
.t-head   { font: var(--weight-semibold) var(--font-head)/1.3    var(--font-sans); color: var(--text-main); }
.t-sub    { font: var(--weight-semibold) var(--font-sub)/1.35    var(--font-sans); color: var(--text-main); }
.t-body   { font: var(--weight-regular)  var(--font-body)/1.4    var(--font-sans); color: var(--text-main); }
.t-small  { font: var(--weight-regular)  var(--font-small)/1.4   var(--font-sans); color: var(--text-main); }
.t-tiny   { font: var(--weight-regular)  var(--font-tiny)/1.4    var(--font-sans); color: var(--text-dim); }
.t-num    { font: var(--weight-semibold) var(--font-num)/1.2     var(--font-sans); font-variant-numeric: tabular-nums; }
.t-num-lg { font: var(--weight-semibold) var(--font-num-lg)/1.2  var(--font-sans); font-variant-numeric: tabular-nums; }

/* Caps label — the 8pt-semibold uppercase voice used for stat titles, table
   headers, KPI eyebrows (fonts.py "label"; cards.py 153, 184). The slight
   tracking approximates Segoe UI Semibold 8pt caps rendering on Windows. */
.caps-label {
  font: var(--weight-semibold) var(--font-label)/1.3 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.045em;
  color: var(--text-dim);
}
/* Hand-spaced caps — Classic inserts literal spaces ("P R I V A T E   B A N K I N G",
   chrome.py 73; the Dashboard hero eyebrow). Tracking stands in for the spaces. */
.caps-label--spaced { letter-spacing: 0.34em; }

/* Fluent icon glyph carrier — icons.py _ICON_GLYPHS codepoints go in the
   markup (e.g. &#xE80F;); emoji fallback per icons.py _ICON_FALLBACK.
   web-only: {{ icon(...) }} (api/templating.py) now emits an inline
   <svg class="ico"> instead of a glyph char (api/icons_svg.py) so icons
   render with no font dependency on phones/iPads/Macs, where "Segoe Fluent
   Icons" doesn't exist. font-family here is now vestigial (kept in case any
   stray literal glyph text still relies on it) — the real sizing/colour
   contract for the new SVGs is the `.ico` rule below: it needs no parent
   selector because every icon() call carries the class itself, so it picks
   up `color`/`font-size` from WHATEVER ancestor sets them (.icon, .icon-tile,
   .kpi-tile__icon, .mgmt-card__icon, .manage-row__icon-btn, .row-icon-btn,
   .nav-pill .icon, .mnav__tab .icon, …) exactly like the old glyph char did. */
.icon { font-family: var(--font-icons); font-style: normal; line-height: 1; }
.ico {
  width: 1em; height: 1em;
  display: inline-block;
  vertical-align: -0.125em;   /* baseline nudge — matches the glyph's optical center */
  flex: none;
  color: inherit;
}

/* ── Card — the 1px BORDER reveal (cards.py _BorderCard 70-97) ─────────────
   Classic: Frame(bg=BORDER) wrapping Frame(bg=BG_CARD) at 1px inset.
   Square corners — Obsidian cards are never rounded. */
.card {
  background: var(--bg-card);
  border: var(--hairline) solid var(--border);
  border-radius: 0;
}

/* ── Stat card — cards.py stat_card 128-166 ────────────────────────────────
   BORDER reveal + 3px accent strip + caps title + num_lg value + tiny sub.
   Set the accent per instance: <div class="card stat-card" style="--stat-accent: var(--mint)"> */
.stat-card { --stat-accent: var(--accent); display: flex; flex-direction: column; }
.stat-card::before {
  content: "";
  display: block;
  height: var(--stat-strip-h);
  background: var(--stat-accent);
}
.stat-card__body { padding: 12px var(--card-pad-x); }        /* cards.py 149-164 */
.stat-card__value {
  margin-top: 3px;                                            /* cards.py 157 */
  font: var(--weight-semibold) var(--font-num-lg)/1.2 var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--stat-accent);
}
.stat-card__sub {
  margin-top: 1px;                                            /* cards.py 162 */
  font: var(--weight-regular) var(--font-tiny)/1.4 var(--font-sans);
  color: var(--text-dim);
  min-height: 1.4em;               /* Classic pads an " " even when empty */
}

/* ── KPI tile — cards.py KpiTile 199-275 ───────────────────────────────────
   BORDER-reveal card whose top edge carries the light leak instead of a
   solid strip; caps wrapping title + right-aligned accent icon head. */
.kpi-tile { display: flex; flex-direction: column; }
.kpi-tile__head {
  display: flex; align-items: flex-start; gap: 4px;
  padding: 10px var(--card-pad-x) 2px;                        /* cards.py 181 */
}
.kpi-tile__title { flex: 1; max-width: 125px; }               /* wraplength=125, cards.py 186 */
.kpi-tile__icon { font-size: 14.67px; }                       /* icon_qfont(11), cards.py 190 */
.kpi-tile__value {
  display: flex; align-items: flex-end;
  padding: 0 var(--card-pad-x);
  font: var(--weight-semibold) var(--font-kpi-whole)/1.1 var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--text-main);
}
.kpi-tile__frac {                                             /* cards.py 218-221 */
  font-size: var(--font-kpi-frac);
  color: var(--text-dim);
  padding-bottom: 5px;                                        /* pady=(0,5) */
}
.kpi-tile__sub {
  display: flex; align-items: center; gap: 5px;
  padding: 4px var(--card-pad-x) 12px;                        /* cards.py 237 */
  font: var(--weight-regular) var(--font-tiny)/1.4 var(--font-sans);
  color: var(--text-dim);
}

/* ── KPI row — the shared top-of-page KPI grid (uifix-kpi normalization;
   pages/subscriptions.html is the reference treatment: .card.kpi-tile
   shells on a 14px-gap grid). Base = 4 equal columns; --3/--2 for pages
   with fewer tiles. Pages with a bespoke shape keep overriding in their
   own <style> (subscriptions' repeat(3,1fr) 1.25fr portfolio split,
   dashboard's 6-up) — page CSS loads after this sheet, so those rules
   win the tie. */
.kpi-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
.kpi-row--3 { grid-template-columns: repeat(3, 1fr); }
.kpi-row--2 { grid-template-columns: repeat(2, 1fr); }
/* Uniform tile height ACROSS the Wealth tabs: a standard tile's only
   variable-height part is its caps title (it wraps at the 125px
   .kpi-tile__title cap), so tiles in a --uniform row reserve two title
   lines — the reference's tallest ("Total Monthly Subscriptions") — and
   the head+value+sub stack comes out the same height on every page. */
.kpi-row--uniform .kpi-tile__title { min-height: calc(2 * 1.3 * var(--font-label)); }

/* ── Light leak — primitives.py LightLeak 420-447 ──────────────────────────
   2px violet->cyan strip, alpha ramping 0 -> .55 -> 0 across the width:
   a(t) = (1 - |2t - 1|) * 0.55. Gradient + linear alpha mask reproduces the
   per-column loop exactly. Use as the first child of a .kpi-tile, atop a
   modal card, etc. */
.light-leak {
  height: var(--light-leak-height);
  background: var(--gradient-brand);
  -webkit-mask-image: linear-gradient(90deg, transparent,
                        rgb(0 0 0 / var(--light-leak-peak-alpha)) 50%, transparent);
  mask-image: linear-gradient(90deg, transparent,
                rgb(0 0 0 / var(--light-leak-peak-alpha)) 50%, transparent);
}

/* ── Pill badge — primitives.py PillBadge 215-235 ──────────────────────────
   20px pill, badge_bg(color) fill (= colour at 15% over BG_CARD), caps text
   in the colour. Set --pill-color per instance; the precomputed fallback
   background covers engines without color-mix(). */
.pill-badge {
  --pill-color: var(--accent);
  display: inline-flex; align-items: center; justify-content: center;
  height: var(--pill-badge-h);
  padding: 0 var(--pill-badge-padx);
  border-radius: var(--radius-pill);
  background: var(--badge-bg-accent);                          /* fallback */
  background: color-mix(in srgb, var(--pill-color) 15%, var(--bg-card));
  color: var(--pill-color);
  font: var(--weight-semibold) var(--font-label)/1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.045em;
  white-space: nowrap;
}

/* ── Dot pill — primitives.py DotPill 240-267 ──────────────────────────────
   Same chassis as .pill-badge but a leading 4px dot and original-case text. */
.dot-pill {
  --pill-color: var(--mint);
  display: inline-flex; align-items: center; gap: 5px;
  height: var(--pill-badge-h);
  padding: 0 var(--pill-badge-padx);
  border-radius: var(--radius-pill);
  background: var(--badge-bg-mint);                            /* fallback */
  background: color-mix(in srgb, var(--pill-color) 15%, var(--bg-card));
  color: var(--pill-color);
  font: var(--weight-semibold) var(--font-label)/1 var(--font-sans);
  white-space: nowrap;
}
.dot-pill::before {
  content: "";
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--pill-color);
}

/* ── Category chip — cards.py _CategoryChip 374-406 ────────────────────────
   Radius-5 (not pill) tinted chip: fill colour@12%, outline colour@40%,
   caps text in the colour. */
.cat-chip {
  --chip-color: var(--mint);
  display: inline-flex; align-items: center;
  height: var(--chip-h);
  padding: 0 8px;
  border-radius: var(--radius-chip);
  background: color-mix(in srgb, var(--chip-color) 12%, var(--bg-card));
  border: 1px solid color-mix(in srgb, var(--chip-color) 40%, var(--bg-card));
  color: var(--chip-color);
  font: var(--weight-semibold) var(--font-label)/1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.045em;
}

/* ── Buttons — primitives.py GradientButton 84-210 ─────────────────────────
   Pill (radius h/2) at 32px; text is the "sub" role; icon leads at 8px gap.
   gradient: signature fill, both stops mixed 14% to white on hover.
   outline : hairline ghost, HOVER fill on hover.
   danger  : flat DANGER, mixed 18% to black on hover. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: var(--btn-h);
  padding: 0 var(--btn-padx);
  border: none;
  border-radius: var(--radius-pill);
  font: var(--weight-semibold) var(--font-sub)/1 var(--font-sans);
  color: #FFFFFF;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
}
.btn .icon { font-size: 0.9em; }              /* icon_size = text pt − 1 (primitives.py 130-131) */
.btn--gradient { background: var(--gradient-brand); }
.btn--gradient:hover { background: var(--gradient-brand-hover); }
.btn--outline {
  background: transparent;
  border: var(--hairline) solid var(--border);   /* primitives.py 173-183 */
  color: var(--text-main);
}
.btn--outline:hover { background: var(--hover); }
.btn--danger { background: var(--danger); }
.btn--danger:hover { background: var(--danger-hover); }
.btn:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; }

/* ── Rounded bar — primitives.py RoundedBar 272-315 ────────────────────────
   8px full-pill track (SUNKEN) + fill; fill never narrower than its height
   so the cap geometry stays intact (primitives.py 305). Optional brand
   gradient fill; optional 2px WARN threshold marker. */
.rounded-bar {
  position: relative;
  height: var(--bar-h);
  border-radius: var(--radius-pill);
  background: var(--sunken);
  overflow: hidden;
}
.rounded-bar__fill {
  --bar-color: var(--accent);
  height: 100%;
  min-width: var(--bar-h);                       /* cap geometry floor */
  border-radius: var(--radius-pill);
  background: var(--bar-color);
}
.rounded-bar__fill--gradient { background: var(--gradient-brand); }
.rounded-bar__marker {                            /* primitives.py 312-315 */
  position: absolute; top: 0; bottom: 0;
  width: 2px;
  background: var(--warn);
}

/* ── Flat bar — primitives.py FlatBar 329-364 ──────────────────────────────
   Hard-edged sibling of .rounded-bar (Home Equity / Emergency Fund dossier
   bars): square corners, SUNKEN track, optional 2px marker. */
.flat-bar {
  position: relative;
  height: var(--flat-bar-h);
  background: var(--sunken);
}
.flat-bar__fill { --bar-color: var(--accent); height: 100%; background: var(--bar-color); }
.flat-bar__marker {
  position: absolute; top: 0; bottom: 0;
  width: 2px;
  background: var(--warn);
}

/* ── Filter pill row — the list pages' pill filter strips ──────────────────
   Same pill chassis as .btn at 28px; active state = the signature gradient
   (matches NavItem's active treatment applied to filters on Bills/Subs). */
.pill-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.filter-pill {
  display: inline-flex; align-items: center;
  height: 28px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  border: var(--hairline) solid var(--border);
  background: transparent;
  color: var(--text-dim);
  font: var(--weight-semibold) var(--font-label)/1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.045em;
  cursor: pointer;
}
.filter-pill:hover { background: var(--hover); color: var(--text-main); }
.filter-pill--active {
  background: var(--gradient-brand);
  border-color: transparent;
  color: #FFFFFF;
}

/* ── Table conventions ──────────────────────────────────────────────────────
   Header band: BG_HEAD with caps labels (palette.py 62; list-page headers).
   Rows: hairline BORDER separators (subscriptions_page: separators, NOT
   zebra) — or zebra striping BG_CARD / blend(#FFF,BG_CARD,.025) on the
   ledger pages (_ledger_kit.zebra_bg). Pick one convention per table. */
.table { width: 100%; border-collapse: collapse; }
.table-head { background: var(--bg-head); }
.table-head th {
  padding: 8px 12px;
  text-align: left;
  font: var(--weight-semibold) var(--font-label)/1.3 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.045em;
  color: var(--text-dim);
}
.trow td { padding: 10px 12px; font: var(--weight-regular) var(--font-body)/1.4 var(--font-sans); color: var(--text-main); }
.trow--lined { border-bottom: var(--hairline) solid var(--border); }
.trow--zebra:nth-child(even) { background: var(--zebra-alt); }
.trow--hover:hover { background: var(--hover); }

/* Hairline — chrome.py Hairline 97-112 */
.hairline    { height: var(--hairline); background: var(--border); }
.hairline--v { width:  var(--hairline); background: var(--border); }

/* ── Sidebar nav pill — chrome.py NavItem 118-195 ──────────────────────────
   38px row, pill inset 2px top/bottom; icon column at x=18, label at x=46.
   Active: full-width brand gradient, white. Hover: white@5% over BG_DARK.
   Idle: TEXT_DIM. Danger (Admin): DANGER-blend idle text, badge_bg(DANGER)
   active pill with DANGER text. */
.nav-pill {
  display: flex; align-items: center;
  height: var(--nav-item-h);
  padding: 2px 0;
  border-radius: var(--radius-nav);
  color: var(--text-dim);
  font: var(--weight-semibold) var(--font-nav)/1 var(--font-sans);
  cursor: pointer;
  text-decoration: none;
}
.nav-pill .icon {
  width: 28px;
  margin-left: 18px;
  font-size: 16px;                                /* icon_qfont(12) — chrome.py 190 */
}
.nav-pill:hover { background: var(--nav-hover); color: var(--text-main); }
.nav-pill--active { background: var(--gradient-brand); color: #FFFFFF; }
.nav-pill--active:hover { background: var(--gradient-brand); color: #FFFFFF; }
.nav-pill--danger { color: var(--nav-danger-idle); }
.nav-pill--danger:hover { background: var(--nav-hover); color: var(--danger); }
.nav-pill--danger.nav-pill--active { background: var(--badge-bg-danger); color: var(--danger); }

/* ── Icon tile — primitives.py IconTile 452-496 ────────────────────────────
   Rounded-square row leader: tinted fill = badge_bg(tint), glyph in tint;
   untinted = white@7% fill, TEXT_MAIN glyph. Glyph size = 36% of the tile. */
.icon-tile {
  --tile-tint: var(--text-main);
  --tile-size: var(--icon-tile-size);
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tile-size); height: var(--tile-size);
  border-radius: var(--radius-tile);
  background: var(--icon-tile-idle);
  color: var(--tile-tint);
  font-family: var(--font-icons);
  font-size: calc(var(--tile-size) * 0.36);
  flex: none;
}
.icon-tile--tinted {
  background: color-mix(in srgb, var(--tile-tint) 15%, var(--bg-card));
}
.icon-tile--round { border-radius: 50%; }

/* ── Legend dot — cards.py _Dot 112-125 ──────────────────────────────────── */
.dot {
  display: inline-block;
  width: 6px; height: 6px; border-radius: 50%;
  margin: 1px;
  background: var(--text-dim);
  flex: none;
}

/* ── Avatar — chrome.py _draw_identity 649-662 ─────────────────────────────
   Solid accent disc + white semibold initial + 1px ring of the accent at
   55% over the card, offset 3px outside the disc. */
.avatar {
  --avatar-color: var(--accent);
  --avatar-size: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--avatar-size); height: var(--avatar-size);
  border-radius: 50%;
  background: var(--avatar-color);
  color: #FFFFFF;
  font: var(--weight-semibold) calc(var(--avatar-size) * 0.48)/1 var(--font-sans);
  box-shadow: 0 0 0 3px var(--bg-card),
              0 0 0 4px color-mix(in srgb, var(--avatar-color) 55%, var(--bg-card));
  flex: none;
}

/* Uploaded-photo variant of .avatar — same disc/ring, image cropped to fill. */
.avatar--photo { object-fit: cover; padding: 0; }

/* ── Modal kit (W3) — dialog-equivalent forms. Opened by CSS :target (no-JS)
   or a server-forced .is-open (error re-render); modal.js adds Escape/backdrop
   /focus on top, but nothing here depends on it. Ports the QDialog look of
   add-bill-dialog / manage-buckets-dialog / calculator-dialog: square
   corners, --bg-card body, a light-leak strip under the header, violet
   .t-hero title, dim subtitle, a 2-col field grid, a toggle-switch row, and
   a footer with outline + gradient buttons. ─────────────────────────────── */
.modal-overlay {
  position: fixed; inset: 0; display: none;
  align-items: center; justify-content: center;
  background: rgb(0 0 0 / 0.55); z-index: 100;
}
.modal-overlay:target, .modal-overlay.is-open { display: flex; }
.modal {
  position: relative; width: min(480px, 94vw); max-height: 92vh;
  overflow-y: auto; background: var(--bg-card);
  border: var(--hairline) solid var(--border); border-radius: 0;
}
.modal__close {
  position: absolute; top: 12px; right: 14px;
  color: var(--text-dim); font-size: var(--font-body); line-height: 1;
  text-decoration: none; cursor: pointer;
}
.modal__close:hover { color: var(--text-main); }
.modal__header {
  padding: 18px var(--card-pad-x) 14px;
  border-bottom: var(--hairline) solid var(--border);
}
.modal__title { color: var(--accent); }               /* .t-hero base, accent-tinted */
.modal__sub { color: var(--text-dim); margin-top: 2px; }  /* .t-small base */
.modal__body { padding: 16px var(--card-pad-x); }
.modal__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
.modal__grid .field--wide { grid-column: 1 / -1; }
.field { display: flex; flex-direction: column; gap: 5px; }
/* Caption font stays on .field__label so standalone `<label class="field__label">`
   / `<span class="field__label">` usages (HK read-only captions, member role,
   updates log) render unchanged. The flex column + gap lets a label that WRAPS
   its control (the _fields.html macros — see that file for why the control is
   nested rather than id/for-linked) stack the caption above the control with
   the same 5px rhythm .field used to provide between two siblings. */
.field__label {
  display: flex; flex-direction: column; gap: 5px;
  font: var(--weight-semibold) var(--font-label)/1.3 var(--font-sans);
  text-transform: uppercase; letter-spacing: 0.045em; color: var(--text-dim);
}
.field__input, .field__select, .field__area {
  height: var(--input-h, 40px); background: var(--sunken);
  border: var(--hairline) solid var(--border); border-radius: 0;
  color: var(--text-main); padding: 0 10px;
  font: var(--weight-regular) var(--font-body)/1.4 var(--font-sans);
  /* The control sits inside the uppercase caption label — don't inherit its
     text-transform / letter-spacing onto the user's typed value. */
  text-transform: none; letter-spacing: normal;
}
.field__input:focus, .field__select:focus, .field__area:focus {
  outline: none; border-color: var(--accent);
}
.field__area { height: auto; min-height: 68px; padding: 8px 10px; resize: vertical; }
/* Toggle switch row (add-bill-dialog auto-pay control) */
.switch-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0; border-top: var(--hairline) solid var(--border);
}
.switch { position: relative; display: inline-block; width: 40px; height: 22px; flex: none; }
.switch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.switch__track {
  position: absolute; inset: 0;
  background: var(--sunken); border: var(--hairline) solid var(--border);
  border-radius: var(--radius-pill); transition: background-color .15s ease;
}
.switch__track::before {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%; background: var(--text-dim);
  transition: transform .15s ease, background-color .15s ease;
}
.switch input:checked + .switch__track { background: var(--accent); border-color: var(--accent); }
.switch input:checked + .switch__track::before { transform: translateX(18px); background: #FFFFFF; }
.modal__footer {
  display: flex; gap: 12px; padding: 14px var(--card-pad-x);
  border-top: var(--hairline) solid var(--border);
}
.modal__footer .btn--gradient { flex: 1; }
/* A .modal__footer or .switch-row dropped straight into a .modal__grid
   (the write forms' own field container) always spans the full width,
   like .field--wide does -- never sits half-width next to a field. */
.modal__grid > .modal__footer,
.modal__grid > .switch-row { grid-column: 1 / -1; }
/* List-manage variant (manage-buckets-dialog): row-card + actions cluster.
   Structure mirrors apps/budget/views/obsidian/modal.py::manage_list_row
   (icon tile -> title/caps-subtitle info block, flex:1 -> a right-hand
   column stacking the $/mo value over the edit/delete icon cluster). */
.manage-row {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px; border: var(--hairline) solid var(--border);
  margin-bottom: 12px;
}
.manage-row__info {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; justify-content: center; gap: 4px;
}
.manage-row__title { font: var(--weight-semibold) var(--font-sub)/1.3 var(--font-sans); color: var(--text-main); }
.manage-row__right {
  display: flex; flex-direction: column; align-items: flex-end;
  justify-content: center; gap: 6px;
}
.manage-row__value {
  font: var(--weight-semibold) var(--font-sub)/1.3 var(--font-sans);
  color: var(--text-main); white-space: nowrap;
}
.manage-row__actions { display: flex; gap: 12px; }
.manage-row__icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; background: none; border: none; padding: 0;
  color: var(--text-dim); font-family: var(--font-icons); font-size: 14px;
  cursor: pointer; text-decoration: none;
}
.manage-row__icon-btn:hover { color: var(--accent); }
.manage-row__icon-btn--danger:hover { color: var(--danger); }
.manage-empty { padding: 8px 0 18px; color: var(--text-dim); }
.manage-footer { display: flex; flex-direction: column; gap: 10px; padding-top: 4px; border-top: none; }
.manage-footer .btn--gradient { width: 100%; }
/* Validation banner atop .modal__body when a write is rejected (422). */
.form-error {
  background: var(--badge-bg-danger);
  border: var(--hairline) solid var(--danger);
  color: var(--danger);
  padding: 10px var(--card-pad-x);
  margin: 0 0 14px;
  font: var(--weight-regular) var(--font-small)/1.4 var(--font-sans);
}

/* ── Grid inline-edit + drag-to-sum (web/design/grid-edit.js) ──────────────
   Reference wiring: api/templates/pages/bills.html. Cells carry a .ge-view
   (shown normally) + a hidden .ge-input (shown only in edit mode); the page
   gear toggles [data-grid-editing] on the grid container. */
.ge-input { display: none; }
[data-grid-editing] .ge-view { display: none; }
[data-grid-editing] .ge-cell .ge-input {
  display: block; width: 100%; box-sizing: border-box;
  background: var(--bg-dark); color: var(--text-main);
  border: 1px solid var(--border); border-radius: var(--radius-chip);
  padding: 4px 7px; font: inherit; line-height: 1.3;
}
[data-grid-editing] .ge-cell .ge-input:focus { outline: none; border-color: var(--accent); }
.ge-input--num { text-align: right; }
.ge-form { display: none !important; }                 /* per-row forms never render */
[data-grid-editing] .row-menu { visibility: hidden; }  /* hide the kebab while editing */
tr.ge-row-error > td { box-shadow: inset 0 0 0 1px var(--danger); }

/* the gear / save-cancel swap in the page header */
.ge-actions { display: none; }
[data-grid-editing] .ge-actions { display: inline-flex; gap: 8px; }
[data-grid-editing] .ge-toggle { display: none; }

/* drag-to-sum: numeric cells select on drag, a floating pill shows the total */
[data-grid-edit] [data-num] { cursor: cell; user-select: none; }
.ge-selected { background: color-mix(in srgb, var(--accent) 24%, transparent) !important; }
.ge-sum-pill {
  position: fixed; z-index: 4000; pointer-events: none; white-space: nowrap;
  background: var(--bg-card); color: var(--text-main);
  border: 1px solid var(--border); border-radius: var(--radius-tile);
  padding: 6px 11px; font: var(--weight-regular) var(--font-small)/1 var(--font-sans);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
}
.ge-sum-pill b { color: var(--accent-cyan); font-weight: 600; }

/* ── Manage screen (gear pill) card list — matches web/prototype/Manage Bills
   Menu. A .modal--mgmt overlay gets a gradient title; each entry is an
   elevated card: dark icon tile + name/category, amount + edit/× on the right.
   Generic (.mgmt-*) so every grid's Manage screen shares it. */
.modal--mgmt .modal__title {
  background: var(--gradient-brand);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.mgmt-list { display: flex; flex-direction: column; gap: 12px; max-height: 62vh; overflow-y: auto; padding-right: 2px; }
.mgmt-card {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; background: var(--bg-head);
  border: 1px solid var(--border); border-radius: var(--radius-tile);
  transition: border-color .15s ease;
}
.mgmt-card:hover { border-color: var(--accent); }
.mgmt-card__main { display: flex; align-items: center; gap: 13px; min-width: 0; }
.mgmt-card__icon {
  flex: none; width: 40px; height: 40px; border-radius: var(--radius-tile);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-dark); color: var(--accent); font-size: 18px;
}
.mgmt-card__text { min-width: 0; }
.mgmt-card__name {
  font: var(--weight-semibold) var(--font-body)/1.25 var(--font-sans); color: var(--text-main);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mgmt-card__side { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; flex: none; }
.mgmt-card__amount { font: var(--weight-semibold) var(--font-body)/1 var(--font-sans); color: var(--text-main); }
.mgmt-card__actions { display: flex; gap: 6px; }

/* ── Hide scrollbars app-wide but keep scrolling (the visible scroll wheel
   reads as ugly). Applies to the page, modals, table wraps, the mgmt list —
   anything that scrolls still scrolls, just without a rendered bar. */
* { scrollbar-width: none; -ms-overflow-style: none; }
*::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* Kill Chrome's white/yellow autofill background on inputs (e.g. the login
   username field) — paint the field's own dark surface instead and keep the
   text light. The long transition defers the UA's autofill repaint. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px var(--sunken, #0e0e13) inset !important;
  -webkit-text-fill-color: var(--text-main) !important;
  caret-color: var(--text-main);
  transition: background-color 9999s ease-in-out 0s;
}
