/* Shell layout only. Every colour, space and radius comes from nocturne.css —
   nothing here invents a value the token sheet already carries. */

html, body { height: 100%; }
body { margin: 0; }

/* Numeric columns line up; money is never ragged. */
.num { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }

/* A figure's column heading sits over the figure. nocturne's .table th sets
   text-align: left and outranks .num on its own, so every "Balance", "Spent"
   and "Total" heading in the app sat at the left of a right-aligned column. */
.table th.num { text-align: right; }

/* Semantic money colours, kept apart from the accent: the accent is brand, and
   these are meaning. Same values the design concept used. */
:root { --money-neg: oklch(0.70 0.15 25); --money-pos: oklch(0.76 0.10 165); }
.neg { color: var(--money-neg); }
.pos { color: var(--money-pos); }
.muted { color: color-mix(in srgb, var(--color-text) 58%, transparent); }

/* ---------- boot / error ---------- */

.boot {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-3); height: 100vh; color: var(--color-text);
  background: var(--color-bg); font-family: var(--font-body);
}
.boot p { margin: 0; font-size: 14px; opacity: .7; }

#blazor-error-ui {
  display: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000;
  padding: 12px 16px; background: var(--color-surface); color: var(--color-text);
  border-top: 1px solid var(--color-divider); font-family: var(--font-body); font-size: 14px;
}
#blazor-error-ui .reload, #blazor-error-ui .dismiss { margin-left: 12px; cursor: pointer; color: var(--color-accent); }

/* ---------- shell ---------- */

.shell {
  display: grid; grid-template-columns: 236px minmax(0, 1fr);
  min-height: 100vh; background: var(--color-bg); color: var(--color-text);
  font-family: var(--font-body);
}

.shell-side {
  display: flex; flex-direction: column; gap: var(--space-4);
  padding: var(--space-4) var(--space-3);
  border-right: 1px solid var(--color-divider); min-width: 0;
}

.brand { display: flex; align-items: center; gap: 9px; padding: 2px 8px; }
.brand svg { width: 24px; height: 20px; flex: none; }
.brand span { font-family: var(--font-heading); font-size: 16px; }

.ws-switch {
  display: flex; align-items: center; gap: 9px; padding: 7px 9px;
  background: var(--color-surface); border: 1px solid var(--color-divider);
  border-radius: var(--radius-md, 8px); color: var(--color-text);
  text-align: left; cursor: pointer; font: inherit;
}
.ws-switch:hover:not(:disabled) { border-color: var(--color-accent); }
.ws-switch[aria-expanded="true"] { border-color: var(--color-accent); }
.ws-switch:disabled { cursor: default; }
.ws-switch .ws-name, .ws-item .ws-name {
  display: block; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ws-switch .ws-role, .ws-item .ws-role { display: block; font-size: 11px; color: color-mix(in srgb, var(--color-text) 50%, transparent); }
.ws-icon { font-size: 16px; opacity: .75; flex: none; }
.ws-text { flex: 1; min-width: 0; }

/* The workspace list opens under the button, over the nav. */
.ws-picker { position: relative; display: flex; flex-direction: column; }
.ws-scrim { position: fixed; inset: 0; z-index: 700; }
.ws-menu {
  /* At least as wide as the button, wider where a company's full name needs it. */
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 710;
  min-width: 100%; width: max-content; max-width: min(340px, calc(100vw - 24px));
  display: flex; flex-direction: column; gap: 2px; padding: 6px;
  background: var(--color-surface); border: 1px solid var(--color-divider);
  border-radius: var(--radius-md, 8px); box-shadow: var(--shadow-lg);
  max-height: min(70vh, 480px); overflow-y: auto;
}
.ws-menu h6 {
  margin: 4px 8px 4px; font-size: 10px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 58%, transparent);
}
.ws-item {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 8px;
  background: none; border: 0; border-radius: var(--radius-sm, 6px);
  color: var(--color-text); font: inherit; text-align: left; cursor: pointer;
}
.ws-item:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.ws-item:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.ws-item.on .ws-name { color: var(--color-accent); }
.ws-check { color: var(--color-accent); font-size: 15px; flex: none; }
.ws-sep { height: 1px; margin: 4px 2px; background: var(--color-divider); }
.ws-new .ws-icon { color: var(--color-accent); opacity: 1; }

/* People, joining, and the view-only notice. */
.notice.view-only {
  color: var(--color-text); padding: 9px 12px; border-radius: var(--radius-md, 8px);
  background: color-mix(in srgb, var(--color-accent) 9%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent);
}
.notice.view-only .ph { color: var(--color-accent); font-size: 16px; flex: none; }

/* Someone who may look at a workspace but not change it (spec §7.1) is not
   offered the controls that would: every one is marked "edits", and MainLayout
   puts "viewing" on the page in a view-only workspace. The API refuses their
   changes regardless; this only stops the page offering them. */
.viewing .edits { display: none !important; }

/* The fields of a record opened to be looked at rather than changed, locked
   in one place. display: contents keeps the fieldset out of the layout, so
   the form lays out exactly as it does when it can be edited. */
fieldset.lock { display: contents; border: 0; margin: 0; padding: 0; min-width: 0; }
.input-compact { min-height: 30px; padding: 3px 8px; width: auto; }
.people-table td { vertical-align: middle; }
.people-table .tag { margin-left: 6px; }
.role-list { display: grid; grid-template-columns: max-content 1fr; gap: 6px var(--space-3); margin: 0; font-size: 13px; }
.role-list dt { font-weight: 500; }
.role-list dd { margin: 0; }
.invite-link { display: flex; gap: var(--space-2); }
.invite-link .input { flex: 1; min-width: 0; font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px; }
.join { display: flex; justify-content: center; padding-top: var(--space-6); }
.join-card { width: min(540px, 100%); gap: var(--space-3); }
.join-card h3 { margin: 0; display: flex; align-items: center; gap: 10px; font-family: var(--font-heading); font-weight: 500; }
.join-card h3 .ph { color: var(--color-accent); }
.join-card p { margin: 0; line-height: 1.5; }
@media (max-width: 620px) {
  .role-list { grid-template-columns: 1fr; }
  .role-list dd { margin-bottom: 6px; }
}

.modal-narrow { width: min(460px, 100%); }
.field-note { display: block; margin-top: 6px; font-size: 12px; line-height: 1.45; }
p.field-note { margin: 0; }
.shell-page { display: contents; }

.side-nav { display: flex; flex-direction: column; gap: 2px; }
.side-nav h6 {
  margin: 0 0 4px 12px; font-size: 10px; font-weight: 500;
  color: color-mix(in srgb, var(--color-text) 58%, transparent);
}
.side-nav h6:not(:first-child) { margin-top: var(--space-3); }

/* NavLink renders an <a>; `active` is Blazor's own class. */
.side-nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px 7px 8px; border-radius: var(--radius-md, 8px);
  color: var(--color-text); font-size: 14px; text-decoration: none;
}
.side-nav a:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.side-nav a i { font-size: 17px; opacity: .75; }
.side-nav a .rail { width: 2px; height: 15px; border-radius: 1px; background: var(--color-accent); opacity: 0; }
.side-nav a.active .rail { opacity: 1; }
.side-nav a.active { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.side-nav a .grow { flex: 1; }
/* A group that opens to its own entries: Staff, in a business workspace. The
   button reads as one more row; its children sit indented under it, so the
   rail of the page on screen still lines up with every other row's. */
.side-nav .nav-group {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 12px 7px 8px; border: 0; border-radius: var(--radius-md, 8px);
  background: none; color: var(--color-text); font: inherit; font-size: 14px; text-align: left; cursor: pointer;
}
.side-nav .nav-group:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.side-nav .nav-group:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.side-nav .nav-group i { font-size: 17px; opacity: .75; }
.side-nav .nav-group .rail { width: 2px; height: 15px; opacity: 0; }
.side-nav .nav-group .grow { flex: 1; }
.side-nav .nav-group .caret { font-size: 12px; transition: transform .15s ease; }
.side-nav .nav-group.open .caret { transform: rotate(90deg); }
.side-nav .nav-children { display: flex; flex-direction: column; gap: 2px; }
.side-nav .nav-children a { padding-left: 8px; font-size: 13.5px; }
.side-nav .nav-children a .grow { padding-left: 27px; }
@media (prefers-reduced-motion: reduce) { .side-nav .nav-group .caret { transition: none; } }
.side-nav a .count { font-size: 11px; color: color-mix(in srgb, var(--color-text) 58%, transparent); }

.shell-main { min-width: 0; padding: var(--space-6) var(--space-6) var(--space-8); }

.page-head { display: flex; align-items: flex-end; gap: var(--space-3); margin-bottom: var(--space-4); }
.page-head .titles { flex: 1; min-width: 0; }
.page-head h6 {
  margin: 0 0 2px; font-size: 11px; font-weight: 500;
  color: color-mix(in srgb, var(--color-text) 58%, transparent);
}
.page-head h3 { margin: 0; font-family: var(--font-heading); font-size: 22px; font-weight: 500; }

.card-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-3); margin-bottom: var(--space-4); }

.error-line { color: var(--money-neg); font-size: 13px; margin: 0 0 var(--space-3); }

@media (max-width: 900px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  .shell-side { border-right: 0; border-bottom: 1px solid var(--color-divider); }
  /* --space-6, not the --space-5 this used to name: nocturne has no
     --space-5, and one undefined value in the shorthand threw out the whole
     declaration, so on a phone the page ran into both edges of the screen. */
  .shell-main { padding: var(--space-4) var(--space-3) var(--space-6); }
}

/* ---------- phones and tablets ---------- */

/* The menu button beside the brand, for when the sidebar sits above the page.
   The workspace picker stays in view whatever the menu is doing: which books
   are open must be unmissable (spec §7.1), on a phone as much as anywhere. */
.menu-toggle {
  display: none; margin-left: auto; width: 40px; height: 40px; padding: 0;
  align-items: center; justify-content: center;
  background: none; border: 1px solid var(--color-divider); border-radius: var(--radius-md, 8px);
  color: var(--color-text); font-size: 20px; cursor: pointer;
}
.menu-toggle:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }

@media (max-width: 900px) {
  /* The sidebar row takes only its own height. Without this a short page
     split the spare height between the two rows, leaving a gap under the
     sidebar the size of what the page did not fill. */
  .shell { grid-template-rows: auto 1fr; }
  .shell-side { gap: var(--space-3); padding: var(--space-3); }
  .menu-toggle { display: inline-flex; }
  .side-nav:not(.open) { display: none; }

  /* A table wider than the screen scrolls sideways inside whatever holds it,
     rather than pushing the whole page off the side of the screen. The
     holder scrolls and not the table itself, because a table made a
     scrolling block stops filling the width: its figures no longer line up
     at the right edge. The page's own main area is the fallback for a table
     sitting straight in a page, whose parent (.shell-page) is display:
     contents and cannot scroll. */
  :has(> .table), .shell-main { overflow-x: auto; }

  /* Tables laid out from fixed column widths hand the name column whatever
     the figure columns leave, which on a phone is nothing. A floor on the
     table's width gives the name its room back and scrolls the rest, and
     keeps the fixed layout — so the budget period's two tables still line
     up column for column. */
  .budget-table { min-width: 860px; }
  .line-list { min-width: 720px; }
}

/* iOS zooms the page into any field set smaller than 16px the moment it is
   tapped, and leaves it zoomed. On a touch screen the fields are 16px. */
@media (pointer: coarse) {
  .input { font-size: 16px; }
}

/* ---------- accounts table ---------- */

.stat { display: block; font-size: 22px; font-family: var(--font-heading); letter-spacing: -0.02em; }

.page-head .btn { flex: none; }

.sect-row { cursor: pointer; }
.sect-row:hover { background: color-mix(in srgb, var(--color-text) 5%, transparent); }
.sect-row td { border-top: 1px solid var(--color-divider); }
/* A budget period's note: on its page above the table, kept as typed (line
   breaks included); on the budgets list, its first line under the dates. */
.period-notes {
  display: flex; align-items: flex-start; gap: var(--space-2); flex-wrap: wrap;
  margin: 0 0 var(--space-4); padding: var(--space-3);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md, 8px);
}
.period-notes > .ph { font-size: 16px; transform: translateY(2px); }
.period-notes-text { flex: 1 1 0; min-width: 0; margin: 0; white-space: pre-wrap; max-width: 90ch; }
.period-notes textarea { flex: 1 1 100%; resize: vertical; font: inherit; }
.period-notes-actions { display: flex; align-items: center; gap: var(--space-2); }
.period-notes-add { margin: 0 0 var(--space-3); }
.period-note-line {
  display: block; max-width: 36ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px;
}

/* The Recurring page's account filter, and the account cards that set it. */
.rule-filter {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-3);
  margin: var(--space-3) 0; font-size: 13px;
}
.rule-filter label { font-size: 12px; color: color-mix(in srgb, var(--color-text) 62%, transparent); }
.rule-filter select { width: auto; min-width: 220px; }
.commitment-card { cursor: pointer; }
.commitment-card:hover { border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-divider)); }
.commitment-card.is-filtered { border-color: var(--color-accent); }

/* The facility under an account's available figure, on a line of its own. */
.facility-of { display: block; font-size: 11px; }

/* A section heads a group of accounts, so it sits above them in size as well
   as in the tree: 20px against the account's 16px against the row's 14px.
   Three steps, each doing one job — the group, the account, the details about
   it — which is what makes the grouping legible without reading the words.

   The first try at this set the section to 18px, and on the page the left
   column still read as one size throughout: 18 against 16 is two points, and
   at the same weight two points is not a step anyone sees. It was only obvious
   in the balance column, where the same 18 sat against the rows' 14. So the
   gap is now four points on both sides, and the weight moves with it — 600
   against the 500 an account name carries — because size alone at these
   sizes is doing too little of the work.

   The section's own total is set with it, since it summarises the group rather
   than belonging to any row in it. The "· 2 accounts" aside stays small: it
   qualifies the name, it does not compete with it. */
.sect-row strong {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 20px;
  letter-spacing: -0.015em;
}
.sect-row .muted { font-size: 13px; }

/* The section's name in the brand accent, and only the name: its total stays
   the ordinary text colour, because a money figure in green would read as a
   judgement on the number rather than as the heading it sits in. */
.sect-name { color: var(--color-accent); }

/* ---------- payroll template ---------- */

/* An accent kicker over the thing being named, as the design concept sets it
   on every detail screen. */
.kicker-accent { color: var(--color-accent); }

/* Payroll lines are a list rather than a grid of facts: the label, a quiet
   note saying why the line exists, the amount where it is read, and the
   controls. No column headings — with three or four rows a header row is
   more furniture than the table it labels. The recurring page's rules are
   the same kind of list and share everything here.

   Each section is its own table, and an automatic layout sizes every table to
   its own contents — so a section with shorter labels set its columns further
   left than the one above it, and the lists did not line up. A fixed layout,
   with one colgroup repeated in every table on the page, puts each column in
   the same place in every section; truncating long text instead of wrapping it
   keeps each line to one row, with the full text on hover. The controls are
   exempt from the clipping: truncating text is what keeps a row to one line,
   truncating a button just hides the thing you came to press. */
.line-list { table-layout: fixed; }
.line-list td { vertical-align: middle; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.line-list td.line-actions { overflow: visible; }
.line-list .line-note { font-size: 12px; }
.line-list .line-actions { width: 78px; white-space: nowrap; text-align: right; }

/* A section heading that heads something: the name in the accent at the size
   the accounts page gives its sections, the section's total beside it in the
   text colour — a money figure in green reads as a verdict on it. The small
   section-label these replaced was the size of a table header, which is to
   say it did not read as a heading at all. */
.section-head { display: flex; align-items: baseline; gap: var(--space-3); margin: var(--space-6) 0 var(--space-2); }
.section-head-name {
  flex: 1; margin: 0;
  font-family: var(--font-heading); font-weight: 600; font-size: 20px; letter-spacing: -0.015em;
  color: var(--color-accent);
}
.section-head-total { font-weight: 600; font-size: 20px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.section-head-total .muted { font-size: 12px; font-weight: 400; }

/* The payroll template's columns; the note takes what is left. The amount box
   is wider than the budget's 110px: a monthly gross salary runs to six
   figures, and "101660.67" beside the browser's spin buttons lost its last
   digit. 124px holds seven. */
.pay-list .c-label { width: 40%; }
.pay-list .c-amount { width: 140px; }
.pay-list .c-actions { width: 88px; }
.pay-list .alloc-input { width: 124px; }

/* What a payroll line, or a section of them, does to the budget: one line with
   a leading icon, kept apart from the notes about tax and net pay so the
   question a budget raises about a payslip — does this need an envelope — is
   answered beside the figure. The icon is the accent where the budget sees the
   money and goes quiet where it does not. */
.budget-effect {
  display: flex; align-items: baseline; gap: var(--space-2); margin: 0 0 var(--space-2);
  font-size: 12px; color: color-mix(in srgb, var(--color-text) 80%, transparent);
}
.budget-effect .ph { flex: none; font-size: 14px; color: var(--color-accent); transform: translateY(2px); }
.budget-effect.none .ph { color: color-mix(in srgb, var(--color-text) 45%, transparent); }

/* The ring chart (Components/RingChart.razor). A conic-gradient disc with the
   card's own background punched out of the middle, so it reads as a ring
   without an SVG or a charting library for what is one shape. */
.ring-chart { display: flex; align-items: center; gap: var(--space-4); margin-top: var(--space-2); }
.ring-chart .ring {
  width: 96px; height: 96px; flex: none; border-radius: 50%;
  -webkit-mask: radial-gradient(circle, transparent 54%, #000 55%);
          mask: radial-gradient(circle, transparent 54%, #000 55%);
}
.ring-chart .ring-key { display: flex; flex-direction: column; gap: 4px; font-size: 12px; min-width: 0; }
.ring-chart .swatch { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 7px; }

/* ---------- recurring ---------- */

/* A rule whose end date has passed stays listed, so the history is visible,
   but reads as finished and is left out of every monthly figure. */
.line-list tr.ended td { opacity: .55; }

/* The recurring page's columns: one colgroup shared by the due list and both
   sections. The fixed layout and the clipping are .line-list's, above, and
   the section headings are .section-head. */
.rule-list .c-payee { width: 21%; }
.rule-list .c-note { width: 33%; }
.rule-list .c-sched { width: 22%; }
.rule-list .c-amount { width: 132px; }
/* Wide enough for the due row's Confirm button as well as a rule's two icon
   buttons. */
.rule-list .c-actions { width: 100px; }
.line-list .auto-post { margin-left: 4px; color: var(--color-accent); }

/* What is waiting on you keeps the accent tint the old boxed rows had — the
   one signal worth keeping from them — in a single-line row instead of a
   card per rule. */
.due-list td { background: color-mix(in srgb, var(--color-accent) 6%, transparent); }
.due-list .line-actions { width: auto; }

/* The account's name is what the page is a list of, so it is the one thing in
   the row set larger than the table's 14px — enough to separate one account
   from the next at a glance, without the rows growing into a different table.
   The weight matters more than the family: --font-heading and --font-body are
   both Inter, so a family swap on its own renders identically. That is why
   --font-heading-weight is set explicitly here. */
.table td.acct-name {
  /* Set in from its section and traced by a guide line down the column, in
     the budget page's child-row geometry exactly: the line 9px in, just past
     where the section's name starts, and the name 26px in, clear of it.

     The selector carries .table td on purpose. As .acct-name alone it lost to
     nocturne's .table td padding, which is more specific: the indent never
     applied while the guide line did, so the line ran through the first
     letter of every account name. .budget-child td:nth-child(2) never had
     the problem because it already outranks .table td. */
  padding-left: 26px;
  position: relative;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 16px;
  letter-spacing: -0.01em;
}
.table td.acct-name::before {
  content: ""; position: absolute; left: 9px; top: 0; bottom: 0;
  border-left: 1px solid var(--color-divider);
}


/* ---------- valuation accounts ---------- */

/* The chart of a valuation account (Components/ValuationChart.razor): what it
   is worth as a solid line over a soft fill, what went into it as a dashed
   line, the gap between them being growth. Colours are the theme's, set here
   because an SVG presentation attribute cannot read a CSS variable. */
.vchart-card { margin: var(--space-4) 0; }
.vchart { display: block; width: 100%; height: auto; margin-top: var(--space-2); }
.vchart-grid { stroke: var(--color-divider); stroke-width: 1; }
.vchart-label { fill: color-mix(in srgb, var(--color-text) 58%, transparent); font-size: 11px; font-family: var(--font-body); }
.vchart-area { fill: color-mix(in srgb, var(--color-accent) 14%, transparent); }
.vchart-value { fill: none; stroke: var(--color-accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.vchart-putin { fill: none; stroke: color-mix(in srgb, var(--color-text) 55%, transparent); stroke-width: 1.5; stroke-dasharray: 5 4; }
.vchart-dot { fill: var(--color-accent); stroke: var(--color-surface); stroke-width: 2; }
.vchart-legend { display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: 12px; color: color-mix(in srgb, var(--color-text) 70%, transparent); }
.vchart-key { display: inline-block; width: 18px; margin-right: 6px; vertical-align: middle; border-top: 2px solid var(--color-accent); }
.vchart-key.putin { border-top: 2px dashed color-mix(in srgb, var(--color-text) 55%, transparent); }

/* ---------- ledger ---------- */

.ledger-row { cursor: pointer; }
.ledger-row:hover { background: color-mix(in srgb, var(--color-text) 5%, transparent); }
.nowrap { white-space: nowrap; }

.crumb { color: inherit; text-decoration: none; }
.crumb:hover { color: var(--color-accent); }

.head-amount {
  font-size: 15px; font-weight: 400; margin-left: var(--space-3);
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.head-amount.neg { color: var(--money-neg); }

/* ---------- editor modal ---------- */

.modal-backdrop {
  position: fixed; inset: 0; z-index: 900;
  background: color-mix(in srgb, #000 62%, transparent);
  display: flex; align-items: flex-start; justify-content: center;
  /* A clear gap above and below the dialog. This named --space-5, which
     nocturne does not define, so the whole padding was thrown out and every
     editor sat flush against the top of the window. A tall dialog scrolls
     within the backdrop and keeps the same gap at its foot. */
  padding: calc(var(--space-8) * 2) var(--space-3) calc(var(--space-8) * 2); overflow-y: auto;
}
@media (max-width: 900px) {
  .modal-backdrop { padding: var(--space-6) var(--space-3); }
}

.modal {
  width: min(620px, 100%);
  background: var(--color-surface);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-lg, 12px);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3);
}

.modal-head { display: flex; align-items: flex-start; gap: var(--space-3); }
.modal-head > div { flex: 1; min-width: 0; }
.modal-head h6 { margin: 0 0 2px; font-size: 11px; font-weight: 500; color: color-mix(in srgb, var(--color-text) 58%, transparent); }
.modal-head h4 { margin: 0; font-family: var(--font-heading); font-size: 18px; font-weight: 500; }

.modal-body { display: flex; flex-direction: column; gap: var(--space-3); }
.modal-actions { display: flex; align-items: center; gap: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--color-divider); }

.two-up { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }

.section-label {
  margin: var(--space-2) 0 0; font-size: 10px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 58%, transparent);
}

.split {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border: 1px solid var(--color-divider); border-radius: var(--radius-md, 8px);
  background: color-mix(in srgb, var(--color-text) 3%, transparent);
}
.split-top { display: flex; align-items: center; gap: var(--space-2); }
.split-top select { flex: 1; min-width: 0; }
.split-amount { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.split-amount .input { width: 130px; }

.total-line { display: flex; align-items: baseline; gap: var(--space-3); padding-top: var(--space-2); }
.total-line strong { font-size: 16px; }

.tag-picker { display: flex; flex-wrap: wrap; gap: var(--space-3); }

@media (max-width: 620px) {
  .two-up { grid-template-columns: 1fr; }
}

/* ---------- budget period (variant A: table + sticky rail) ---------- */

.budget-layout { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: var(--space-4); align-items: start; }

/* The budget period's two tables share one set of column widths — a fixed
   layout and the same colgroup — so Expected, Received and Still to come sit
   over Allocated, Spent and Left instead of each table sizing its columns to
   its own figures. The category column takes what is left. The caret and
   delete columns are wide enough for their 36px buttons, which an automatic
   layout used to stretch them to. */
.budget-table { table-layout: fixed; }
.budget-table .c-caret { width: 48px; }
.budget-table .c-amount { width: 136px; }
.budget-table .c-actual { width: 140px; }
.budget-table .c-balance { width: 170px; }
.budget-table .c-del { width: 48px; }
.budget-rail { position: sticky; top: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.budget-rail .rail-line { display: flex; justify-content: space-between; gap: var(--space-3); font-size: 12px; color: color-mix(in srgb, var(--color-text) 62%, transparent); }
.budget-rail .stat.neg { color: var(--money-neg); }
.rail-warn { border-color: color-mix(in srgb, var(--money-neg) 45%, var(--color-divider)); }

/* A child row is indented under its parent and traced by a guide line down
   the left of the name column. The indent alone was var(--space-4) — 11.2px
   at this density — which is too slight to read as structure at a glance;
   the line is what actually makes "Fuel sits under Transport" visible.
   Shared by the budget period, budget templates and the categories list, all
   three of which put the name in the second column for exactly this reason. */
.budget-child td:nth-child(2) { padding-left: 26px; position: relative; }
.budget-child td:nth-child(2)::before {
  content: ""; position: absolute; left: 9px; top: 0; bottom: 0;
  border-left: 1px solid var(--color-divider);
}
/* Weight, not just family: both font tokens resolve to Inter, so setting the
   family alone left a parent category rendering exactly like its children —
   the indent and guide line above were carrying the whole hierarchy on their
   own. */
.budget-parent-name { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }

.alloc-input { width: 110px; text-align: right; font-variant-numeric: tabular-nums; }

/* The row under each half of a budget period that adds a category to it
   (Components/BudgetAddLine.razor). Its picker and name box are narrower than
   a settings row's 340px, so a new category's name, parent and amount fit
   beside the picker on one line. */
.budget-add { margin: var(--space-2) 0 var(--space-3); }
.budget-add .input.budget-add-pick { max-width: 260px; }
.budget-add .input.budget-add-name { max-width: 200px; }

/* No spin buttons on an amount box. Nobody steps a budget up a cent at a
   time, and in Chrome they take some 15px of a 110px box — enough that a
   six-figure salary, now that income has budget lines, lost its last digit.
   Without them the box holds seven figures at the width every table here
   was already laid out for. */
.alloc-input { -moz-appearance: textfield; appearance: textfield; }
.alloc-input::-webkit-outer-spin-button,
.alloc-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* The new budget template dialog's starting points: a row per source with its
   figure beside it, then a preview of the lines Save will create, scrolling
   inside the dialog rather than stretching it past the screen. */
.seed-options { display: flex; flex-direction: column; gap: var(--space-2); }
.seed-option { display: flex; align-items: center; gap: var(--space-3); font-size: 14px; flex-wrap: wrap; }
.seed-option .input { max-width: 260px; }
.seed-option .muted { font-size: 12px; }
.seed-preview { max-height: 260px; overflow-y: auto; border: 1px solid var(--color-divider); border-radius: var(--radius-md); }
.seed-preview .table { margin: 0; }
.seed-preview .seed-group td {
  padding-top: var(--space-3); font-size: 10px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 58%, transparent);
}

/* A figure that opens the money behind it should look like it does. */
/* A section's CSV download, at the end of its heading row. */
.section-head .csv-button { margin-left: auto; padding: 4px 10px; font-size: 12px; }
.section-head .csv-button + .btn { padding: 4px 10px; font-size: 12px; }

/* An import's preview: long files scroll inside the dialog, not the page. */
.import-preview { max-height: 50vh; overflow: auto; }
.import-preview .import-choice { min-width: 190px; }

/* The profit and loss: section names in the accent, subtotals under a rule,
   gross and net profit set apart, the last one closed by a heavier rule. */
.pnl td { vertical-align: middle; }
.pnl .pnl-heading td { padding-top: var(--space-4); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-accent); border-bottom: none; }
.pnl .pnl-line { padding-left: var(--space-4); }
.pnl .pnl-subtotal td { font-weight: 600; border-top: 1px solid var(--color-divider); }
.pnl .pnl-strong td { font-weight: 700; font-size: 15px; border-top: 1px solid var(--color-divider); }
.pnl .pnl-final td { border-bottom: 2px solid var(--color-accent); }
/* A figure for the range before: still opens what is behind it, but reads as muted. */
.drill-link-quiet { color: color-mix(in srgb, var(--color-text) 58%, transparent); }
.drill-link { padding: 0; font-variant-numeric: tabular-nums; text-decoration: underline dotted; text-underline-offset: 3px; }

.drill-total { margin: 0; font-size: 15px; }

@media (max-width: 1000px) {
  .budget-layout { grid-template-columns: minmax(0, 1fr); }
  .budget-rail { position: static; }
}

/* ---------- stage 4: claims, categories, recurring ---------- */

.notice {
  display: flex; align-items: center; gap: 8px; margin: 0 0 var(--space-3);
  font-size: 13px; color: var(--money-pos);
}

/* Tags */
.tag-list { display: flex; flex-wrap: wrap; gap: 6px; margin: var(--space-2) 0; }
.tag-removable { display: inline-flex; align-items: center; gap: 6px; }
.tag-removable button {
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  color: inherit; opacity: .6; line-height: 1;
}
.tag-removable button:hover { opacity: 1; color: var(--money-neg); }
.tag-add { display: flex; gap: var(--space-2); margin-top: var(--space-2); }
.tag-add .input { flex: 1; min-width: 0; }

/* Recurring: what is waiting on you */

/* Claims: list beside detail */
.claims-layout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: var(--space-4); align-items: start; }

/* Your business: its details beside its logo, one column on a narrow screen.
   The logo sits on white in either theme, because white paper is where it
   ends up. */
.business-layout { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--space-4); align-items: start; max-width: 1080px; }
@media (max-width: 1000px) { .business-layout { grid-template-columns: minmax(0, 1fr); } }
.logo-preview {
  align-self: flex-start; max-width: 100%; max-height: 90px; object-fit: contain;
  padding: var(--space-2); background: #ffffff; border: 1px solid var(--color-divider); border-radius: var(--radius-md, 8px);
}
.claims-list { display: flex; flex-direction: column; gap: var(--space-2); }
/* A search and a filter heading a list, stacked in its column. */
.list-filter { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-2); }
.claim-item {
  display: flex; flex-direction: column; gap: 4px; text-align: left;
  padding: var(--space-3); cursor: pointer; font: inherit; color: var(--color-text);
  background: var(--color-surface); border: 1px solid var(--color-divider);
  border-radius: var(--radius-md, 8px);
}
.claim-item:hover { border-color: var(--color-accent); }
.claim-item.on { border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface)); }
.claim-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }

.claims-detail { display: flex; flex-direction: column; gap: var(--space-3); }
.status-flow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: var(--space-2); }
.claim-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

@media (max-width: 1000px) {
  .claims-layout { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- stage 5: reports, settings, import, payroll ---------- */

.report-range { display: flex; align-items: flex-end; gap: var(--space-3); margin-bottom: var(--space-4); flex-wrap: wrap; }

/* A control in a section heading's place for a total — the payee search —
   sits level with the heading rather than on a line of its own. */
.section-head .input { max-width: 240px; align-self: center; }

/* A report section's heading rolls the section up. The button sits inside the
   h4 and takes the heading's type and colour rather than a button's; the
   caret leads, as on the accounts page's section rows, and the browser's own
   focus ring is left alone. The count beside the name is the quiet aside the
   accounts sections carry, at their size. */
.section-toggle {
  padding: 0; border: 0; background: none; cursor: pointer; text-align: left;
  color: inherit; font: inherit; letter-spacing: inherit;
}
.section-toggle .ph { font-size: 15px; margin-right: var(--space-3); opacity: .7; vertical-align: 1px; }
.section-toggle:hover .ph { opacity: 1; }
.section-head-name .muted { font-size: 13px; font-weight: 400; letter-spacing: 0; }

/* Share bar - proportion of the largest row, so the longest is full width. */
.bar { display: block; height: 8px; border-radius: 4px; background: color-mix(in srgb, var(--color-text) 10%, transparent); overflow: hidden; }
.bar > span { display: block; height: 100%; background: var(--color-accent); border-radius: 4px; }

.settings-list { display: flex; flex-direction: column; gap: var(--space-3); max-width: 820px; }
.settings-list section { display: flex; flex-direction: column; gap: var(--space-2); }
.settings-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.settings-row .input { max-width: 340px; }

.danger-zone { border-color: color-mix(in srgb, var(--money-neg) 45%, var(--color-divider)); }
.btn.danger { border-color: var(--money-neg); color: var(--money-neg); }
.btn.danger:hover:not(:disabled) { background: color-mix(in srgb, var(--money-neg) 14%, transparent); }

/* CSV sample and preview both scroll rather than pushing the page sideways. */
.csv-sample { overflow-x: auto; max-height: 340px; overflow-y: auto; margin: var(--space-2) 0; }
.csv-sample .table { font-size: 12px; }

/* The retirement-deduction gauge in the Reports page's Investments section:
   how much of the tax year's allowance is used, with the figures it is made
   of under it. The bar is the same one the spending rows are drawn with. */
.deduction {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin: var(--space-3) 0; padding: var(--space-3);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.deduction-head { display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--space-3); font-size: 13px; }
.deduction-figures { display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: 13px; font-variant-numeric: tabular-nums; }
.deduction-figures .muted {
  margin-right: 6px; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
}

/* The carried-over column, between the category and what it was given this
   period: what last period's envelope handed over. Sized like the other
   figure columns so the two tables keep lining up. */
.budget-table .c-carry { width: 140px; }

/* A bar past full is not a fuller bar: a plan that assigns more than it
   expects has a different problem, and says so in the money colour. */
.bar > span.over { background: var(--money-neg); }

/* The Reports card on the settings page: a tick per section, its note under
   the tick, and the category picker under that for the sections that read
   one. Indented to sit under the label text rather than the box. */
.report-settings { display: flex; flex-direction: column; gap: var(--space-3); }
.report-setting { display: flex; flex-direction: column; gap: 4px; }
.report-setting .muted { margin-left: 26px; font-size: 12px; }
.report-setting .input { margin-left: 26px; max-width: 300px; }

/* A bar inside a table row: the bar takes the column and its own figure
   sits beside it, so a column of them reads down as well as across. */
/* The budget periods list: shorter, slimmer bars with room between the
   columns, so two bars on one row read as two figures rather than one band. */
.periods-table { table-layout: fixed; }
.periods-table th, .periods-table td { padding-right: 32px; }
.periods-table th:last-child, .periods-table td:last-child { padding-right: var(--space-2); }
.periods-table .bar { max-width: 120px; height: 6px; }
.periods-table .pos, .periods-table .neg { font-variant-numeric: tabular-nums; }

.bar-cell { display: flex; align-items: center; gap: var(--space-2); }
.bar-cell .bar { flex: 1; }
.bar-cell .muted { min-width: 42px; font-size: 12px; font-variant-numeric: tabular-nums; text-align: right; }

/* VAT on a split, in a business workspace: the amount beside the standard-rate
   button, with the claim tick under them. */
.split-vat { display: flex; align-items: flex-end; gap: var(--space-3); flex-wrap: wrap; }
.split-vat .field { width: 150px; }
.split-vat .radio { padding-bottom: 8px; }

/* Invoicing: the draft editor is a table of lines, so it needs more width than
   a form modal, and the totals under it read as a column of figures. */
.modal-wide { width: min(860px, 100%); }

.invoice-lines td { vertical-align: middle; }
.invoice-lines .input { min-width: 0; }
.invoice-lines td:first-child .input { min-width: 140px; }
/* The item a line sells, above its description in the same cell. */
.invoice-lines .line-item { margin-bottom: 4px; }
.invoice-lines .num.input { width: 92px; }
.invoice-lines .line-actions { width: 48px; text-align: right; }

.invoice-totals {
  display: grid; grid-template-columns: 1fr auto; gap: 6px var(--space-4);
  justify-content: end; margin-left: auto; min-width: 260px;
  font-variant-numeric: tabular-nums;
}
.invoice-totals strong { font-size: 16px; }

.terms-field { display: flex; align-items: center; gap: var(--space-2); }
.terms-field .input { width: 90px; }

@media (max-width: 900px) {
  .invoice-lines .num.input { width: 72px; }
}

/* The supplier's tax invoice behind a claimed line: three short fields on one
   row under the VAT, wrapping on a narrow screen. */
.tax-invoice { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.tax-invoice .field { flex: 1 1 140px; min-width: 0; }
.tax-invoice .field:last-child { flex: 0 1 160px; }

/* A notice that asks for something to be done rather than confirming it did:
   the text stays readable, the icon and a rule carry the warning colour. */
.notice.warn {
  align-items: flex-start; color: var(--color-text);
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--money-neg);
  background: color-mix(in srgb, var(--money-neg) 7%, transparent);
}
.notice.warn .ph { color: var(--money-neg); font-size: 16px; flex: none; transform: translateY(2px); }

/* An action inside a sentence: reads as a link, behaves as a button. */
.btn-link {
  padding: 0; border: 0; background: none; font: inherit; color: var(--color-accent);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.btn-link:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* A tag that marks a problem on a row, in the warning colour. */
.tag.neg-outline { border: 1px solid var(--money-neg); color: var(--money-neg); background: none; margin-left: 6px; }

/* Documents behind a transaction: one line each, the name as the link that
   opens it, then its size and whether it is kept as a VAT record. */
.doc-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.doc-list li { display: flex; align-items: center; gap: var(--space-2); font-size: 13px; min-width: 0; }
.doc-list li > .ph:first-child { font-size: 16px; color: var(--color-accent); flex: none; }
.doc-list .doc-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.doc-list .muted { font-size: 12px; flex: none; }
.doc-list .doc-remove { margin-left: auto; width: 28px; height: 28px; }
.doc-list .doc-pending .doc-name { color: color-mix(in srgb, var(--color-text) 70%, transparent); }
.doc-list .doc-failed > .ph:first-child { color: var(--money-neg); }
.doc-list .doc-failed .muted:last-of-type { color: var(--money-neg); white-space: normal; flex: 1 1 auto; }
.doc-kept { border: 1px solid var(--color-divider); border-radius: 4px; padding: 0 6px; }
.doc-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-2); }
.doc-actions .btn { cursor: pointer; }
.ph-spinner-gap { animation: doc-spin 1s linear infinite; }
@keyframes doc-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ph-spinner-gap { animation: none; } }

/* The paperclip on a ledger row that has its paperwork. */
.has-docs { color: color-mix(in srgb, var(--color-text) 55%, transparent); font-size: 13px; margin-left: 4px; }

/* A table wider than its card scrolls inside it rather than pushing the page
   sideways: the tax figures grow a column for every figure payroll adds. */
.table-scroll { overflow-x: auto; }
.tax-figures td { vertical-align: top; }
.tax-figures .field-note { margin-top: 2px; }

/* A staff claim's lines: a slip or a trip per row, with the fields that
   kind of line needs. */
.claim-lines { display: flex; flex-direction: column; gap: var(--space-3); }
.claim-line {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border: 1px solid var(--color-divider); border-radius: var(--radius-md, 8px);
}
.claim-line-head { display: flex; align-items: center; gap: var(--space-2); }
.claim-line-head .grow { flex: 1; }
.claim-line .three-up { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
@media (max-width: 640px) { .claim-line .three-up { grid-template-columns: minmax(0, 1fr); } }

/* One tax year in Settings: its figures beside its brackets. */
.tax-year { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: var(--space-6); align-items: start; }
@media (max-width: 900px) { .tax-year { grid-template-columns: minmax(0, 1fr); } }
.tax-figure-list { display: flex; flex-direction: column; gap: var(--space-2); }
.tax-figure { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px var(--space-3); font-size: 13px; }
.tax-figure .field-note { grid-column: 1 / -1; margin-top: 0; }
.tax-bands .input { max-width: 150px; margin-left: auto; }
.tax-bands td { vertical-align: middle; }
