/* ---------------------------------------------------------------- tokens ---
   Warm paper ground, one confident accent (the same green money "being
   funded" already meant, now doing double duty as the app's primary color)
   rather than a flat gray dashboard with color firing on every row. Cards
   separate by soft elevation, not hairline borders -- --rule still exists
   for the handful of places a real rule reads better than a shadow.
--------------------------------------------------------------------------- */
:root {
  --ground:    #F6F3EC;
  --panel:     #FFFFFF;
  --structure: #146B4A;
  --ink:       #1C1A16;
  --muted:     #79705F;
  --rule:      #E8E1D2;
  --funded:    var(--structure);
  --short:     #B3492F;
  --live:      #A17A22;

  --sans: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --gap: 1.5rem;
  --radius: 18px;
  --radius-sm: 10px;
  --shadow: 0 1px 2px rgba(28,22,14,0.04), 0 10px 24px rgba(28,22,14,0.07);
  --surface-quiet: #F2EDE2;
}

*, *::before, *::after { box-sizing: border-box; }

/* Any `display` rule beats [hidden]'s UA default of display:none, so a
   label styled `display: grid` stays visible when hidden — which silently
   showed inapplicable recurrence fields (e.g. "Every N weeks" on a monthly
   rule) and let them be edited. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* Added to Home Screen (apple-mobile-web-app-capable) launches full-screen
     with no Safari chrome to push content clear of the notch/Dynamic Island —
     viewport-fit=cover means we own that area and must pad it ourselves. */
  padding-top: env(safe-area-inset-top, 0px);
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.45;
}

button { font: inherit; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--structure);
  outline-offset: 2px;
}

/* Every figure on screen is monospaced and tabular so columns align and digits
   never shift width while you are typing into them. */
.money {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'zero' 1;
}

/* iOS's decimal-pad keyboard has no minus-sign key — this button is the only
   reliable way to enter an outflow on a phone. Pairs with every editable
   money input in the app (see the delegated click handler in app.js).
   Deliberately no default width: in a full-width label (.field, the account
   ledger balance/floor fields) it should fill; where a compact row needs it
   input-sized, that row sets the width itself. */
.money-field { display: inline-flex; align-items: stretch; gap: 0.3rem; }
.money-field input { flex: 1 1 auto; min-width: 0; }
.field .money-field, .ledger-balance__field .money-field,
.oblig-field .money-field, .card-row__fields .money-field { width: 100%; }
.money-sign {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 0.85rem; line-height: 1;
  width: 1.9rem;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--surface-quiet); color: var(--muted);
  cursor: pointer; padding: 0;
}
.money-sign:hover { background: var(--panel); border-color: var(--structure); color: var(--ink); }
.money-sign:active { background: var(--structure); color: #fff; border-color: var(--structure); }

/* ------------------------------------------------------------------ gate --- */
.gate {
  min-height: 100dvh;
  display: grid; place-items: center;
  padding: var(--gap);
}
.gate[hidden] { display: none; }
.gate__card {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 2rem;
  width: min(22rem, 100%);
  display: grid; gap: 1rem;
}
.gate__title {
  margin: 0;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 1.1rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.gate__error { margin: 0; color: var(--short); font-size: 0.85rem; }

/* minmax(0, 1fr): same implicit-grid-track trap as .cashflow -- a select
   whose longest option is a long account name ("Line of Credit *4093
   (4093)") has a wide min-content size, and without this the field (and
   the whole dialog around it) grew to fit it instead of the select
   shrinking to fit the dialog, overflowing well past the dialog's edge. */
.field { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.35rem; }
.field select, .field input:not([type="checkbox"]) { width: 100%; min-width: 0; }
.field__label {
  font-size: 0.7rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted);
}
input[type="password"], input[type="text"] {
  font: inherit;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: inherit;
}

/* ------------------------------------------------------- accounts header ---
   Dark bar, because .headactions' buttons are styled for one — they use a
   translucent white fill that vanishes on a light background.
--------------------------------------------------------------------------- */
.acctview-head {
  background: var(--structure);
  color: #EEF1F3;
  padding: 0.9rem var(--gap);
  border-radius: var(--radius);
}

.headactions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.5rem;
}
.syncnote {
  margin: 0; font-size: 0.7rem;
  color: rgba(238,241,243,0.6);
  font-family: var(--mono);
}
/* Sign out is the one thing in this bar that isn't a daily action, so it
   sits with the sync note as a text link rather than taking a button slot
   (on a phone the six buttons were wrapping to four rows). */
.headmeta { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; margin-top: 0.6rem; }
.headmeta .btn--ghost { color: rgba(238,241,243,0.7); font-size: 0.72rem; white-space: nowrap; }
.headmeta .btn--ghost:hover { color: #fff; }

/* ---------------------------------------------------------------- buttons --- */
.btn {
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.9rem;
  font-size: 0.85rem;
  font-weight: 500;
}
.btn:hover { border-color: var(--structure); }
.btn:disabled { opacity: 0.5; cursor: default; }
.btn--primary { background: var(--structure); color: #fff; border-color: var(--structure); box-shadow: 0 2px 8px rgba(20,107,74,0.28); }
.btn--primary:hover { border-color: var(--structure); filter: brightness(1.08); }
.btn--ghost { background: none; border: none; color: var(--muted); padding: 0.3rem 0; font-size: 0.78rem; text-decoration: underline; }
.btn--ghost:hover { color: var(--ink); }

/* Row-level secondary actions (add a note, undo a match) are faint text,
   not buttons: one per row would otherwise read as a column of controls.
   Where a pointer exists they appear only on the row under it; a touch
   screen has no hover, so there they stay visible. The padding/negative
   margin pair gives a finger something to hit without widening the text's
   footprint in the row. */
.quiet-action {
  font: inherit; font-size: 0.68rem; font-weight: 400; color: var(--muted);
  background: none; border: none; white-space: nowrap;
  padding: 0.25rem 0.35rem; margin: -0.25rem -0.35rem; border-radius: var(--radius-sm);
  text-decoration: underline; text-decoration-color: var(--rule); text-underline-offset: 0.15em;
}
.quiet-action:hover { color: var(--ink); text-decoration-color: currentColor; }
.quiet-action:disabled { color: var(--muted); text-decoration: none; cursor: default; }
@media (hover: hover) {
  .quiet-action { opacity: 0; transition: opacity 0.12s; }
  :is(.ledgerrow, .cf-history-row):is(:hover, :focus-within) .quiet-action,
  .quiet-action:focus-visible { opacity: 1; }
}
.headactions .btn { background: rgba(255,255,255,0.08); color: #EEF1F3; border-color: rgba(255,255,255,0.3); }
.headactions .btn:hover { background: rgba(255,255,255,0.16); }

/* -------------------------------------------------------------------- tabs --- */
.tabs {
  display: flex;
  gap: 0.25rem;
  padding: 0.6rem var(--gap) 0;
  max-width: 78rem;
  margin: 0 auto;
}
.tabs__btn {
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--muted);
  padding: 0.5rem 0.8rem;
  font-size: 0.85rem;
  font-weight: 500;
}
.tabs__btn[aria-current="page"] {
  color: var(--structure);
  background: rgba(20,107,74,0.1);
  font-weight: 600;
}
.tabs__btn:hover { color: var(--ink); }

/* ------------------------------------------------------------------ cycle --- */
.cycle__figure {
  margin: 0.3rem 0 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2.3rem, 12vw, 3.4rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
}
.cycle__figure[data-state="short"] { color: var(--short); }
.cycle__figure[data-state="funded"] { color: var(--funded); }
.cycle__caption {
  margin: 0.3rem 0 0;
  font-size: 0.85rem; font-weight: 500; color: var(--muted);
}
.cycle__path {
  margin: 0.5rem 0 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
  color: var(--muted);
}
.cycle__gap {
  margin: 0;
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius-sm);
  background: rgba(179,73,47,0.09);
  color: var(--short);
  font-size: 0.85rem;
  font-weight: 500;
}

/* Things the number above can't account for: a stale bank balance, a sync
   the bank reported trouble with, a bill left out of the projection. Amber
   (--live), not red: the forecast isn't short, it's incomplete. */
.cycle__warnings {
  margin: 0;
  padding: 0.7rem 0.9rem 0.7rem 1.9rem;
  border-radius: var(--radius-sm);
  background: rgba(161,122,34,0.1);
  color: var(--live);
  font-size: 0.85rem;
  font-weight: 500;
}
.cycle__warnings li + li { margin-top: 0.35rem; }
.cycle__math { margin: 1.1rem 0 0; display: grid; gap: 0.1rem; }
.cycle__math-row {
  display: flex; justify-content: space-between; align-items: baseline; gap: 0.8rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.9rem;
}
.cycle__math-row:last-child { border-bottom: none; }
.cycle__math-row dt { color: var(--muted); min-width: 0; }
.cycle__math-row dd { margin: 0; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cycle__math-row dd[data-state="short"] { color: var(--short); }
.cycle__math-row dd[data-state="funded"] { color: var(--funded); }
.cycle__math-row--total {
  border-top: 2px solid var(--structure); border-bottom: none;
  margin-top: 0.3rem; padding-top: 0.6rem; font-weight: 600; font-size: 1rem;
}
.cycle__math-row--total dt { color: var(--ink); }
.cycle__note { margin: 0.8rem 0 0; font-size: 0.78rem; color: var(--live); }



/* ---------------------------------------------------------------- overdue --- */
.overdue { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.overdue__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.15rem 0.6rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  background: rgba(179,73,47,0.08);
  align-items: baseline;
}
.overdue__name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.overdue__amount { color: var(--short); font-weight: 600; white-space: nowrap; }
.overdue__meta { grid-column: 1 / -1; font-size: 0.75rem; color: var(--short); }
.overdue__row .btn--ghost { grid-column: 1 / -1; justify-self: start; color: var(--short); }

/* In-flight is committed but not yet late — amber, not the red of overdue. */
.overdue__row--inflight { background: rgba(161,122,34,0.09); }
.overdue__row--inflight .overdue__amount,
.overdue__row--inflight .overdue__meta { color: var(--live); }

/* ------------------------------------------------------------ card pace --- */
.pace { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.9rem; }
.pace__row {
  display: grid;
  gap: 0.35rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--rule);
}
.pace__row:last-child { border-bottom: none; }
.pace__head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.pace__name { font-weight: 600; }
.pace__days { font-size: 0.78rem; color: var(--muted); white-space: nowrap; }

.pace__timebar, .pace__bar {
  height: 6px;
  border-radius: 3px;
  background: var(--ground);
  overflow: hidden;
}
.pace__timebar-fill { height: 100%; background: var(--structure); }
.pace__bar-fill { height: 100%; }
.pace__bar-fill[data-state="short"]  { background: var(--short); }
.pace__bar-fill[data-state="funded"] { background: var(--funded); }
.pace__bar-fill[data-state="even"]   { background: var(--live); }

.pace__note { margin: 0; font-size: 0.78rem; }
.pace__note[data-state="short"]  { color: var(--short); }
.pace__note[data-state="funded"] { color: var(--funded); }
.pace__note[data-state="even"]   { color: var(--live); }
.pace__note--muted { color: var(--muted); }
.pace__landing { margin: 0; font-size: 0.75rem; color: var(--muted); }

/* ------------------------------------------------------------- plan/advisor --- */
.plan-goals, .plan-actions { list-style: none; margin: 0 0 0.5rem; padding: 0; display: grid; gap: 0.5rem; }
.plan-goal, .plan-action {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule);
}
.plan-goal:last-child, .plan-action:last-child { border-bottom: none; }
.plan-goal__row { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.plan-goal__desc { font-weight: 600; }
.plan-goal__status { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }
.plan-goal__status[data-status="done"] { color: var(--funded); }
.plan-goal__target { display: block; font-size: 0.78rem; color: var(--muted); margin-top: 0.1rem; }
.plan-goal__actions { display: flex; gap: 0.6rem; margin-top: 0.35rem; }

.plan-action__row { display: flex; align-items: baseline; gap: 0.5rem; cursor: pointer; }
.plan-action__desc[data-status="done"] { color: var(--muted); text-decoration: line-through; }
.plan-action__due { font-size: 0.75rem; color: var(--muted); margin-left: auto; white-space: nowrap; }
.plan-action__actions { display: flex; gap: 0.6rem; margin-top: 0.35rem; }

/* Live progress against a goal/action's metric -- see advisor.js's
   computeProgress. Same bar language as .pace__bar (Budget tab's card-pace
   feature) on purpose: it's the same idea, a real number checked against a
   limit, just for a plan item instead of a card cycle. */
.plan-progress { margin-top: 0.4rem; display: grid; gap: 0.25rem; }
.plan-progress__bar { height: 5px; border-radius: 3px; background: var(--ground); overflow: hidden; }
.plan-progress__bar-fill { height: 100%; }
.plan-progress__bar-fill[data-state="short"]   { background: var(--short); }
.plan-progress__bar-fill[data-state="funded"]  { background: var(--funded); }
.plan-progress__bar-fill[data-state="live"]    { background: var(--live); }
.plan-progress__label { font-size: 0.76rem; color: var(--muted); }
.plan-progress__label[data-state="short"]  { color: var(--short); }
.plan-progress__label[data-state="funded"] { color: var(--funded); }
.plan-progress__label[data-state="live"]   { color: var(--live); }

.advisor-thread {
  list-style: none; margin: 0 0 0.6rem; padding: 0;
  display: grid; gap: 0.5rem;
  max-height: 22rem; overflow-y: auto;
}
.advisor-msg { padding: 0.5rem 0.7rem; border-radius: var(--radius-sm); font-size: 0.86rem; line-height: 1.4; }
.advisor-msg p { margin: 0 0 0.5rem; }
.advisor-msg p:last-child { margin-bottom: 0; }
.advisor-msg--user { background: var(--ground); justify-self: end; max-width: 85%; }
.advisor-msg--assistant { background: var(--panel); border: 1px solid var(--rule); max-width: 85%; }
.advisor-msg--pending { color: var(--muted); font-style: italic; }

.advisor-input { display: flex; gap: 0.5rem; align-items: flex-start; }
.advisor-input textarea {
  flex: 1 1 auto;
  font: inherit;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: inherit;
  resize: vertical;
}

/* -------------------------------------------------------- transfer queue --- */
.xfers { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.xfer {
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 0.6rem;
  display: grid; gap: 0.35rem;
}
.xfer__head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.xfer__kind {
  font-size: 0.65rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); background: var(--surface-quiet);
  padding: 0.1rem 0.4rem; border-radius: 999px;
}
.xfer__kind--advance { color: var(--short); background: rgba(179,73,47,0.1); }
.xfer__amount { font-size: 0.95rem; font-weight: 600; }
.xfer__side {
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr);
  gap: 0.1rem 0.5rem;
  font-size: 0.82rem;
}
.xfer__dir {
  grid-row: span 2;
  font-size: 0.62rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted); align-self: center;
}
.xfer__acct { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xfer__meta { font-size: 0.72rem; color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xfer__actions { display: flex; justify-content: flex-end; gap: 0.5rem; align-items: center; margin-top: 0.15rem; }
.xfer__actions .btn { font-size: 0.78rem; padding: 0.3rem 0.7rem; }

/* --------------------------------------------------------------- advances --- */
.advances__summary {
  margin: 0 0 0.7rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  background: rgba(179,73,47,0.08);
  color: var(--short);
  font-size: 0.85rem; font-weight: 500;
}
.advances { list-style: none; margin: 0; padding: 0; }
.advances__row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) auto;
  gap: 0.6rem;
  align-items: baseline;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
}
.advances__row:last-child { border-bottom: none; }
.advances__date { font-family: var(--mono); font-size: 0.78rem; color: var(--muted); }
.advances__source { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.advances__amount { color: var(--short); }

/* --------------------------------------------------------------- spending --- */
.spend__summary { margin: 0 0 0.8rem; font-size: 0.85rem; color: var(--muted); line-height: 1.7; }
.spend__summary strong { color: var(--ink); font-size: 1rem; }
.spend__elapsed { white-space: nowrap; }

.spend-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 5.5rem;
  gap: 0.15rem 0.6rem;
  padding: 0.45rem 0;
  border: none; border-bottom: 1px solid var(--rule);
  align-items: baseline;
  width: 100%;
  font: inherit; font-size: 0.85rem; color: inherit; text-align: left;
  background: none; cursor: pointer;
}
.spend-row:last-child { border-bottom: none; }
.spend-row:hover .spend-row__name { text-decoration: underline; }
.spend-row__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spend-row__actual { text-align: right; font-variant-numeric: tabular-nums; }

/* The bar shows this month's spend; the tick marks where a typical month
   lands, so over/under is read positionally instead of by doing arithmetic. */
.spend-row__bar {
  grid-column: 1 / -1;
  position: relative;
  height: 5px;
  border-radius: 999px;
  background: var(--rule);
  overflow: visible;
}
.spend-row__bar-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  border-radius: 999px;
  background: var(--muted);
}
.spend-row__bar-mark {
  position: absolute; top: -3px; bottom: -3px;
  width: 2px;
  background: var(--structure);
}
.spend-row[data-state="over"] .spend-row__bar-fill { background: var(--short); }
.spend-row[data-state="under"] .spend-row__bar-fill { background: var(--funded); }

.spend-row__typical { grid-column: 1 / -1; font-size: 0.72rem; color: var(--muted); }
.spend-row[data-state="over"] .spend-row__typical { color: var(--short); }

/* ---------------------------------------------------------------- reports --- */
.report-note { margin: 0.6rem 0 0; font-size: 0.76rem; color: var(--live); }

/* Axis labels are HTML beside the SVG, not <text> inside it: the SVG scales
   with the card and its text would shrink with it. app.js sets --plot-top,
   --plot-bottom and --plot-x from the plot's own padding so the labels sit
   on the gridlines at any width. */
.networth-chart { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 0.5rem; margin-top: 0.8rem; }
.networth-chart > .empty { grid-column: 1 / -1; }
.networth-axis { font-size: 0.68rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.networth-axis--y { grid-column: 1; grid-row: 1; position: relative; width: 3.4rem; font-family: var(--mono); }
.networth-axis--y > span { position: absolute; right: 0; transform: translateY(-50%); line-height: 1; white-space: nowrap; }
.networth-axis__hi { top: var(--plot-top); }
.networth-axis__lo { top: var(--plot-bottom); }
.networth-axis--x { grid-column: 2; grid-row: 2; display: flex; justify-content: space-between; padding: 0.25rem var(--plot-x) 0; }
.networth-svg { grid-column: 2; grid-row: 1; display: block; width: 100%; height: auto; }
/* non-scaling-stroke: the viewBox is scaled to the card, which would thin
   the line to under a pixel on a phone and fatten it on a wide screen. */
.networth-svg__grid { stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.networth-svg__line { stroke: var(--structure); stroke-width: 2; stroke-linecap: round; fill: none; vector-effect: non-scaling-stroke; }
.networth-svg__line--estimated { stroke: var(--live); stroke-dasharray: 4 3; }
.networth-svg__dot { fill: var(--structure); }
.networth-svg__dot--estimated { fill: var(--live); }

.spend-history-row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) 5.5rem;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.4rem 0;
  border: none; border-bottom: 1px solid var(--rule);
  background: none; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
/* The bar is shared with .spend-row, where it spans the full row under the
   label; here it's the middle column, and inheriting that span stacked
   every month into three lines. */
.spend-history-row > .spend-row__bar { grid-column: auto; }
.spend-history-row:last-child { border-bottom: none; }
.spend-history-row:hover { background: var(--surface-quiet); }
.spend-history-row__month { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); }
.spend-history-row__total { text-align: right; font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------- cash flow --- */
.cashflow {
  display: grid;
  /* minmax(0, 1fr): an implicit grid track's min-width otherwise defaults to
     its content's min-content size -- a row of several buttons wide enough
     (e.g. .acctview-head's) silently forces the whole page wider than the
     viewport and scrollable sideways instead of wrapping, same class of bug
     .cf-cols already guards against below. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap);
  padding: var(--gap);
  max-width: 78rem;
  margin: 0 auto;
}
.cashflow[hidden] { display: none; }

/* Headline left, actions right, and any alert (a floor breach, a stale
   balance) on its own full-width row beneath. This used to flex-wrap, which
   put the breach sentence beside the number only when the window happened
   to be wide enough for it, and dropped the buttons wherever room was left
   -- a different place in every state. */
.cf-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 1rem 1.5rem;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.5rem 1.5rem;
}
.cf-headline { min-width: 0; }
.cf-head__title {
  margin: 0;
  font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.cf-head__actions { grid-column: 2; grid-row: 1; display: flex; gap: 0.5rem; align-items: center; }
.cf-head > .cycle__gap, .cf-head > .cycle__warnings { grid-column: 1 / -1; }
@media (max-width: 44rem) {
  .cf-head { grid-template-columns: minmax(0, 1fr); }
  /* Last, so an alert reads before the buttons. */
  .cf-head__actions { grid-column: 1; grid-row: auto; order: 10; }
}

.cf-chart__no-floor {
  margin: 0 0 1rem; font-size: 0.78rem; color: var(--muted);
  background: var(--surface-quiet); border-radius: var(--radius-sm); padding: 0.6rem 0.8rem;
}
.cf-chart__set-floor {
  background: none; border: none; color: var(--structure); text-decoration: underline;
  font: inherit; font-size: inherit; padding: 0; margin-left: 0.3rem; cursor: pointer;
}

.cf-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20rem;
  gap: var(--gap);
  align-items: start;
}
/* Both cards hide themselves when empty; with one gone the other used to sit
   beside a 20rem hole, and with both gone the empty grid still cost the page
   a double gap. */
.cf-cols:has(> [hidden]) { grid-template-columns: minmax(0, 1fr); }
.cf-cols:not(:has(> :not([hidden]))) { display: none; }

.cf-events, .cf-missed, .cf-suggestions { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.2rem; }

.cf-events__summary {
  margin: 0 0 0.6rem;
  font-size: 0.82rem;
  font-weight: 500;
}
.cf-events__summary[data-state="todo"] { color: var(--live); }
.cf-events__summary[data-state="clear"] { color: var(--funded); }

/* Mobile-first: the stacked card layout below is the only layout up to
   50rem, and the dense six-column table only switches on above that (see
   the @media (min-width: 50rem) block after .cf-history). The six fixed
   columns alone need ~31rem before a label column gets any room at all --
   a container between roughly 30-50rem (an in-between browser window, a
   tablet, a split-screen phone) used to get the dense grid anyway with
   nothing left for the label, which collapsed to 0 width and, depending on
   the label's white-space handling, either showed blank or (worse)
   wrapped one character per line. The stacked layout has no such cliff. */
.cf-events__cols { display: none; }

.cf-event {
  display: grid;
  /* Sized for the number alone. The +/- toggle and revert button only
     appear once the amount is focused (see .cf-event__amount-cell below),
     and a column wide enough for them at rest cost the label a third of a
     phone's width -- "Target CircleCard (1667) payment" ran to three lines.
     The :has rule below widens it for the controls while editing; labels
     wrap rather than ellipsize, so that reflow loses nothing. */
  grid-template-columns: 1.3rem minmax(0, 1fr) 6.75rem;
  grid-template-areas:
    "schedule label  amount"
    "schedule date   balance"
    "schedule badge  badge";
  row-gap: 0.1rem;
  column-gap: 0.4rem;
  align-items: center;
  padding: 0.45rem 0.2rem;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
}
.cf-event:last-child { border-bottom: none; }
.cf-event:has(.cf-event__amount-cell:focus-within) { grid-template-columns: 1.3rem minmax(0, 1fr) 9.5rem; }
.cf-event__schedule    { grid-area: schedule; }
.cf-event__date-cell   { grid-area: date; }
.cf-event__label       { grid-area: label; }
.cf-event__amount-cell { grid-area: amount; }
.cf-event__balance     { grid-area: balance; }
.cf-event__schedule { display: flex; align-items: center; justify-content: center; }
.cf-event__schedule input { margin: 0; accent-color: var(--structure); }
/* Autopay: a settled state, not an unticked box asking for action. */
.cf-event__schedule--auto { color: var(--funded); font-size: 0.8rem; }
.cf-event__date { font-family: var(--mono); font-size: 0.78rem; color: var(--muted); }
.cf-event__date-cell { display: flex; align-items: center; gap: 0.2rem; }
/* appearance:none matters more than the border/background rules below it —
   without it, iOS Safari draws its own inset field chrome on top of
   "border: transparent; background: none" regardless, so a field meant to
   read as plain text until touched shows as a boxed input all the time. */
.cf-event__date-input {
  appearance: none; -webkit-appearance: none;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: inherit;
  width: 100%;
  /* A date input's min-width is its own intrinsic width, so as a flex item
     it refused to shrink and the revert button beside it spilled over into
     the label column ("↺Christy co-expense"). */
  min-width: 0;
  flex: 1 1 auto;
  padding: 0.1rem 0.2rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
}
.cf-event__date-input:hover:not(:disabled) { border-color: var(--rule); background: var(--surface-quiet); }
.cf-event__date-input:focus { border-color: var(--structure); background: var(--panel); outline: none; }
.cf-event__date-input:disabled { background: none; border-color: transparent; color: inherit; }
/* Wraps rather than ellipsizing -- a long card/bill label cut off mid-word
   ("REI Co-op Mastercard (7652) pa...") loses exactly the part that
   disambiguates it from other cards, which an ellipsis never buys back. */
.cf-event__label { min-width: 0; overflow-wrap: break-word; }
.cf-event__account { display: block; font-size: 0.68rem; color: var(--muted); }
/* Scheduled/overdue used to also tint the whole row -- on a list where most
   rows are outflows already in red, a second layer of background color on
   top just made every state look equally urgent. The label weight and the
   badge below carry the same information on their own. */
.cf-event--scheduled .cf-event__label { font-weight: 700; }
.cf-event__badge {
  grid-area: badge;
  justify-self: end;
  font-size: 0.65rem; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 0.1rem 0.4rem; border-radius: 999px;
}
.cf-event__badge:empty { padding: 0; }
.cf-event__badge[data-confidence="estimated"] { color: var(--live); background: rgba(161,122,34,0.1); }
.cf-event__badge[data-overdue="true"] { color: var(--short); background: rgba(179,73,47,0.1); }

.cf-event__amount-cell { display: flex; align-items: center; justify-content: flex-end; gap: 0.2rem; }
/* input.cf-event__amount, not .cf-event__amount: it's an input[type="text"],
   and that base rule up top is one attribute selector more specific than a
   bare class -- same fix already applied to .ledgerrow__notes/amount-input
   below, and the same bug this had until qualifying it here: the plain,
   borderless-until-touched look was losing to the global boxed default on
   every single row, which is the "everything looks like an input" look
   this list was flagged for. */
input.cf-event__amount {
  appearance: none; -webkit-appearance: none;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: inherit;
  width: 100%;
  min-width: 0;
  padding: 0.15rem 0.3rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
}
/* .money[data-negative="true"]'s class+attribute selector otherwise outranks
   the rule above -- needs the same tag-qualify treatment. Negative-money-is-
   red is useful where an outflow is the exception on the row (an account
   ledger, a balance); here almost every row is one, so painting nearly the
   whole list red drowned out the actual signals (the overdue badge, a floor
   breach) instead of adding to them. */
input.cf-event__amount[data-negative="true"] { color: inherit; }
.cf-event__amount:hover:not(:disabled) { border-color: var(--rule); background: var(--surface-quiet); }
.cf-event__amount:focus { border-color: var(--structure); background: var(--panel); outline: none; }
.cf-event__amount:disabled { background: none; border-color: transparent; color: inherit; }
/* The +/- toggle and revert-to-estimate button are edit affordances, not
   things worth seeing on every row of a list that's read far more often
   than it's edited -- shown only once you've actually focused the amount. */
.cf-event__amount-cell .money-sign,
.cf-event__amount-cell .cf-event__reset { display: none; }
.cf-event__amount-cell:focus-within .money-sign,
.cf-event__amount-cell:focus-within .cf-event__reset { display: inline-flex; }
.cf-event__reset {
  background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 0.9rem; line-height: 1; padding: 0 0.1rem;
}
.cf-event__reset:hover { color: var(--ink); }
.cf-event__balance {
  text-align: right; font-size: 0.8rem; color: var(--muted);
}
.cf-event__balance--breach {
  color: var(--short); font-weight: 600;
  background: rgba(179,73,47,0.08); border-radius: 6px; padding: 0.1rem 0.3rem;
}
.money[data-negative="true"] { color: var(--short); }

.cf-history { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.1rem; }
.cf-history__cols, .cf-history-row {
  display: grid;
  grid-template-columns: 6rem minmax(0, 1fr) 5rem 6rem 6rem 6rem;
  gap: 0.6rem;
  align-items: center;
}
/* Two amounts and two dates per row, none of which say which is which
   without this. */
.cf-history__cols {
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--rule);
  font-size: 0.62rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted);
}
.cf-history__cols > :nth-child(4), .cf-history__cols > :nth-child(5) { text-align: right; }
.cf-history-row {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
}
.cf-history-row:last-child { border-bottom: none; }
.cf-history-row .money { text-align: right; }
.cf-history__status {
  justify-self: start;
  font-size: 0.65rem; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 0.1rem 0.4rem; border-radius: 999px;
}
.cf-history__status[data-status="paid"] { color: var(--funded); background: rgba(20,107,74,0.09); }
.cf-history__status[data-status="missed"] { color: var(--short); background: rgba(179,73,47,0.1); }
.cf-history__status[data-status="skipped"] { color: var(--muted); background: var(--surface-quiet); }

.cf-history__match {
  grid-column: 1 / -1;
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 0.15rem;
}
.cf-history__match--none { color: var(--live); }

@media (max-width: 34rem) {
  .cf-history__cols { display: none; }
  .cf-history-row {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "label label"
      "date status"
      "expected actual"
      "posted posted"
      "match match";
    row-gap: 0.2rem;
    padding: 0.6rem 0.2rem;
  }
  .cf-history-row > :nth-child(1) { grid-area: date; }
  .cf-history-row > :nth-child(2) { grid-area: label; }
  .cf-history-row > :nth-child(3) { grid-area: status; justify-self: end; }
  .cf-history-row > :nth-child(4) { grid-area: expected; text-align: left; }
  .cf-history-row > :nth-child(5) { grid-area: actual; }
  .cf-history-row > :nth-child(6) { grid-area: posted; font-size: 0.7rem; color: var(--muted); }
  /* grid-area alone: a trailing grid-column reset was undoing the span and
     pinning the match line to the left column. */
  .cf-history__match { grid-area: match; }
  /* The column header is gone at this width, so each figure labels itself. */
  .cf-history-row > :nth-child(1)::before { content: 'due '; }
  .cf-history-row > :nth-child(4)::before { content: 'expected '; }
  .cf-history-row > :nth-child(5)::before { content: 'actual '; }
  .cf-history-row > :nth-child(6)::before { content: 'posted '; }
  .cf-history-row > :nth-child(1)::before, .cf-history-row > :nth-child(4)::before,
  .cf-history-row > :nth-child(5)::before, .cf-history-row > :nth-child(6)::before {
    font-family: var(--sans); font-size: 0.68rem; color: var(--muted);
  }
}

.cf-missed-item {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
}
.cf-missed-item:last-child { border-bottom: none; }
.cf-missed__row { display: flex; justify-content: space-between; gap: 0.5rem; }
.cf-missed__actions { display: flex; gap: 0.6rem; margin-top: 0.35rem; }
.cf-missed__attach { margin-top: 0.5rem; display: grid; gap: 0.4rem; }
.cf-missed__attach[hidden] { display: none; }
.cf-missed__txn-select {
  font: inherit; font-size: 0.8rem; width: 100%;
  padding: 0.3rem; border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--panel);
}
.cf-missed__attach-actions { display: flex; justify-content: flex-end; gap: 0.4rem; }

.cf-suggestion {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
  display: grid; gap: 0.35rem;
}
.cf-suggestion:last-child { border-bottom: none; }
.cf-suggestion__row { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
/* Inline, unlike .cf-event__account: this follows a separator mid-sentence,
   and a block here left "Oct 15 · Christina Hilton ·" dangling on its own. */
.cf-suggestion__acct { color: var(--muted); font-size: 0.78rem; }
.cf-suggestion__match { color: var(--muted); font-size: 0.78rem; }
.cf-suggestion__actions { display: flex; gap: 0.4rem; }

@media (max-width: 62rem) {
  .cf-cols { grid-template-columns: minmax(0, 1fr); }
}

/* Dense six-column table, only once there's real room for it. See the
   comment above .cf-events__cols for why this is a min-width override on
   top of the stacked default rather than the old max-width arrangement. */
@media (min-width: 50rem) {
  /* 8rem for the date: Chrome's date input wants ~6.5rem for mm/dd/yyyy
     plus its picker icon at this size, and the revert button sits beside
     it -- 6.2rem was clipping the year to "10/19/202". */
  .cf-events__cols {
    display: grid;
    grid-template-columns: 1.4rem 8rem minmax(0, 1fr) 5.5rem 8.5rem 7rem;
    gap: 0.5rem;
    padding-bottom: 0.3rem;
    border-bottom: 1px solid var(--rule);
    font-size: 0.62rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted);
  }
  .cf-events__cols > :nth-child(5) { text-align: right; }
  .cf-events__cols > :nth-child(6) { text-align: right; }

  /* Both selectors, so the stacked layout's focus-widening rule (more
     specific than a bare class) can't override the dense grid here. */
  .cf-event, .cf-event:has(.cf-event__amount-cell:focus-within) {
    grid-template-columns: 1.4rem 8rem minmax(0, 1fr) 5.5rem 8.5rem 7rem;
    /* Cancels the stacked layout's named areas so these items fall back to
       plain source-order placement across the six tracks above -- schedule,
       date, label, badge, amount, balance, the same order renderEvents
       already emits them in. */
    grid-template-areas: none;
    row-gap: 0;
    column-gap: 0.5rem;
    padding: 0.4rem 0;
  }
  .cf-event__schedule, .cf-event__date-cell, .cf-event__label,
  .cf-event__badge, .cf-event__amount-cell, .cf-event__balance {
    grid-area: auto;
  }
  .cf-event__badge { justify-self: start; }
}

/* --------------------------------------------------------- obligations manager --- */
.oblig-section { margin-bottom: 1.25rem; }
.oblig-section__title {
  margin: 0 0 0.3rem;
  font-size: 0.68rem; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.oblig-section__hint { margin: 0 0 0.5rem; font-size: 0.78rem; color: var(--muted); }
.oblig-showinactive { margin-bottom: 0.5rem; font-size: 0.82rem; }

.card-row {
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 0.6rem;
  margin-bottom: 0.6rem;
  display: grid; gap: 0.5rem;
}
.card-row__head { display: flex; align-items: baseline; gap: 0.5rem; }
.card-row__name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-row__balance { font-size: 0.85rem; color: var(--muted); margin-left: auto; }
.card-row__remove { flex: 0 0 auto; }
.card-row__fields { display: flex; flex-wrap: wrap; gap: 0.6rem 0.8rem; }
.card-row__fields label {
  display: grid; gap: 0.2rem;
  font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
.card-row__fields select, .card-row__fields input {
  font: inherit; font-size: 0.82rem; text-transform: none; letter-spacing: normal; color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.25rem 0.4rem; background: var(--panel);
}
.card-row__fields input.money { width: 6rem; text-align: right; }
.card-row__fields label[hidden] { display: none; }

.card-row__statement {
  border-top: 1px solid var(--rule);
  padding-top: 0.5rem;
  display: grid; gap: 0.3rem;
}
.card-row__statement label {
  display: grid; gap: 0.2rem; justify-items: start;
  font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
.card-row__statement input {
  font: inherit; font-size: 0.85rem; text-transform: none; letter-spacing: normal; color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.35rem 0.45rem; background: var(--panel); text-align: right; width: 7rem;
}
.card-row__statement-hint { margin: 0; font-size: 0.72rem; color: var(--muted); }
.card-row__statement--needed { background: rgba(161,122,34,0.07); border-radius: var(--radius-sm); padding: 0.5rem; }
.card-row__statement--needed .card-row__statement-hint { color: var(--live); }
.card-row__statement-hint--plan { color: var(--live); margin-top: 0.3rem; }

.card-row__plans { border-top: 1px solid var(--rule); padding-top: 0.5rem; display: grid; gap: 0.5rem; }
.card-row__plans-title { margin: 0; font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.plan-form__toggle { justify-self: start; }

.plan-row { border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 0.5rem 0.6rem; display: grid; gap: 0.25rem; }
.plan-row__head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.plan-row__desc { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plan-row__remaining { color: var(--short); white-space: nowrap; }
.plan-row__meta { margin: 0; font-size: 0.72rem; color: var(--muted); }
.plan-row__actions { display: flex; gap: 0.5rem; }
.plan-row__actions .btn--ghost { font-size: 0.76rem; padding: 0.2rem 0.5rem; }

.plan-form { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.plan-form input { font: inherit; font-size: 0.82rem; border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 0.25rem 0.4rem; background: var(--panel); }
.plan-form__desc { flex: 1 1 12rem; }
.plan-form input.money { width: 6.5rem; text-align: right; }
.plan-form__term { width: 4.5rem; }
.plan-form__started { width: 9rem; }

.oblig-card {
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  margin-bottom: 0.4rem;
  background: var(--panel);
}
.oblig-card[open] { border-color: var(--structure); }

.oblig-card__summary {
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
  padding: 0.55rem 0.6rem;
  cursor: pointer;
  list-style: none;
}
.oblig-card__summary::-webkit-details-marker { display: none; }
.oblig-card__summary:hover { background: var(--surface-quiet); }
.oblig-card__summary-main { min-width: 0; display: grid; gap: 0.1rem; }
.oblig-card__summary-name {
  font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.oblig-card__summary-meta {
  font-size: 0.72rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.oblig-card__summary-amount { font-size: 0.85rem; white-space: nowrap; }

/* Two columns on the narrowest phone, so a label and its control stay
   together instead of flex-wrapping into an unpredictable order. */
.oblig-card__body {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem 0.7rem;
  padding: 0.2rem 0.6rem 0.7rem;
  border-top: 1px solid var(--rule);
}
.oblig-field--wide { grid-column: 1 / -1; }

.oblig-field, .oblig-addform label, .rrule-editor label {
  display: grid; gap: 0.2rem; min-width: 0;
  font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
.oblig-field select, .oblig-field input,
.oblig-addform select, .oblig-addform input,
.rrule-editor select, .rrule-editor input, .rrule-editor textarea {
  font: inherit; font-size: 0.85rem; text-transform: none; letter-spacing: normal; color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.4rem 0.45rem; background: var(--panel);
  width: 100%; min-width: 0;
}
.oblig-field input.money { text-align: right; }
/* Tag-qualified: inside the add form, `.oblig-addform label` above is one
   type selector more specific than a bare class and was winning -- the
   Autopay checkbox rendered stacked above an uppercase label there while
   the same row in the edit cards looked right. */
label.oblig-field--check {
  grid-column: 1 / -1;
  display: flex; flex-direction: row; align-items: center; gap: 0.45rem;
  text-transform: none; letter-spacing: normal; font-size: 0.85rem; color: var(--ink);
}
.oblig-field--check input { width: auto; }

.oblig-card__footer { display: flex; justify-content: flex-end; }

.rrule-editor {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem 0.7rem;
}
.rrule-field--custom { grid-column: 1 / -1; }
.rrule-editor__actions { display: flex; justify-content: flex-end; margin-top: 0.5rem; }
.rrule-editor__actions .btn { font-size: 0.8rem; padding: 0.35rem 0.7rem; }
.rrule-field--custom textarea {
  font-family: var(--mono); font-size: 0.78rem; text-transform: none; letter-spacing: normal;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.35rem 0.5rem; resize: vertical; width: 100%;
}

.oblig-addform { display: grid; gap: 0.7rem; }

/* ----------------------------------------------------------------- dialog --- */
/* A <dialog> shown with showModal() renders in the top layer, positioned by
   the UA's own centering — body's safe-area padding (see above) never
   reaches it. On an installed PWA with viewport-fit=cover, a tall dialog's
   title can render flush under the status bar / Dynamic Island with nothing
   to push it clear. Pin real minimum margins so it never gets that close. */
.dialog {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 0;
  color: inherit;
  /* A bare max-width leaves an unconstrained dialog sized by fit-content --
     driven by its widest native control's own preferred width. Chrome and
     Safari don't agree on how wide a <select> or <input type="date">
     prefers to be, so the whole dialog (and everything inside it sized off
     that, like a width:100% money-field row) could land a few px narrower
     in one than the other, clipping content that assumed the wider result.
     An explicit width makes every dialog's size deterministic instead. */
  width: min(24rem, 92vw);
  margin-top: max(1.25rem, calc(env(safe-area-inset-top, 0px) + 0.5rem));
  margin-bottom: max(1.25rem, calc(env(safe-area-inset-bottom, 0px) + 0.5rem));
  max-height: calc(100vh - max(2.5rem, calc(env(safe-area-inset-top, 0px) + env(safe-area-inset-bottom, 0px) + 1rem)));
}
.dialog::backdrop { background: rgba(28,26,22,0.35); }
.dialog__form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; padding: 1.5rem; }
.dialog__title {
  margin: 0;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.dialog__actions { display: flex; justify-content: flex-end; gap: 0.5rem; }
.field--row { display: flex; flex-direction: row; align-items: center; gap: 0.5rem; }

.dialog--wide { width: min(40rem, 94vw); }
.dialog--wide .dialog__form { max-height: 80vh; overflow-y: auto; }
/* Pinned to the bottom of the scrolling area rather than the end of the
   content — a long list (categories, transactions, obligations) otherwise
   means scrolling past everything already read just to reach Done. */
.dialog--wide .dialog__actions {
  position: sticky;
  bottom: -1.5rem;
  margin: 0.5rem -1.5rem -1.5rem;
  padding: 0.8rem 1.5rem;
  background: var(--panel);
  border-top: 1px solid var(--rule);
}

/* ------------------------------------------------------- category manager --- */
.catgroup {
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  margin-bottom: 0.75rem;
}
.catgroup__head {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  background: var(--surface-quiet);
  border-bottom: 1px solid var(--rule);
}
.catgroup__name {
  flex: 1;
  font: inherit; font-weight: 600;
  border: 1px solid transparent;
  background: none;
  padding: 0.2rem 0.35rem;
  border-radius: var(--radius-sm);
}
.catgroup__name:hover, .catgroup__name:focus { border-color: var(--rule); background: var(--panel); }

.catgroup__rows { display: grid; gap: 0.4rem; padding: 0.6rem; }
.catrow { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.catrow__name {
  flex: 1 1 8rem;
  font: inherit;
  border: 1px solid transparent;
  background: var(--surface-quiet);
  padding: 0.2rem 0.35rem;
  border-radius: var(--radius-sm);
}
.catrow__name:hover, .catrow__name:focus { border-color: var(--rule); background: var(--panel); }
.catrow__target-type, .catrow__group {
  font: inherit; font-size: 0.78rem;
  padding: 0.2rem; border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--panel);
}
.catrow__target-amount {
  width: 5.5rem;
  font-family: var(--mono); text-align: right;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.2rem 0.35rem;
}

.addcat-form, .addgroup { display: flex; gap: 0.5rem; padding: 0.5rem 0.6rem; }
.addcat-form input, .addgroup input {
  flex: 1; font: inherit;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.3rem 0.5rem;
}

/* ------------------------------------------------------------------ ledger view --- */
.ledger-balance:empty { margin: 0; }
/* Balance and floor sit side by side where both apply, stacking when narrow. */
.ledger-balance { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: -0.2rem 0 0.6rem; }
.ledger-balance__field {
  flex: 1 1 12rem; min-width: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  background: var(--surface-quiet); border: 1px solid var(--rule); border-radius: var(--radius-sm);
}
.ledger-balance__label { font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.ledger-balance__input {
  font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right;
  width: 8rem; border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.2rem 0.4rem; background: var(--panel); color: inherit;
}
.ledger-balance__input:focus { border-color: var(--structure); outline: none; }

.starting-balance__row { flex-wrap: wrap; }
.starting-balance__amount { width: 6.5rem; }
.starting-balance__date {
  font: inherit; border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.2rem 0.4rem; background: var(--panel); color: inherit;
}
.starting-balance__clear {
  font: inherit; font-size: 0.75rem; color: var(--muted);
  background: none; border: none; padding: 0; cursor: pointer; text-decoration: underline;
}
.starting-balance__clear:hover { color: inherit; }

.starting-balance__toggle {
  font: inherit; font-size: 0.78rem; color: var(--muted);
  background: none; border: 1px dashed var(--rule); border-radius: var(--radius-sm);
  padding: 0.35rem 0.6rem; margin: -0.2rem 0 0.6rem; cursor: pointer;
}
.starting-balance__toggle:hover { color: inherit; border-color: var(--structure); }

.upcoming-payment__scheduled {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.8rem; color: var(--muted); white-space: nowrap;
}

.ledgerview { max-height: 60vh; overflow-y: auto; }
.ledgerview__cols, .ledgerrow {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr) 11rem 7.5rem 1.6rem;
  grid-template-areas: "date payee category amount delete";
  gap: 0.6rem;
  align-items: center;
  padding: 0.4rem 0.2rem;
}
/* Named by position so the same 5 elements (date, payee, category/account,
   amount, delete) can be reflowed into a stacked layout on narrow screens
   below without touching how app.js builds each row. */
.ledgerview__cols > :nth-child(1), .ledgerrow > :nth-child(1) { grid-area: date; }
.ledgerview__cols > :nth-child(2), .ledgerrow > :nth-child(2) { grid-area: payee; }
.ledgerview__cols > :nth-child(3), .ledgerrow > :nth-child(3) { grid-area: category; }
.ledgerview__cols > :nth-child(4), .ledgerrow > :nth-child(4) { grid-area: amount; }
.ledgerview__cols > :nth-child(5), .ledgerrow > :nth-child(5) { grid-area: delete; }
#xfer-picker-body { display: grid; gap: 0.35rem; max-height: 50vh; overflow-y: auto; }
.xfer-pick {
  display: flex; justify-content: space-between; align-items: baseline; gap: 0.7rem;
  width: 100%; text-align: left;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--panel); padding: 0.55rem 0.6rem;
  font: inherit; font-size: 0.85rem; color: inherit;
}
.xfer-pick:hover { border-color: var(--structure); background: var(--surface-quiet); }
.xfer-pick__main { display: grid; gap: 0.1rem; min-width: 0; }
.xfer-pick__acct { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xfer-pick__meta { font-size: 0.72rem; color: var(--muted); }

.inbox__transfer { margin-top: 0.2rem; justify-self: start; }

.ledgerrow__transfer {
  display: inline-block;
  font-size: 0.65rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); background: var(--surface-quiet);
  padding: 0.1rem 0.4rem; border-radius: 999px;
}
.ledgerrow__unlink { display: block; font-size: 0.68rem; margin-top: 0.1rem; }

.ledgerrow__delete {
  justify-self: end;
  background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 1.1rem; line-height: 1; padding: 0.2rem;
}
.ledgerrow__delete:hover { color: var(--short); }
.ledgerview__cols {
  font-size: 0.65rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--rule);
}
.ledgerrow { font-size: 0.85rem; }
.ledgerrow + .ledgerrow { border-top: 1px solid var(--rule); }
/* The name and "+ note" share a line of their own (the name truncates, the
   action never does); the memo and the note field each take a full line
   below. */
.ledgerrow__payee { min-width: 0; }
.ledgerrow__head { display: flex; align-items: baseline; gap: 0.45em; min-width: 0; }
.ledgerrow__name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ledgerrow__note-add { flex: 0 0 auto; }
.ledgerrow__memo { display: block; font-size: 0.68rem; color: var(--muted); }
/* `input[type="text"]` above is one attribute selector more specific than a
   bare class, so it was winning the cascade on border/background and this
   was rendering as a plain bordered box always — never the flat, borderless
   look meant to make an empty note read as "nothing here" rather than "a
   field waiting to be filled." Qualifying with the tag name ties its
   specificity and this rule's later source position wins the tie. */
input.ledgerrow__notes {
  display: block; width: 100%; min-width: 0; margin-top: 0.1rem;
  font: inherit; font-size: 0.72rem; color: var(--ink);
  border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 0.1rem 0.25rem; background: transparent;
}
.ledgerrow__notes::placeholder { color: var(--muted); }
.ledgerrow__notes:hover { border-color: var(--rule); background: var(--surface-quiet); }
.ledgerrow__notes:focus { border-color: var(--structure); background: var(--panel); outline: none; }
.ledgerrow .money { text-align: right; }
.ledgerrow .money[data-negative="true"] { color: var(--short); }
.ledgerrow select { font: inherit; font-size: 0.78rem; width: 100%; padding: 0.2rem; border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--panel); }
/* Same specificity fix as the note field above — .ledgerrow__amount-input is
   an input[type="text"] too, so its flat/muted look was losing the same way. */
.ledgerrow__date-input, input.ledgerrow__amount-input {
  font: inherit; font-size: 0.78rem; color: inherit;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 0.15rem 0.3rem; background: var(--surface-quiet); width: 100%;
}
.ledgerrow__date-input:hover, .ledgerrow__amount-input:hover { border-color: var(--rule); }
.ledgerrow__date-input:focus, .ledgerrow__amount-input:focus { border-color: var(--structure); background: var(--panel); outline: none; }
.ledgerrow__amount-input { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }

/* --------------------------------------------------------- split editor --- */
.category-cell { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.category-cell select { flex: 1 1 auto; min-width: 0; }
.split-start, .split-badge {
  font: inherit; font-size: 0.72rem; color: var(--muted);
  background: none; border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 0.2rem 0.4rem; cursor: pointer; white-space: nowrap;
}
.split-start:hover, .split-badge:hover { color: var(--ink); border-color: var(--structure); }
.split-badge { width: 100%; }

.split-row { display: grid; grid-template-columns: minmax(0, 1fr) 6.5rem 1.6rem; gap: 0.5rem; align-items: center; padding: 0.3rem 0; }
.split-row select { font: inherit; font-size: 0.85rem; padding: 0.3rem; border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--panel); width: 100%; }
.split-row__amount { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; width: 100%; border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 0.3rem 0.4rem; background: var(--panel); color: inherit; }
.split-row__remove { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0.2rem; }
.split-row__remove:hover { color: var(--short); }

.split-remainder { margin: 0.5rem 0 0; font-size: 0.85rem; font-weight: 600; }
.split-remainder[data-state="short"] { color: var(--short); }
.split-remainder[data-state="funded"] { color: var(--funded); }

/* Fixed rem-wide columns (5.5 + 11 + 7.5 = 24rem, plus gaps) don't fit inside
   a dialog capped at min(40rem, 94vw) -- category/payee overlapped and
   amount ran off the edge with nothing to scroll it into view. This isn't
   just a phone-width problem: the dialog only reaches its own 40rem cap
   once the *viewport* is around 42.6rem (94vw = 40rem at that point), so a
   30rem viewport breakpoint left a wide gap -- anything from a phone up to
   a genuinely large desktop window -- where the dialog was still narrower
   than the columns need, but this override wasn't active. 46rem is safely
   past where the dialog has reached full width with room to spare. Below
   it, stack each row instead of forcing the same columns into less space
   than they need -- date/amount/delete on a slim header line, the payee as
   the visually dominant line since it's what you actually read a ledger by,
   category/split/recurring controls demoted to a quieter line below since
   they're occasional actions, not the primary content. */
@media (max-width: 46rem) {
  .ledgerview__cols { display: none; }
  .ledgerrow {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "date amount delete"
      "payee payee payee"
      "category category category";
    row-gap: 0.3rem;
    padding: 0.7rem 0.2rem;
  }
  .ledgerrow > :nth-child(1) {
    font-size: 0.72rem; color: var(--muted); align-self: start; white-space: nowrap;
  }
  .ledgerrow__delete { align-self: start; }
  /* Back to inline flow here: the name wraps rather than truncating (it has
     a whole line, and there's no hover to reveal what an ellipsis hid), the
     memo follows it mid-line, and "+ note" floats to the right of the
     block's first line. display: contents lifts the name and the action
     out of their desktop wrapper so they flow with the memo. */
  .ledgerrow__payee { font-weight: 600; }
  .ledgerrow__head { display: contents; }
  .ledgerrow__name { white-space: normal; overflow: visible; text-overflow: clip; }
  /* input[type="text"] in the base stylesheet is more specific than a bare
     .ledgerrow__notes class, so it was winning the font-weight cascade and
     the note field was inheriting the payee's bold — qualify the selector
     with its parent to outrank it. */
  .ledgerrow__memo, .ledgerrow__payee .ledgerrow__notes { font-weight: 400; }
  /* The bank's own memo text is secondary to the payee name it's already
     redundant with most of the time — fold it onto the same line instead
     of giving it a full row of its own. */
  .ledgerrow__memo { display: inline; margin-left: 0.4em; }
  .ledgerrow__note-add { float: right; margin: 0 -0.35rem 0 0.4em; }
  .category-cell { margin-top: 0.05rem; }

  /* The balance/floor/upcoming-payment boxes read as label-beside-value
     on desktop; at this width that squeezed a real input into a sliver
     next to its own label, so stack label above value instead. */
  .ledger-balance__field { flex-direction: column; align-items: stretch; gap: 0.3rem; }
  .ledger-balance__input { width: 100%; }
  .starting-balance__amount { width: auto; flex: 1 1 auto; }
}

/* ------------------------------------------------------------------- cards --- */
.card {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem;
}
.card__title {
  margin: 0 0 0.75rem;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.accounts, .inbox { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.15rem; }

.accounts li {
  display: flex; justify-content: space-between; gap: 0.5rem;
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
  cursor: pointer;
}
.accounts li:hover { background: var(--surface-quiet); }
.accounts li:last-child { border-bottom: none; }
.accounts .money[data-negative="true"] { color: var(--short); }
.account__org { display: block; font-size: 0.68rem; color: var(--muted); }
.account__right { display: flex; align-items: baseline; gap: 0.4rem; }
.account__chevron { color: var(--muted); font-weight: 400; }

.inbox li {
  display: grid;
  gap: 0.3rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule);
}
.inbox li:last-child { border-bottom: none; }
.inbox__top { display: flex; justify-content: space-between; gap: 0.5rem; font-size: 0.85rem; }
.inbox__payee { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inbox__meta { font-size: 0.68rem; color: var(--muted); font-family: var(--mono); }
.inbox select { font: inherit; font-size: 0.78rem; width: 100%; padding: 0.25rem; border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--panel); }

.empty { margin: 0; font-size: 0.8rem; color: var(--muted); }

/* ------------------------------------------------------------------ toast --- */
.toast {
  position: fixed;
  left: 50%; bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  background: var(--structure);
  color: #fff;
  padding: 0.5rem 0.9rem;
  border-radius: var(--radius-sm);
  font-size: 0.82rem;
  box-shadow: 0 6px 20px rgba(28,26,22,0.25);
  z-index: 20;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
