/* SPACING: reach for a token — `--card-pad`, `--card-gap`, `--sp-1`…`--sp-6` —
   not a raw rem, and not the value in the rule above. Cards on the padding
   contract are enforced by `test/layout-tokens.test.js`; the contract and the
   list of what is on it are in `docs/visual-language.md` → Tokens. Raw values
   still in this file are legacy from a 2px grid, not a precedent to follow. */
/* Section control bars (Expenses / Income) and per-section layout. */
/* ══ Section control bar (Expenses / Income) ══════════════════════ */
.sec2-bar{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;margin:2px 0 1.25rem;}
.sec2-month{
  display:flex;
  align-items:center;
  gap:0.375rem;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:var(--radius-sm);
  padding:0.3125rem 0.375rem;
}
.sec2-month select{
  border:0 !important;
  background:transparent;
  font-size:var(--fs-body);
  font-weight:600;
  padding:var(--sp-1) 0.375rem;
  color:var(--text);
  cursor:pointer;
}
.sec2-nav{
  width:1.75rem;
  height:1.75rem;
  border-radius:var(--radius-sm);
  border:0;
  background:transparent;
  color:var(--muted);
  font-size:var(--fs-figure);
  line-height:1;
  cursor:pointer;
}
.sec2-nav:hover{color:var(--text);}
.sec2-search{position:relative;display:flex;align-items:center;flex:1;min-width:13.75rem;max-width:420px;}
.sec2-search svg{position:absolute;inset-inline-start:12px;width:1rem;height:1rem;color:var(--muted);pointer-events:none;}
/* Same corner as the `.sec2-month` stepper beside it: both are controls in one
   bar, matched at the control radius (--radius-sm). */
.sec2-search input{
  width:100%;
  padding-block:0.625rem;padding-inline:2.25rem 2.125rem;
  border-radius:var(--radius-sm);
  border:1px solid var(--border);
  background:var(--surface);
  font-size:var(--fs-body);
}
.sec2-search .exp-search-clear{
  position:absolute;
  inset-inline-end:8px;
  border:0;
  background:transparent;
  color:var(--muted);
  cursor:pointer;
  font-size:var(--fs-label);
  padding:var(--sp-1) 0.375rem;
  border-radius:50%;
}
/* ── Expenses: the month's totals on top, then two lists of expense cards ──────
   Summary (narrow) beside By category (the rest), then Recurring beside One-off. Each
   expense is a small card in its list: day chip, name, category and tags, amount, ⋮.
   Colour is meaning: the amount is money out (--neg); a recurring card that has not
   been charged yet is dashed and quiet, because it has not gone out. */
.exp-top{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:var(--split-gap);
  align-items:start;
  margin-bottom:var(--sp-5);
}
.exp-top > div > .card-head:first-child{margin-top:0;}
.exp-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--split-gap);align-items:start;}
.exp-group{border-radius:var(--radius-lg);container-type:inline-size;}
.exp-ghead{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--sp-3);margin-bottom:var(--sp-3);}
.exp-ghead > div{min-width:0;}
.exp-gtitle{font-size:var(--fs-label);font-weight:500;color:var(--text);}
.exp-gtotal{font-size:var(--fs-figure);font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap;}
.exp-ghead .cap{display:block;font-size:var(--fs-meta);font-weight:500;color:var(--muted);margin-top:var(--sp-1);}
.exp-sorts{display:flex;flex-wrap:wrap;gap:var(--sp-1);margin-bottom:var(--sp-3);}
.exp-list{display:flex;flex-direction:column;gap:var(--sp-2);}
.exp-list > .empty-state{margin:0;max-width:none;background:none;box-shadow:none;border-style:dashed;}
.exp-tile{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  align-items:start;
  gap:var(--sp-3);
  padding-block:var(--sp-2);
  padding-inline:var(--sp-2) 0;
  background:var(--surface2);
  border:1px solid var(--border);
  border-radius:var(--radius-sm);
  overflow:hidden;
  transition:border-color .15s;
}
.exp-tile:has(.exp-entries > .exp-entry:only-child){align-items:center;}
.exp-tile:hover{border-color:var(--border-hover);}
/* A day's expenses, one line each beside the day; a hairline between lines. */
.exp-entries{min-width:0;display:flex;flex-direction:column;}
.exp-entry{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto 2.125rem;
  align-items:center;
  column-gap:var(--sp-2);
  padding-inline-end:var(--sp-3);
}
.exp-new-mark{display:flex;align-items:center;justify-content:flex-end;min-width:0;}
.exp-entry + .exp-entry{margin-top:var(--sp-2);padding-top:var(--sp-2);border-top:1px solid var(--border);}
.exp-day{
  width:2.5rem;height:2.5rem;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:var(--radius-sm);
  line-height:1;
}
.exp-day b{font-size:var(--fs-body);font-weight:600;font-variant-numeric:tabular-nums;}
.exp-day i{font-style:normal;font-size:var(--fs-meta);font-weight:500;color:var(--muted);margin-top:var(--sp-1);}
.exp-main{min-width:0;}
.exp-name{font-size:var(--fs-body);}
.exp-name-row{display:flex;align-items:baseline;gap:var(--sp-1);min-width:0;}
.exp-name-row .exp-name{min-width:0;}
.exp-recur{flex-shrink:0;color:var(--muted);}
/* Today or yesterday: a small mark after the amount; the third column keeps amounts aligned. */
.exp-new{
  padding:0.125rem 0.3125rem;
  border-radius:4px;
  background:color-mix(in srgb,var(--pos) 28%,var(--tint-green));
  color:var(--pos);
  font-size:0.625rem;
  font-weight:700;
  letter-spacing:.08em;
  line-height:1.15;
  text-transform:uppercase;
}
.exp-tile .exp-amt{font-size:var(--fs-body);}
.exp-name .cat-mark{display:flex;font-weight:600;}
.exp-meta{
  display:flex;flex-wrap:wrap;align-items:center;
  column-gap:var(--sp-2);row-gap:var(--sp-1);
  margin-top:var(--sp-1);
  font-size:var(--fs-meta);font-weight:500;color:var(--muted);
}
.exp-meta .cat-mark{display:inline-flex;max-width:100%;font-size:var(--fs-meta);}
.exp-meta .desc-tags{margin-top:0;flex-wrap:wrap;}
.exp-meta .pill.source{max-width:8rem;overflow:hidden;text-overflow:ellipsis;}
.exp-amt{font-size:var(--fs-figure);font-weight:600;text-align:end;font-variant-numeric:tabular-nums;}
.exp-amt .rec-fill-note{font-size:var(--fs-body);}
.exp-tile.pending,.exp-tile.needs-amt{background:transparent;border-style:dashed;}
.exp-tile.pending .exp-day,.exp-tile.needs-amt .exp-day{background:transparent;border-style:dashed;}
.exp-entry.pending .exp-amt{color:var(--muted);font-size:var(--fs-body);}
.exp-tile.needs-amt{border-color:var(--chart-neg);}
/* The month's cards scroll inside the group (same pattern as Recent transactions). */
.exp-list-scroll{position:relative;flex:1;min-height:0;}
.exp-group .exp-list-scroll > .exp-list{
  position:absolute;
  inset:0;
  overflow:auto;
  scrollbar-width:none;
  -ms-overflow-style:none;
  padding-block-end:var(--sp-2);
}
.exp-group .exp-list-scroll > .exp-list::-webkit-scrollbar{display:none;}
.exp-group .exp-list-scroll > .exp-list > .exp-tile{flex-shrink:0;}
/* A tile being edited is the whole card: fields, then the save and cancel squares. */
.exp-tile.editing{display:block;padding:var(--sp-3);}
.exp-tile.editing .entry-edit-actions{margin-top:var(--sp-3);}
/* A narrow card keeps the amount but gives the name every pixel the rest leaves. */
@container (max-width:22rem){
  .exp-tile,.exp-entry{gap:var(--sp-2);}
  .exp-day{width:2.25rem;height:2.25rem;}
}
/* By category is a grid of tiles, largest first, every category visible: the name on the
   first line with its share of the month, then a bar against the largest and the amount.
   The name never shares its line with the amount, so it keeps the whole tile. As many
   columns as fit, so a month with twice the categories adds rows slowly. */
#exp-cat-rows{display:grid;grid-template-columns:repeat(auto-fill,minmax(13rem,1fr));column-gap:var(--sp-2);}
#exp-cat-rows .exp-cat-row{padding-block:var(--sp-2);border-radius:var(--radius-sm);border-bottom:0;}
#exp-cat-rows button.exp-cat-row::before{border-radius:inherit;}
#exp-cat-rows button.exp-cat-row.on::after{opacity:0;width:0;}
#exp-cat-rows .exp-cat-top{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:"name share" "tick amt";
  font-size:var(--fs-label);
  align-items:center;
  column-gap:var(--sp-3);
  row-gap:var(--sp-1);
  margin-bottom:0;
}
#exp-cat-rows .exp-cat-top .cat-mark{grid-area:name;}
#exp-cat-rows .exp-cat-tick{grid-area:tick;display:block;height:4px;border-radius:2px;overflow:hidden;background:color-mix(in srgb,var(--c) 16%,transparent);}
#exp-cat-rows .exp-cat-tick > i{display:block;height:100%;min-width:3px;width:calc(var(--w) * 1%);background:var(--c);}
#exp-cat-rows .exp-cat-amt,
#exp-cat-rows .exp-cat-share{
  text-align:end;
  font-variant-numeric:tabular-nums;
  white-space:nowrap;
}
#exp-cat-rows .exp-cat-amt{grid-area:amt;}
#exp-cat-rows .exp-cat-share{grid-area:share;color:var(--muted);font-weight:500;font-size:var(--fs-meta);}
/* Wide: Summary is a narrow card beside the category list. Compact: the two stack, and
   the Summary lays its ring and its sources side by side so it does not sit half empty. */
@media (min-width:1151px){
  .exp-top{grid-template-columns:minmax(19rem,24rem) minmax(0,1fr);}
}
@media (max-width:1150px){
  .exp-sum-card{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--sp-5);align-items:center;}
  .exp-sum-card .exp-sum-src{margin-top:0;padding-top:0;border-top:0;padding-inline-start:var(--sp-5);border-inline-start:1px solid var(--border);}
  #panel-expenses .exp-list-scroll{flex:none;max-height:clamp(240px,45vh,28rem);}
}
/* Income: one card beside the summary — height follows the rows; only cap and
   scroll when the month has many lines (expense lists stretch to fill the pane). */
#panel-income .exp-list-scroll{flex:none;min-height:0;}
#panel-income .exp-group .exp-list-scroll > .exp-list{
  position:static;
  inset:auto;
  overflow:auto;
  max-height:clamp(240px,50vh,32rem);
}
@media (max-width:760px){
  .exp-top,.exp-pair{grid-template-columns:minmax(0,1fr);}
  .exp-sum-card{display:block;}
  .exp-sum-card .exp-sum-src{margin-top:var(--sp-4);padding-top:var(--sp-3);padding-inline-start:0;border-top:1px solid var(--border);border-inline-start:0;}
}
/* tables live inside a card now — kill the old wrapper chrome and let the card
   provide the border, so headers/rows align with every other list in v9 */
.sec2-tablecard{overflow:hidden;}
.sec2-tablecard table{margin:0;}
.sec2-tablecard thead th{background:transparent;position:static;}
.sec2-tablecard td:first-child,.sec2-tablecard th:first-child{padding-inline-start:var(--sp-4);}
.sec2-tablecard td:last-child,.sec2-tablecard th:last-child{padding-inline-end:var(--sp-4);}
.card-head .cap b{color:var(--text);font-weight:600;}
/* rail category rows with an inline share bar */
.exp-cat-row{
  display:block;
  width:100%;
  margin:0;
  padding:0.6875rem var(--sp-4);
  border:0;
  border-bottom:1px solid var(--border);
  border-radius:0;
  background:transparent;
  color:inherit;
  font:inherit;
  text-align:inherit;
  cursor:pointer;
  transition:background .12s;
}
/* Clickable category filter rows: full-width wash on ::before; only opacity animates
   (background is fixed per row) so hover stays on the compositor. Selected row uses a
   stronger wash plus ::after for the start bar. Kept out of the gliding wash (js/08-ui.js). */
button.exp-cat-row{position:relative;z-index:1;isolation:isolate;}
button.exp-cat-row::before,button.exp-cat-row::after{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  transform:translateZ(0);
}
button.exp-cat-row::before{
  background:color-mix(in srgb,var(--c) 7%,var(--surface2));
  opacity:0;
  transition:opacity .12s var(--hover-ease);
}
button.exp-cat-row::after{inset:auto;width:0;opacity:0;}
button.exp-cat-row:hover::before,button.exp-cat-row:focus-visible::before{opacity:1;}
button.exp-cat-row:focus-visible{outline:2px solid color-mix(in srgb,var(--accent) 40%,transparent);outline-offset:-2px;}
button.exp-cat-row.on{background:transparent;box-shadow:none;}
button.exp-cat-row.on::before{
  background:color-mix(in srgb,var(--c) 14%,var(--surface2));
  opacity:1;
  transition:none;
}
button.exp-cat-row.on::after{
  inset-inline-start:0;top:0;bottom:0;width:3px;inset-inline-end:auto;
  background:var(--c);
  opacity:1;
}
#exp-cat-rows:has(.exp-cat-row.on) .exp-cat-row:not(.on):not(:hover){opacity:.6;}
@media (prefers-reduced-motion: reduce){
  button.exp-cat-row::before{transition:none;}
}
.exp-cat-row:last-child{border-bottom:0;}
#an-merchants .exp-cat-row{border-bottom:0;cursor:default;}
.exp-cat-top{display:flex;justify-content:space-between;align-items:center;gap:var(--sp-3);font-size:var(--fs-body);margin-bottom:var(--sp-1);}
.exp-cat-top .cat-mark{flex:1;min-width:0;}
.exp-cat-top b{font-weight:600;font-variant-numeric:tabular-nums;}
/* the old source card uppercased its label for the wide grid; in the rail it
   reads as shouting next to normal-case rows */
/* The row is one statement, not six columns. The menu is the only fixed
   column, so a long note cannot shove it off the card. clip, not auto:
   overflow-x:auto forces overflow-y to auto and a scrollbar shows inside
   the rounded corner. */
#panel-expenses .sec2-tablecard,#panel-income .sec2-tablecard{overflow:clip;}
#panel-expenses .sec2-tablecard table,#panel-income .sec2-tablecard table{
  width:100%;
  table-layout:fixed;
}
#panel-expenses .sec2-tablecard .row-actions,
#panel-expenses .sec2-tablecard .stmt-menu,
#panel-income .sec2-tablecard .row-actions,
#panel-income .sec2-tablecard .stmt-menu{width:3.25rem;}
.stmt-sorts{font-weight:500;text-align:start;}
button.stmt-sort{
  background:none;
  border:0;
  padding:0;
  margin-inline-end:var(--sp-3);
  color:var(--muted);
  font-family:'Quicksand','Heebo',system-ui,sans-serif;
  font-size:var(--fs-label);
  font-weight:500;
  cursor:pointer;
}
button.stmt-sort:last-child{margin-inline-end:0;}
button.stmt-sort:hover{color:var(--text);}
.stmt-dayhead td{
  color:var(--muted);
  font-family:'Quicksand','Heebo',system-ui,sans-serif;
  font-size:var(--fs-meta);
  font-weight:600;
  padding-block:var(--sp-3) var(--sp-1);
}
#panel-expenses .stmt-cell,#panel-income .stmt-cell{
  padding-block:var(--sp-2);
  white-space:normal;
  vertical-align:middle;
}
/* One line per row, one centre line for every part: day, dot + category (fixed width,
   so the boxes start at the same x down the card), description box filling the rest,
   tags, amount. Narrow cards drop the tags onto a second line. */
.stmt{display:flex;align-items:center;column-gap:var(--sp-3);row-gap:var(--sp-1);flex-wrap:wrap;min-height:2rem;}
.stmt > *{min-width:0;}
.stmt-day{
  flex:0 0 1.75rem;
  color:var(--muted);
  font-family:'Quicksand','Heebo',system-ui,sans-serif;
  font-size:var(--fs-meta);
  font-weight:600;
  font-variant-numeric:tabular-nums;
  text-align:center;
}
.stmt > .cat-mark{flex:0 0 9rem;font-family:'Quicksand','Heebo',system-ui,sans-serif;font-weight:600;}
.stmt > .desc-text{flex:0 1 auto;min-width:0;}
.stmt-amt{flex:0 0 auto;margin-inline-start:auto;min-width:6.5rem;text-align:end;font-size:var(--fs-figure);}
.stmt-amt .rec-fill-note{color:var(--accent);font-size:var(--fs-body);}
/* Same shape as the month control: a rounded field only as wide as its text. */
.stmt-desc{
  flex:0 1 auto;
  display:block;
  width:max-content;
  max-width:100%;
  min-width:0;
  overflow:hidden;
  /* Tinted by the row's category (--c on the cell) with a heavier edge at the start, so the
     note reads as belonging to its category rather than as a generic input. */
  background:color-mix(in srgb,var(--c) 9%,var(--input-bg));
  border:1px solid color-mix(in srgb,var(--c) 28%,var(--border));
  border-inline-start:3px solid color-mix(in srgb,var(--c) 72%,var(--surface));
  border-radius:var(--radius-sm);
  /* Sized with the tag pill beside it: same line-height and block padding, text one
     step above the pill's, so the two read as one row of equal-height marks. */
  padding:0.125rem var(--sp-3);
  font-size:var(--fs-label);
  line-height:1.35;
}
.stmt .pill.source{padding-block:0.1875rem;}
.stmt-desc .desc-text{font-size:inherit;line-height:inherit;}
.stmt-end{display:flex;align-items:center;justify-content:flex-start;gap:var(--sp-2);flex:0 1 auto;min-width:0;max-width:40%;}
.stmt-end:empty{display:none;}
.stmt-end .desc-tags{margin-top:0;flex-wrap:nowrap;}
.stmt-end .pill.source{max-width:9rem;overflow:hidden;text-overflow:ellipsis;}
@media (max-width:640px){
  .stmt-end{order:5;flex:1 0 100%;max-width:none;justify-content:flex-start;}
}
.stmt-since{
  color:var(--muted);
  font-family:'Quicksand','Heebo',system-ui,sans-serif;
  font-size:var(--fs-meta);
  font-weight:500;
  white-space:nowrap;
}
/* Edit boxes use the table columns, so each one starts where its header does.
   The day select fits the date column. The menu column stays as wide as the
   ⋮, and the two square buttons hang off it while the row is open. A second
   line holds a rule's start month, year and cadence. */
tr.editing-row + tr.editing-row{border-top:0;}
tr.editing-row + tr.editing-row td{padding-top:0;}
#panel-expenses .entry-edit-actions,#panel-income .entry-edit-actions{display:flex;gap:var(--sp-1);align-items:center;justify-content:flex-end;}
/* The menu column is only as wide as the ⋮. While a row is open, the two
   buttons hang off that cell toward the field beside them, and that field
   keeps a gap so the buttons do not cover it. */
tr.editing-row td.row-actions{position:relative;overflow:visible;padding-inline:0;vertical-align:top;}
tr.editing-row .entry-edit-actions{
  position:absolute;
  inset-inline-end:0.375rem;
  top:50%;
  transform:translateY(-50%);
  z-index:2;
}
#panel-expenses tr.editing-row td.entry-edit-cell,
#panel-income tr.editing-row td.entry-edit-cell{padding-inline-end:2.75rem;}
.entry-edit-fields > input,
.entry-edit-fields > select,
.entry-edit-fields > .ee-amt-view{flex:1 1 8rem;min-width:0;width:auto;}
.entry-edit-fields > .select-day{flex:0 0 4.5rem;}
/* Proportional widths: short fields stay short, the description takes the slack. */
.entry-edit-fields > select:not(.select-day){flex:1 1 7rem;}
.entry-edit-fields > .ee-amt-view,.entry-edit-fields > input[type=number]{flex:0 1 6.5rem;}
.entry-edit-fields > input[type=text]{flex:2 1 10rem;font-size:var(--fs-label);}
.entry-edit-extra{margin-top:var(--sp-2);}
#panel-expenses .entry-edit-meta,#panel-income .entry-edit-meta{
  display:flex;
  flex-wrap:wrap;
  gap:var(--sp-2);
  align-items:center;
  min-width:0;
  margin-top:var(--sp-2);
}
#panel-expenses .entry-edit-meta > select,
#panel-expenses .entry-edit-meta > input,
#panel-income .entry-edit-meta > select,
#panel-income .entry-edit-meta > input{width:auto;min-width:7rem;flex:0 0 auto;}
#panel-expenses .entry-edit-meta > .rule-start-year,
#panel-income .entry-edit-meta > .rule-start-year{width:4.625rem;min-width:0;}
#panel-income .entry-edit-meta .rxe-fixed-lbl{margin-top:0;}
#panel-expenses .tip-if-trunc{position:relative;min-width:0;}
#panel-expenses .cat-mark,#panel-income .cat-mark{
  display:flex;
  max-width:100%;
  white-space:nowrap;
}
/* [hidden] is only display:none at UA-stylesheet strength; the flex rules for
   .exp-src-cards outrank it, so state it explicitly. */
[hidden]{display:none !important;}
/* month stepper shrunk to sit inside a section label */
.sec2-month.inline{padding:2px 0.1875rem;border-radius:var(--radius-sm);}
.sec2-month.inline select{font-size:var(--fs-meta);padding:2px var(--sp-1);font-weight:600;}
.sec2-month.inline .sec2-nav{width:1.375rem;height:1.375rem;font-size:var(--fs-body);}
.sec2-note{color:var(--muted);font-size:var(--fs-meta);font-weight:400;line-height:1.45;}
#panel-analytics .chart-legend{margin-top:0.625rem;}
/* long meta text next to a section label must shrink, not spill */
.card-head .cap{letter-spacing:0;text-transform:none;font-weight:400;font-size:var(--fs-meta);color:var(--muted);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* Trends headline: two cards. The total spent is the one figure the section is
   about, so it gets a card to itself; the two baselines it is measured against
   share the card beside it, divided by the hairline every `.kpi-strip` uses.
   They were all three in one joined strip, which set a headline and its own
   footnotes at the same rank.
   Every column here is `max-content`, so the row is as wide as the figures and
   no wider. Stretched across the section the cards held 533px for a 132px total
   and 481px per 146px baseline — three short numbers spaced out until they
   stopped reading as one row. A card's job is to hold its number.
   Three rows — name, figure, sub-line — are shared by both cards through
   subgrid, so the total and the two deltas sit on one baseline across the gap.
   Both cards carry the same padding, which is what keeps the first row honest.
   The cells carry no chip — a delta is the cell's own figure — so the
   `white-space:nowrap` that kept those chips on one line went with them. */
.tr-tiles{
  display:grid;
  grid-template-columns:max-content max-content;
  grid-template-rows:repeat(3,auto);
  justify-content:start;
  gap:var(--card-gap);
  margin-bottom:1.375rem;
}
/* A five-digit month filled this card to the padding. The floor is a little
   wider than that figure; a larger month still grows past it. */
.tr-tiles > .kpi.lead{min-width:13.5rem;}
.tr-tiles > .kpi,.tr-tiles > .tr-cmp{
  grid-row:span 3;
  display:grid;
  grid-template-rows:subgrid;
  row-gap:0;
  align-content:start;
  min-height:5.5rem;
  padding:var(--card-pad);
}
/* Before the month has any spending there is one card, and it takes the row. */
.tr-tiles > .kpi:only-child{grid-column:1/-1;}
/* Both cards inset their text by `--card-pad`, the same as every other card on
   the page — a KPI card is not a different kind of card.
   Two equal halves on a floor, not two content-width ones. Each half used to be
   as wide as whichever caption it happened to print, so the halves differed by
   up to 52px, the hairline slid between them, and which one looked larger
   flipped from month to month. Worse, the whole card breathed 284→332px as you
   paged months, because the current month says "same stretch" where a past one
   says "Jul". A Level 4 caption was setting the geometry of a Level 2 figure.
   `1fr` twice makes the halves equal whatever they hold; the floor clears the
   longest caption this section prints at ordinary amounts (10.07rem — measured,
   "+67% · same stretch 18,596.22"), so paging months no longer resizes the card
   at all. A six-figure month still grows past it, and then both halves grow
   together and stay equal, which is the part that matters. */
.tr-cmp{grid-template-columns:repeat(2,minmax(10.25rem,1fr));column-gap:var(--sp-6);}
/* The shared hairline is offset for a 1.25rem gap (06-overview.css); centre it
   in this one. */
.tr-cmp > .kpi + .kpi::before{inset-inline-start:-1rem;}
.tr-cmp > .kpi{grid-row:span 3;display:grid;grid-template-rows:subgrid;row-gap:0;}
/* One rhythm down every cell: the name sits on the bottom of its row, the
   figures share a baseline whatever their size, and the sub-line hangs from the
   top of the last row. The gaps are set here rather than left to `.kpi`'s own
   margins, which gave 6px under the total and 16px under a delta — the same
   three lines reading as two different stacks. */
.tr-tiles .kpi > .k{grid-row:1;align-self:end;}
.tr-tiles .kpi > .v{grid-row:2;align-self:center;margin-top:0.375rem;}
.tr-tiles .kpi > .s{grid-row:3;align-self:start;margin-top:0.375rem;}
/* A content-width row fits far longer than a stretched one did, so it holds to
   760px — where the shared hairline turns horizontal (03-base-layout.css) and the
   two baselines stack with it. One column means there is nothing left to line up
   across, so the subgrid unwinds — left in place, both cells would claim the same
   three rows and print on top of each other. */
@media (max-width:760px){
  .tr-tiles{grid-template-columns:1fr;}
  .tr-tiles > .tr-cmp{grid-template-columns:1fr;row-gap:var(--sp-4);grid-template-rows:none;}
  /* On its side, centred in the row gap — the same turn the Overview's KPI card
     makes (06-overview.css). Each stacking strip states it: the generic form
     would also hit the strips that stay in columns at this width. */
  .tr-cmp > .kpi + .kpi::before{
    inset-inline-start:var(--sp-3);
    inset-inline-end:var(--sp-3);
    top:calc(var(--sp-4) / -2);
    bottom:auto;
    width:auto;
    height:1px;
  }
  .tr-cmp > .kpi{grid-row:auto;display:flex;flex-direction:column;}
  .tr-cmp .kpi > .k,.tr-cmp .kpi > .v,.tr-cmp .kpi > .s{grid-row:auto;align-self:stretch;}
}
/* Inside a section label the legend is one inline item on that line, so it drops
   the top margin it carries when it stands on its own — that margin made the
   left label 12px taller than "Biggest movers" and started the two cards of the
   band at different heights. */
.card-head .tr-legend{
  display:inline-flex;
  align-items:center;
  gap:0.375rem;
  margin-top:0;
  text-transform:none;
  letter-spacing:0;
  font-weight:400;
}
/* movers + subscription rows sit in the 330px rail — let them use two lines
   instead of squeezing name, monthly and yearly onto one */
.split-rail #tr-movers .tr-row{display:block;padding:0.6875rem 0;}
.split-rail #tr-movers .tr-cat{
  display:flex;
  align-items:center;
  gap:0.4375rem;
  font-size:var(--fs-body);
  margin-bottom:var(--sp-1);
}
.split-rail #tr-movers .tr-right{display:flex;justify-content:space-between;gap:var(--sp-2);padding-inline-start:var(--sp-4);}
.split-rail #tr-movers .tr-prevnext{font-size:var(--fs-meta);color:var(--muted);white-space:nowrap;}
.split-rail #tr-movers .tr-delta{font-size:var(--fs-meta);white-space:nowrap;}
.split-rail .subs-row{flex-wrap:wrap;row-gap:2px;padding:0.6875rem var(--sp-4);border-bottom:1px solid var(--border);}
.split-rail .subs-row:last-of-type{border-bottom:0;}
.split-rail .subs-nm{flex:1 1 100%;white-space:normal;}
.split-rail #exp-subs .empty-state{padding:1.375rem var(--sp-4);}
/* Yearly: the best/worst tiles stack full width in the rail */
/* Category Trends moved into the rail — one sparkline per row, not a tiling grid */
/* Recurring bills now render inside Analytics too */
#panel-analytics #exp-subs-card{overflow:clip;}
#panel-analytics #exp-subs .empty-state{padding:1.375rem var(--sp-4);}
/* Recurring bills: name, tags, yearly/monthly across the width. Padding used to
   live on .split-rail .subs-row; the card left the rail, so those rules miss. */
#panel-analytics #exp-subs .subs-row{
  flex-direction:row;
  align-items:center;
  gap:var(--sp-2) var(--sp-3);
  flex-wrap:wrap;
  padding:0.6875rem var(--sp-4);
  border-bottom:1px solid var(--border);
}
#panel-analytics #exp-subs .subs-row:last-of-type,
#panel-analytics #exp-subs .subs-row:nth-last-child(2){border-bottom:0;}
#panel-analytics #exp-subs .subs-nm{flex:1 1 8rem;min-width:0;}
#panel-analytics #exp-subs .cat-mark{font-size:var(--fs-meta);}
#panel-analytics #exp-subs .subs-tags{display:flex;flex-wrap:wrap;align-items:center;gap:0.3125rem;}
#panel-analytics #exp-subs .subs-est{margin-inline-start:auto;font-size:var(--fs-label);color:var(--muted);font-variant-numeric:tabular-nums;}
#panel-analytics #exp-subs .subs-est b{color:var(--text);font-weight:600;}
/* Row hover: only Recurring bills has one in Analytics, and it is the wash every
   list row in the app uses — --surface2 in 0.12s, as tbody tr:hover on the Expenses
   tab and the Recent transactions rows on the Overview. Top merchants is a chart,
   not a list of things to point at, so it has none. The card clips so the first
   and last row's wash follows its rounded corners. */
#panel-analytics .split-rail > .card{overflow:clip;}
#panel-analytics .catbar-row{padding:0.4375rem 0;margin:0 0 0.125rem;}
#panel-analytics .catbar-row:last-child{margin-bottom:0;}
/* Trends: Category Trends caps at four columns (4×2 with eight categories) so a
   wide window does not stretch into five or six skinny cells. */
#panel-trends .spark-grid{grid-template-columns:repeat(4,minmax(0,1fr));}
@media (max-width:1150px){
  #panel-trends .spark-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
}
@media (max-width:560px){
  #panel-trends .spark-grid{grid-template-columns:minmax(0,1fr);}
}
#panel-trends .tr-band{align-items:stretch;}
#panel-trends .tr-band .split-main,#panel-trends .tr-band .split-rail{display:flex;flex-direction:column;}
#panel-trends .tr-band .card{flex:1;}
#panel-trends > .card-head{margin-top:var(--sp-5);white-space:nowrap;}
#panel-trends > .card-head .cap{min-width:0;overflow:hidden;text-overflow:ellipsis;}
/* Analytics: Daily + merchants stretch; Items + Recurring sit start-aligned so
   the pie can grow under the picker without stretching Recurring. */
#panel-analytics .an-band{align-items:stretch;}
#panel-analytics .an-band + .an-rec-items{margin-top:var(--sp-5);}
#panel-analytics .an-band .split-main,#panel-analytics .an-band .split-rail{display:flex;flex-direction:column;}
#panel-analytics .an-band .card{flex:1;}
#panel-analytics .an-rec-items{
  grid-template-columns:minmax(0,1fr) minmax(var(--rail-min),var(--rail-max));
  align-items:start;
}
#panel-analytics .an-rec-items .split-main,#panel-analytics .an-rec-items .split-rail{display:flex;flex-direction:column;}
/* Picker always; pie + legend open under it once a category has items. */
#panel-analytics .an-items .card{
  display:flex;
  flex-direction:column;
  gap:var(--sp-3);
}
#panel-analytics .an-items #cat-items-cat{width:100%;}
#panel-analytics .an-items #cat-items-chart-wrap{position:relative;}
#panel-analytics .an-items.has-pie .card{
  display:grid;
  grid-template-columns:minmax(10rem,14rem) minmax(0,1fr);
  column-gap:var(--sp-5);
  row-gap:var(--sp-3);
  align-items:center;
}
#panel-analytics .an-items.has-pie #cat-items-cat{grid-column:1/-1;}
#panel-analytics .an-items.has-pie #cat-items-chart-wrap{height:12.5rem;}
#panel-analytics .an-items #cat-items-chart{height:100%;justify-content:center;}
@media (max-width:1150px){
  #panel-analytics .an-band.split{
    grid-template-areas:"main" "rail";
  }
  #panel-analytics .an-band .split-main{grid-area:main;}
  #panel-analytics .an-band .split-rail{grid-area:rail;}
  #panel-analytics .an-rec-items{
    grid-template-columns:minmax(0,1fr);
    grid-template-areas:"rail" "main";
  }
  #panel-analytics .an-rec-items .split-rail{grid-area:rail;}
  #panel-analytics .an-rec-items .split-main{grid-area:main;}
}
@media (max-width:760px){
  #panel-analytics .an-items.has-pie .card{grid-template-columns:minmax(0,1fr);}
}
/* Daily Balance is stretched to Top Merchants' height; let its chart take the extra. */
/* Two-column layout only (.split stacks at 1150px), where there is height to fill. */
@media (min-width:1151px){
  #panel-analytics .an-band .card:has(#daily-chart){display:flex;flex-direction:column;}
  #panel-analytics .an-band .chart-wrap:has(#daily-chart){flex:1;min-height:320px;height:auto !important;}
}
/* The line and bar charts of Analytics, Yearly, Forecast and Planner (svgChart in
   js/14-chart-helpers.js) are the Overview spend chart's look — its guide, knob, tip
   and hit layer are the .ov-spend-* pieces from 05-overview.css — with a value scale
   and, where a chart needs them, a filled area, a reference line and marked points.
   Dots on the line are opt-in. A chart fills its chart-wrap, which sets the height. */
.chart-wrap-h320{height:20rem;}
.chart-wrap-h280{height:17.5rem;}
.chart-wrap-h400{height:25rem;}
.chart-wrap-h360{height:22.5rem;}
.ch-chart{position:absolute;inset:0;direction:ltr;}
/* The value scale takes a gutter of its own (3.75rem) so no label sits on the line. */
.ch-plot{position:absolute;inset-block:0 18px;inset-inline:3.75rem 0;}
.ch-chart .ov-spend-hit{inset-inline-start:3.75rem;}
.ch-plot svg{display:block;width:100%;height:100%;}
.ch-chart.on .ov-spend-guide,
.ch-chart.on .ov-spend-knob,
.ch-chart.on .ov-spend-tip{opacity:1;}
.ch-dot{
  position:absolute;
  width:0.375rem;
  height:0.375rem;
  border-radius:50%;
  transform:translate(-50%,-50%);
  pointer-events:none;
}
/* A point the chart is about (the month a trip is paid): larger, with a caption. */
.ch-mark{
  position:absolute;
  width:0.75rem;
  height:0.75rem;
  border-radius:50%;
  border:2px solid var(--surface);
  transform:translate(-50%,-50%);
  pointer-events:none;
}
.ch-mark-label{
  position:absolute;
  transform:translate(-50%,-170%);
  font-size:var(--fs-micro);
  font-weight:600;
  white-space:nowrap;
  font-variant-numeric:tabular-nums;
  pointer-events:none;
}
.ch-mark-label.below{transform:translate(-50%,80%);}
.ch-ref-label{
  position:absolute;
  inset-inline-end:0.25rem;
  transform:translateY(-120%);
  font-size:var(--fs-micro);
  white-space:nowrap;
  font-variant-numeric:tabular-nums;
  pointer-events:none;
}
/* Each value sits in the gutter, level with its gridline. */
.ch-y{
  position:absolute;
  inset-inline-start:-3.75rem;
  width:3.25rem;
  text-align:end;
  transform:translateY(-50%);
  font-size:var(--fs-micro);
  line-height:1.2;
  color:var(--muted);
  font-variant-numeric:tabular-nums;
  pointer-events:none;
}
.ch-axis{
  position:absolute;
  inset-inline:3.75rem 0;
  bottom:0;
  height:18px;
  font-size:var(--fs-micro);
  color:var(--muted);
  pointer-events:none;
}
.ch-axis > span{position:absolute;top:0.25rem;transform:translateX(-50%);white-space:nowrap;}
.ch-axis > span.l{transform:none;}
.ch-axis > span.r{transform:translateX(-100%);}
.ch-axis > span.mid{transform:translateX(-35%);}
.ch-chart .ov-spend-tip .x span{display:block;}
.ch-chart .tip-exp{color:var(--neg);}
.ch-chart .tip-inc{color:var(--pos);}
/* The ring (svgDonut): plot with the total in its middle, legend beside it. */
.ch-donut{display:flex;align-items:center;gap:1.25rem;}
.ch-donut-plot{position:relative;flex:0 0 11rem;width:11rem;}
.ch-donut-plot svg{display:block;width:100%;height:auto;}
.ch-donut .seg{transition:stroke-width var(--hover-dur) var(--hover-ease);}
.ch-donut .seg:hover{stroke-width:20;}
@media (prefers-reduced-motion: reduce){
  .ch-donut .seg{transition:none;}
}
.ch-donut-plot.on .ov-spend-tip{opacity:1;}
.ch-donut-centre{
  position:absolute;
  inset:0;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  pointer-events:none;
}
.ch-donut-centre b{font-size:var(--fs-figure);font-weight:600;font-variant-numeric:tabular-nums;}
.ch-donut-centre span{font-size:var(--fs-meta);color:var(--muted);font-weight:500;}
.ch-donut-legend{flex:1;min-width:0;display:flex;flex-direction:column;gap:0.4375rem;font-size:var(--fs-meta);}
.ch-donut-legend .lg,.topbar-title-row{display:flex;align-items:center;gap:var(--sp-2);min-width:0;}
.ch-donut-legend .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ch-donut-legend b{color:var(--muted);font-weight:600;font-variant-numeric:tabular-nums;}
/* Yearly: full-width cards only. Totals and best/worst month share one row; the
   category bars flow into columns, so a longer list adds rows, not a taller rail. */
/* The five totals are one Overview KPI group (see .yr-kpis in 02-base-layout.css). */
#panel-yearly .yr-kpis{margin-bottom:1.375rem;}
@media (max-width:1150px){
  #panel-yearly .yr-kpis{
    display:grid;
    grid-template-columns:1fr 1fr;
    grid-auto-flow:row;
    row-gap:var(--sp-4);
    column-gap:var(--sp-4);
  }
}
/* The exception to the stacked hairline (06-overview.css): these four stay in two
   columns all the way down, so their rule stays upright, centred in the column
   gap. A pane that starts a row has its neighbour above it rather than beside it
   — the 3rd of four — and an upright rule there would stand in the card's left
   padding dividing nothing. */
@media (max-width:760px){
  #panel-yearly .yr-kpis > .kpi + .kpi::before{
    inset-inline-start:calc(var(--sp-4) / -2);
    inset-inline-end:auto;
    top:var(--sp-3);
    bottom:var(--sp-3);
    width:1px;
    height:auto;
  }
  #panel-yearly .yr-kpis > .kpi:nth-child(odd)::before{display:none;}
}
#panel-trends .tr-tiles{margin-bottom:1.375rem;}
#panel-yearly .yr-kpis + .card-head{margin-top:0;}
#panel-yearly .yr-cat-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(17rem,1fr));
  column-gap:var(--sp-6);
}
#panel-yearly .yr-savings > .card-head,#panel-yearly > .card-head{margin-top:var(--sp-5);}
#panel-yearly .yr-savings .sr-kpis.kpi-strip{
  background:transparent;
  border:0;
  box-shadow:none;
  padding:0;
  border-radius:0;
}
/* Summary card: the ring (by category) and, under a hairline, where the same total
   was paid from. Rows follow the By category list's type and weight — neutral
   amounts, muted shares — so sources no longer read as a different component with
   tinted tiles and amounts in each source's colour. */
.exp-sum-card{text-align:center;}
.exp-sum-src{text-align:start;margin-top:var(--sp-4);padding-top:0.875rem;border-top:1px solid var(--border);}
.exp-sum-src:empty{display:none;}
.exp-sum-src-h,.danger-zone-label{
  font-size:var(--fs-label);
  font-weight:500;
  color:var(--muted);
  margin-bottom:var(--sp-2);
}
.exp-sum-src-bar{
  display:flex;
  gap:2px;
  margin-bottom:0.625rem;
}
.exp-sum-src-bar i{min-width:3px;}
.meter-fill-full{width:100%;}
.exp-sum-src-row{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto 2.75rem;
  align-items:center;
  gap:var(--sp-2);
  font-size:var(--fs-label);
  padding:0.3125rem 0;
}
.exp-sum-src-row .nm{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.exp-sum-src-row b{font-weight:600;font-variant-numeric:tabular-nums;}
.exp-sum-src-row.zero{opacity:.5;}
/* By type inside the Income Summary card: the card already has padding, so the
   rows drop their own side padding and keep only the hairline between them. */
.inc-sum-types .exp-cat-row{padding:var(--sp-2) 0;}
.inc-sum-types .exp-cat-row:first-child{padding-top:0;}
.inc-sum-types .exp-cat-row:last-child{padding-bottom:0;}
/* Summary card top: pie on the left, the month's figures beside it (renderSumPie). */
.sum-top{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  gap:1.125rem;
  align-items:center;
  text-align:start;
}
.sum-top svg{display:block;}
.sum-figs{min-width:0;}
.sum-k{font-size:var(--fs-meta);font-weight:500;color:var(--muted);}
.sum-line{
  font-size:var(--fs-meta);
  display:flex;
  align-items:center;
  gap:0.375rem;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  margin-top:var(--sp-1);
}
.sum-line b{font-weight:600;overflow:hidden;text-overflow:ellipsis;}
.sum-line.muted{color:var(--muted);}
/* Fill the window on wide screens: Summary and By category stay on top; the two
   expense lists scroll inside their cards (see .exp-list-scroll). */
@media (min-width:1151px) and (min-height:700px){
  main:has(> #panel-expenses.active){display:flex;flex-direction:column;min-height:0;}
  #panel-expenses.active{flex:1;min-height:0;}
  :root:has(#panel-expenses.active) .app{height:100vh;min-height:0;}
  :root:has(#panel-expenses.active) .content{min-height:0;}
  #panel-expenses .sec2-bar,#panel-expenses .exp-top{flex-shrink:0;}
  #panel-expenses .exp-pair{flex:1;min-height:0;align-items:stretch;}
  #panel-expenses .exp-group{display:flex;flex-direction:column;min-height:0;}
}
