/* ==========================================================================
   Ledger — homeBuddy finance dashboard
   Vanilla CSS. No build step, no imports, no remote fonts.

   Two ideas drive most of the visual decisions in here:

   1. bucket (WHAT the money was for) and entity (WHOSE books it lands on) are
      independent axes. They are never styled alike: a bucket is a FILLED, fully
      rounded chip with a leading dot; an entity is an OUTLINED, square-cornered
      chip in uppercase with a leading glyph. Shape and case carry the meaning,
      so the distinction survives greyscale and colour-blindness.
   2. Amounts span five orders of magnitude ($3 to $220,000). Everything
      numeric is tabular-nums and right-aligned in a reserved column so the
      decimal points line up and a long number can never shove a merchant name
      off the screen.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Dark is the default: this is a phone, at night, in a hurry. */
  --bg:          #0e1013;
  --surface:     #171a1f;
  --surface-2:   #1e222a;
  --surface-3:   #262b34;
  --line:        #2a2f38;
  --line-strong: #3b4250;

  --text:   #e8eaee;
  --text-2: #aab2bf;
  --muted:  #7e8794;

  /* bucket axis */
  --bucket:    #4dd0b1;
  --bucket-bg: #143b34;
  --bucket-fg: #7fe6cc;

  /* entity axis — deliberately a different hue AND a different chip shape */
  --entity:    #b79cff;
  --entity-bg: #241f3d;
  --entity-fg: #c9b6ff;

  --money-in:  #5fd08a;
  --money-out: #e8eaee;
  --warn:      #ffb454;
  --danger:    #ff7b72;
  --movement:  #8a93a3;

  --focus: #7fb2ff;

  /* What shared/ui.css draws with (buttons, toasts, the dock). The action
     colour IS the bucket teal — an alias, so light mode follows for free —
     and its ink is dark on the teal here, white in light mode below. */
  --accent:    var(--bucket);
  --on-accent: #06231d;
  --shadow:    rgba(0, 0, 0, .34);
  /* The dock clears the tab bar; Pantry's only clears the home indicator. */
  --dock-bottom: calc(var(--tabbar-h) + var(--safe-b) + 12px);

  --radius:    14px;
  --radius-sm: 10px;
  --tap:       44px;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  --header-h: 52px;
  --tabbar-h: 56px;

  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Segoe UI",
          Roboto, Helvetica, Arial, sans-serif;
  /* No web fonts anywhere. System stacks only. */
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg:          #f4f6f8;
    --surface:     #ffffff;
    --surface-2:   #eef1f5;
    --surface-3:   #e3e8ef;
    --line:        #dde2ea;
    --line-strong: #c3cbd8;

    --text:   #131720;
    --text-2: #454e5c;
    --muted:  #6b7481;

    --bucket:    #0f8f78;
    --bucket-bg: #d9f2ec;
    --bucket-fg: #0a6e5c;

    --entity:    #6b4ee0;
    --entity-bg: #ece6ff;
    --entity-fg: #543ab8;

    --money-in:  #12794a;
    --money-out: #131720;
    --warn:      #9a6100;
    --danger:    #c0362c;
    --movement:  #6b7481;

    --focus: #1f6feb;

    --on-accent: #fff;
  }
}

/* --------------------------------------------------------------------------
   2. Reset / base
   -------------------------------------------------------------------------- */

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

html {
  background: var(--bg);
  /* Stop the whole document rubber-banding when a list hits its end. */
  overscroll-behavior-y: none;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.45;
  overscroll-behavior-y: none;
  /* Every interactive surface opts into this too, but setting it on the body
     kills the 300ms tap delay and double-tap-to-zoom app-wide. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* An author `display` beats the UA stylesheet's [hidden]{display:none}, so any
   flex/grid/block rule silently un-hides the element. That has now bitten this
   file three times (.stale-bar, the row action strip, the sheet's confirm
   button), each time as "why is that still on screen?". Once, globally. */
[hidden] { display: none !important; }

h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

/* 16px minimum on anything focusable-and-typable, or Safari zooms the page. */
input, select, textarea { font-size: 16px; }

/* :focus-visible is shared/ui.css's — one ring on both pages. */

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: -0.01em;
}

/* --------------------------------------------------------------------------
   3. App frame: header / scrolling main / bottom tab bar
   -------------------------------------------------------------------------- */

.app-header {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 10px;
  min-height: var(--header-h);
  padding: calc(var(--safe-t) + 6px) calc(var(--safe-r) + 16px) 6px calc(var(--safe-l) + 16px);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
}

.app-title {
  font-size: 20px; font-weight: 700; letter-spacing: -0.02em;
  margin-right: auto;
}

.header-slot { display: flex; align-items: center; gap: 8px; }

.view {
  /* Bottom padding clears the tab bar AND the home indicator. */
  padding:
    14px calc(var(--safe-r) + 16px)
    calc(var(--tabbar-h) + var(--safe-b) + 28px)
    calc(var(--safe-l) + 16px);
  -webkit-overflow-scrolling: touch;
  outline: none;
}

.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  display: grid; grid-template-columns: repeat(5, 1fr);
  padding-bottom: var(--safe-b);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  backdrop-filter: saturate(180%) blur(16px);
  border-top: 1px solid var(--line);
}

.tab {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px;
  min-height: var(--tabbar-h);
  padding: 6px 2px;
  color: var(--muted);
  touch-action: manipulation;
}
.tab-icon { width: 22px; height: 22px; fill: currentColor; }
.tab-label { font-size: 11px; font-weight: 600; letter-spacing: 0.01em; }

/* Selected state is colour + weight + an indicator bar: never colour alone. */
.tab[aria-current="page"] { color: var(--text); }
.tab[aria-current="page"]::before {
  content: ""; position: absolute; top: 0; left: 50%;
  width: 26px; height: 2.5px; margin-left: -13px;
  background: var(--bucket); border-radius: 0 0 3px 3px;
}
.tab:active { background: var(--surface-2); }

.tab-badge {
  position: absolute; top: 6px; left: 50%; margin-left: 6px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 9px;
  background: var(--warn); color: #1a1206;
  font-size: 10.5px; font-weight: 800; line-height: 17px; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   4. Stale / offline banner
   -------------------------------------------------------------------------- */

/* Fixed band with a DETERMINISTIC height, so the layout below can reserve
   exactly that much room. Toggled by a body class rather than :has(), which
   keeps it working on older iOS Safari. */
:root { --stale-h: 28px; }

.stale-bar {
  position: fixed; z-index: 60; left: 0; right: 0; top: 0;
  height: calc(var(--safe-t) + var(--stale-h));
  padding: var(--safe-t) 14px 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--warn); color: #1a1206;
  font-size: 12.5px; font-weight: 700; text-align: center;
}
/* An author `display` beats the UA stylesheet's [hidden]{display:none}, so the
   flex above would keep the band painted while it is meant to be gone. */
.stale-bar[hidden] { display: none; }
.stale-bar[data-kind="error"] { background: var(--danger); color: #2a0b08; }

/* Push everything down by the band's height instead of only offsetting the
   sticky header, which would let the header overlap the first control. */
body.is-stale { padding-top: calc(var(--safe-t) + var(--stale-h)); }
body.is-stale .app-header { top: calc(var(--safe-t) + var(--stale-h)); }
body.is-stale .app-header { padding-top: 6px; }

/* --------------------------------------------------------------------------
   5. Primitives: cards, chips, buttons, amounts
   -------------------------------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
}

.section-head {
  display: flex; align-items: baseline; gap: 8px;
  margin: 22px 2px 10px;
}
.section-head h2 {
  font-size: 13px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.section-head .section-aside { margin-left: auto; font-size: 12.5px; color: var(--muted); }

/* --- The two axes. Shape carries the meaning, not just hue. --- */

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  max-width: 100%;
  font-size: 12px; line-height: 1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* bucket: filled, pill, sentence case, leading dot */
.chip-bucket {
  padding: 5px 9px 5px 7px;
  border-radius: 999px;
  background: var(--bucket-bg); color: var(--bucket-fg);
  font-weight: 600;
  border: 1px solid transparent;
}
.chip-bucket::before {
  content: ""; flex: none;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--bucket);
}
.chip-bucket .sub { opacity: .72; font-weight: 500; }

/* entity: outlined, squared, UPPERCASE + tracked, leading glyph */
.chip-entity {
  padding: 4px 8px;
  border-radius: 5px;
  background: transparent; color: var(--entity-fg);
  border: 1px solid var(--entity);
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em;
}
/* Drawn, not typed: a glyph like U+25EB is missing from plenty of system fonts
   and falls back to tofu. An outlined square is the entity mark; the bucket
   mark is a filled circle. Different shape, different fill, no font dependency. */
.chip-entity::before {
  content: ""; flex: none;
  width: 7px; height: 7px;
  border: 1.5px solid currentColor; border-radius: 1px;
}

/* unset bucket reads as work-to-do, not as a category */
.chip-none {
  padding: 5px 9px;
  border-radius: 999px;
  border: 1px dashed var(--line-strong);
  color: var(--muted); font-weight: 600;
}

/* money movement: not spending. Struck-through-ish, deliberately drab. */
.chip-movement {
  padding: 5px 9px;
  border-radius: 999px;
  background: transparent; color: var(--movement);
  border: 1px solid var(--line-strong);
  font-weight: 600;
}
.chip-movement::before { content: "\21C4"; flex: none; }

/* --- provenance: how did this row get its category? glyph + word, not colour --- */
.prov {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted);
}
.prov::before { font-size: 11px; }
.prov-override { color: var(--bucket-fg); }
.prov-override::before { content: "\2713"; }      /* checkmark: you fixed this  */
.prov-rule::before      { content: "\2699"; }      /* gear: a rule did it        */
.prov-feed              { opacity: .75; }
.prov-feed::before      { content: "\00B7"; }      /* dot: straight from the bank */
/* A model answered. Warn-coloured rather than accent-coloured on purpose: this is
   the badge that means "nobody has checked this", and it has to read as an open
   question next to the checkmark that means someone did. */
.prov-ai                { color: var(--warn); }
.prov-ai::before        { content: "\25C7"; }      /* open diamond: unverified    */

/* --- buttons ---
   .btn / .btn-primary / .btn-sm are shared/ui.css's, drawn with --accent /
   --on-accent (aliased to the bucket teal in the tokens above). Only the
   variants this app alone uses live here. */

.btn-quiet { background: transparent; border-color: transparent; color: var(--text-2); }
.btn-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }

/* Segmented period selector */
.segmented {
  display: flex; gap: 2px; padding: 3px;
  background: var(--surface-2); border-radius: 11px;
  border: 1px solid var(--line);
}
.segmented button {
  flex: 1 1 0; min-height: 38px; padding: 0 10px;
  border: 0; border-radius: 8px; background: transparent;
  color: var(--text-2); font-size: 13.5px; font-weight: 650;
  touch-action: manipulation; cursor: pointer;
}
.segmented button[aria-pressed="true"] {
  background: var(--surface); color: var(--text);
  box-shadow: 0 1px 3px rgba(0,0,0,.28);
}

/* --- amounts --- */

.amount { font-variant-numeric: tabular-nums; letter-spacing: -0.015em; white-space: nowrap; }
.amount-in::before  { content: "+"; }             /* income is never shown negative */
.amount-in  { color: var(--money-in); }
.amount-out { color: var(--money-out); }
.amount-movement { color: var(--movement); }

/* --------------------------------------------------------------------------
   6. Overview
   -------------------------------------------------------------------------- */

.tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
.tile {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px;
  min-width: 0;
}
.tile-label {
  display: flex; align-items: center; gap: 5px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--muted);
}
.tile-value {
  margin-top: 5px;
  font-size: 24px; font-weight: 700; letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  /* A seven-figure number must shrink, not wrap and not clip. */
  overflow-wrap: anywhere;
}
.tile-sub { margin-top: 3px; font-size: 11.5px; color: var(--muted); }
.tile-wide { grid-column: 1 / -1; }

.tile-movement { border-style: dashed; }
.tile-movement .tile-value { font-size: 19px; color: var(--movement); }

/* Trend chart */
.chart-wrap { margin-top: 12px; }
.chart { display: block; width: 100%; height: auto; overflow: visible; }
.chart-grid   { stroke: var(--line); stroke-width: 1; }
.chart-bar-out { fill: var(--bucket); }
.chart-bar-in  { fill: var(--money-in); opacity: .55; }
.chart-label   { fill: var(--muted); font-size: 9px; font-family: var(--sans); }
.chart-legend {
  display: flex; gap: 14px; margin-top: 8px;
  font-size: 11.5px; color: var(--muted);
}
.chart-legend span { display: inline-flex; align-items: center; gap: 5px; }
.swatch { width: 10px; height: 10px; border-radius: 3px; }
.swatch-out { background: var(--bucket); }
.swatch-in  { background: var(--money-in); opacity: .55; }

/* Ranked category list with proportional bars */
.rank { display: flex; flex-direction: column; gap: 2px; }
.rank-row {
  display: block; width: 100%;
  padding: 11px 8px 10px;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; text-align: left;
  touch-action: manipulation; cursor: pointer;
  min-height: var(--tap);
}
.rank-row:active { background: var(--surface-2); }
.rank-top { display: flex; align-items: baseline; gap: 8px; }
.rank-name {
  font-size: 15px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0; flex: 1 1 auto;
}
.rank-amt { font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; flex: none; }
.rank-pct {
  flex: none; width: 42px; text-align: right;
  font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.rank-meta { display: block; margin-top: 3px; font-size: 11.5px; color: var(--muted); }

/* The bar is linear (honest), but a category that rounds to zero still gets a
   visible stub so "tiny" never reads as "missing". */
/* display:block matters — these are <span>s inside a <button>, and an inline
   box ignores height, which silently erases the whole bar. */
.bar {
  display: block;
  margin-top: 7px; height: 6px; border-radius: 3px;
  background: var(--surface-2); overflow: hidden;
}
.bar-fill {
  display: block;
  height: 100%; min-width: 3px; border-radius: 3px;
  background: var(--bucket);
}
.rank-row[aria-expanded="true"] .bar-fill { background: var(--bucket-fg); }

.drill { margin: 2px 0 8px; padding-left: 10px; border-left: 2px solid var(--line); }
.drill-row {
  display: flex; align-items: baseline; gap: 8px;
  padding: 8px 8px 8px 10px; min-height: 40px;
}
.drill-name {
  font-size: 13.5px; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0;
}
.drill-amt { font-size: 13.5px; font-variant-numeric: tabular-nums; flex: none; }

/* Entity split — outlined treatment, echoing the entity chip */
.entity-row {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 4px; border-bottom: 1px solid var(--line);
}
.entity-row:last-child { border-bottom: 0; }
.entity-row .grow { flex: 1 1 auto; min-width: 0; }
.entity-amts { display: flex; flex-direction: column; align-items: flex-end; flex: none; }
.entity-amts .amount { font-size: 14.5px; font-weight: 650; }
.entity-amts .amount-in { font-size: 12px; }

.footnote {
  margin: 14px 2px 0;
  font-size: 12.5px; line-height: 1.5; color: var(--muted);
}
.footnote strong { color: var(--text-2); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   7. Transactions
   -------------------------------------------------------------------------- */

.filters { display: flex; flex-direction: column; gap: 8px; }
.search-wrap { position: relative; display: flex; }
.search {
  flex: 1 1 auto; min-height: var(--tap);
  padding: 0 38px 0 14px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: 16px; /* iOS zoom guard */
  -webkit-appearance: none; appearance: none;
}
.search::placeholder { color: var(--muted); }
.search-clear {
  position: absolute; right: 2px; top: 0; bottom: 0;
  width: 40px; border: 0; background: transparent; color: var(--muted);
  font-size: 18px; cursor: pointer; touch-action: manipulation;
}

/* Horizontally scrolling filter pills — momentum on, scrollbar off. */
.pill-scroller {
  display: flex; gap: 7px;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* Bleed to the screen edges so the row reads as scrollable. */
  margin: 0 calc(-1 * (var(--safe-l) + 16px)) 0 calc(-1 * (var(--safe-l) + 16px));
  padding: 2px calc(var(--safe-r) + 16px) 2px calc(var(--safe-l) + 16px);
}
.pill-scroller::-webkit-scrollbar { display: none; }
.pill {
  flex: none; min-height: 36px; padding: 0 13px;
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--surface); color: var(--text-2);
  font-size: 13.5px; font-weight: 600; white-space: nowrap;
  touch-action: manipulation; cursor: pointer;
}
.pill[aria-pressed="true"] {
  background: var(--surface-3); color: var(--text); border-color: var(--text-2);
}
.pill[aria-pressed="true"]::before { content: "\2713"; font-size: 12px; }
.pill-entity[aria-pressed="true"] { border-color: var(--entity); color: var(--entity-fg); }

.txlist { margin-top: 6px; }
.txrow {
  display: flex; align-items: flex-start; gap: 10px;
  width: 100%; padding: 11px 6px; min-height: 58px;
  border: 0; border-bottom: 1px solid var(--line);
  background: transparent; text-align: left;
  touch-action: manipulation; cursor: pointer;
}
.txrow:active { background: var(--surface-2); }
.txrow-main { display: block; flex: 1 1 auto; min-width: 0; }
.txrow-name {
  display: block;
  font-size: 15px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.txrow-meta {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 5px;
  font-size: 11.5px; color: var(--muted);
}
.txrow-date { flex: none; font-variant-numeric: tabular-nums; }
/* Reserved amount column: a $220,000.00 never squeezes the merchant name. */
.txrow-amt {
  flex: none; width: 94px; text-align: right;
  font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums;
  padding-top: 1px;
}
.txrow-pending .txrow-name::after {
  content: "pending"; margin-left: 6px;
  font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--warn);
}
.txrow-movement .txrow-name, .txrow-movement .txrow-amt { color: var(--movement); }
/* Uncategorised is the work queue: give it a left edge you can see at a glance. */
.txrow-todo { box-shadow: inset 3px 0 0 var(--warn); padding-left: 12px; }

.load-more { display: flex; justify-content: center; padding: 18px 0 6px; }
.end-cap { padding: 18px 0; text-align: center; font-size: 12.5px; color: var(--muted); }

/* --------------------------------------------------------------------------
   8. Recategorize — the flagship. Everything here is thumb-sized.
   -------------------------------------------------------------------------- */

.queue-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.queue-progress {
  flex: 1 1 auto; height: 6px; border-radius: 3px;
  background: var(--surface-2); overflow: hidden;
}
.queue-progress i { display: block; height: 100%; background: var(--bucket); border-radius: 3px; }
.queue-count { flex: none; font-size: 12.5px; font-weight: 650; color: var(--muted); font-variant-numeric: tabular-nums; }
/* Skip, at the top of the panel. Thumb-sized (44px) but quiet: it must be
   instantly reachable without competing with the pickers for attention. */
.queue-skip {
  flex: none; display: flex; align-items: center; gap: 2px;
  min-height: 44px; padding: 0 4px 0 10px; margin: -10px -4px -10px 0;
  border: 0; background: none; color: var(--muted);
  font-size: 13.5px; font-weight: 650; font-family: inherit;
  touch-action: manipulation; cursor: pointer;
}
.queue-skip:active { color: var(--text); }
.queue-skip[hidden] { display: none; }
.queue-skip-arrow { font-size: 18px; line-height: 1; }

.qcard {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 18px; padding: 16px;
}
.qcard-amt {
  font-size: 34px; font-weight: 750; letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
  overflow-wrap: anywhere;
}
.qcard-name {
  margin-top: 6px;
  font-size: 19px; font-weight: 650; line-height: 1.25;
  overflow-wrap: anywhere;
}
.qcard-raw {
  margin-top: 4px;
  font-family: var(--mono); font-size: 11.5px; color: var(--muted);
  overflow-wrap: anywhere;
}
.qcard-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 10px; font-size: 12.5px; color: var(--muted);
}
.qcard-current {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
}

.field-label {
  display: flex; align-items: center; gap: 6px;
  margin: 18px 2px 8px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.field-label .hint { margin-left: auto; text-transform: none; letter-spacing: 0; font-weight: 500; font-size: 11.5px; }

/* Bucket picker: 2-up at phone width => ~174px x 52px targets. */
.picker { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.pick {
  display: flex; align-items: center; gap: 8px;
  min-height: 52px; padding: 8px 12px;
  border: 1px solid var(--line-strong); border-radius: 12px;
  background: var(--surface); color: var(--text);
  font-size: 14.5px; font-weight: 600; text-align: left;
  touch-action: manipulation; cursor: pointer;
  overflow: hidden;
}
.pick span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick::before {
  content: ""; flex: none; width: 9px; height: 9px; border-radius: 50%;
  background: var(--bucket); opacity: .5;
}
.pick:active { background: var(--surface-3); }
.pick[aria-pressed="true"] {
  background: var(--bucket-bg); border-color: var(--bucket); color: var(--bucket-fg);
}
.pick[aria-pressed="true"]::before { opacity: 1; }

/* Entity picker: squared + outlined + uppercase, matching the entity chip. */
.picker-entity { display: flex; flex-wrap: wrap; gap: 8px; }
.pick-entity {
  min-height: var(--tap); padding: 0 14px;
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line-strong); border-radius: 6px;
  background: var(--surface); color: var(--text-2);
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  touch-action: manipulation; cursor: pointer;
}
.pick-entity::before {
  content: ""; flex: none;
  width: 9px; height: 9px;
  border: 1.5px solid currentColor; border-radius: 2px; opacity: .6;
}
.pick-entity[aria-pressed="true"] {
  border-color: var(--entity); background: var(--entity-bg); color: var(--entity-fg);
}
.pick-entity[aria-pressed="true"]::before { opacity: 1; }

.qactions { display: flex; gap: 8px; margin-top: 18px; }
.qactions .btn { flex: 1 1 0; }

/* --------------------------------------------------------------------------
   Merchant lookup. Sits between the descriptor and the pickers, so it must stay
   visually quieter than both — it is a research aid, not the decision.
   -------------------------------------------------------------------------- */
.ai-bar { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.ai-effort {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.ai-effort select {
  font: inherit; font-size: 13px; text-transform: none; letter-spacing: 0;
  color: var(--text); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 7px 8px; min-height: 36px;
}
.ai-bar + .footnote { margin-top: 7px; }

.ai-out {
  margin-top: 14px; padding: 12px 13px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2);
}
.ai-status {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--text-2);
}
.ai-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--bucket);
  flex: 0 0 auto; animation: aipulse 1.1s ease-in-out infinite;
}
@keyframes aipulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .ai-dot { animation: none; opacity: .8; } }

/* The model's reasoning while it works. Deliberately dim: it's progress, not the
   answer, and it disappears once there is an answer to replace it. */
.ai-think {
  margin-top: 8px; font-size: 12.5px; line-height: 1.5;
  color: var(--muted); white-space: pre-wrap;
}
.ai-text {
  margin-top: 8px; font-size: 13.5px; line-height: 1.5;
  color: var(--text-2); white-space: pre-wrap;
}
.ai-err { margin-top: 8px; font-size: 13px; color: var(--danger); }

.ai-rec {
  margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--line);
}
.ai-rec-name {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 15px; font-weight: 700; color: var(--text);
}
.ai-conf {
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--muted) 22%, transparent); color: var(--text-2);
}
/* Confidence is colour-coded because it is the one thing that decides whether to
   trust the button below it. Low reads as a warning, not as a neutral label. */
.ai-conf-high { background: var(--bucket-bg); color: var(--bucket-fg); }
.ai-conf-low  { background: color-mix(in srgb, var(--warn) 20%, transparent); color: var(--warn); }
.ai-rec-what { margin-top: 4px; font-size: 13px; color: var(--text-2); line-height: 1.45; }
.ai-use { width: 100%; margin-top: 11px; }
.ai-use em { font-style: normal; opacity: .75; font-weight: 500; }

.ai-sources { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.ai-sources a {
  font-size: 11.5px; color: var(--text-2); text-decoration: none;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 4px 8px; border: 1px solid var(--line); border-radius: 999px;
}
.ai-sources a:active { background: var(--surface-3); }

.ai-actions { display: flex; gap: 8px; margin-top: 11px; }

/* Inline rule suggestion — non-modal, appears under the card, never blocks. */
.suggest {
  padding: 13px 14px;
  border: 1px solid var(--bucket); border-radius: 14px;
  background: var(--bucket-bg);
  box-shadow: 0 10px 26px rgba(0,0,0,.34);
  pointer-events: auto;
  animation: rise .22s ease-out;
}
/* @keyframes rise comes from shared/ui.css. */
@media (prefers-reduced-motion: reduce) { .suggest { animation: none; } }
.suggest-title { font-size: 15px; font-weight: 700; color: var(--bucket-fg); }
.suggest-body { margin-top: 4px; font-size: 13px; color: var(--text-2); line-height: 1.45; }
.suggest-body code {
  font-family: var(--mono); font-size: 12px;
  background: color-mix(in srgb, var(--text) 10%, transparent);
  padding: 1px 5px; border-radius: 4px;
}
.suggest-actions { display: flex; gap: 8px; margin-top: 11px; }
.suggest-actions .btn { flex: 1 1 auto; }

/* While a suggestion is docked at the bottom, give the view room so the last
   control is still reachable. The suggestion never covers the tab bar. */
body.has-suggest .view { padding-bottom: calc(var(--tabbar-h) + var(--safe-b) + 210px); }

.qdone { text-align: center; padding: 40px 16px; }
.qdone-mark { font-size: 44px; line-height: 1; }
.qdone h2 { margin-top: 12px; font-size: 20px; font-weight: 700; }
.qdone p { margin-top: 6px; color: var(--muted); font-size: 14px; }

/* --------------------------------------------------------------------------
   9. Rules
   -------------------------------------------------------------------------- */

.rule {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 12px 10px;
  margin-bottom: 8px;
}
.rule-head { display: flex; align-items: center; gap: 8px; }
.prio {
  flex: none; min-width: 34px; height: 26px; padding: 0 7px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 7px; background: var(--surface-3); color: var(--text-2);
  font-size: 12.5px; font-weight: 750; font-variant-numeric: tabular-nums;
}
.prio-top { background: var(--bucket-bg); color: var(--bucket-fg); }
.rule-when { flex: 1 1 auto; min-width: 0; font-size: 14.5px; font-weight: 600; overflow-wrap: anywhere; }
.rule-then { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 9px; }
.rule-foot {
  display: flex; align-items: center; gap: 10px;
  margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--line);
  font-size: 12px; color: var(--muted);
}
.rule-foot .btn { margin-left: auto; }
.rule-off { opacity: .55; }
.arrow { color: var(--muted); flex: none; }

.form-row { margin-bottom: 12px; }
.form-row label {
  display: block; margin-bottom: 6px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
.form-row input, .form-row select {
  width: 100%; min-height: var(--tap); padding: 0 12px;
  background: var(--surface-2); border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); font-size: 16px;
  -webkit-appearance: none; appearance: none;
}
.form-row select {
  /* Inline SVG caret — no remote image. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%237e8794' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 12px 8px;
  padding-right: 34px;
}
.form-help { margin-top: -6px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }
/* The -6px above is for a help line that FOLLOWS a .form-row, pulling it up
   under the block it explains. A help line INSIDE one sits directly beneath its
   own input, where the same -6px drags it onto the input's border — legible at
   one line, cramped at two. Same element, two positions, two spacings. */
.form-row .form-help { margin-top: 5px; margin-bottom: 0; }

/* --------------------------------------------------------------------------
   9b. Categories — managing the list of buckets itself
   --------------------------------------------------------------------------
   A category's WEIGHT is the point of this screen: 900 transactions must look
   heavier than 3 before you tap anything. Weight is carried three ways so it
   survives greyscale and a squinting glance — the count in tabular figures,
   a linear bar against the largest category, and a dashed outline for the
   ones nothing uses at all.
   -------------------------------------------------------------------------- */

.header-lead { display: flex; align-items: center; }
.header-lead:empty { display: none; }
.btn-back { padding-left: 4px; padding-right: 8px; font-weight: 650; }
/* Drawn, not typed: a chevron glyph is a font dependency. */
.btn-back .chev {
  width: 8px; height: 8px; flex: none;
  border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); margin-right: 2px;
}

.cat-toolbar { display: flex; align-items: center; gap: 8px; margin: 2px 0 10px; }
.cat-toolbar .segmented { flex: 1 1 auto; }
.cat-toolbar .btn[aria-pressed="true"] {
  background: var(--surface-3); border-color: var(--text-2); color: var(--text);
}
.cat-summary { margin: 0 2px 10px; font-size: 12.5px; color: var(--muted); }

.cat {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 8px; overflow: hidden;
}
.cat-main {
  display: block; width: 100%; min-height: 64px;
  padding: 12px; border: 0; background: transparent;
  text-align: left; color: inherit;
  touch-action: manipulation; cursor: pointer;
}
.cat-main:active { background: var(--surface-2); }
.cat-top { display: flex; align-items: center; gap: 8px; }
.cat-name {
  flex: 1 1 auto; min-width: 0;
  font-size: 15.5px; font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cat-n {
  flex: none; font-size: 15px; font-weight: 750;
  font-variant-numeric: tabular-nums;
}
/* Same filled circle as the bucket chip: this list is the bucket axis. */
.cat-dot {
  flex: none; width: 9px; height: 9px; border-radius: 50%;
  background: var(--bucket);
}
.cat .bar { margin-top: 9px; }
.cat-meta {
  display: block; margin-top: 7px;
  font-size: 11.5px; color: var(--muted);
}
.cat-subs {
  display: block; margin-top: 3px;
  font-size: 11.5px; color: var(--muted); opacity: .85;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Declared but unused: dashed, dimmed dot, no bar. Deleting one is harmless. */
.cat-unused { border-style: dashed; }
.cat-unused .cat-dot { background: transparent; border: 1.5px dashed var(--line-strong); }
.cat-unused .cat-n { color: var(--muted); }

.cat-actions { display: flex; gap: 8px; padding: 0 12px 12px; }
.cat-actions .btn { flex: 1 1 0; padding: 0 6px; }

/* Multi-select: a checkbox mark, a filled border AND a background — never
   colour on its own. */
.cat-check {
  flex: none; width: 22px; height: 22px;
  border: 2px solid var(--line-strong); border-radius: 7px;
}
.cat-picked { border-color: var(--bucket); background: var(--bucket-bg); }
.cat-picked .cat-check, .xfer-on .cat-check {
  border-color: var(--bucket); background: var(--bucket);
  position: relative;
}
.cat-picked .cat-check::after, .xfer-on .cat-check::after {
  content: ""; position: absolute; left: 5px; top: 1px;
  width: 6px; height: 11px;
  border: solid #06231d; border-width: 0 2.5px 2.5px 0;
  transform: rotate(42deg);
}
@media (prefers-color-scheme: light) {
  .cat-picked .cat-check::after, .xfer-on .cat-check::after { border-color: #fff; }
}

/* A row that just changed lights up briefly, so an optimistic edit is visible
   even if you were looking at the other end of the list. */
.cat-fresh { animation: freshen .9s ease-out; }
@keyframes freshen {
  0%, 100% { background: var(--surface); }
  25%      { background: var(--bucket-bg); }
}
@media (prefers-reduced-motion: reduce) { .cat-fresh { animation: none; } }

.form-err {
  margin: -6px 0 12px;
  font-size: 12.5px; font-weight: 600; color: var(--danger);
}
.form-err::before { content: "\26A0"; margin-right: 5px; }

/* --- the docked multi-select bar --- */
.actionbar {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px 9px 14px;
  border-radius: 14px;
  background: var(--surface-3); border: 1px solid var(--line-strong);
  box-shadow: 0 10px 26px rgba(0,0,0,.34);
  pointer-events: auto;
  animation: rise .18s ease-out;
}
@media (prefers-reduced-motion: reduce) { .actionbar { animation: none; } }
.actionbar-msg { flex: 1 1 auto; font-size: 13.5px; font-weight: 650; }
body.has-actionbar .view {
  padding-bottom: calc(var(--tabbar-h) + var(--safe-b) + 110px);
}

/* --- the confirm sheet ---
   The rule suggestion is deliberately non-modal. This is the opposite: it
   asks about thousands of rows, so it takes the screen, traps focus, and
   sits at the bottom where the thumb already is. */
.sheet-wrap { position: fixed; inset: 0; z-index: 300; }
.sheet-wrap[hidden] { display: none; }
.sheet-backdrop {
  position: absolute; inset: 0;
  background: rgba(0,0,0,.55);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  animation: fade .18s ease-out;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.sheet {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: 88vh; display: flex; flex-direction: column;
  padding: 6px calc(var(--safe-r) + 16px) calc(var(--safe-b) + 14px)
           calc(var(--safe-l) + 16px);
  background: var(--surface);
  border-top: 1px solid var(--line-strong);
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -14px 40px rgba(0,0,0,.42);
  animation: rise .2s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .sheet, .sheet-backdrop { animation: none; }
}
.sheet-grab {
  width: 38px; height: 4px; margin: 4px auto 10px;
  border-radius: 2px; background: var(--line-strong);
}
.sheet-title {
  font-size: 18px; font-weight: 700; letter-spacing: -0.01em;
  outline: none;
}
.sheet-body {
  flex: 1 1 auto; min-height: 0; margin-top: 8px;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.sheet-lead { font-size: 14px; line-height: 1.5; color: var(--text-2); }
/* Every other thing a lead can precede brings its own top margin; a form row
   does not, so the label ran straight into the last line of the sentence. */
.sheet-lead + .form-row { margin-top: 16px; }
.sheet-actions {
  display: flex; gap: 8px; padding-top: 12px; margin-top: 4px;
  border-top: 1px solid var(--line);
}
.sheet-actions .btn { flex: 1 1 0; }
/* Body scroll lock while the sheet owns the screen. Its own class, not the
   lock screen's, so a Face ID re-prompt can't unlock the page underneath. */
body.has-sheet { overflow: hidden; }

/* Preview numbers: the whole point of the sheet. */
.prev-stats {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  margin: 14px 0 0;
}
.prev-stats li {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 8px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  text-align: center;
}
.prev-stats b {
  font-size: 20px; font-weight: 750; letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}
.prev-stats span { font-size: 11px; color: var(--muted); }
.prev-stats .skel { display: inline-block; margin: 2px auto 4px; }
.prev-note { margin-top: 10px; font-size: 12.5px; color: var(--muted); }
.prev-err {
  margin-top: 14px; padding: 11px 12px;
  border: 1px solid var(--danger); border-radius: var(--radius-sm);
  font-size: 13px; line-height: 1.45; color: var(--danger);
}

/* The rename-became-a-merge warning. Icon + words + border, not just colour. */
.warn-box {
  display: flex; gap: 9px; align-items: flex-start;
  margin-top: 4px; padding: 11px 12px;
  border: 1px solid var(--warn); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  font-size: 13px; line-height: 1.45; color: var(--text-2);
}
.warn-box strong { color: var(--text); }
.warn-mark { flex: none; font-size: 15px; color: var(--warn); line-height: 1.2; }

/* Option lists inside the sheet: merge targets, delete destinations. */
.opt-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.opt-scroll { max-height: 42vh; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; padding: 2px; }
.opt-sep {
  margin: 16px 2px 0;
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.opt {
  display: flex; flex-direction: column; gap: 3px;
  width: 100%; min-height: 56px; padding: 10px 12px;
  border: 1px solid var(--line-strong); border-radius: 12px;
  background: var(--surface-2); color: var(--text);
  text-align: left; touch-action: manipulation; cursor: pointer;
}
.opt:active { background: var(--surface-3); }
.opt-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 650;
}
.opt-sub { font-size: 12px; color: var(--muted); line-height: 1.4; }
/* "Leave them uncategorised" is a real choice, not a footnote — but it does
   send rows back to the Fix queue, so it wears the warning colour. */
.opt-warn { border-color: var(--warn); }
.opt-warn .opt-title::before {
  content: "\26A0"; color: var(--warn); font-size: 14px;
}

/* Filled danger, for a confirm button that really does destroy something. */
.btn-solid-danger {
  background: var(--danger); border-color: var(--danger);
  color: #2a0b08; font-weight: 700;
}
@media (prefers-color-scheme: light) { .btn-solid-danger { color: #fff; } }
.btn-solid-danger:active { filter: brightness(.92); }

/* --------------------------------------------------------------------------
   10. States: skeleton, empty, error
   -------------------------------------------------------------------------- */

/* .skel + shimmer are shared/ui.css's; only this app's skeleton SHAPES here. */
.skel-line { height: 13px; margin-bottom: 9px; }
.skel-row { display: flex; align-items: center; gap: 10px; padding: 12px 6px; border-bottom: 1px solid var(--line); }

.empty { text-align: center; padding: 44px 20px; }
.empty-mark { font-size: 34px; line-height: 1; opacity: .8; }
.empty h2 { margin-top: 12px; font-size: 17px; font-weight: 700; }
.empty p { margin-top: 6px; font-size: 14px; color: var(--muted); line-height: 1.5; }
.empty .btn { margin-top: 16px; }

/* --------------------------------------------------------------------------
   11. Toasts
   -------------------------------------------------------------------------- */

/* The dock and toast stack are shared/ui.css's, positioned by --dock-bottom
   (set in the tokens to clear the tab bar). Toasts sit above the suggestion,
   which stays nearest the thumb because it's the persistent, actionable one. */

/* A row that failed to save flashes back visibly rather than silently reverting. */
.rollback { animation: rollback .6s ease-out; }
@keyframes rollback {
  0%, 100% { background: transparent; }
  30%      { background: color-mix(in srgb, var(--danger) 26%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .rollback { animation: none; } }

/* --------------------------------------------------------------------------
   12. Wider screens — desktop is secondary but should not look broken
   -------------------------------------------------------------------------- */

@media (min-width: 700px) {
  .view { max-width: 720px; margin: 0 auto; padding-bottom: 40px; }
  .app-header { justify-content: center; }
  .app-header > * { max-width: 720px; }
  .tiles { grid-template-columns: repeat(4, 1fr); }
  .tile-wide { grid-column: span 2; }
  .picker { grid-template-columns: repeat(3, 1fr); }
  .tabbar {
    left: 50%; right: auto; transform: translateX(-50%);
    width: 720px; border-radius: 16px 16px 0 0;
    border-left: 1px solid var(--line); border-right: 1px solid var(--line);
  }
  .toast-host { left: 50%; right: auto; transform: translateX(-50%); width: 460px; }
  /* The sheet stays bottom-anchored (that's where the thumb is on a laptop
     trackpad too) but stops being a full-width band. */
  .sheet {
    left: 50%; right: auto; transform: translateX(-50%);
    width: 560px; max-width: calc(100vw - 32px);
    border-radius: 20px 20px 0 0;
  }
  .prev-stats { gap: 10px; }
}

/* Shown only when the app is running on invented data. Deliberately loud and
   unmissable: a finance dashboard quietly displaying fake numbers looks exactly
   like one that is working, which is the most dangerous state it can be in. */
.demo-banner {
  position: sticky;
  top: 0;
  z-index: 200;
  padding: calc(env(safe-area-inset-top, 0px) + 0.5rem) 0.75rem 0.5rem;
  background: #b45309;
  color: #fff;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: center;
}

/* --- passkey lock screen --------------------------------------------------
   Covers everything. The app is not merely hidden behind this — app.js has not
   started, so no figure has been fetched, cached, or read out to a screen
   reader while the page is locked.

   Palette note: this block used to reach for --accent and --card, neither of
   which exists in :root, so every colour silently fell through to a hardcoded
   dark-mode value and the button stayed mint-on-white in light mode. It uses the
   real tokens now. --lock-on-accent is the one genuinely new value: the accent
   flips from a light mint to a dark teal between schemes, so the text sitting on
   it has to flip the other way. */
.lockscreen {
  --lock-on-accent: #06231c;

  position: fixed;
  inset: 0;
  z-index: 500;                 /* above the tab bar (50) and the stale bar (200) */
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: calc(env(safe-area-inset-top, 0px) + 1.25rem)
           calc(env(safe-area-inset-right, 0px) + 1.25rem)
           calc(env(safe-area-inset-bottom, 0px) + 1.25rem)
           calc(env(safe-area-inset-left, 0px) + 1.25rem);
  background: var(--bg);
  color: var(--text);
  /* Long error + help copy must still be reachable in landscape or at large
     text sizes, so the cover scrolls rather than clipping. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* Set on <html> so the rubber-band is killed at the root, not just the body. */
.is-locked, .is-locked body { overflow: hidden; height: 100%; }

.lock-card {
  width: 100%;
  max-width: 22rem;
  margin: auto;                 /* stays centred until the content outgrows the
                                   viewport, then scrolls from the top */
  text-align: center;
}
.lock-icon { color: var(--bucket); margin-bottom: 1rem; line-height: 0; }
.lock-title { font-size: 1.5rem; margin: 0 0 0.4rem; color: var(--text); }
.lock-sub {
  margin: 0 0 1.5rem;
  color: var(--text-2);         /* --muted fails contrast on light at this size */
  font-size: 0.95rem;
  line-height: 1.45;
}
.lock-status { margin: 0; color: var(--muted); font-size: 0.95rem; min-height: 1.45em; }
.lock-note {
  margin: 0 0 0.9rem;
  color: var(--bucket);
  font-size: 0.9rem;
  font-weight: 600;
}
.lock-btn {
  width: 100%;
  min-height: 48px;             /* thumb-sized: it's the only control on screen */
  border: 0;
  border-radius: 12px;
  background: var(--bucket);
  color: var(--lock-on-accent);
  font-family: inherit;         /* buttons don't inherit the app font by default */
  font-size: 1rem;
  font-weight: 650;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
}
.lock-btn:disabled { opacity: 0.6; cursor: default; }
/* There is no hover on a phone; pressed state is the only affordance that pays. */
.lock-btn:not(:disabled):active { transform: scale(0.985); filter: brightness(0.93); }
.lock-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .lock-btn:not(:disabled):active { transform: none; }
}

.lock-err {
  margin: 0.9rem 0 0;
  color: var(--danger);
  font-size: 0.9rem;
  line-height: 1.4;
}
/* Colour is never the only signal: the rule is the second one. */
.lock-err:not([hidden]) {
  padding-top: 0.9rem;
  margin-top: 0.9rem;
  border-top: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
}

/* Shown only after repeated failures — the three things that actually unstick
   someone, rather than a louder version of "try again". */
.lock-help {
  margin-top: 1.1rem;
  text-align: left;
  color: var(--text-2);
  font-size: 0.85rem;
  line-height: 1.5;
}
.lock-help p { margin: 0 0 0.35rem; font-weight: 600; color: var(--text); }
/* The global reset strips list markers; these need them back, because without
   them three separate remedies read as one run-on paragraph. */
.lock-help ul { margin: 0; padding-left: 1.15rem; list-style: disc; }
.lock-help li { margin-bottom: 0.3rem; }

@media (prefers-color-scheme: light) {
  /* --bucket darkens in light mode, so its foreground has to lighten. */
  .lockscreen { --lock-on-accent: #ffffff; }
}

/* --------------------------------------------------------------------------
   13. Accounts  (#/accounts)

   The single hardest thing on this screen is that a large number can mean two
   opposite things. $1,252,308 sitting in a brokerage and $1,252,308 owed on a
   mortgage are the same glyphs. Debt is therefore marked three ways that all
   survive greyscale: a leading minus sign, the WORD "Owed" under the figure,
   and a warn-coloured left edge on the card. Colour is the least of them.

   A broken feed borrows the danger colour and the left-edge treatment from
   debt, but sits above it in the cascade — a card you can't trust matters more
   than the fact that it's a card.
   -------------------------------------------------------------------------- */

.band {
  display: flex; gap: 10px; align-items: flex-start;
  margin-bottom: 12px; padding: 12px;
  border-radius: var(--radius);
  font-size: 13px; line-height: 1.45; color: var(--text-2);
}
.band strong { color: var(--text); }
.band-bad {
  border: 1px solid color-mix(in srgb, var(--danger) 55%, transparent);
  background: color-mix(in srgb, var(--danger) 11%, transparent);
}
.band-bad .warn-mark { color: var(--danger); }

/* "What you owe" is already a word, so the colour is reinforcement, not the
   signal. The net tile spells the subtraction out underneath it. */
.tile-debt { border-color: color-mix(in srgb, var(--warn) 42%, transparent); }
.tile-debt .tile-value { color: var(--warn); }
.tile-value-debt { color: var(--warn); }

.acct-note { margin: 10px 2px 0; font-size: 12.5px; color: var(--muted); }
.acct-note .num { color: var(--text-2); }

/* Drawn, not typed — a chevron glyph is a font dependency (see .btn-back). */
.chev-r, .chev-d {
  width: 8px; height: 8px; flex: none; opacity: .55;
  border-right: 2px solid currentColor; border-top: 2px solid currentColor;
  transform: rotate(45deg);
}
.chev-d { border-top: 0; border-bottom: 2px solid currentColor; }

/* --- the transfer-check entry point --- */

.xcard {
  display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: var(--tap); margin-top: 12px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: inherit;
  touch-action: manipulation;
}
.xcard-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.xcard-body strong { font-size: 14.5px; font-weight: 650; }
.xcard-sub { font-size: 12px; color: var(--muted); line-height: 1.4; }
.xcard-mark { flex: none; display: flex; align-items: center; }
.xcard-ok { border-color: color-mix(in srgb, var(--bucket) 45%, transparent); }
.xcard-ok .xcard-mark { color: var(--bucket); }
.xcard-todo {
  border-color: color-mix(in srgb, var(--warn) 55%, transparent);
  background: color-mix(in srgb, var(--warn) 9%, transparent);
}
.xcard-mark-todo { color: var(--warn); font-size: 20px; line-height: 1; }
.xcard-err { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.xcard-err .btn { flex: none; }
.xcard .skel-line { margin-bottom: 6px; }
.xcard:active { background: var(--surface-2); }

/* --- the list --- */

.acct-group {
  display: flex; align-items: center; gap: 8px;
  margin: 18px 2px 8px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.acct-group-n { margin-left: auto; }

.acct {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 8px; overflow: hidden;
}
.acct-main {
  display: block; width: 100%; min-height: 64px;
  padding: 12px; border: 0; background: transparent;
  text-align: left; color: inherit;
  touch-action: manipulation; cursor: pointer;
}
.acct-main:active { background: var(--surface-2); }

.acct-line { display: flex; align-items: flex-start; gap: 10px; }
.acct-name {
  flex: 1 1 auto; min-width: 0;
  font-size: 15.5px; font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Reserved column: a seven-figure balance can never squeeze the name out. */
.acct-bal {
  flex: none; min-width: 96px;
  display: flex; flex-direction: column; align-items: flex-end;
}
.acct-amt { font-size: 15.5px; font-weight: 650; white-space: nowrap; }
.acct-amt-debt { color: var(--warn); }
.acct-amt-none { color: var(--muted); }
.acct-baltag {
  margin-top: 2px;
  font-size: 10px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap;
}
.acct-baltag-debt { color: var(--warn); }

.acct-line-2 { margin-top: 3px; }
.acct-ident {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 12.5px; color: var(--muted); min-width: 0;
}
.acct-mask { flex: none; letter-spacing: .04em; }
.acct-kind { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Once you've renamed it, the bank's name stays visible — with four accounts
   called CREDIT CARD it's often the only way to know which one you fixed. */
.acct-was {
  display: block; margin-top: 5px;
  font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acct-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }

.acct-flag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 8px; border-radius: 5px;
  border: 1px solid var(--line-strong); color: var(--text-2);
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; white-space: nowrap;
}
.acct-flag-bad {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 55%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}
.acct-flag-bad::before { content: "\26A0"; font-size: 12px; }
.acct-flag-warn {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 50%, transparent);
}
.acct-flag-info { color: var(--muted); border-style: dashed; }

/* Debt: warn edge. Broken feed: danger edge, and it wins. */
.acct-debt { box-shadow: inset 3px 0 0 var(--warn); }
.acct-debt .acct-main { padding-left: 15px; }
.acct-broken {
  box-shadow: inset 3px 0 0 var(--danger);
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
}
.acct-broken .acct-main { padding-left: 15px; }
.acct-off { border-style: dashed; opacity: .78; }

.acct-actions { padding: 0 12px 12px; border-top: 1px solid var(--line); }
.acct-detail { margin-top: 10px; font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.acct-notes { color: var(--text-2); }
.acct-buttons { display: flex; gap: 8px; margin-top: 11px; }
.acct-buttons .btn { flex: 1 1 0; padding: 0 8px; }

/* Hidden accounts are collapsed, never dropped: the excluded total is printed
   on the header whether or not you open it. */
.acct-hidden-wrap {
  margin-top: 20px; border: 1px dashed var(--line-strong);
  border-radius: var(--radius); overflow: hidden;
}
.acct-hidden-head {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: var(--tap); padding: 10px 12px;
  border: 0; background: transparent; color: var(--text-2);
  font-size: 13.5px; font-weight: 650; text-align: left;
  touch-action: manipulation; cursor: pointer;
}
.acct-hidden-title { flex: 1 1 auto; min-width: 0; }
.acct-hidden-n {
  flex: none; min-width: 22px; padding: 2px 6px;
  border-radius: 999px; background: var(--surface-3);
  font-size: 12px; text-align: center;
}
.acct-hidden-sum { flex: none; color: var(--muted); font-size: 13px; }
.acct-hidden-head[aria-expanded="true"] .chev-d { transform: rotate(-135deg); }
.acct-hidden-list { padding: 2px 8px 8px; }

/* --- the editor sheet --- */

.sheet-title-sub { font-size: 13px; font-weight: 600; color: var(--muted); }

/* Everything the bank says, none of it editable. Renaming without this is
   guesswork when four accounts share a name. */
.idblock {
  display: grid; grid-template-columns: auto 1fr; gap: 7px 12px;
  margin: 6px 0 18px; padding: 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); font-size: 13px;
}
.idblock dt {
  align-self: center;
  font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
.idblock dd { margin: 0; color: var(--text); overflow-wrap: anywhere; line-height: 1.35; }

.warn-box-bad {
  border-color: color-mix(in srgb, var(--danger) 55%, transparent);
  background: color-mix(in srgb, var(--danger) 11%, transparent);
}
.warn-box-bad .warn-mark { color: var(--danger); }

/* A field whose value is chosen on another step of the sheet. */
.rowbtn {
  display: flex; align-items: center; gap: 8px;
  width: 100%; min-height: var(--tap); padding: 0 12px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text);
  font-size: 15px; font-weight: 600; text-align: left;
  touch-action: manipulation; cursor: pointer;
}
.rowbtn > span:first-child {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 8px; overflow: hidden;
}
.rowbtn-none { color: var(--muted); font-weight: 500; }
.rowbtn:active { background: var(--surface-3); }

.form-row textarea {
  width: 100%; padding: 10px 12px;
  background: var(--surface-2); border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  font-family: var(--sans); font-size: 16px; line-height: 1.4;
  resize: vertical; -webkit-appearance: none; appearance: none;
}

/* Picked option in a sheet list. The book picker keeps the entity axis's
   shape and case so it can't be mistaken for the bucket picker. */
.opt[aria-pressed="true"] {
  border-color: var(--bucket); background: var(--bucket-bg);
}
/* The tick is the point: tint alone said "chosen", which is nothing at all to
   anyone who can't separate these two greys — and this list is now how you say
   whose books a house is on, not just which type it is. `margin-left: auto`
   parks it at the far edge, so a long book name wraps against the tick rather
   than under it. */
.opt[aria-pressed="true"] .opt-title::after {
  content: "\2713"; margin-left: auto; padding-left: 8px;
  font-size: 14px; font-weight: 700;
}
.opt-entity { border-radius: 6px; }
.opt-entity .opt-title {
  font-size: 13px; letter-spacing: .06em; text-transform: uppercase;
}
.opt-entity[aria-pressed="true"] {
  border-color: var(--entity); background: var(--entity-bg); color: var(--entity-fg);
}

/* --------------------------------------------------------------------------
   13b. Things you own that no bank reports  (manual.js)

   THE HARD PART IS NOT LOOKING LIKE A BROKEN FEED
   ----------------------------------------------
   The channels on this screen were already spoken for: a warn left edge means
   debt, a danger left edge means the connection died, a dashed border means
   "kept out of the totals". A manual asset is none of those and must not
   borrow any of them, because "no feed" here is the healthy state, not the
   alarm.

   So it takes a channel nobody was using — a tinted card face — plus a pill
   that says in words where the number came from. Both survive greyscale; the
   pill survives being read aloud, which the tint does not, so the row's
   aria-label carries the same sentence.

   Only the AGE pill is ever allowed to go warn, and only past a year. A screen
   that shouts about a six-month-old house valuation is a screen you learn to
   ignore before the three-year-old one turns up.
   -------------------------------------------------------------------------- */

.acct-manual { background: var(--surface-2); }
.acct-manual .acct-main:active { background: var(--surface-3); }
/* The mark sits at the same left edge as debt and broken-feed do, so a row can
   carry both without them fighting: debt keeps its solid warn bar, and a manual
   row without debt gets this quieter, dotted one. */
.acct-manual:not(.acct-debt):not(.acct-broken) {
  box-shadow: inset 3px 0 0 var(--line-strong);
}
.acct-manual:not(.acct-debt):not(.acct-broken) .acct-main { padding-left: 15px; }

/* Where the figure came from. Never coloured — it is not a warning, it is a
   provenance, and colouring it would put it in the same visual sentence as the
   feed-health flags right beside it. */
.acct-flag-manual {
  color: var(--text-2);
  border-style: dotted; border-color: var(--line-strong);
  background: color-mix(in srgb, var(--movement) 10%, transparent);
}

.acct-group-manual { color: var(--text-2); }

/* Optimistic row, not yet acknowledged by the server. No buttons on it, so it
   is only ever a few hundred milliseconds of "nearly". */
.acct-pending { opacity: .72; }

/* The row's own fix, next to the row's own nudge. Outlined rather than filled:
   the sheet's confirm button is the only solid primary in the app, and a second
   one on a list row would compete with it. */
.btn-nudge {
  border-color: color-mix(in srgb, var(--warn) 60%, transparent);
  color: var(--warn);
}

/* Restates the typed figure in full. This is the entire defence against
   entering a $1.75M house as $175,000, so it is bigger than help text. */
.money-echo {
  margin: 7px 2px 0;
  font-size: 13.5px; line-height: 1.4; color: var(--text-2);
  min-height: 19px;
}
.money-echo strong { color: var(--text); font-size: 15px; font-weight: 700; }
.money-echo-none { color: var(--muted); }

.idblock-age { color: var(--muted); }

.man-danger { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line); }

/* --- the sequence of beliefs --- */

.val-spark { display: block; width: 100%; height: auto; color: var(--bucket); }
.val-spark-cap {
  margin: 2px 2px 0; font-size: 12px; color: var(--muted); line-height: 1.4;
}

.val-list {
  list-style: none; margin: 14px 0 0; padding: 0;
  max-height: 46vh; overflow-y: auto;
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
}
.val-row {
  padding: 11px 12px; margin-bottom: 8px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
/* The one in the totals. A left edge and the words "in your totals" — the list
   is newest-first, but "first in a list" is not a claim about which number the
   dashboard is actually using. */
.val-row-now {
  border-color: color-mix(in srgb, var(--bucket) 45%, transparent);
  box-shadow: inset 3px 0 0 var(--bucket);
}
.val-top { display: flex; align-items: baseline; gap: 10px; }
.val-amt { flex: 1 1 auto; font-size: 16px; font-weight: 650; }
.val-row-now .val-amt { font-size: 18px; }
.val-change { flex: none; font-size: 13px; color: var(--text-2); white-space: nowrap; }
.val-word { color: var(--muted); font-size: 11.5px; }
.val-first, .val-flat { color: var(--muted); font-size: 11.5px;
  letter-spacing: .04em; text-transform: uppercase; }
.val-meta {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px;
  margin-top: 4px; font-size: 12.5px; color: var(--muted);
}
.val-dot { color: var(--line-strong); }
.val-now {
  margin-left: auto;
  font-size: 10px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--bucket-fg);
}
.val-note {
  margin: 7px 0 0; font-size: 12.5px; line-height: 1.4; color: var(--text-2);
}
.val-age { margin: 6px 0 0; font-size: 12px; color: var(--muted); }
.val-skel { padding: 12px 2px; }
/* Last-known figures beat no figures, but never unmarked. The page-level stale
   bar is behind this sheet's backdrop, so it says so for itself. */
.val-stale {
  margin: 10px 0 0; padding: 9px 11px;
  border: 1px dashed var(--line-strong); border-radius: var(--radius-sm);
  font-size: 12.5px; line-height: 1.45; color: var(--text-2);
}

/* --------------------------------------------------------------------------
   14. Money movement review  (#/transfers)

   An empty list here is the SUCCESS case, so it gets a mark, a heading and an
   explanation rather than the grey "nothing here" that means a screen failed
   to load.
   -------------------------------------------------------------------------- */

.clean { text-align: center; padding: 34px 20px 8px; }
.clean-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 58px; height: 58px; border-radius: 50%;
  background: var(--bucket-bg); color: var(--bucket);
}
.clean h2 { margin-top: 14px; font-size: 20px; font-weight: 700; }
.clean p {
  margin: 8px auto 0; max-width: 34em;
  font-size: 14px; line-height: 1.5; color: var(--text-2);
}

.xhow { margin-top: 18px; }
.xhow h3 {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.xhow p { margin-top: 9px; font-size: 13px; line-height: 1.5; color: var(--text-2); }

.xhead { margin: 14px 2px 12px; }
.xhead p { font-size: 13.5px; line-height: 1.5; color: var(--text-2); }
.xhead strong { color: var(--text); }
.xhead-sub { margin-top: 7px; font-size: 12.5px; color: var(--muted); }
.xhead .btn { margin-top: 11px; }

.xfer {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 8px; overflow: hidden;
}
.xfer-on { border-color: var(--bucket); background: var(--bucket-bg); }
.xfer-main {
  display: block; width: 100%; padding: 12px;
  border: 0; background: transparent; color: inherit; text-align: left;
  touch-action: manipulation; cursor: pointer;
}
.xfer-top { display: flex; align-items: center; gap: 10px; }
.xfer-amt { flex: 1 1 auto; min-width: 0; font-size: 17px; font-weight: 700; }
.xfer-badge {
  flex: none; padding: 4px 8px; border-radius: 5px;
  border: 1px solid var(--line-strong); color: var(--text-2);
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; white-space: nowrap;
}
.xfer-halves { display: block; margin-top: 10px; }

/* Both halves, always. A pair shown as one line is a pair you can't check. */
.xhalf { display: flex; gap: 8px; padding: 8px 0; }
.xhalf + .xhalf { border-top: 1px solid var(--line); }
.xhalf-dir {
  flex: none; width: 40px; padding-top: 2px;
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.xhalf-dir-out::before { content: "\2191\00A0"; }
.xhalf-dir-in::before  { content: "\2193\00A0"; }
.xhalf-body { flex: 1 1 auto; min-width: 0; }
.xhalf-top { display: flex; align-items: baseline; gap: 8px; }
.xhalf-acct {
  flex: 1 1 auto; min-width: 0; font-size: 13.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.xhalf-date { flex: none; font-size: 12px; color: var(--muted); }
.xhalf-desc {
  display: block; margin-top: 2px;
  font-size: 11.5px; color: var(--muted); line-height: 1.4;
  overflow-wrap: anywhere;
}

/* Compact restatement inside the confirm sheet. */
.xsum {
  display: flex; align-items: baseline; gap: 10px;
  padding: 9px 2px; border-bottom: 1px solid var(--line);
}
.xsum-amt { flex: none; font-size: 14px; font-weight: 700; }
.xsum-path {
  flex: 1 1 auto; min-width: 0;
  font-size: 12px; color: var(--muted); line-height: 1.4;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   Transaction detail. Opened from the Fix card, rendered in the one Sheet.
   A read-only answer to "what is this and who decided it" — no controls, so
   nothing here competes with the pickers behind it.
   -------------------------------------------------------------------------- */
.qcard-open { cursor: pointer; }
.qcard-open:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.qcard-open-hint {
  margin-left: auto; font-size: 12px; font-weight: 600; color: var(--muted);
  white-space: nowrap;
}
.qcard-current { align-items: center; }

.det { display: flex; flex-direction: column; gap: 2px; }
.det-row {
  display: grid; grid-template-columns: 118px 1fr; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.det-row:last-child { border-bottom: 0; }
.det-row dt {
  font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); padding-top: 1px;
}
/* overflow-wrap, not just min-width:0 — Plaid's categories are single unbroken
   tokens like GENERAL_MERCHANDISE_OTHER with nowhere to break, and one of them
   sent the whole sheet sideways. */
.det-row dd {
  margin: 0; font-size: 14px; color: var(--text);
  min-width: 0; overflow-wrap: anywhere;
}
/* The reason sits under the value, quieter than it. It explains the line above;
   it is never the answer itself. */
.det-why { display: block; margin-top: 3px; font-size: 12px; color: var(--text-2); }
.det-raw {
  font-family: var(--mono); font-size: 12px; line-height: 1.45;
  color: var(--text-2); word-break: break-word;
}
.det-mask { color: var(--muted); font-variant-numeric: tabular-nums; }
.det-err { font-size: 14px; color: var(--danger); }

/* Only the genuinely narrow ones stack. At 375px the two-column layout still
   leaves 245px for the value and halves the height of the sheet, which matters
   more than a wrapped descriptor — this is a list you scan, not read. */
@media (max-width: 340px) {
  .det-row { grid-template-columns: 1fr; gap: 2px; }
}

/* --------------------------------------------------------------------------
   Net worth. One number, then everything that qualifies it.
   The caveats sit under the figure rather than in a tooltip, because on this
   household a two-year-old house valuation is millions, not a detail.
   -------------------------------------------------------------------------- */
.nw-head { padding: 6px 0 2px; }
.nw-total {
  font-size: 40px; font-weight: 800; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.05; color: var(--text);
}
.nw-sub { margin-top: 5px; font-size: 13px; color: var(--muted); }

.nw-split {
  margin-top: 16px; padding: 4px 14px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.nw-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 11px 0; border-bottom: 1px solid var(--line);
}
.nw-row:last-child { border-bottom: 0; }
.nw-row-sub { padding-top: 0; margin-top: -6px; border-bottom: 0; }
.nw-row-sub .nw-row-label, .nw-row-sub .nw-row-val { font-size: 12.5px; }
.nw-row-label { font-size: 14px; color: var(--text-2); }
.nw-row-val { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.nw-pos { color: var(--text); }
.nw-neg { color: var(--text-2); }
.nw-quiet { color: var(--muted); }

/* The warning is a sibling of the number, not a footnote to it. */
.nw-stale {
  display: flex; gap: 10px; margin-top: 14px; padding: 11px 13px;
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--warn) 11%, transparent);
  font-size: 13px; line-height: 1.5; color: var(--text-2);
}
.nw-stale a { color: var(--text); }

.nw-chart {
  margin-top: 18px; padding: 12px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.nw-chart svg { display: block; width: 100%; height: auto; }
.nw-chart-empty { color: var(--text-2); font-size: 13.5px; line-height: 1.55; }
.nw-chart-empty p { margin: 0; }
.nw-caption { margin: 9px 0 0; font-size: 12px; color: var(--muted); }
.nw-ranges { margin-bottom: 12px; }

.nw-books { display: flex; flex-direction: column; gap: 2px; }
.nw-book {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.nw-book:last-child { border-bottom: 0; }
.nw-book-name { font-size: 14px; color: var(--text); }
.nw-book-val { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }

/* Five tabs, not four: the labels need the room. */
.tabbar .tab-label { font-size: 10.5px; }

/* A filter that arrived from a link rather than a tap — an account, a month.
   It reads as a removable token, because it's the only filter on the bar you
   can't re-apply by tapping the same pill again. */
.pill-on {
  border-color: var(--bucket); color: var(--bucket-fg);
  background: var(--bucket-bg);
}
.pill-on::before { content: none !important; }

/* --------------------------------------------------------------------------
   Trend. A chart of months, a line for "typical", and the merchants that
   explain the gap between them.
   -------------------------------------------------------------------------- */
.tr-chart {
  padding: 12px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.tr-chart svg { display: block; width: 100%; height: auto; }

.tr-head {
  margin-top: 14px; padding: 12px 14px; border-radius: var(--radius);
  font-size: 14px; line-height: 1.45; color: var(--text-2);
  border: 1px solid var(--line); background: var(--surface);
}
.tr-head-n { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; }
.tr-over .tr-head-n { color: var(--warn); }
.tr-under .tr-head-n { color: var(--money-in); }
.tr-head-sub { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); }
.tr-note { margin-top: 14px; font-size: 13px; color: var(--muted); }

.tr-movers { display: flex; flex-direction: column; gap: 8px; }
.tr-mover {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 12px;
  width: 100%; min-height: var(--tap); padding: 11px 13px; text-align: left;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--text); font: inherit;
  cursor: pointer;
}
.tr-mover:active { background: var(--surface-2); }
.tr-mover-name { font-size: 14px; font-weight: 600; }
.tr-mover-d { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.tr-up { color: var(--warn); }
.tr-down { color: var(--money-in); }
.tr-mover-sub {
  grid-column: 1 / -1; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.tr-tag {
  font-style: normal; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; padding: 1px 5px; border-radius: 4px;
  background: color-mix(in srgb, var(--text-2) 20%, transparent);
  color: var(--text-2);
}
/* `.drill-go` was the old one-off action row. The two actions now live in
   `.drill-actions` below and get the same treatment as each other. */
.drill-go { color: var(--bucket-fg); }

/* A merchant row you can follow. Same rhythm as `.drill-row` — this IS one — plus a
   chevron and a press state, because a row that navigates has to look different from a
   row that just reports. Before this they were inert divs: the biggest uncategorised
   figure on the screen was the one thing you couldn't tap. */
a.drill-tap { text-decoration: none; color: inherit; }
a.drill-tap:active { background: color-mix(in srgb, var(--text) 6%, transparent); }
a.drill-tap .drill-name { color: var(--text); }
/* `.chev-r` is already defined once, further up, in `currentColor` — I wrote a second
   copy here before noticing, which would have silently overridden the first for every
   chevron in the app. Reusing it instead; the only thing this row needs is the colour
   it inherits. */
a.drill-tap .chev-r { color: var(--text-2); }

/* THE ACTIONS, divided from the data. They used to carry `.drill-row` — the same class
   as the merchant list — so two things you can DO looked like eight things you can only
   read. The divider and the weight do the work, not colour, so it survives greyscale. */
.drill-actions {
  margin-top: 6px; padding-top: 4px;
  border-top: 1px solid var(--line);
}
.drill-act {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 8px 11px 10px; min-height: 44px;   /* a real tap target */
  font-size: 14px; font-weight: 600;
  color: var(--bucket-fg); text-decoration: none;
}
.drill-act span:first-child { flex: 1 1 auto; min-width: 0; }
.drill-act:active { background: color-mix(in srgb, var(--bucket-fg) 8%, transparent); }
.drill-act .chev-r { opacity: 1; }

/* ==========================================================================
   Transaction detail (#/txn) — the read-only "what was that charge?" screen.
   Only classes the screen actually emits. A class emitted and never styled is
   how `amount-in-tile` sat in this file's markup for months doing nothing.
   ========================================================================== */
.txn-head { text-align: center; }
.txn-amt {
  font-size: 34px; font-weight: 700; letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  /* A seven-figure number shrinks rather than wrapping or clipping. */
  overflow-wrap: anywhere;
  color: var(--money-out);
}
.txn-amt-in { color: var(--money-in); }
/* The direction word. Sign is never carried by colour alone — a screen reader and
   a colour-blind reader both get "out" or "in" in text. */
.txn-dir {
  display: inline-block; margin-left: 6px;
  font-size: 13px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-2);
  vertical-align: middle;
}
.txn-when { margin-top: 4px; font-size: 15px; color: var(--text); }
.txn-where { margin-top: 2px; font-size: 13px; color: var(--text-2); }
.txn-stat { margin: 0; font-size: 14px; color: var(--text); line-height: 1.45; }
/* One-handed on a phone: the primary action is a full-width target, not a link. */
.btn-wide { display: block; width: 100%; margin: 12px 0 4px; }
/* "pending" beside the date. Same treatment as the other inline markers. */
.tag {
  display: inline-block; font-size: 11px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase;
  padding: 1px 5px; border-radius: 4px; vertical-align: middle;
  background: color-mix(in srgb, var(--text-2) 20%, transparent);
  color: var(--text-2);
}

/* --------------------------------------------------------------------------
   Setup — Accounts, Categories, Rules, and connecting a bank, in one place.

   Rows, not buttons in a grid: each one needs a line of explanation, because
   "Categories" and "Rules" are indistinguishable to anyone who has not just been
   told the difference. That is the whole reason the screen exists.
   -------------------------------------------------------------------------- */
.setup-list { display: flex; flex-direction: column; gap: 10px; }
.setup-row {
  display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: 64px; padding: 13px 14px;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--surface); color: var(--text);
  font: inherit; text-align: left; text-decoration: none;
  touch-action: manipulation; cursor: pointer;
}
.setup-row:active { background: var(--surface-2); }
.setup-row[disabled] { opacity: .55; cursor: default; }
.setup-row-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.setup-row-title { font-size: 16px; font-weight: 650; }
/* Wraps rather than truncating: a half-sentence explaining the difference between
   Categories and Rules explains nothing. */
.setup-row-note { font-size: 12.5px; line-height: 1.4; color: var(--text-2); }
.setup-row-go { flex: none; font-size: 22px; line-height: 1; color: var(--muted); }

/* The confidence chips, on their own line under the filter strip. They are REVEALED by
   the AI-labelled pill, and appending them to the horizontal scroller put them past the
   right edge — a control that appears where you cannot see it reads as nothing having
   happened. Wraps rather than scrolls: three short words always fit at 375px. */
.filters-sub {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 8px;
}
.filters-sub-label {
  font-size: 12px; font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.pill-conf { text-transform: capitalize; }

/* Each month in the Overview trend is a link to its own page. The visible bars are a
   few pixels wide, so the tap target is a full-height transparent rect spanning the
   month's slot — invisible, and the whole column wide. */
.chart-hit { fill: transparent; }
.chart-month { cursor: pointer; }
.chart-month:hover .chart-hit,
.chart-month:focus-visible .chart-hit { fill: var(--surface-2); }
.chart-month:focus-visible { outline: 2px solid var(--bucket-fg); outline-offset: 1px; }

/* The month page. */
.month-vs { margin: 10px 2px 0; font-size: 13.5px; color: var(--text-2); }
.month-todo {
  display: flex; align-items: center; gap: 8px;
  margin-top: 14px; padding: 12px 14px; min-height: 44px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface); color: var(--text);
  font-size: 13.5px; text-decoration: none;
}
.month-todo-go { margin-left: auto; font-size: 20px; color: var(--muted); }
.month-all { display: block; margin-top: 18px; text-align: center; }
.movers { display: flex; flex-direction: column; gap: 2px; }
.mover {
  display: flex; align-items: center; gap: 10px; min-height: 44px;
  padding: 8px 2px; text-decoration: none; color: var(--text);
  border-bottom: 1px solid var(--line);
}
.mover-name { font-size: 14.5px; overflow-wrap: anywhere; }
.mover-delta { margin-left: auto; font-weight: 650; font-variant-numeric: tabular-nums; }
.mover-tag {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted);
}

/* The rule bar on a vendor-filtered list. Sits between the filters and the rows
   because it describes the filter, not any one transaction — and above the fold on
   a phone, which is the only place it is any use. */
.tx-rule {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  margin: 0 0 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
}
.tx-rule-txt { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--muted); }
.tx-rule-txt strong { color: var(--fg); font-weight: 600; }
.tx-rule .btn { flex: none; }

/* "Change rule", inline in the explanation under a filed value. Deliberately not a
   .btn: it sits inside a small grey why-line and a real button there would outweigh
   the value it is explaining. */
.why-edit {
  margin-left: 6px;
  padding: 1px 7px;
  font: inherit;
  font-size: 12px;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
}
.why-edit:hover { background: var(--card); }
