/* Calm Budget — styles.
   Brand palette: cream, ink, sage, coral. Every colour in the app is defined in
   the three token blocks below and nowhere else; the rest of this file only
   references var(--…). Light is the base; dark redefines the same tokens.
   The --surface-2 / --ink-2 / --ink-3 / --neg names are aliases kept so the
   rules further down did not have to change when the palette did. */

:root {
  /* brand */
  --bg: #FCF5EC;
  --bg-2: #EEE9E1;
  --surface: #FFFFFF;
  --ink: #0C3533;
  --ink-soft: #486561;
  --muted: #456562;
  --line: #DADAD2;
  --line-strong: #C2C7C0;
  --sage: #738D89;
  --sage-soft: #DEDED6;
  --accent: #FF6B48;
  --accent-ink: #0C3533;
  --accent-soft: #FDDCCE;
  --accent-text: #9E3A22;
  --warn: #FF6B48;
  --negative: #8F4A36;
  /* derived from the brand for states the rules need */
  /* Hover lightens the coral rather than deepening it. The button's text is the
     deep green ink, so a darker coral moves towards it: #F2593A read 3.98:1
     against #0C3533, under the 4.5 it needs. This is the dark theme's value,
     not a new colour. */
  --accent-strong: #FF8A6E;
  --warn-soft: #FDDCCE;
  --negative-soft: #F7E4DE;
  --on-danger: #FFFFFF;
  --check: #FFFFFF;
  --check-shadow: rgba(12, 53, 51, 0.75);
  --backdrop: rgba(12, 53, 51, 0.45);
  --shadow: 0 10px 30px rgba(12, 53, 51, 0.14);
  /* aliases (older rule names) */
  --surface-2: var(--bg-2);
  --ink-2: var(--ink-soft);
  --ink-3: var(--muted);
  --neg: var(--negative);
  --neg-soft: var(--negative-soft);
  /* metrics */
  --radius: 16px;
  --radius-sm: 10px;
  --tap: 44px;
  --sat: env(safe-area-inset-top, 0px);
  --sab: env(safe-area-inset-bottom, 0px);
  --sal: env(safe-area-inset-left, 0px);
  --sar: env(safe-area-inset-right, 0px);
  --motion: 180ms;
  color-scheme: light;
}

[data-theme="dark"] {
  --bg: #0C3533;
  --bg-2: #244846;
  --surface: #244846;
  --ink: #FCF5EC;
  --ink-soft: #BEC6BF;
  --muted: #C6CDC7;
  --line: #4F6B67;
  --line-strong: #5F7B77;
  --sage: #738D89;
  --sage-soft: #375854;
  --accent: #FF6B48;
  --accent-ink: #0C3533;
  --accent-soft: #5A4A43;
  --accent-text: #FFB9A6;
  --warn: #FF6B48;
  --negative: #FF9B85;
  --accent-strong: #FF8A6E;
  --warn-soft: #5A4A43;
  --negative-soft: #5A3A33;
  --on-danger: #0C3533;
  --check: #FCF5EC;
  --check-shadow: rgba(12, 53, 51, 0.85);
  --backdrop: rgba(6, 26, 25, 0.6);
  --shadow: 0 10px 30px rgba(6, 26, 25, 0.5);
  --surface-2: #375854;
  color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0C3533;
    --bg-2: #244846;
    --surface: #244846;
    --ink: #FCF5EC;
    --ink-soft: #BEC6BF;
    --muted: #C6CDC7;
    --line: #4F6B67;
    --line-strong: #5F7B77;
    --sage: #738D89;
    --sage-soft: #375854;
    --accent: #FF6B48;
    --accent-ink: #0C3533;
    --accent-soft: #5A4A43;
    --accent-text: #FFB9A6;
    --warn: #FF6B48;
    --negative: #FF9B85;
    --accent-strong: #FF8A6E;
    --warn-soft: #5A4A43;
    --negative-soft: #5A3A33;
    --on-danger: #0C3533;
    --check: #FCF5EC;
    --check-shadow: rgba(12, 53, 51, 0.85);
    --backdrop: rgba(6, 26, 25, 0.6);
    --shadow: 0 10px 30px rgba(6, 26, 25, 0.5);
    --surface-2: #375854;
    color-scheme: dark;
  }
}

[data-text="large"] { font-size: 112.5%; }

@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="off"]) { --motion: 0ms; }
}
[data-motion="on"] { --motion: 0ms; }

/* ------------------------------------------------------------ base */

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 1rem;
  line-height: 1.45;
  min-height: 100vh;
  min-height: 100dvh;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: none;
}

body.has-sheet { overflow: hidden; }

h1, h2, h3, p { margin: 0; }
button { font: inherit; color: inherit; }
input, select, textarea { font: inherit; color: inherit; }

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

.money { font-variant-numeric: tabular-nums; }

:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }

#app {
  padding: var(--sat) var(--sar) 0 var(--sal);
  max-width: 720px;
  margin: 0 auto;
}

/* ------------------------------------------------------------ banners */

#banners { display: grid; gap: 8px; }
#banners:not(:has(.banner:not([hidden]))) { display: none; }
.banner-top { margin: 8px 16px 0; }
.banner {
  min-width: 0; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 12px;
  padding: 10px 14px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--line); font-size: 0.9375rem;
}
.banner-warn { background: var(--warn-soft); border-color: transparent; }
.banner-info { background: var(--accent-soft); border-color: transparent; }
.banner-actions { display: flex; gap: 6px; }

/* ------------------------------------------------------------ top bar */

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px 6px;
}
.brand { font-size: 1.125rem; font-weight: 650; letter-spacing: -0.01em; display: flex; align-items: center; gap: 10px; }
.brand-mark {
  width: 22px; height: 22px; border-radius: 7px; background: var(--ink);
  position: relative; display: inline-block;
}
.brand-mark::after {
  content: ""; position: absolute; left: 5px; right: 5px; top: 11px; height: 4px; border-radius: 2px; background: var(--bg);
}
.brand-mark::before {
  content: ""; position: absolute; right: 4px; top: 4px; width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
}
.topbar-actions { display: flex; gap: 4px; }

.icon-btn {
  width: var(--tap); height: var(--tap); border-radius: 12px; border: 0; background: transparent;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer; color: var(--ink-2);
  font-size: 1.5rem; line-height: 1;
}
.icon-btn svg { width: 24px; height: 24px; }
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }

/* ------------------------------------------------------------ home */

#home { padding: 8px 16px calc(96px + var(--sab)); display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; }
/* Below the desktop breakpoint the two columns melt into one flow, ordered like the phone layout. */
.home-main, .home-side { display: contents; }
#banners { order: 0; } .hero { order: 1; } .week-strip { order: 2; } .tiles { order: 2; } .sort-badge { order: 3; } .bills-banner { order: 4; } .debt-summary { order: 4; } .categories { order: 5; }
.goals-summary { display: none; }
.sidebar { display: none; }
.insights { display: none; }

.card {
  min-width: 0; background: var(--surface); border-radius: var(--radius); padding: 18px;
  border: 1px solid var(--line);
}
.card-title { font-size: 0.9375rem; font-weight: 600; color: var(--ink-2); margin-bottom: 8px; }

.hero { text-align: center; padding: 26px 18px 20px; }
.hero-label, .eyebrow {
  font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3);
}
.hero-amount {
  /* A money value never breaks inside the number. If a long amount or a wide
     currency format still will not fit, fitText() in js/ui.js steps the size
     down; wrapping mid-digit reads as a different number. */
  white-space: nowrap;
  font-size: clamp(2.2rem, 10vw, 3.6rem); font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; margin: 6px 0 8px;
  color: var(--ink);
}
.hero[data-state="negative"] .hero-amount { color: var(--negative); }
.hero[data-state="low"] .hero-amount { color: var(--accent-text); }
.hero[data-state="empty"] .hero-amount { color: var(--muted); }
.hero-msg { color: var(--ink-2); font-size: 1rem; max-width: 34ch; margin: 0 auto 16px; min-height: 2.9em; }

.progress {
  height: 10px; border-radius: 999px; background: var(--sage-soft); overflow: hidden;
}
.progress-fill {
  height: 100%; width: 0; border-radius: 999px; background: var(--accent);
  transition: width var(--motion) ease-out;
}
.hero[data-state="low"] .progress-fill { background: var(--warn); }
.hero[data-state="negative"] .progress-fill { background: var(--neg); }
.hero-meta { display: flex; justify-content: space-between; gap: 12px; margin-top: 10px; font-size: 0.9375rem; color: var(--ink-2); min-height: 1.4em; }
.hero-range { margin-top: 4px; font-size: 0.8125rem; color: var(--ink-3); min-height: 1.3em; }

.tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.tile {
  /* minmax(0, 1fr) here and min-width: 0 below keep the value's own box inside
     the tile. Without it a nowrap number grows the box instead of overflowing
     it, and nothing downstream can tell that it no longer fits. */
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 4px; text-align: left; padding: 12px; min-height: 76px; min-width: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
}
/* narrow phones: buy back the side padding, then fall to two columns */
@media (max-width: 399px) { .tile { padding: 12px 8px; } }
@media (max-width: 339px) {
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tiles > :nth-child(3) { grid-column: 1 / -1; }
}
.tile:hover { border-color: var(--accent); }
.tile-label { font-size: 0.75rem; color: var(--ink-3); font-weight: 600; min-width: 0; }
.tile-value {
  white-space: nowrap; min-width: 0; min-height: 1.4em; font-weight: 650;
  font-size: clamp(0.8125rem, 4.2vw, 1.0625rem);
}

.sort-badge {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: var(--tap);
  padding: 10px 14px; border-radius: var(--radius-sm); cursor: pointer; text-align: left;
  background: var(--accent-soft); border: 0; color: var(--ink);
}
[data-theme="dark"] .sort-badge { background: var(--surface-2); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .sort-badge { background: var(--surface-2); } }
/* The soft tint, not the filled coral: Home already spends its one coral on the
   Add button, and two filled corals on one screen compete with each other. */
.badge-count {
  min-width: 26px; height: 26px; padding: 0 8px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-text);
  display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem;
}
.chev { margin-left: auto; color: var(--ink-3); font-size: 1.25rem; }

.section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.section-head .card-title { margin-bottom: 0; }
.section-head h2 { font-size: 1.0625rem; font-weight: 650; }

.category-grid { display: grid; grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 10px; }
.cat-card {
  display: grid; gap: 6px; padding: 12px; border-radius: var(--radius-sm); text-align: left; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line); border-left: 5px solid var(--cat, var(--accent));
}
.cat-card:hover { border-color: var(--accent); border-left-color: var(--cat, var(--accent)); }
.cat-top { display: flex; align-items: center; gap: 8px; font-weight: 600; min-width: 0; }
.cat-emoji { font-size: 1.25rem; }
.cat-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-amount { font-weight: 650; }
.cat-sub { font-size: 0.8125rem; color: var(--ink-3); }
.cat-card .progress { height: 6px; }
.cat-card .progress-fill { background: var(--cat, var(--accent)); }
.cat-card.over .progress-fill { background: var(--warn); }
.empty-note { color: var(--ink-3); font-size: 0.9375rem; padding: 8px 0; grid-column: 1 / -1; }

.bills-banner .plain-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
/* The same treatment as .row, and for the same reason: with `flex: 1` the name
   was the only thing that could shrink, so at 280px a long bill name was a 36px
   column with the text spilling over the amount beside it. The basis is 110 and
   not .row's 150 because these rows have no icon and live in a card that is a
   300px side column on desktop — 150 made even "Rent" wrap on a 390px phone. */
.bill-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-height: var(--tap); }
.bill-row .grow { flex: 1 1 110px; min-width: 0; }
.bill-row .title { overflow-wrap: anywhere; }
.bill-row .sub { font-size: 0.8125rem; color: var(--ink-3); }
.bill-row.overdue .sub { color: var(--accent-text); }

/* ------------------------------------------------------------ bottom bar */

.bottombar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  padding: 6px max(8px, var(--sar)) calc(6px + var(--sab)) max(8px, var(--sal));
  background: var(--surface); border-top: 1px solid var(--line);
}
.nav-btn {
  display: grid; justify-items: center; gap: 2px; padding: 6px 4px; min-height: 52px; border: 0; border-radius: 12px;
  background: transparent; color: var(--ink-2); font-size: 0.75rem; font-weight: 600; cursor: pointer;
}
.nav-btn svg { width: 24px; height: 24px; }
.nav-btn:hover { background: var(--surface-2); color: var(--ink); }
.nav-add { color: var(--accent-ink); background: var(--accent); }
.nav-add:hover { background: var(--accent-strong); color: var(--accent-ink); }
@media (min-width: 720px) {
  .bottombar { max-width: 720px; margin: 0 auto; border-left: 1px solid var(--line); border-right: 1px solid var(--line); border-radius: 16px 16px 0 0; }
}

/* ------------------------------------------------------------ focus mode */

.focus {
  position: fixed; inset: 0; z-index: 60; background: var(--bg);
  display: grid; align-content: center; justify-items: center; gap: 12px; text-align: center;
  padding: calc(24px + var(--sat)) 24px calc(24px + var(--sab));
}
.focus-amount { font-size: clamp(3rem, 16vw, 6rem); font-weight: 700; letter-spacing: -0.03em; color: var(--ink); }
.focus[data-state="negative"] .focus-amount { color: var(--negative); }
.focus[data-state="low"] .focus-amount { color: var(--accent-text); }
.focus .hero-msg { margin-bottom: 24px; }

/* ------------------------------------------------------------ buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: var(--tap); padding: 10px 18px; border-radius: 12px; border: 1px solid transparent;
  font-weight: 600; cursor: pointer; text-decoration: none; background: var(--surface-2); color: var(--ink);
  transition: background var(--motion), transform var(--motion);
}
.btn:active { transform: scale(0.98); }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-strong); }
/* Secondary action. Coral is reserved for the one primary action on a screen
   (Add expense, Unlock, Open), so everything else that used to be coral or a
   ghost link is an outlined ink button. --ink flips to cream in dark. */
.btn-outline { background: transparent; border: 1.5px solid var(--ink); color: var(--ink); }
.btn-outline:hover { background: var(--sage-soft); }
.btn-secondary { background: var(--surface); border-color: var(--line); }
.btn-secondary:hover { border-color: var(--accent); }
.btn-ghost { background: transparent; color: var(--accent-text); }
.btn-ghost:hover { background: var(--accent-soft); }
/* inside a tinted banner the ghost button sits on accent-soft, where ink reads best */
.banner .btn-ghost { color: var(--ink); }
.btn-danger { background: var(--negative); color: var(--on-danger); }
.btn-danger-ghost { background: transparent; color: var(--neg); border-color: var(--line); }
.btn-danger-ghost:hover { background: var(--neg-soft); }
.btn-small { min-height: var(--tap); padding: 6px 12px; font-size: 0.875rem; border-radius: 10px; }
.btn-big { min-height: 56px; font-size: 1.0625rem; }
.btn-wide { width: 100%; }
.btn[disabled] { opacity: 0.5; cursor: default; }
.file-btn { position: relative; overflow: hidden; }
.file-btn input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.file-btn:focus-within { outline: 3px solid var(--accent); outline-offset: 2px; }

.stack { display: grid; gap: 10px; }

/* ------------------------------------------------------------ sheets */

.backdrop {
  position: fixed; inset: 0; background: var(--backdrop); z-index: 99;
  opacity: 1; transition: opacity var(--motion);
}
.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 100;
  max-height: 92vh; max-height: 92dvh; display: flex; flex-direction: column;
  background: var(--surface); border-radius: 22px 22px 0 0; box-shadow: var(--shadow);
  transform: translateY(24px); opacity: 0; transition: transform var(--motion) ease-out, opacity var(--motion);
  padding-bottom: var(--sab);
}
.sheet.open { transform: none; opacity: 1; }
.sheet:focus { outline: none; }
.sheet-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 16px 12px 8px 20px; flex: 0 0 auto;
}
.sheet-title { font-size: 1.25rem; font-weight: 650; letter-spacing: -0.01em; }
.sheet-close {
  width: var(--tap); height: var(--tap); border-radius: 50%; border: 0; background: var(--surface-2); color: var(--ink-2);
  font-size: 1.5rem; line-height: 1; cursor: pointer;
}
.sheet-close:hover { color: var(--ink); }
.sheet-body {
  overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 4px 20px 20px; display: grid; gap: 14px; align-content: start;
  overscroll-behavior: contain;
  /* An implicit `auto` track takes its minimum from the widest child's
     min-content, so a single long bill name pushed every amount in the sheet
     off the right of the screen. A zero floor is the same fix #home, .tile and
     .rail-main already carry. */
  grid-template-columns: minmax(0, 1fr);
}
.sheet-body > * { min-width: 0; }
.sheet-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 6px; }
.sheet-actions .btn-danger-ghost { margin-right: auto; }
@media (min-width: 720px) {
  .sheet {
    left: 50%; right: auto; bottom: auto; top: 50%; width: min(560px, 92vw);
    transform: translate(-50%, -50%) scale(0.98); border-radius: 22px; max-height: 86vh; padding-bottom: 0;
  }
  .sheet.open { transform: translate(-50%, -50%); }
}

.lead { font-size: 1.0625rem; color: var(--ink-2); }
.hint { font-size: 0.875rem; color: var(--ink-3); }
.hint.center { text-align: center; }
.group-title { font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); margin-top: 8px; }
.status-line { font-weight: 600; }

/* ------------------------------------------------------------ forms */

.field { display: grid; gap: 6px; }
.field > span { font-size: 0.875rem; font-weight: 600; color: var(--ink-2); }
.field textarea { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 0.75rem; resize: vertical; }
.key-input { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 1.25rem; letter-spacing: 0.1em; text-transform: uppercase; text-align: center; }
.field input, .field select, .field textarea, .filters input, .filters select {
  width: 100%; min-height: var(--tap); padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink);
}
.field input:focus, .field select:focus, .filters input:focus, .filters select:focus { border-color: var(--accent); outline: 3px solid var(--accent-soft); }
.field input[type="date"] { -webkit-appearance: none; appearance: none; min-width: 0; }
/* hybrid date entry: a text field in the chosen format, a calendar button,
   and the native input kept (rendered but invisible) as the ISO value and as
   the anchor showPicker() needs. */
.datefield { position: relative; display: flex; align-items: center; gap: 8px; }
.date-text { flex: 1; min-width: 0; }
.date-cal {
  flex: 0 0 auto; width: var(--tap); height: var(--tap); border-radius: 12px; cursor: pointer;
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink-soft);
  display: grid; place-items: center;
}
.date-cal:hover { border-color: var(--accent); color: var(--ink); }
.date-cal svg { width: 20px; height: 20px; }
.date-native {
  position: absolute; right: 44px; bottom: 0; width: 1px; height: 1px; min-height: 0;
  padding: 0; border: 0; opacity: 0; pointer-events: none;
}
.date-error { color: var(--negative); font-size: 0.8125rem; font-weight: 600; }
.date-text[aria-invalid="true"] { border-color: var(--negative); }
.check { display: flex; align-items: center; gap: 10px; min-height: var(--tap); font-weight: 600; cursor: pointer; }
.check input { width: 22px; height: 22px; accent-color: var(--accent); }
.form-error { color: var(--neg); font-weight: 600; font-size: 0.9375rem; }
/* live reading of a typed amount, so "12,50" and "12.50" are both unambiguous */
.amount-preview { font-size: 0.8125rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; min-height: 1.2em; }

.choice-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.choice-grid button {
  min-height: var(--tap); border-radius: 12px; border: 1px solid var(--line); background: var(--surface-2); font-weight: 600; cursor: pointer;
}
.choice-grid button[aria-pressed="true"], .choice-list button[aria-checked="true"] {
  background: var(--accent-soft); border-color: var(--accent); color: var(--ink);
}
.choice-list { display: grid; gap: 8px; }
.choice-list button {
  min-height: 52px; text-align: left; padding: 10px 14px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface-2); cursor: pointer; font-weight: 600;
}
.ob-step { display: grid; gap: 14px; }

/* ------------------------------------------------------------ keypad / chips */

.amount-display { font-size: 2.75rem; font-weight: 700; text-align: center; letter-spacing: -0.02em; min-height: 3.5rem; }
.amount-display.shake { animation: shake 300ms; }
@keyframes shake { 25% { transform: translateX(-6px); } 75% { transform: translateX(6px); } }
.keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.keypad button {
  min-height: 52px; border-radius: 12px; border: 0; background: var(--surface-2); font-size: 1.375rem; font-weight: 600; cursor: pointer;
}
.keypad button:active { background: var(--accent-soft); }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--tap); padding: 8px 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface); cursor: pointer; font-weight: 600;
  border-left: 5px solid var(--cat, var(--line));
}
.chip:hover { border-color: var(--accent); border-left-color: var(--cat, var(--accent)); }
.chip[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); }
.chip-later { border-style: dashed; border-left-style: dashed; color: var(--ink-2); }
.toggle-row { display: flex; gap: 8px; flex-wrap: wrap; }
.toggle {
  min-height: var(--tap); padding: 6px 12px; border-radius: 999px; border: 1px solid var(--line); background: transparent; color: var(--ink-2);
  font-size: 0.875rem; font-weight: 600; cursor: pointer;
}
.toggle[aria-expanded="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--ink); }
.extras { display: grid; gap: 10px; }
.extras:empty { display: none; }

.emoji-row { display: grid; gap: 8px; }
.emoji-input { width: 72px; text-align: center; font-size: 1.5rem; min-height: var(--tap); border-radius: 12px; border: 1px solid var(--line); background: var(--surface-2); }
.emoji-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--tap), 1fr)); gap: 4px; }
.emoji-grid button { min-height: var(--tap); border: 0; border-radius: 8px; background: transparent; font-size: 1.25rem; cursor: pointer; }
.emoji-grid button:hover, .emoji-grid button[aria-pressed="true"] { background: var(--accent-soft); }
.swatches { display: flex; flex-wrap: wrap; gap: 8px; }
.swatch {
  width: 36px; height: 36px; border-radius: 50%; border: 3px solid transparent; cursor: pointer; background: var(--sw);
  position: relative;
}
.swatch[aria-checked="true"] { border-color: var(--ink); }
.swatch[aria-checked="true"]::after { content: "✓"; position: absolute; inset: 0; display: grid; place-items: center; color: var(--check); font-weight: 700; text-shadow: 0 0 3px var(--check-shadow); }

/* ------------------------------------------------------------ lists */

.row {
  flex-wrap: wrap;
  display: flex; align-items: center; gap: 12px; width: 100%; min-height: 56px; padding: 8px 4px;
  border: 0; border-bottom: 1px solid var(--line); background: transparent; text-align: left; cursor: pointer;
}
.row:last-child { border-bottom: 0; }
.row:hover { background: var(--surface-2); }
.row .ico { width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center; font-size: 1.25rem; background: var(--surface-2); flex: 0 0 auto; }
.row .ico.tinted { background: var(--cat); }
/* The name asks for 150px before the amount and the button take theirs; below
   that the row wraps and the action drops under the name, at any width rather
   than only under 320px. A name broken as "electricit / y and gas" is not a
   layout that fits, it is one that gave up. */
.row .grow { flex: 1 1 150px; min-width: 0; }
/* A long bill or category name wraps onto a second line instead of being cut to
   "Very lon…" — the row is already two lines tall for its sub-line, so this
   costs nothing and shows the whole thing. */
.row .title { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.row .sub { overflow-wrap: anywhere; }
.row .sub { font-size: 0.8125rem; color: var(--ink-3); }
.row .amt { font-weight: 650; white-space: nowrap; }
.row .amt.in { color: var(--accent-text); }
.row .amt.muted { color: var(--ink-3); text-decoration: line-through; }
/* The amount and the action are one unit: on the same line as the name when
   there is room, on a line of their own when there is not — never one of each,
   which is what made a card of three bills read as three different layouts. */
.row-end { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; margin-left: auto; }
.row-actions { display: flex; gap: 6px; flex: 0 0 auto; }

.list-title { font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); margin: 10px 0 2px; }
.list-empty { color: var(--ink-3); padding: 12px 0; }
.day-head { font-size: 0.8125rem; color: var(--ink-3); font-weight: 600; margin-top: 8px; }

.goal { display: grid; gap: 8px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.goal:last-child { border-bottom: 0; }
/* The third row shape with the same trap in it: a goal's name sat beside
   "$40.00 / $400.00", and with `flex: 1` the name was the only part that could
   give, so "Weekend away" came out as "Weeken / d away". The name asks for
   110px first, and the row wraps rather than squeezing it — same as .bill-row,
   which this matches deliberately. */
.goal-top { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.goal-top .grow { flex: 1 1 110px; min-width: 0; }
/* Still the last resort, for one unbreakable string longer than the column. */
.goal-top .title { font-weight: 650; overflow-wrap: anywhere; }
.goal-top .sub { font-size: 0.8125rem; color: var(--ink-3); }
.goal .progress { height: 8px; }
.goal-actions { display: flex; gap: 8px; justify-content: flex-end; }

.menu-list { gap: 6px; }
.menu-item {
  min-height: 52px; text-align: left; padding: 12px 14px; border-radius: 12px; border: 0; background: var(--surface-2); font-weight: 600; cursor: pointer;
}
.menu-item:hover { background: var(--accent-soft); }

.guide { padding-left: 22px; display: grid; gap: 12px; color: var(--ink-2); }
.guide strong { color: var(--ink); }

/* ------------------------------------------------------------ activity */

.cycle-nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.totals { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.total { padding: 10px 12px; border-radius: var(--radius-sm); background: var(--surface-2); display: grid; gap: 2px; min-width: 0; }
/* A seven-figure amount cannot fit half of a 280px screen at any size a person
   would want to read. Below 360px the tiles take the full width instead of
   shrinking the number until it is cut in half. */
@media (max-width: 359px) { .totals { grid-template-columns: minmax(0, 1fr); } }
.total .tile-label { font-size: 0.75rem; }
.total .tile-value { font-size: clamp(0.8125rem, 3.6vw, 1rem); }
.breakdown { display: grid; gap: 6px; }
.bd-row { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; font-size: 0.875rem; }
.bd-row .progress { grid-column: 1 / -1; height: 6px; }
.bd-row .progress-fill { background: var(--cat, var(--accent)); }
.filters { display: grid; grid-template-columns: 1fr; gap: 8px; }
@media (min-width: 480px) { .filters { grid-template-columns: 1.4fr 1fr 1fr; } }
.export-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 8px; border-top: 1px solid var(--line); }

/* ------------------------------------------------------------ toasts */

.toasts {
  position: fixed; left: 50%; bottom: calc(84px + var(--sab)); transform: translateX(-50%); z-index: 200;
  /* A sheet's own actions sit at its foot; a toast over them covered the field
     a person was about to fill in. With a sheet open the toast moves to the top. */
  width: min(520px, calc(100vw - 24px)); display: grid; gap: 8px; pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 10px; padding: 10px 10px 10px 16px; border-radius: 14px;
  background: var(--ink); color: var(--bg); box-shadow: var(--shadow); pointer-events: auto;
  opacity: 0; transform: translateY(8px); transition: opacity var(--motion), transform var(--motion);
}
.toast.show { opacity: 1; transform: none; }
.toast-text { flex: 1; font-size: 0.9375rem; }
/* 44px, not 36: "Reload" is a deliberate call to action on a toast that can
   appear over anything, and it was the one control here small enough to miss. */
.toast-action {
  min-height: 44px; padding: 10px 14px; border-radius: 10px; border: 0; background: var(--accent); color: var(--accent-ink); font-weight: 700; cursor: pointer;
}
.toast-close { width: 44px; height: 44px; border: 0; background: transparent; color: inherit; font-size: 1.25rem; cursor: pointer; border-radius: 10px; }

@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="off"]) .amount-display.shake { animation: none; }
}
[data-motion="on"] .amount-display.shake { animation: none; }


/* ------------------------------------------------------------ desktop (≥ 900px) */

@media (min-width: 900px) {
  /* The sidebar is fixed to the viewport (nothing in the page can push or scroll it) and the content is left-aligned next to it. */
  #app { display: block; max-width: none; min-height: 100vh; min-height: 100dvh; padding: 0 0 0 64px; }
  .topbar, .bottombar { display: none; }
  .content { min-width: 0; max-width: 1156px; padding: 24px 28px 40px; }
  .banner-top { margin: 0 0 16px; max-width: 1100px; }
  #home { max-width: 1100px; margin: 0; padding: 0; grid-template-columns: minmax(0, 1fr) 300px; gap: 24px; align-items: start; }
  .home-main, .home-side { display: grid; gap: 16px; min-width: 0; }
  #banners, .hero, .week-strip, .tiles, .sort-badge, .bills-banner, .debt-summary, .categories { order: 0; }
  .goals-summary { display: block; }
  .insights { display: block; }
  .insights-fold { display: none; }
  .hero { padding: 34px 24px 24px; }
  .hero-amount { font-size: 3.6rem; }
  .category-grid { grid-template-columns: repeat(3, 1fr); }
  .sheet { width: min(560px, 92vw); }
  #sheet-expense { width: min(420px, 92vw); }

  /* sidebar: icon rail between 900 and 1099px, full at 1100px+ */
  .sidebar {
    display: flex; flex-direction: column; gap: 6px; position: fixed; top: 0; left: 0; bottom: 0; width: 64px; z-index: 30; overflow-y: auto;
    padding: 18px 10px calc(18px + var(--sab)); background: var(--surface); border-right: 1px solid var(--line);
  }
  .sidebar-brand { display: flex; align-items: center; justify-content: center; gap: 10px; font-weight: 650; font-size: 1.125rem; letter-spacing: -0.01em; min-height: var(--tap); margin-bottom: 6px; }
  .sidebar-add { width: 44px; height: 44px; padding: 0; border-radius: 50%; margin: 0 auto 10px; }
  .sidebar-add svg { width: 22px; height: 22px; }
  .sidebar-nav { display: grid; gap: 4px; }
  .sidebar-bottom { margin-top: auto; display: grid; gap: 4px; }
  .side-item {
    display: flex; align-items: center; justify-content: center; gap: 12px; width: 100%; min-height: var(--tap); padding: 10px;
    border: 0; border-radius: 12px; background: transparent; color: var(--ink-2); font-weight: 600; cursor: pointer; text-align: left;
  }
  .side-item svg { width: 22px; height: 22px; flex: 0 0 auto; }
  .side-item:hover { background: var(--surface-2); color: var(--ink); }
  .side-item[aria-current="page"] { background: var(--accent-soft); color: var(--ink); }
  .sidebar-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

@media (min-width: 1100px) {
  #app { padding-left: 240px; }
  .sidebar { width: 240px; }
  #home { grid-template-columns: minmax(0, 1fr) 360px; }
  .category-grid { grid-template-columns: repeat(4, 1fr); }
  .insights-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .insights-grid[data-layout="columns"] .insights-col { display: grid; gap: 16px; align-content: start; min-width: 0; }
  .sidebar { padding: 22px 16px calc(22px + var(--sab)); }
  .sidebar-brand { justify-content: flex-start; padding: 0 8px; }
  .sidebar-add { width: 100%; height: auto; padding: 12px 16px; border-radius: 12px; justify-content: flex-start; }
  .side-item { justify-content: flex-start; padding: 10px 12px; }
  .sidebar-label { position: static; width: auto; height: auto; overflow: visible; clip: auto; }
}

.goals-summary .goal { padding: 10px 0; }
.goals-summary .goal-actions { justify-content: flex-start; }

/* ------------------------------------------------------------ insights charts */

.insights-grid { display: grid; gap: 16px; align-items: start; }
/* One column (phones and the 900–1099px rail): the desktop stacks flatten back into reading order. */
.insights-col { display: contents; }
.chart-card.ch-c1 { order: 1; } .chart-card.ch-c2 { order: 2; } .chart-card.ch-c3 { order: 3; } .chart-card.ch-c4 { order: 4; }
.insights-fold summary { cursor: pointer; font-weight: 600; min-height: var(--tap); display: flex; align-items: center; gap: 8px; }
.insights-fold[open] summary { margin-bottom: 10px; }
.chart-card { padding: 14px 16px 12px; }
.chart-takeaway { font-weight: 600; margin: 2px 0 10px; }
.chart { width: 100%; height: auto; display: block; animation: chfade 150ms ease-out; }
@keyframes chfade { from { opacity: 0; } }
[data-motion="on"] .chart { animation: none; }
@media (prefers-reduced-motion: reduce) { :root:not([data-motion="off"]) .chart { animation: none; } }
.chart-summary { font-size: 0.8125rem; color: var(--ink-2); margin-top: 8px; }
.chart-empty { color: var(--ink-3); font-size: 0.9375rem; padding: 10px 0; }
.ch-bar { fill: var(--sage); }
.ch-bar.over { fill: var(--accent); }
.ch-bar.out { fill: var(--muted); }
.ch-guide { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; }
.ch-today { stroke: var(--accent); stroke-width: 1.5; }
.ch-axis { stroke: var(--line); stroke-width: 2; }
.ch-label { font-size: 10px; fill: var(--ink-3); font-family: inherit; }
.ch-safe-line { fill: none; stroke: var(--sage); stroke-width: 1.5; }
.ch-dot { fill: var(--ink); }
.ch-dot.neg { fill: var(--negative); }
.ch-bill { outline: none; cursor: default; }
.ch-bill:focus-visible circle { stroke: var(--ink); stroke-width: 2; }
.ch-rows { display: grid; gap: 6px; }
.ch-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 7.5em; gap: 8px; align-items: center;
  border: 0; background: transparent; padding: 5px 4px; border-radius: 8px; text-align: left; font: inherit; color: inherit; cursor: pointer;
}
.ch-row:hover { background: var(--surface-2); }
.ch-row.muted { cursor: default; color: var(--ink-3); }
.ch-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; font-size: 0.875rem; }
.ch-hbar { width: 100%; height: 8px; border-radius: 6px; background: var(--sage-soft); display: block; grid-column: 1 / -1; order: 3; overflow: hidden; }
.ch-hbar-fill { height: 100%; border-radius: 6px; }
.ch-hbar-fill.ch-later { background: var(--sage); }
.ch-later { fill: var(--sage); }
.ch-num { display: flex; justify-content: space-between; gap: 6px; font-size: 0.875rem; }
.ch-pct { color: var(--ink-3); min-width: 3em; text-align: right; }
.ch-legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 0.8125rem; color: var(--ink-3); margin-top: 6px; }
.ch-key::before { content: ""; display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 5px; background: var(--sage); vertical-align: -1px; }
.ch-key.out::before { background: var(--muted); }
.ch-key.dot::before { background: var(--ink); }
.ch-key.dot::before { border-radius: 50%; }

/* ------------------------------------------------------------ simple / everything

   One setting decides how much of the app is on screen. It only ever hides
   chrome: every screen keeps its data, and switching back brings it all into
   view again. Advanced surfaces carry data-adv; the two bottom-bar buttons that
   stand in for them in Simple carry data-simple. */

html[data-complexity="simple"] [data-adv] { display: none !important; }
[data-simple] { display: none; }
html[data-complexity="simple"] .bottombar [data-simple] { display: flex; }

.greeting { font-size: 1.0625rem; font-weight: 650; color: var(--ink-2); padding: 4px 2px 0; }

/* ------------------------------------------------------------ the number, explained */

.hero-note { font-size: 0.8125rem; color: var(--ink-3); max-width: 34ch; margin: 0 auto 2px; }
.hero-explain { margin-bottom: 8px; }
.linkish {
  border: 0; background: transparent; padding: 12px 8px; min-height: var(--tap); font: inherit; font-size: 0.875rem; font-weight: 600;
  color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; border-radius: 8px;
}
.linkish:hover { background: var(--accent-soft); }

.explain { display: grid; gap: 2px; margin: 10px 0 14px; }
.explain-row {
  display: grid; grid-template-columns: 1.5em minmax(0, 1fr) auto; gap: 4px 10px; align-items: baseline;
  width: 100%; text-align: left; font: inherit; color: inherit; padding: 10px 6px; min-height: var(--tap);
  border: 0; border-bottom: 1px solid var(--line); background: transparent; border-radius: 8px;
}
button.explain-row { cursor: pointer; }
button.explain-row:hover { background: var(--surface-2); }
.explain-op { font-weight: 700; color: var(--ink-3); font-size: 1.0625rem; }
.explain-label { font-weight: 600; }
.explain-amount { font-weight: 650; white-space: nowrap; font-variant-numeric: tabular-nums; }
.explain-sub { grid-column: 2 / -1; font-size: 0.8125rem; color: var(--ink-3); }
/* Same as .cat-row: on the narrowest phones a label and a seven-figure amount
   cannot share a line. "Yours until payday" was a 66px column three lines deep
   next to -$2,488,141.69. The amount drops under the label instead, which is
   where this row's own sub-line already goes. */
@media (max-width: 359px) {
  .explain-row { grid-template-columns: 1.5em minmax(0, 1fr); }
  .explain-amount { grid-column: 2 / -1; }
}
.explain-row.explain-total { border-bottom: 0; background: var(--surface-2); margin-top: 6px; }
.explain-row.explain-total .explain-label, .explain-row.explain-total .explain-amount { font-size: 1.0625rem; font-weight: 700; }

/* ------------------------------------------------------------ debt */

.debt-summary .debt-free { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; }
.debt-hero { text-align: center; padding: 22px 18px 18px; }
.debt-headline { font-size: clamp(1.9rem, 8vw, 2.6rem); font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; }
.debt-headline-date { color: var(--ink-2); margin-top: 6px; }
.debt-change { margin-top: 8px; font-weight: 650; color: var(--accent-text); }

.seg { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.seg button {
  display: grid; gap: 2px; min-height: var(--tap); padding: 10px 12px; border-radius: 12px;
  border: 1.5px solid var(--line); background: var(--surface); font: inherit; font-weight: 650; color: var(--ink); cursor: pointer; text-align: left;
  min-width: 0;
}
.seg button > * { min-width: 0; overflow-wrap: anywhere; }
.seg button:hover { border-color: var(--accent); }
.seg button[aria-checked="true"] { background: var(--accent-soft); border-color: var(--accent); }
.seg-sub { font-size: 0.75rem; font-weight: 500; color: var(--ink-3); }

.tag {
  display: inline-block; font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; background: var(--accent-soft); color: var(--accent-text); white-space: nowrap;
}
.row .tag { margin-left: 6px; }

.btn-tall { min-height: 64px; flex-direction: column; align-items: flex-start; gap: 2px; padding: 12px 16px; text-align: left; }
.btn-sub { font-size: 0.8125rem; font-weight: 500; opacity: 0.85; white-space: normal; }
/* 0.85 over the coral fill lands at 4.03:1, under the 4.5 that 13px text needs.
   On a filled button the sub-line takes the label's own colour instead. */
.btn-primary .btn-sub { opacity: 1; }

/* ------------------------------------------------------------ while you were away
   One calm surface in the existing card style. No red, no streak, no badge:
   somebody who has been away for a fortnight does not need a scoreboard. It is
   above the number on purpose, and it is a card so it can be scrolled past. */

.catchup { display: grid; gap: 12px; }
.catchup-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.catchup-head .card-title { margin: 0; }
.catchup-head .icon-btn { margin: -10px -10px 0 0; flex: 0 0 auto; }
.catchup-lead { margin: 0; color: var(--ink-2); }
.catchup .list-title { margin: 4px 0 2px; }
.catchup .check { min-height: var(--tap); }
.catchup-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.catchup-actions .btn { flex: 0 0 auto; }

/* ------------------------------------------------------------ milestones */

.ms-card { display: grid; gap: 4px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.ms-card:last-child { border-bottom: 0; }
.ms-top { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
.ms-title { font-weight: 650; flex: 1 1 110px; min-width: 0; }
.ms-date { font-size: 0.8125rem; color: var(--ink-3); white-space: nowrap; }
/* Below 360px a title and a date cannot share a line without the title being
   cut to three or four words a line — "A full cycle inside / your number" next
   to "August 31, 2026". The date takes a line of its own, for every card
   rather than only the long ones, so the list reads as one shape. */
@media (max-width: 359px) {
  .ms-title { flex: 1 1 100%; }
}
.ms-text { color: var(--ink-2); font-size: 0.9375rem; }
.ms-card.soon .ms-title { color: var(--ink-3); font-weight: 600; }
.ms-card.soon .ms-text { color: var(--ink-3); }

/* Focus mode takes the rest of the app out of the page entirely, so nothing
   behind the overlay is reachable by Tab or a screen reader. */
html[data-focus] #app { display: none; }
html[data-focus] .toasts { bottom: calc(24px + var(--sab)); }
body.has-sheet .toasts { bottom: auto; top: calc(12px + var(--sat)); }
.focus .btn-ghost { margin-top: 4px; }

/* ------------------------------------------------------------ planner

   The calendar answers "what will I have on the 23rd", so the projected
   figure is part of the cell, not a tooltip. Cells are a fixed 6-row grid so
   the sheet never changes height between months.

   Three marks, three existing colours, and no fourth: a bill and a debt
   minimum are the same kind of promise, so they share the coral dot. */

.week-strip { padding: 12px 14px 14px; }
.week-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  min-height: var(--tap); padding: 4px 0 8px; border: 0; background: transparent; cursor: pointer; text-align: left; font: inherit; color: inherit;
}
.week-head:hover .week-more { color: var(--ink); }
.week-head .card-title { margin-bottom: 0; }
.week-more { font-size: 0.8125rem; font-weight: 600; color: var(--ink-3); display: flex; align-items: center; gap: 2px; }
.week-more .chev { margin-left: 0; font-size: 1rem; }
.week-days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; }
.week-day {
  display: grid; gap: 2px; justify-items: center; padding: 6px 2px 7px; min-height: 62px;
  border: 1px solid transparent; border-radius: 10px; background: transparent; cursor: pointer; font: inherit; color: inherit;
}
.week-day:hover { background: var(--surface-2); }
.week-day[data-today="1"] { border-color: var(--ink); }
.week-dow { font-size: 0.6875rem; font-weight: 700; color: var(--ink-3); text-transform: uppercase; }
.week-num { font-size: 1rem; font-weight: 650; line-height: 1.1; }
.week-proj { font-size: 0.625rem; color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

/* Seven columns in 280px leave each day about 20px of content box, and four
   digits need 22 — so "9999" was ellipsed before any thousands formatting was
   involved. The gap and the side padding buy the room back; the smaller type
   keeps the widest string the formatter can print inside it. */
@media (max-width: 359px) {
  .week-days { gap: 2px; }
  .week-day { padding-left: 0; padding-right: 0; }
  .week-proj { font-size: 0.5625rem; }
}

.dots { display: flex; gap: 3px; min-height: 6px; align-items: center; }
/* Scoped to .dots on purpose: a bare `.dot` also matches the Insights legend's
   `ch-key dot` span, which is a text label, and squashing that to 5px breaks
   the legend. It did, in 1.6.0. */
.dots .dot { width: 5px; height: 5px; border-radius: 50%; display: block; }
.dot-bill { background: var(--accent); }
.dot-pay { background: var(--sage); }
.dot-spend { background: var(--muted); }

/* The month and its two arrows stay together as one cluster however wide the
   screen gets; Today is the only thing pushed to the far side. */
/* Four controls in one row. On a 280px phone they do not fit, and the month
   heading was the only flexible one, so it was the one that got cut: "Sep 20…".
   A heading that cannot be read is worse than a heading on its own line, so the
   row wraps and the heading keeps its width. */
.cal-nav { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cal-month { text-align: center; font-weight: 650; font-size: 1.0625rem; white-space: nowrap; }
#cal-today { margin-left: auto; }
/* min-width: 0 all the way down, or the seven cells' min-content width props
   the grid open and it slides out under the month rail on a narrow phone. */
.cal { display: grid; gap: 4px; min-width: 0; }
.cal-weekdays, .cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; min-width: 0; }
.cal-cell, .cal-dow { min-width: 0; }
.cal-dow { text-align: center; font-size: 0.6875rem; font-weight: 700; color: var(--ink-3); text-transform: uppercase; padding-bottom: 2px; }
.cal-cell {
  display: grid; gap: 1px; justify-items: center; align-content: start; min-height: 54px; padding: 5px 2px;
  border: 1px solid transparent; border-radius: 10px; background: var(--surface-2); cursor: pointer; font: inherit; color: inherit;
}
.cal-cell:hover { border-color: var(--accent); }
.cal-cell[data-out="1"] { background: transparent; color: var(--ink-3); }
.cal-cell[data-today="1"] { border-color: var(--ink); }
.cal-num { font-size: 0.875rem; font-weight: 650; line-height: 1.2; }
.cal-cell[data-today="1"] .cal-num { color: var(--accent-text); }
.cal-proj { font-size: 0.5625rem; color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
@media (min-width: 480px) {
  .cal-cell { min-height: 64px; }
  .cal-num { font-size: 0.9375rem; }
  .cal-proj { font-size: 0.625rem; }
}
/* On a wide screen the seven columns get generous, so the rows grow with them
   and the month keeps a calendar's proportions instead of a ribbon's. */
@media (min-width: 900px) { .cal-cell { min-height: 88px; } }
@media (min-width: 1200px) { .cal-cell { min-height: 104px; } }

.cal-legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 0.75rem; color: var(--ink-3); }
.key::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px; vertical-align: 1px; }
.key-bill::before { background: var(--accent); }
.key-pay::before { background: var(--sage); }
.key-spend::before { background: var(--muted); }
.cal-summary .totals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 480px) { .cal-summary .totals { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
/* Same rule as .totals, restated at this specificity so it is not overridden:
   below 360px a seven-figure amount gets the whole width rather than half of it. */
@media (max-width: 359px) { .cal-summary .totals { grid-template-columns: minmax(0, 1fr); } }

.day-hero { text-align: center; padding: 18px 16px 14px; }
.day-amount { font-size: clamp(1.9rem, 9vw, 2.6rem); font-weight: 700; letter-spacing: -0.02em; }
.day-empty { color: var(--ink-3); padding: 14px 0; }
.week-head[disabled] { cursor: default; }

/* ------------------------------------------------------------ the month rail

   Twelve tabs down the right edge of the Planner, like the dividers in a paper
   diary: the year at the top with an arrow above and below it, then Jan to Dec
   with their names turned on their side.

   The tints are not new colours. Every tab sits on the section grey the app
   already uses for a recessed surface, and a single layer of an existing token
   is laid over it at a low opacity — sage for the cool half of the year, coral
   for the warm one — so the rail drifts from cool to warm and back without
   introducing a hue that is not already in the palette. */

/* The layout of the Planner screen itself lives with the other screens, at the
   end of this file. What follows is only the rail's own furniture. */
.rail-year { display: grid; justify-items: center; gap: 2px; }
.rail-arrow {
  width: 100%; min-height: var(--tap); border: 0; border-radius: 8px; background: transparent;
  color: var(--ink-3); font-size: 1.0625rem; line-height: 1; cursor: pointer;
}
.rail-arrow:hover { background: var(--surface-2); color: var(--ink); }
.rail-year-num { font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.02em; font-variant-numeric: tabular-nums; }

/* The twelve share whatever height the rail has, so the stack always reaches
   from the year down to the bottom of the grid. */
.rail-months { flex: 1; display: grid; grid-template-rows: repeat(12, minmax(24px, 1fr)); }
.rail-tab {
  position: relative; isolation: isolate; overflow: hidden; color: var(--ink);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  border: 0; padding: 2px 0; cursor: pointer;
  background: var(--surface-2); font: inherit; font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  border-radius: 9px 0 0 9px;                 /* the edge that faces the grid */
  margin-left: 8px;                            /* selected tabs reach past this */
  transition: margin-left var(--motion), background var(--motion);
}
.rail-tab::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--tint, var(--sage)); opacity: var(--tint-o, 0.08);
}
.rail-tab span { writing-mode: vertical-rl; text-orientation: mixed; line-height: 1; }
.rail-tab:hover { color: var(--ink); }
/* The tab is inset from the rail edge so the selected one can sit proud of the
   others. The touch target is not: this reaches the full width of the rail. */
.rail-tab::after { content: ""; position: absolute; inset: 0 -8px 0 0; }
/* The selected tab is wider and reaches the grid, like a tab pulled forward. */
.rail-tab[aria-selected="true"] {
  margin-left: 0; color: var(--ink); font-weight: 800;
  box-shadow: inset 2px 0 0 var(--accent);
}
.rail-tab[aria-selected="true"]::before { opacity: calc(var(--tint-o, 0.08) * 2.2); }
/* The month you are actually in. A separate element rather than a pseudo on the
   rotated text, so it lands after the name in the vertical flow rather than
   wherever the writing mode decides to put it. */
.rail-now { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }

/* Cool to warm and back. Only two tokens do the tinting; the grey underneath
   is the third, and it is the one that shows through at the shoulders. */
.rail-tab[data-m="1"]  { --tint: var(--sage);   --tint-o: 0.17; }
.rail-tab[data-m="2"]  { --tint: var(--sage);   --tint-o: 0.14; }
.rail-tab[data-m="3"]  { --tint: var(--sage);   --tint-o: 0.11; }
.rail-tab[data-m="4"]  { --tint: var(--sage);   --tint-o: 0.08; }
.rail-tab[data-m="5"]  { --tint: var(--sage);   --tint-o: 0.05; }
.rail-tab[data-m="6"]  { --tint: var(--accent); --tint-o: 0.06; }
.rail-tab[data-m="7"]  { --tint: var(--accent); --tint-o: 0.10; }
.rail-tab[data-m="8"]  { --tint: var(--accent); --tint-o: 0.14; }
.rail-tab[data-m="9"]  { --tint: var(--accent); --tint-o: 0.09; }
.rail-tab[data-m="10"] { --tint: var(--sage);   --tint-o: 0.06; }
.rail-tab[data-m="11"] { --tint: var(--sage);   --tint-o: 0.10; }
.rail-tab[data-m="12"] { --tint: var(--sage);   --tint-o: 0.14; }

/* Phones: the rail narrows but never becomes a dropdown. */
@media (max-width: 319px) {
  /* "September 2026" cannot fit beside two arrows and Today at this width, and
     "Septe…" is worse than "Sep". The rail says the year. */
  .cal-month { font-size: 0.9375rem; }
}
@media (max-width: 599px) {
  .rail-tab { font-size: 0.625rem; letter-spacing: 0.04em; margin-left: 5px; border-radius: 7px 0 0 7px; }
  .rail-year-num { font-size: 0.75rem; }
}

/* ------------------------------------------------------------ screens

   Two top-level screens live in the content column: Home and the Planner.
   Everything else in the app is a sheet over one of them; these two are the app
   itself. The [hidden] rule at the top of this file does the hiding. */

.screen-head { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; }
.screen-title { font-size: 1.25rem; font-weight: 650; letter-spacing: -0.01em; }
.screen-title:focus { outline: none; }
.screen-back { flex: 0 0 auto; font-size: 1.375rem; line-height: 1; }

/* The rail is out of flow, so a rail screen is one column with room kept for
   it. Two screens use this now — the Planner and Categories — and they share
   one layout for the same reason they share one rail. */
#planner, #categories { display: block; padding: 8px 44px calc(96px + var(--sab)) 16px; }
#planner .screen-head, #categories .screen-head { padding-right: 8px; }
.rail-layout { display: block; }
.rail-main { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-content: start; min-width: 0; }
.rail-main > * { min-width: 0; }

/* The rail is pinned to the right edge of the screen and runs its full height,
   so every month stays in reach however far the grid scrolls. */
.cal-body { display: block; }
.cal-main { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-content: start; min-width: 0; }
.cal-main > * { min-width: 0; }
/* Fixed, so it is genuinely pinned to the right edge of the screen and runs the
   whole height of it however far the grid scrolls. It stops clear of the two
   fixed bars: the topbar is 64px tall and the bottom nav 68px, both of which
   only exist below 900px, where the sidebar takes over and the rail gets the
   full height instead. */
.cal-rail {
  position: fixed; right: 0; z-index: 25;
  top: calc(64px + var(--sat)); bottom: calc(68px + var(--sab));
  width: 44px;
  display: flex; flex-direction: column; gap: 8px;
}

@media (min-width: 600px) {
  #planner, #categories { padding-right: 64px; }
  .cal-rail { width: 64px; }
}
@media (min-width: 900px) {
  /* Home keeps its reading width; the Planner runs to the edge of the window so
     the rail is pinned against it rather than floating in the margin. */
  html[data-view="planner"] .content,
  html[data-view="categories"] .content { max-width: none; padding-right: 0; }
  #planner, #categories { padding: 0 64px 40px 0; }
  #planner .screen-head, #categories .screen-head { padding-right: 0; }
  .screen-back { display: none; }          /* the sidebar is the way back */
  /* No topbar and no bottom nav up here: the rail gets the whole height. */
  .cal-rail { top: 0; bottom: 0; padding: 24px 0; }
}

/* ------------------------------------------------------------ categories screen

   One month at a time, biggest spender first, with a bar scaled to the biggest
   row rather than to a budget — the question this screen answers is "where did
   September go", not "am I within my limits". Categories with nothing that
   month stay on the list, dimmed: an unused category is a fact about the month.
   No new colours; the bar is each category's own swatch, as on Home. */

.cat-total { font-size: 1.0625rem; font-weight: 650; }
.cat-total .sub { font-weight: 500; color: var(--ink-3); font-size: 0.875rem; }

.cat-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 4px 10px; align-items: center;
  width: 100%; padding: 10px 6px; min-height: var(--tap);
  border: 0; border-bottom: 1px solid var(--line); background: transparent; border-radius: 8px;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.cat-row:last-of-type { border-bottom: 0; }
.cat-row:hover { background: var(--surface-2); }
.cat-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--cat, var(--muted)); flex: 0 0 auto; }
/* "Sort la…" beside "$2,469,144.38". The name wraps now instead of being cut;
   the amount is the one thing in the row that cannot usefully be shortened. */
.cat-row .name { font-weight: 600; overflow-wrap: anywhere; }
.cat-row .amt { font-weight: 650; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cat-row .count { grid-column: 2 / -1; font-size: 0.8125rem; color: var(--ink-3); }
/* On the narrowest phones a name and a seven-figure amount cannot share a line
   without the name being cut to two or three words a line. The amount moves
   under the name instead, where the count and the bar already are. */
@media (max-width: 359px) {
  .cat-row { grid-template-columns: auto minmax(0, 1fr); }
  .cat-row .amt { grid-column: 2 / -1; }
}
/* Both need display: block — a <span> inside a non-flex parent stays inline,
   and an inline box ignores width and height. */
.cat-row .bar { display: block; grid-column: 2 / -1; height: 6px; border-radius: 6px; background: var(--sage-soft); overflow: hidden; }
.cat-row .bar-fill { display: block; height: 100%; border-radius: 6px; background: var(--cat, var(--sage)); }
.cat-row[data-empty="1"] { opacity: 0.55; }
.cat-row[data-empty="1"] .amt { color: var(--ink-3); }

/* ------------------------------------------------------------ filter chips */

/* The sheet body is a grid, and a grid item stretches across its column unless
   told otherwise. `align-self` is the wrong axis for that, so the pill was
   drawn 234px wide around 155px of content: a "September 2026 ×" chip with
   70px of empty pill trailing after the ×. */
.filter-chip {
  display: inline-flex; align-items: center; gap: 4px;
  justify-self: start; align-self: start; width: fit-content;
  padding: 4px 4px 4px 12px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-text); font-size: 0.875rem; font-weight: 600;
}
.chip-clear {
  width: var(--tap); height: var(--tap); margin: -9px -9px -9px 0; border: 0; border-radius: 50%;
  background: transparent; color: inherit; font-size: 1.0625rem; line-height: 1; cursor: pointer;
}
.chip-clear:hover { background: var(--accent); color: var(--accent-ink); }

/* ------------------------------------------------------------ print

   Nothing in this app is meant to be printed except the month: no bills to
   post, no receipts to file. So printing gives you the calendar on white
   paper, without the furniture that only makes sense on a screen. */

@media print {
  .sidebar, .topbar, .bottombar, .cal-rail, .screen-back, .toasts, #banners,
  .backdrop, .sheet, .cal-legend, .cal-nav .icon-btn, #cal-today, #cat-today { display: none !important; }
  html, body, #app, .content { background: #fff; margin: 0; padding: 0; max-width: none; }
  #planner, #categories { padding: 0; }
  .cal-cell, .total, .card { break-inside: avoid; }
  .cal-cell { border: 1px solid #999; background: #fff; }
  .cat-row .bar { display: none; }   /* the projected figures are the point of the page, so they print */
  .screen-head { margin-bottom: 4mm; }
  @page { margin: 12mm; }
}
