/* Scirius — "Colophon".
 *
 * The printed-newspaper identity the app always had (serif throughout, tabular figures,
 * hairline rules, no card grids), rebuilt around one idea: each screen opens with a deep-ink
 * block carrying the single figure that screen exists to answer, with a large gold ghost
 * numeral behind it; everything below is quiet ink-on-paper ledger.
 *
 * Three rules this file exists to hold, all of them regressions waiting to happen:
 *   1. Uppercase + letter-spacing is ONE role: the kicker above a headline figure, and table
 *      column heads. Every other label is sentence case. (.kicker / .col-head — there is no
 *      general-purpose small-caps class any more, on purpose.)
 *   2. --neutral-600 is 3.94:1 on paper, under the 4.5:1 bar PRODUCT.md sets. It is allowed on
 *      10-11px uppercase kickers ONLY. Every 12-15px muted label takes --neutral-700 (5.97:1).
 *   3. Colour on a figure means direction of money, and only that. Oxblood is out, green is in,
 *      and they appear ONLY where a figure is a change the reader is meant to weigh: the Delta
 *      column in Forecast's day table, and a holding's move against cost on Worth. Balances,
 *      totals and every other figure stay ink — a screen where most numbers are coloured is a
 *      screen where colour has stopped meaning anything, which is the failure the original
 *      one-accent rule was written to prevent.
 *
 * Design tokens below are the single source of truth — views reference these custom
 * properties, never raw hex.
 */

/* Cormorant Garamond (figures/headings) and Lora (body). Loaded from Google Fonts because
   this project has no build step and therefore no font pipeline; the local serif stack below
   is a real fallback, not a formality — the app stays legible offline, in the installed PWA,
   and on the household network with the CDN unreachable. */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;600&family=Lora:wght@400;600&display=swap');

:root {
  /* ---- colour ---- */
  --color-bg: #f3f2f2;          /* app ground behind panels */
  --color-paper: #f8f4f4;       /* the screen surface */
  --color-text: #201f1d;        /* body and figures */

  --color-neutral-200: #eae7e7; /* meter tracks, emphasised table cell fill */
  --color-neutral-300: #d7d3d3; /* panel borders */
  --color-neutral-400: #bab6b6; /* the one outlined-button border that isn't accent */
  --color-neutral-600: #7d7979; /* 10-11px uppercase kickers ONLY — see rule 2 above */
  --color-neutral-700: #605d5d; /* all body-size muted text (12-15px) */
  --color-neutral-800: #444141; /* muted prose that still has to read as prose */
  --color-divider: color-mix(in srgb, #201f1d 16%, transparent);

  --color-accent: #b68235;      /* gold: rules, active tab mark, outlined buttons */
  --color-accent-100: #fff3e4;  /* highlighted table row tint */
  --color-accent-300: #facb8d;  /* kickers and links on ink */
  --color-accent-400: #e1ad66;  /* pressed state on ink */
  --color-accent-600: #a06f24;  /* pressed state on paper */
  --color-accent-700: #7d5411;  /* accent text on paper (body sizes) */
  --color-accent-800: #5a3b0a;  /* accent text on the accent-100 tint */

  --color-ink: #191817;         /* the colophon block, dark tab bar, login ground */
  --color-ink-muted: #a5a09e;   /* secondary text on ink */
  --color-ink-body: #c4bfbc;    /* body text on ink */
  --color-ink-field: #221f1e;   /* input fill on ink */
  --color-ink-rule: #454140;    /* borders/dividers on ink */
  --color-ink-tab-off: #8b8785; /* inactive tab label on ink (4.98:1) */

  --color-negative: #8b1a1a;    /* oxblood — money out */
  /* Money in. Deliberately matched to oxblood's weight rather than picked for hue: #0e5132 is
     8.58:1 on paper against oxblood's 8.51:1, so a column of gains and losses reads as one
     column with two signs rather than one loud colour and one quiet one. */
  --color-positive: #0e5132;

  /* ---- type ----
     Weights are 400 for display figures and 600 for section headings and totals. Never 700:
     the larger the text, the lighter it sets. */
  --font-heading: "Cormorant Garamond", 'Charter', Georgia, 'Times New Roman', serif;
  --font-body: "Lora", Georgia, 'Charter', 'Times New Roman', serif;

  /* ---- space ----
     Screen side padding is 28px and the ink block shares it, so the mark, the kicker, the
     headline figure and every row below them hang off one left edge down the whole screen. */
  --gutter: 28px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 22px;
  --space-6: 28px;
  --space-7: 40px;

  --radius-md: 4px;             /* 4px everywhere; there is no second radius */
  --tabbar-h: 62px;             /* content height; the safe-area inset is added on top */
  --max-width: 960px;
}

/* ---------- reset ---------- */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
h1, h2, h3, h4, p, figure { margin: 0; }
button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
a { color: inherit; }
table { border-collapse: collapse; width: 100%; }
img { max-width: 100%; }

html {
  /* Ink, not paper — and this is about the browser's own chrome, not the page.
     iOS Safari tints the bar behind the status bar and the bar behind its toolbar to match the
     document's background colour. With paper here, both ended up near-white while the page
     between them was the ink block: two bright bands framing a dark screen. Ink is the right
     answer at BOTH ends independently of Safari, because the top of every screen is the ink
     block and the bottom is the ink tab bar, so overscrolling at either end now reveals more
     of the thing it is next to instead of a white flash. The paper regions paint their own
     background over this (see .paper / .login). */
  background-color: var(--color-ink);
}

body {
  font-family: var(--font-body);
  color: var(--color-text);
  font-size: 15px;
  line-height: 1.5;
  background: var(--color-ink);
}

/* Elevation: none, anywhere inside the app. Panels are 1px --neutral-300 borders on paper.
   The design system's elevation is "a whisper" and this app has nothing to raise. */

/* ---------- type roles ----------
   Each class below is one row of the handoff's type table. Nothing else in this file sets a
   font-size that isn't one of these, which is what keeps the "exactly one largest thing per
   screen" rule enforceable by reading the markup. */

.kicker {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
}

/* On ink the kicker is gold, which is also the only place accent-300 appears as text. */
.ink .kicker,
.kicker.on-ink { color: var(--color-accent-300); }

.col-head {
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
  font-variant-numeric: tabular-nums;
}

.section-heading {
  font-family: var(--font-heading);
  font-size: 20px;
  font-weight: 600;
  line-height: 1.2;
}

.figure {
  font-family: var(--font-heading);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.footnote {
  font-size: 12px;
  color: var(--color-neutral-700);
  line-height: 1.5;
}

.tnum, .num { font-variant-numeric: tabular-nums; }
.muted { color: var(--color-neutral-700); }
.neg { color: var(--color-negative); }
.pos { color: var(--color-positive); }
.hidden { display: none !important; }

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

/* ---------- shell ----------
   The view owns the whole screen: its ink block bleeds to the edges, its paper region is a
   flex column, and the footnote is pushed to the bottom by a spacer so the last line of the
   screen is always the caveat rather than whatever row happened to be last. */

/* An app shell, not a document.
   The viewport is divided once: a pinned bar at the top of the screen, a pinned tab bar at the
   bottom, and exactly one scrolling region between them (#view). Nothing else on the page
   scrolls, which is why body is overflow:hidden — on iOS that also stops the whole document
   rubber-banding underneath the bars, which is what made the tab bar look like it was moving. */
html, body { height: 100%; overflow: hidden; }

.app-shell {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
}

#view {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* The scroll container, so a screen taller than the viewport scrolls INSIDE the shell. No
     padding-bottom for the tab bar any more: the bar is a sibling flex item below this box
     rather than a fixed overlay on top of it, so it cannot cover anything and there is nothing
     to reserve space for. */
  overscroll-behavior: contain;
}

/* Login has no tab bar. */
body.no-chrome #view { padding-bottom: 0; }

/* A view is: pinned bar, ink block, paper.
   min-height:100% so a screen with little content still fills the scroller and its spacer can
   push the footnote to the bottom edge, exactly as it did when the shell itself was the box. */
.screen {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}

/* The pinned bar. Sticky rather than fixed: it is a normal box inside the one scrolling region,
   so it cannot drift during momentum scrolling the way a fixed overlay does on iOS, and it
   needs no z-index gymnastics against the content it sits above. */
.screen-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  flex: none;
  padding: env(safe-area-inset-top) var(--gutter) 0;
  background: var(--color-ink);
  /* The bar used to be inside .ink, which set this. Lifting it out left the brand word
     inheriting the body's near-black ink colour ON the ink ground — invisible. */
  color: var(--color-paper);
}

.screen-bar .ink-head { padding-bottom: 0; }
.screen-bar .ink-rule { margin-top: 16px; }
.screen-bar--detail { padding-bottom: var(--space-2); }

.paper {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 20px var(--gutter) 0;
  background: var(--color-paper);
}

/* The spacer that pushes the footnote down. Nothing else may claim the leftover height. */
.spacer { flex: 1 1 auto; min-height: var(--space-4); }

.screen .footnote { padding-bottom: var(--space-2); }

/* ---------- the ink block ---------- */

.ink {
  position: relative;
  overflow: hidden;             /* the ghost numeral hangs off the block's corner */
  flex: none;
  padding: 0 var(--gutter) 18px;
  background: var(--color-ink);
  color: var(--color-paper);
}

.ink--tall { padding-bottom: 22px; }

/* Ghost numeral: the date/quantity the screen is about, in gold at 12%, bleeding off the
   bottom-right corner. Never interactive, never announced. */
.ink-ghost {
  position: absolute;
  right: -8px;
  bottom: -52px;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 200px;
  line-height: 1;
  color: var(--color-accent);
  opacity: 0.12;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  user-select: none;
}

/* Everything in the block sits above the ghost. */
.ink > *:not(.ink-ghost) { position: relative; }

.ink-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 0 16px;
}

.ink-mark { height: 26px; width: auto; flex: none; }

/* Every screen's bar is the same height, because it is pinned and you see one replace another
   when you change tab. Today used a 30px mark and Account a 46px one with a two-line identity,
   so the bar jumped 60 -> 61 -> 78px between tabs and the whole page appeared to shift. One
   mark size, one min-height, and the bar is a fixed frame the screens move behind. */
.ink-head { min-height: 60px; }

/* A household's name is a name, so it keeps its capitals and its spacing — but at the same
   size and on the same baseline as the screen names, so the bar's height never changes. */
.ink-word--name { text-transform: none; letter-spacing: 0; }

.ink-word {
  flex: 1;
  min-width: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 19px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ink-meta {
  font-size: 12px;
  color: var(--color-ink-muted);
  white-space: nowrap;
}

.ink-rule {
  border-top: 1px solid var(--color-accent);
  opacity: 0.55;
}

.ink .kicker { display: block; padding: 22px 0 4px; }
.ink--tall .kicker { padding-top: 20px; }

.ink-figure {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 68px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.ink-figure--md { font-size: 62px; }
.ink-figure--sm { font-size: 60px; }

.ink-sub {
  padding-top: 8px;
  font-size: 13px;
  color: var(--color-ink-body);
}

/* A figure quoted inside a sub-line (the lowest point, the calibration cost) sets in
   Cormorant at 16px and in paper-white, so it reads as a figure and not as prose. */
.ink-sub .fig {
  font-family: var(--font-heading);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  color: var(--color-paper);
}

/* Receipts: the headline figure and its two-line qualifier, baseline-aligned. */
.ink-figure-row { display: flex; align-items: baseline; gap: 14px; }
.ink-figure-row .ink-sub { padding-top: 0; padding-bottom: 6px; }

/* Worth: two label/figure pairs under the headline, split by a hairline. */
.ink-pairs { display: flex; gap: 26px; padding-top: 16px; }
.ink-pairs .divider { width: 1px; background: var(--color-ink-rule); }
.ink-pairs .kicker { display: block; padding: 0 0 2px; letter-spacing: 0.14em; color: var(--color-ink-muted); }
.ink-pairs .fig {
  font-family: var(--font-heading);
  font-size: 20px;
  font-variant-numeric: tabular-nums;
}

/* Account: no rule, no kicker, no figure — just who is signed in, on the ink below the bar. */
.ink-identity { font-size: 13px; color: var(--color-ink-muted); padding: 2px 0 4px; }

/* Today's sparkline. No gridline: an unlabelled dashed rule in a cash-flow chart reads as
   zero and would contradict the caption under it. If a gridline ever comes back it carries
   its real value, the way the day chart labels its ticks. */
.ink-spark {
  display: block;
  width: 100%;
  height: 48px;
  margin-top: 10px;
}
.ink-spark .curve { fill: none; stroke: var(--color-paper); stroke-width: 1.5; }
.ink-spark .low { fill: var(--color-ink); stroke: var(--color-accent); stroke-width: 1.6; }

/* ---------- the Forecast switch ----------
   Two labels, not a control: tapping one changes the route (#/forecast/month <-> /day) so the
   choice survives a reload and the back button works. */

.ink-switch { display: flex; gap: 22px; padding-top: 16px; }

.ink-switch a {
  font-size: 14px;
  padding-bottom: 7px;
  color: var(--color-ink-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  min-height: 30px;
}

.ink-switch a[aria-current="page"] {
  color: var(--color-paper);
  border-bottom-color: var(--color-accent);
}

.ink-switch a:hover { color: var(--color-accent-300); }
.ink-switch a:active { color: var(--color-accent-400); }

/* ---------- hairline tables ----------
   Flex rows, not <table>: every ledger in this design is label-over-sublabel on the left and
   figure-over-subfigure on the right, which a table's column model fights. The real tables
   left in the app are the CRUD ledgers in Account's detail views, further down. */

.rows { display: flex; flex-direction: column; }

.rows-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-bottom: 10px;
  gap: var(--space-3);
}

/* A second table on the same screen (Worth's Cash under its Holdings) needs air above its
   heading, or the heading sits on the last row of the table it is not part of. */
.rows + .rows-head,
.rows + * > .rows-head { padding-top: var(--space-5); }

/* The 1px full-strength rule that opens a table. Its section heading sits above it. */
.rule-top { border-top: 1px solid var(--color-text); }
/* The 2px rule that closes a ledger, above its total. */
.rule-total { border-top: 2px solid var(--color-text); }

.row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: 8px 0;
  border-bottom: 1px solid var(--color-divider);
  text-align: left;
  /* Rows that are buttons (a receipt, a settings group, a month) still set as rows. */
  width: 100%;
  background: transparent;
  border-left: 0;
  border-right: 0;
  border-top: 0;
  font: inherit;
  color: inherit;
}

/* A row that navigates is still a row: no underline, no link colour. The chevron and the
   pressed tint are the affordance. */
.row, a.row { text-decoration: none; }

.row--tap { cursor: pointer; align-items: center; min-height: 48px; }
.row--tap:hover { background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
.row--tap:active { background: color-mix(in srgb, var(--color-accent) 16%, transparent); }

.row-main { flex: 1; min-width: 0; }

/* Label over sub-label, figure over sub-figure — both stacks are spans inside a flex row, so
   they have to be told to be blocks or they set as one running line ("VWCE Vanguard FTSE
   All-World · 120 units" on the label side, "13.536+ 2.088" on the figure side). */
.row-label { display: block; font-size: 15px; }
.row-label--lg { font-size: 16px; }

.row-sub {
  display: block;
  font-size: 12px;
  color: var(--color-neutral-700);
  padding-top: 2px;
  line-height: 1.35;
}
.row-sub--lg { font-size: 13px; padding-top: 3px; }

.row-figure {
  display: block;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 20px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

.row-figure--sm { font-size: 18px; }
.row-figure--md { font-size: 19px; }

/* The one 600-weight figure a ledger is allowed: its total. */
.row--total { padding: 13px 0; border-bottom: 0; }
.row--total .row-figure { font-size: 26px; font-weight: 600; }

/* A right-hand cell that is a figure over a sub-figure (a holding's value over its change). */
.row-figures {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  flex: none;
  text-align: right;
}
.row-figures .row-sub { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }

.row-chevron {
  color: var(--color-neutral-600);
  font-size: 16px;
  line-height: 1;
  flex: none;
}

/* A zero renders as an em dash, never "€ 0" — the row is telling you nothing happened, not
   that something happened and came to nothing. */
.row-figure.is-zero { color: var(--color-neutral-600); }
.row-label.is-zero { color: var(--color-neutral-700); }

/* The highlighted row (the low point, the month carrying extras) tints edge to edge: negative
   gutters with matching padding, so the tint bleeds off both sides of the screen the way a
   printed highlight runs into the margin. */
.row--marked {
  background: var(--color-accent-100);
  /* width:auto (not the inherited .row width:100%) so the box can size to its negative
     margins. With width:100% the horizontal box is over-constrained, the right margin
     collapses to a positive value, and the row shifts left — pulling the Delta/Balance
     columns out of line with the plain rows. auto lets the tint bleed both edges evenly
     while the padding keeps the content on the same grid as every other row. */
  width: auto;
  margin: 0 calc(-1 * var(--gutter));
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}
.row--marked .row-figure--balance { font-weight: 600; }

.row-tag {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-accent-800);
  margin-left: 6px;
  white-space: nowrap;
}

/* Column heads for the two Forecast tables. The widths are fixed so the figures line up down
   the column even when a label wraps. */
.rows-cols {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding-bottom: 8px;
}
.rows-cols .c-main { flex: 1; min-width: 0; }

.c-delta, .row .c-delta { width: 78px; flex: none; text-align: right; }
.c-balance, .row .c-balance { width: 84px; flex: none; text-align: right; }
.c-free, .row .c-free { width: 92px; flex: none; text-align: right; }
.c-end, .row .c-end { width: 104px; flex: none; text-align: right; }

/* The by-month table's rows breathe more than the by-day one's: seven rows in the height nine
   occupy, which is what stops the screen from ending in dead space. */
.rows--airy .row { padding: 14px 0; }

/* ---------- the net-worth strip (Today) ----------
   One bordered row split into three cells. The third — the answer — is filled so the eye lands
   on it without anything being coloured. */

.strip {
  display: flex;
  align-items: stretch;
  margin-top: 20px;
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  overflow: hidden;
}

.strip-cell { flex: 1; min-width: 0; padding: 13px 15px; border-right: 1px solid var(--color-divider); }
.strip-cell:last-child { border-right: 0; }
.strip-cell .kicker { display: block; letter-spacing: 0.14em; padding-bottom: 3px; }
.strip-cell .fig {
  font-family: var(--font-heading);
  font-size: 21px;
  font-variant-numeric: tabular-nums;
}

.strip-cell--sum { flex: 1.1; background: var(--color-neutral-200); }
.strip-cell--sum .kicker { color: var(--color-neutral-700); }
.strip-cell--sum .fig { font-weight: 600; }

/* ---------- the calibration note (Receipts) ----------
   A margin note: a 2px accent rule down its left edge, no radius, no border on the other three
   sides. It is an aside about the figures above it, not a panel of its own. */

.note {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: 18px;
  padding: 13px 16px;
  border-left: 2px solid var(--color-accent);
  background: var(--color-bg);
}

.note-body { flex: 1; font-size: 14px; line-height: 1.55; color: var(--color-neutral-800); }
.note-body .fig {
  font-family: var(--font-heading);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}

/* ---------- buttons ----------
   Outlined, never filled. A solid accent fill would make the one gold thing on the screen the
   loudest thing on it, and the headline figure has to stay the largest and the loudest. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 48px;
  padding: 12px 22px;
  font-family: var(--font-body);
  font-size: 15px;
  text-decoration: none;      /* a .btn is sometimes an <a> — the border is the affordance */
  background: transparent;
  border: 1px solid var(--color-neutral-400);
  border-radius: var(--radius-md);
  color: var(--color-neutral-800);
  cursor: pointer;
}

.btn:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.btn:active { background: color-mix(in srgb, var(--color-text) 12%, transparent); }

.btn-accent { border-color: var(--color-accent); color: var(--color-accent-700); }
.btn-accent:hover { background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
.btn-accent:active { background: color-mix(in srgb, var(--color-accent) 22%, transparent); }

.btn-danger { border-color: var(--color-negative); color: var(--color-negative); }
.btn-danger:hover { background: color-mix(in srgb, var(--color-negative) 10%, transparent); }

.btn:disabled { opacity: 0.45; cursor: default; }
.btn:disabled:hover { background: transparent; }

.btn-block { width: 100%; }
.btn-row { display: flex; justify-content: center; padding-bottom: var(--space-2); }

/* On ink: gold outline, gold-300 label, never a fill. */
.btn-ink {
  border-color: var(--color-accent);
  color: var(--color-accent-300);
  background: transparent;
  width: 100%;
  padding: 15px;
  font-size: 16px;
}
.btn-ink:hover { background: color-mix(in srgb, var(--color-accent) 16%, transparent); }
.btn-ink:active { background: color-mix(in srgb, var(--color-accent-400) 24%, transparent); }

/* The capture button (Receipts) is pinned to the bottom of the paper region, above the tab
   bar: that is the thumb zone, and one-handed capture is the reason the screen exists. */
.capture {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  min-height: 48px;
  padding: 15px 18px;
  margin-bottom: 14px;
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  background: transparent;
  cursor: pointer;
  text-align: left;
}
.capture:hover { background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
.capture:active { background: color-mix(in srgb, var(--color-accent) 22%, transparent); }
.capture svg { flex: none; width: 22px; height: 22px; fill: none; stroke: var(--color-accent-700); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.capture .label { flex: 1; font-size: 16px; color: var(--color-accent-700); }
.capture .hint { font-size: 12px; color: var(--color-neutral-700); white-space: nowrap; }

/* A link set in running text (the "Adjust" beside the calibration note). */
.link {
  font-size: 13px;
  color: var(--color-accent-700);
  border: 0;
  border-bottom: 1px solid var(--color-accent);
  background: transparent;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
  font-family: inherit;
}
.link:hover { color: var(--color-accent-800); }

/* Focus is the same everywhere and it is never the browser default. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.ink :focus-visible, .login :focus-visible { outline-color: var(--color-accent-300); }

/* ---------- fields ----------
   Real fields: 1px border, 4px radius, a fill, 48px minimum height. The old bare bottom-border
   was the app's most confident piece of typography and its least usable control — on a phone
   there was nothing to aim at. */

input, select, textarea {
  width: 100%;
  min-height: 48px;
  padding: 13px 14px;
  font-family: var(--font-body);
  font-size: 16px;              /* 16px, so iOS Safari never zooms on focus */
  color: var(--color-text);
  caret-color: var(--color-accent);
  background: var(--color-paper);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
}

input:hover, select:hover, textarea:hover { border-color: var(--color-neutral-400); }
input:focus, select:focus, textarea:focus { border-color: var(--color-accent); }

select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  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.5L6 6.5L11 1.5' fill='none' stroke='%23605d5d' stroke-width='1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 11px 8px;
}

input[type="checkbox"] {
  width: 20px;
  height: 20px;
  min-height: 20px;
  padding: 0;
  accent-color: var(--color-accent);
}

/* On ink the field fills with --ink-field and the focused one takes a gold border. */
.field-ink input,
.field-ink select {
  background: var(--color-ink-field);
  border-color: var(--color-ink-rule);
  color: var(--color-paper);
  padding: 14px 15px;
}
.field-ink input:hover { border-color: var(--color-ink-muted); }
.field-ink input:focus { border-color: var(--color-accent); }

.field { display: flex; flex-direction: column; gap: 7px; }
.field > .kicker { color: var(--color-neutral-700); }
.field-ink > .kicker { color: var(--color-accent-300); }

.form-grid { display: flex; flex-direction: column; gap: var(--space-4); }
.form-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-2); }
.form-actions .btn { flex: 1 1 auto; }

.form-msg { font-size: 13px; color: var(--color-neutral-700); min-height: 1.3em; padding-top: var(--space-2); }
.form-msg.is-error { color: var(--color-negative); }

/* ---------- login ---------- */

.login {
  /* min-height, not flex:1. #view stopped being a flex container when it became the scroller,
     so `flex: 1` here resolved to nothing and the plate collapsed to its content height at the
     top of the screen with the rest of the viewport empty below it. Filling the scroller is
     what lets justify-content actually centre anything. */
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 40px;
  background: var(--color-ink);
  color: var(--color-paper);
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

/* display:block is load-bearing, not tidiness: an <img> is inline by default, and an inline
   element ignores `margin: auto`, so the lockup sat against the left edge at every width. */
.login-lockup { display: block; width: 168px; height: auto; margin: 0 auto; }
.login-tagline { font-size: 13px; color: var(--color-ink-muted); text-align: center; padding-top: 20px; }
.login-rule { border-top: 1px solid var(--color-accent); opacity: 0.5; margin: 30px 0 32px; }
.login-form { display: flex; flex-direction: column; gap: 18px; }

/* The failed-login message sits above the fields, where it is read before the next attempt
   rather than after it. */
.login-error { color: var(--color-negative); font-size: 13px; min-height: 1.3em; }

/* ---------- tab bar ----------
   Five equal items, dark on every screen. 62px of content plus the home-indicator inset. The
   active item carries a 2px x 26px gold mark flush to the top edge — the one animated thing
   in the app. */

.tabbar {
  flex: none;
  display: flex;
  align-items: stretch;
  padding: 0 var(--space-2);
  padding-bottom: max(22px, env(safe-area-inset-bottom));
  background: var(--color-ink);
  overflow: hidden;
}

.tabbar a {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 5px;
  padding-top: 11px;
  text-decoration: none;
  color: var(--color-ink-tab-off);
}

.tabbar a::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 26px;
  height: 2px;
  background: var(--color-accent);
  opacity: 0;
  transition: opacity 0.15s ease;
}

.tabbar a.active { color: var(--color-paper); }
.tabbar a.active::before { opacity: 1; }
.tabbar a:hover { color: var(--color-accent-300); }
.tabbar a.active:hover { color: var(--color-paper); }

.tabbar svg {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tabbar .label { font-size: 10px; letter-spacing: 0.07em; line-height: 1; }

/* Light variant, kept because a light tab bar is one setting away and its contrast has to be
   right when it arrives: inactive labels are neutral-700 (5.97:1), never neutral-500 (2.65:1). */
.tabbar.tabbar--light {
  background: var(--color-paper);
  border-top: 1px solid var(--color-neutral-300);
}
.tabbar--light a { color: var(--color-neutral-700); }
.tabbar--light a.active { color: var(--color-text); }

/* ---------- pushed detail views (Account) ----------
   Seven stacked forms became seven pushed views, each with a back affordance in the ink block
   and the existing CRUD underneath. */

.detail-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-accent-300);
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
}
.detail-back:hover { color: var(--color-accent); }
.detail-back svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

.detail-title { font-family: var(--font-heading); font-weight: 400; font-size: 30px; line-height: 1.1; padding-top: 2px; }
.detail-sub { font-size: 13px; color: var(--color-ink-muted); padding-top: 6px; }

.detail-section { padding-bottom: var(--space-6); }
.detail-section > .section-heading { padding-bottom: 10px; }
.detail-note { font-size: 13px; color: var(--color-neutral-700); padding-bottom: var(--space-3); max-width: 46ch; }

/* ---------- ledger tables (Account detail views only) ----------
   The one place a real <table> survives: the CRUD ledgers, where the columns are genuinely
   columns and the add/edit row has to line its fields up under them. */

.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-bottom: var(--space-3); }

table.ledger { min-width: 100%; }

table.ledger th, table.ledger td {
  padding: 9px var(--space-2);
  text-align: left;
  white-space: nowrap;
  vertical-align: middle;
}

table.ledger th:first-child, table.ledger td:first-child { padding-left: 0; }
table.ledger th:last-child, table.ledger td:last-child { padding-right: 0; }

table.ledger thead th {
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
  border-bottom: 1px solid var(--color-text);
  text-align: right;
}
table.ledger thead th:first-child { text-align: left; }

table.ledger tbody td {
  border-bottom: 1px solid var(--color-divider);
  font-variant-numeric: tabular-nums;
  text-align: right;
  font-size: 15px;
}
table.ledger tbody td:first-child { text-align: left; font-variant-numeric: normal; }

table.ledger tfoot td {
  border-top: 2px solid var(--color-text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* A table cell whose label is the way into the next level (a portfolio to its positions, a
   position to its trades). It is a link, so it must not look like body text — but it must not
   look like a web link either, so the affordance is the accent on hover, not an underline. */
.cell-link { display: block; text-decoration: none; color: inherit; }
.cell-link:hover .cell-main { color: var(--color-accent-700); }

.cell-main { display: block; white-space: normal; line-height: 1.25; }
.cell-sub { display: block; font-size: 12px; color: var(--color-neutral-700); line-height: 1.3; white-space: normal; }

table.ledger th.col-actions, table.ledger td.col-actions {
  width: 1%;
  white-space: nowrap;
  text-align: right;
  padding-right: 0;
}

/* ---------- the inline edit row ----------
   Pressing Add turns the foot of the list into a row you type into; tapping a row turns that
   row into the same thing. It keeps the READ row's geometry — primary field where the label
   sits, figure field where the figure sits, the rest wrapping across the sub-line — so the row
   you are filling in is visibly the row you are about to create.

   This replaces a <table>-based version whose four or five controls shared one 430px line and
   overlapped into unreadable stubs. Nothing here lines up with a column header, because there
   are no columns: each control simply takes the width it needs and wraps when it runs out. */

.row--edit {
  display: block;
  background: var(--color-accent-100);
  margin: 0 calc(-1 * var(--gutter));
  padding: var(--space-3) var(--gutter);
  border-bottom: 1px solid var(--color-divider);
}

.row-edit-line { display: flex; align-items: center; gap: var(--space-2); }
.row-edit-main { flex: 1 1 auto; min-width: 0; }
.row-edit-figure { flex: 0 0 104px; }
.row-edit-figure input { text-align: right; font-variant-numeric: tabular-nums; }

/* A signed amount (money in / money out): the number pad has no minus, so a +/- button
   next to the field carries the sign. The cell is a touch wider to seat the button. */
.row-edit-figure--signed { flex: 0 0 148px; }
.signed-amount { display: flex; align-items: center; gap: var(--space-1); }
.signed-amount input { flex: 1 1 auto; min-width: 0; }
.sign-toggle {
  flex: 0 0 36px;
  min-height: 44px;
  padding: 0;
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-paper);
  color: var(--color-positive);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.sign-toggle[aria-pressed="true"] { color: var(--color-negative); border-color: currentColor; }

.row-edit-sub {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
/* Two per line on a phone gives each control — the native iOS date field especially —
   enough width to render inside its box instead of spilling over its neighbour; a wider
   screen still fits three. */
.row-edit-sub > * { flex: 1 1 140px; min-width: 140px; }

.row--edit input,
.row--edit select {
  min-height: 44px;
  padding: 9px 11px;
  font-size: 15px;
  background: var(--color-paper);
}

.row-edit-check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
  min-height: 44px;
  flex: 1 1 100%;
}
.row-edit-check input { width: 20px; height: 20px; min-height: 20px; flex: none; }

.row-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-1);
  margin-top: var(--space-2);
}

/* A row that navigates AND can be edited: the link takes the row, the pencil sits outside it
   so tapping the row means "go deeper" and never "edit by accident". */
.row--split { display: flex; align-items: center; gap: var(--space-2); }
.row--split .row-link {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: inherit;
  min-height: 48px;
}
.row--split .row-link:hover { background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
.row--split .row-main { flex: 1; min-width: 0; }

/* Glyph buttons: no fill, no border until hover — three per row, and a bordered button at that
   density reads as a control panel bolted onto a printed table. */
.inline-actions { display: flex; gap: var(--space-1); justify-content: flex-end; }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--color-neutral-700);
  cursor: pointer;
}
.icon-btn:hover { border-color: var(--color-accent); color: var(--color-accent-700); }
.icon-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.25; stroke-linecap: round; stroke-linejoin: round; }
.icon-btn.icon-danger:hover { border-color: var(--color-negative); color: var(--color-negative); }

/* Cost glyphs: line marks at the same 1px weight as the rules around them. Decorative — the
   label beside them carries the meaning, and every glyph is aria-hidden. */
.cost-icon { fill: none; stroke: currentColor; stroke-width: 1; stroke-linecap: round; stroke-linejoin: round; flex: 0 0 auto; color: var(--color-neutral-700); }
td.cell-icon > .icon-cell { display: inline-flex; align-items: center; gap: var(--space-2); }

.checkbox-row { display: flex; align-items: center; gap: var(--space-3); min-height: 48px; font-size: 15px; }
.checkbox-row input { width: 20px; height: 20px; min-height: 20px; flex: none; }
.checkbox-row label { flex: 1; }

/* ---------- state ---------- */

/* A view renders its ink block immediately with an em dash in place of every figure, then
   fills them in. The screen is never blanked and replaced: the full-view "Loading…" swap is
   exactly what made a cold open feel slow. */
.is-pending { color: var(--color-neutral-600); }
.ink .is-pending { color: var(--color-ink-muted); }

/* Looks identical, means the opposite: this figure is not coming, because there is nothing to
   show (a cash line has no gain against cost). Kept a separate class from .is-pending so
   "still loading" stays a state the screenshot pipeline and anyone reading the DOM can test
   for — see the READY checks in tools/screenshots.mjs. */
.is-blank { color: var(--color-neutral-600); }

.empty-state { color: var(--color-neutral-700); font-style: italic; padding: var(--space-4) 0; }

/* The router can write this straight into #view with no .paper around it (a route that threw),
   and the document ground is ink — so it carries an opaque paper ground rather than setting
   oxblood text on near-black. */
.error-banner {
  border-left: 2px solid var(--color-negative);
  color: var(--color-negative);
  background: color-mix(in srgb, var(--color-negative) 6%, var(--color-paper));
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  font-size: 14px;
}

/* A stale figure says so inline. Ink, not oxblood: staleness is a caveat about a number, not a
   negative number. */
.stale-mark { color: var(--color-neutral-700); padding-left: 2px; cursor: help; text-decoration: none; }

/* ---------- receipt confirm ---------- */

.receipt-thumb { width: 100%; max-width: 320px; border: 1px solid var(--color-neutral-300); border-radius: var(--radius-md); }

/* ---------- wide screens ----------
   Desktop was not redesigned. The tokens flow into it, and the layout below is the phone
   layout given a reading column and its tab bar moved to the top — enough that a desk browser
   is usable and nothing overflows, not a reconsidered wide design. Revisit separately. */

@media (min-width: 768px) {
  .app-shell { max-width: var(--max-width); margin: 0 auto; }

  .tabbar {
    order: -1;
    padding: 0 var(--space-4);
    padding-bottom: 0;
  }

  .tabbar a { flex: 0 1 auto; flex-direction: row; gap: var(--space-2); padding: 14px 18px; }
  .tabbar a::before { top: auto; bottom: 0; }
  .tabbar .label { font-size: 12px; }


  /* On the desk the tab bar is the first flex item, so a screen's own pinned bar sticks to the
     top of the scroller beneath it — which is where it belongs, not under the window edge. */
  .screen-bar { padding-left: var(--space-7); padding-right: var(--space-7); padding-top: 0; }
  .ink { padding-left: var(--space-7); padding-right: var(--space-7); padding-top: var(--space-3); }
  .paper { padding-left: var(--space-7); padding-right: var(--space-7); }
  .row--marked { margin: 0 calc(-1 * var(--space-7)); padding-left: var(--space-7); padding-right: var(--space-7); }
  .login { padding-left: 0; padding-right: 0; }
  .login-tagline, .login-rule { max-width: 380px; margin-left: auto; margin-right: auto; }
  .login-form { width: 100%; max-width: 380px; margin-left: auto; margin-right: auto; }

  /* The CRUD ledgers are the widest thing in the app; give them the room the phone can't. */
  body[data-route="settings"] .app-shell { max-width: 1240px; }
  body[data-route="settings"] .tabbar { max-width: 1240px; }
}

/* ---------- motion ----------
   There is none, on purpose, beyond the tab mark above and the pushed detail views. No
   entrance animations, no number count-ups, no skeleton shimmer. */

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