/* Self-hosted, copied out of node_modules by scripts/copy-vendor.js. No CDN is permitted. */
@font-face {
  font-family: 'Source Serif 4';
  src: url('fonts/source-serif-4-latin-wght-normal.woff2') format('woff2');
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('fonts/ibm-plex-mono-latin-400-normal.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('fonts/ibm-plex-mono-latin-500-normal.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* --- Tokens ---------------------------------------------------------------
   The Kitchen Table Ledger. Paper and ink, plus the two colours a bookkeeper
   actually reaches for. Category colours belong to the household and are the
   only saturated thing on screen — nothing here may compete with them.
   Measured contrast (see DESIGN.md): ink/sheet 16.9:1 · ink-muted/sheet 5.9:1
   · rule-strong/sheet 3.1:1 (control boundaries) · over/sheet 7.0:1
   · under/sheet 6.4:1 · graphite/paper 3.5:1.
-------------------------------------------------------------------------- */
:root {
  --ink: #1f1b16;
  --ink-hover: #3a322a;
  --ink-muted: #6b6157;
  --paper: #f6f3ed;
  --sheet: #fdfcfa;
  --rule: #e0d9cd;
  --rule-strong: #9a8e7c;
  --over-ink: #a32d2a;
  --over-ink-bright: #f0b8b4;
  --under-ink: #2f6b46;
  --graphite: #8a7f6e;

  --lifted: 0 6px 20px rgba(31, 27, 22, .14);

  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 40px;
  --radius-sm: 2px;
  --radius-md: 4px;

  /* Language is set; money is ruled. Two families, and the split between them is absolute. */
  --font-text: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font-figure: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', monospace;

  font-family: var(--font-text);
  font-size: 100%;
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  font-synthesis: none;
  -webkit-text-size-adjust: 100%;
}

* { box-sizing: border-box }

body {
  margin: 0;
  background: var(--paper);
  min-height: 100vh;
  font-size: .95rem;
  font-weight: 400;
}

[hidden] { display: none !important }

/* Must precede the role rules: `font` is a shorthand and would reset any size or weight set above it. */
button, input, select, textarea { font: inherit }

/* --- Type roles ----------------------------------------------------------
   display      page titles                serif 400
   headline     section headings           serif 600
   body         prose, table text          serif 400  .95rem
   label        annotation layer           serif 600  .72rem uppercase
   figure       money in a row or cell     mono  500
   figure-lead  headline money             mono  400  display size
   Every amount of money is set in the mono. A euro figure in the serif is a bug.
-------------------------------------------------------------------------- */

.page-title h1,
.login-card h1 {
  font-size: clamp(1.85rem, 3.5vw, 2.6rem);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -.015em;
  margin: .15rem 0;
}

.card h2 {
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: normal;
  margin: 0 0 var(--space-md);
}

/* The annotation layer: field labels, column headers, card subtitles, tab and button text. */
label,
th,
.card-heading p,
.stat p,
.origin,
.shared-toggle,
.cell-label,
.tabs button,
button,
.button {
  font-family: var(--font-text);
  font-size: .72rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .09em;
  text-transform: uppercase;
}

/* Money. Tabular by construction, right-aligned to a shared edge.
   No nowrap: Intl emits a non-breaking space before the currency symbol, so a single amount is
   already atomic, while a composite cell ("412,80 € / 300,00 € · 138%") must be free to break. */
.insight-row b,
.figure,
input[inputmode="decimal"] {
  font-family: var(--font-figure);
  font-size: .9rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: normal;
  font-variant-numeric: tabular-nums;
  text-transform: none;
}

input[inputmode="decimal"] { text-align: right }

/* Sized against the real string: "-12.345,67 €" is 11 monospace advances (0.6em each), and the
   narrowest stat column is ~194px at the 5-across desktop grid. 1.7rem leaves ~15px. */
.stat strong {
  display: block;
  margin-top: var(--space-sm);
  font-family: var(--font-figure);
  font-size: clamp(1.3rem, 2vw, 1.7rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

/* Percentages and counts sit beside figures, so they hold their column too. */
.delta,
.insight-row small,
.pagination span,
.status-item b {
  font-variant-numeric: tabular-nums;
}

/* --- Controls ------------------------------------------------------------- */

button, .button {
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: var(--sheet);
  padding: 11px 18px;
  min-height: 40px; /* matches input height so controls align on a shared baseline in form rows */
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background-color 120ms cubic-bezier(.4, 0, .2, 1),
              color 120ms cubic-bezier(.4, 0, .2, 1),
              border-color 120ms cubic-bezier(.4, 0, .2, 1);
}
button:hover, .button:hover { background: var(--ink-hover) }
button:disabled { opacity: .5; cursor: default }

/* One focus treatment everywhere: ink, never removed, never a coloured glow. */
button:focus-visible,
.button:focus-visible,
a:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.button-quiet {
  background: transparent;
  color: var(--ink-muted);
  border: 1px solid var(--rule-strong);
}
.button-quiet:hover { background: var(--paper); color: var(--ink) }
/* Destructive actions colour their text, never their surface. */
.danger { color: var(--over-ink) !important; background: transparent !important }
.danger:hover { background: var(--paper) !important; border-color: var(--over-ink) }

input, select {
  width: 100%;
  min-height: 40px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-sm);
  padding: 9px 10px;
  color: var(--ink);
  background: var(--sheet);
  outline: none;
  font-size: .95rem;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}
input:focus, select:focus {
  border-color: var(--ink);
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
/* Muted ink, not the rule colour: a placeholder is text and owes the same 4.5:1 as any other
   (5.9:1 here; rule-strong would have been 1.9:1). */
input::placeholder { color: var(--ink-muted); opacity: 1 }
input[type="color"] { padding: 3px; cursor: pointer }
input[type="checkbox"] { min-height: 0; accent-color: var(--ink) }

label {
  display: grid;
  gap: var(--space-xs);
  color: var(--ink-muted);
}

/* Present for assistive tech and for the stacked mobile record; the desktop column header
   already names these, so it is hidden there rather than duplicated. */
.cell-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.muted, .card-heading p { color: var(--ink-muted) }
.error {
  color: var(--over-ink);
  min-height: 1.2em;
  font-size: .84rem;
}

/* --- Login ---------------------------------------------------------------- */

.login-shell {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--space-md);
  background: var(--paper);
}
.login-card {
  width: min(390px, 100%);
  padding: var(--space-xl);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  display: grid;
  gap: var(--space-md);
  text-align: center;
}
.login-card h1 { margin: 0 }
.login-card p.muted {
  margin: 0 0 var(--space-sm);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--rule);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
}
.login-card label { text-align: left }

/* --- Masthead ------------------------------------------------------------- */
/* A ledger's header is a ruled line, not a painted band. */

.topbar {
  min-height: 62px;
  padding: var(--space-sm) max(var(--space-md), calc((100% - 1240px) / 2));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  background: var(--paper);
  border-bottom: 2px solid var(--rule-strong);
  position: sticky;
  top: 0;
  z-index: 10;
}
.brand {
  color: var(--ink);
  text-decoration: none;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: .02em;
}
.top-actions { display: flex; gap: var(--space-sm); align-items: center }
.top-actions input {
  width: 150px;
  font-family: var(--font-figure);
  font-size: .82rem;
  font-variant-numeric: tabular-nums;
}

.shell { width: min(1240px, calc(100% - 2rem)); margin: auto }

/* --- Tabs ----------------------------------------------------------------- */

.tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--rule);
  margin: var(--space-md) 0 var(--space-lg);
  overflow-x: auto;
}
.tabs button {
  background: transparent;
  color: var(--ink-muted);
  border-radius: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 12px 16px;
  min-height: 44px;
  white-space: nowrap;
}
.tabs button:hover { color: var(--ink); background: transparent }
.tabs button:focus-visible { outline-offset: -2px }
/* State is a weight change and a rule, so it survives without hue. */
.tabs button[aria-current="page"] {
  color: var(--ink);
  background: transparent;
  border-bottom-color: var(--ink);
  font-weight: 700;
}

.page-title {
  display: flex;
  justify-content: space-between;
  align-items: end;
  margin: 0 0 var(--space-lg);
  gap: var(--space-md);
}

/* --- Stat figures --------------------------------------------------------- */
/* No cards. A label, a mono figure, and a hairline between columns. */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  margin-bottom: var(--space-xl);
  border-top: 1px solid var(--rule);
  border-bottom: 2px solid var(--rule-strong);
}
.stat { padding: var(--space-md) var(--space-md) var(--space-md) 0 }
.stat + .stat {
  border-left: 1px solid var(--rule);
  padding-left: var(--space-md);
}
.stat p { margin: 0; color: var(--ink-muted) }

/* --- Cards ---------------------------------------------------------------- */
/* Flat by default. Separation is a rule, not a shadow. */

.card {
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  padding: 20px;
}
.card + .card { margin-top: var(--space-md) }
.card-heading {
  display: flex;
  justify-content: space-between;
  align-items: start;
  gap: var(--space-md);
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--rule);
}
.card-heading h2 { margin: 0 }
.card-heading p { margin: var(--space-xs) 0 0; max-width: 65ch }

.chart-grid {
  display: grid;
  grid-template-columns: 1fr 1.5fr;
  gap: var(--space-md);
  margin-top: var(--space-md);
  padding-bottom: var(--space-xl);
}
.chart-grid .card + .card { margin-top: 0 }
.chart-card.wide, .settings-grid .wide { grid-column: 1 / -1 }
.chart-wrap { height: 280px; margin-top: 12px; position: relative }
.chart-empty {
  margin: 0;
  padding: var(--space-xl) 0;
  text-align: center;
  color: var(--ink-muted);
}

/* --- Transactions --------------------------------------------------------- */

.filters {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: var(--space-md);
  margin: var(--space-md) 0;
}
.form-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1.3fr;
  gap: var(--space-md);
  align-items: end;
}
.form-actions { display: flex; gap: var(--space-sm); grid-column: 1 / -1 }

.table-card { padding: 0; overflow: hidden }
.table-scroll { overflow-x: auto }
table { width: 100%; border-collapse: collapse; font-size: .95rem }
th {
  text-align: left;
  color: var(--ink-muted);
  background: transparent;
  border-bottom: 2px solid var(--rule-strong);
  padding: 12px;
}
td { padding: var(--space-sm) 12px; border-bottom: 1px solid var(--rule); vertical-align: middle }
tbody tr:last-child td { border-bottom: 0 }
td input, td select { min-width: 110px; padding: 6px 8px }

.row-actions { display: flex; gap: var(--space-xs) }
/* 44px regardless of pointer: Save sits beside Delete, and the cost of a mis-hit here is a
   destroyed transaction. Density is not worth that anywhere. */
.row-actions button { padding: 8px 10px; min-height: 44px; background: transparent; color: var(--ink) }
.row-actions button:hover { background: var(--paper) }
.pagination {
  padding: 12px;
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  align-items: center;
  border-top: 1px solid var(--rule);
}
.pagination button { padding: 8px 12px }
.pagination span {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
/* Holds the payer's first name as well as the source, so it is never uppercased. */
.source { color: var(--ink-muted); font-size: .84rem }
.empty { padding: var(--space-xl); text-align: center; color: var(--ink-muted) }

/* --- First run ------------------------------------------------------------ */

.first-run {
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  background: var(--sheet);
  padding: 20px;
  margin-bottom: var(--space-md);
}
.first-run h2 {
  margin: 0 0 var(--space-sm);
  font-size: 1.1rem;
  font-weight: 600;
}
.first-run p { margin: 0; max-width: 65ch; color: var(--ink-muted) }

/* Bot commands appear as literal text the household types, so they are set as such. */
code {
  font-family: var(--font-figure);
  font-size: .88em;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
  color: var(--ink);
}

/* --- Insights ------------------------------------------------------------- */

.recommendations {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}
.recommendations .card + .card { margin-top: 0 }
/* The "nothing generated yet" card is alone in a three-column grid, so it takes the full row. */
.recommendations .card:only-child { grid-column: 1 / -1 }
.recommendation p {
  color: var(--ink-muted);
  font-size: .95rem;
  line-height: 1.55;
  max-width: 65ch;
  margin: 0;
}

.insight-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-md);
  padding-bottom: var(--space-xl);
}
.insight-grid .card + .card { margin-top: 0 }

/* The ledger row: label left, figure right, hairline below. The system's signature. */
.insight-list { display: block }
.insight-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
}
.insight-row:last-child { border-bottom: 0 }
.insight-row small {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
/* A group total is set off by a rule above it, not a hairline below. */
.insight-row.total {
  border-bottom: 0;
  border-top: 2px solid var(--rule-strong);
  margin-top: var(--space-xs);
  padding-top: 12px;
  font-weight: 600;
}
.delta.up, .over { color: var(--over-ink) }
.delta.down { color: var(--under-ink) }

/* --- Settings ------------------------------------------------------------- */

.settings-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md);
  padding-bottom: var(--space-xl);
}
.settings-grid .card + .card { margin-top: 0 }
.stack { display: grid; gap: var(--space-md) }
.inline-form {
  display: grid;
  grid-template-columns: 2fr 90px auto;
  gap: 12px;
  align-items: end;
  padding-bottom: var(--space-md);
  margin-bottom: var(--space-md);
  border-bottom: 1px solid var(--rule);
}
.inline-form.rule-form { grid-template-columns: 2fr 1.4fr 100px auto }

.manager-list { display: block }
.manager-row {
  display: grid;
  grid-template-columns: 1.7fr 90px 1.2fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
}
.manager-row:last-child { border-bottom: 0 }
.manager-row.budget { grid-template-columns: 1fr 120px auto }
.manager-row.rule { grid-template-columns: 2fr 1fr 90px 100px auto }
/* Column headers: any record showing more than two values must name them. */
.manager-head {
  padding: 0 0 var(--space-sm);
  border-bottom: 2px solid var(--rule-strong);
  align-items: end;
}
.manager-head span {
  font-size: .72rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
/* One cell mechanism for both editable and read-only values, so the stacked mobile record
   labels itself with the same markup the desktop column header covers. */
.cell { display: grid; gap: 0; min-width: 0 }
.cell > b { font-weight: 600 }
.origin { color: var(--ink-muted) }

.status-list { display: block }
.status-item {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  align-items: baseline;
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
}
.status-item:last-child { border-bottom: 0 }
.status-item b { font-size: .95rem; font-weight: 600 }
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--graphite);
  margin-right: var(--space-sm);
}
.status-dot.ok { background: var(--under-ink) }

/* --- Toast ---------------------------------------------------------------- */
/* The one surface that genuinely leaves the page, so the one that casts a shadow. */

.toast {
  position: fixed;
  right: var(--space-md);
  bottom: var(--space-md);
  max-width: min(420px, calc(100% - 2rem));
  background: var(--ink);
  color: var(--sheet);
  padding: 12px var(--space-md);
  border-radius: var(--radius-md);
  box-shadow: var(--lifted);
  font-size: .9rem;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity 200ms cubic-bezier(.4, 0, .2, 1), transform 200ms cubic-bezier(.4, 0, .2, 1);
  z-index: 50;
}
.toast.show { opacity: 1; transform: none }
.toast.error-toast { color: var(--over-ink-bright) }

.shared-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
  color: var(--ink-muted);
}
.shared-toggle input { width: auto; margin: 0 }

/* --- Adaptation ----------------------------------------------------------- */

@media (max-width: 900px) {
  /* Five figures do not divide into two columns, so the block becomes a ruled list —
     the same shape as every other record in the product. */
  .stat-grid { grid-template-columns: 1fr; border-bottom-width: 2px }
  .stat {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-md);
    padding: 10px 0;
    border-bottom: 1px solid var(--rule);
  }
  .stat:last-child { border-bottom: 0 }
  .stat + .stat { border-left: 0; padding-left: 0 }
  .stat strong { margin-top: 0 }
  .chart-grid { grid-template-columns: 1fr }
  .chart-card.wide { grid-column: auto }
  .form-grid { grid-template-columns: 1fr 1fr }
  .recommendations { grid-template-columns: 1fr }
}

@media (max-width: 600px) {
  .shell { width: min(100% - 1rem, 1240px) }
  /* Wordmark + month picker + sign-out measures ~325px against ~351px of content box at 375px.
     Wrapping rather than shrinking keeps the month picker legible if a locale runs longer. */
  .topbar { padding: var(--space-sm) 12px; flex-wrap: wrap }
  .brand { font-size: .95rem }
  .top-actions { gap: var(--space-xs) }
  .top-actions input { width: 132px }
  .tabs { margin: var(--space-sm) 0 var(--space-md) }
  .tabs button { padding: 12px }
  .page-title { align-items: center }
  /* Title and figure both step down: "Transactions" shares its row with a button, and a stat
     row is only ~343px wide at 375px. */
  .page-title h1 { font-size: 1.55rem }
  .stat strong { font-size: 1.15rem }
  .filters, .form-grid, .settings-grid, .insight-grid { grid-template-columns: 1fr }
  .settings-grid .wide { grid-column: auto }
  .inline-form, .inline-form.rule-form { grid-template-columns: 1fr 1fr }
  .inline-form > *:first-child { grid-column: 1 / -1 }
  .card { padding: 14px }
  .login-card { padding: var(--space-lg) }
  table { min-width: 760px }

  /* Manager records stack, so the column header goes away and each value names itself. */
  .manager-head { display: none }
  .manager-row,
  .manager-row.budget,
  .manager-row.rule {
    grid-template-columns: 1fr;
    gap: var(--space-sm);
    padding: 12px 0;
  }
  .cell { gap: var(--space-xs) }
  .cell-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
    color: var(--ink-muted);
  }
  .row-actions { justify-self: start }
}

/* A fine pointer can be trusted with a dense row; a finger cannot. */
@media (pointer: coarse) {
  button, .button, .row-actions button, .pagination button,
  input, select, td input, td select { min-height: 44px }
  .row-actions { gap: var(--space-sm) }
}

@media (prefers-reduced-motion: reduce) {
  button, .button, input, select, .toast { transition-duration: 1ms }
}
