@import "/static/tokens.css";

/* Self-hosted type (DESIGN-DIRECTION §4B). OFL, self-hosted woff2 (static
   weights, plain `format("woff2")` so every renderer loads them — a variable
   `format("woff2-variations")` hint is silently dropped by some headless
   Chromium builds, the "fonts fall back to system" symptom).

   Inter drives the interface and is declared in fonts/inter.css, which the
   shell links only when the files are actually present, so a tree without them
   loads no broken URL. Plex Sans below carries the interface until they are.
   Plex Serif keeps page titles and of-record surfaces (the type contrast that
   breaks the single-grotesk generic look); Plex Mono keeps genuine machine
   text — hashes, tokens, keyboard hints. */
@font-face { font-family: "IBM Plex Sans"; src: url("/static/fonts/ibm-plex-sans-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Sans"; src: url("/static/fonts/ibm-plex-sans-500.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Sans"; src: url("/static/fonts/ibm-plex-sans-600.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Sans"; src: url("/static/fonts/ibm-plex-sans-700.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Serif"; src: url("/static/fonts/ibm-plex-serif-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Serif"; src: url("/static/fonts/ibm-plex-serif-600.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("/static/fonts/ibm-plex-mono-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("/static/fonts/ibm-plex-mono-500.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("/static/fonts/ibm-plex-mono-600.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
/* Pensius — "Ledger" design system v3 (docs/DESIGN-SYSTEM.md).
   Load order: tokens.css (semantic source of truth) FIRST, then this file,
   which consumes the semantic tokens. No hard-coded hexes in components; the
   only literals here are low-alpha ink washes derived from --ink-900.

   Migration note (DESIGN-SYSTEM §10.2): this is a re-map, not a rewrite. The
   legacy names below (--brand-accent, --card, --ink, …) are BRIDGE ALIASES at
   the new semantic tokens, so 60 templates re-skin without edits. Gradients are
   deleted, cards are flat (border, no shadow), radius capped at 8px, badges are
   squared, data-viz is flat teal / ink-slate. */

:root {
  /* ---- BRIDGE: legacy token names → new semantic tokens ------------------- */
  /* QA-08: the old --brand-navy* aliases (unused, and "navy" was misleading for
     what is now ink/accent green) were removed. --brand-accent* stays — it is
     referenced by a few card borders and is honestly named. */
  --brand-accent:      var(--accent);        /* one green accent, via the token   */
  --brand-accent-soft: var(--accent-soft);
  --accent-ink:        #06231f;              /* dark-green text on a light-accent pill */
  --bg:    var(--stone-50);
  --card:  var(--surface);
  --ink:   var(--text);
  --ink-2: var(--text-secondary);
  --muted: var(--text-muted);
  --border: var(--stone-200);
  --grid:  var(--rule);
  --ok: var(--green-700);  --ok-soft: var(--green-050);
  --warn: var(--amber-700); --warn-soft: var(--amber-050);
  --danger: var(--red-700); --danger-soft: var(--red-050);
  --violet: var(--series-4);                 /* carry-forward = plum, per spec   */
  --violet-soft: #f2e8e3;  /* warm clay tint, not lavender */
  --thead-bg: var(--surface-raised);
  --row-hover: rgba(12, 107, 98, 0.05);      /* teal wash, not grey              */
  --row-total: var(--row-total-bg);
  /* Flat-first: cards carry NO shadow. --shadow-1 stays "none" so every legacy
     `box-shadow: var(--shadow-1)` on a card/tile flattens automatically; true
     overlays reference --elev-1/2 directly. */
  --shadow-1: none;
  --shadow-2: var(--elev-2);
}

* { box-sizing: border-box; }

/* The `hidden` attribute must win over any later `display:` rule. Without this,
   `.bulkbar{display:flex}` kept the "0 selected" bar on screen permanently, and
   `.drawer-iconbtn{display:inline-flex}` showed the drawer back-arrow at the top
   level. Every [hidden] element in the app is toggled by JS, never revealed by
   CSS, so !important is safe here. */
[hidden] { display: none !important; }

/* motion: one consistent, subtle transition vocabulary (colour/border/shadow) */
a, button, .btn, .chiplink, .tile, .board-card, input, select, textarea {
  transition: var(--transition-ui);
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

html { scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Every digit in the product is the same width. It used to be switched on
     per class — a list of fourteen selectors that had to be extended each time
     a screen grew a figure, and a date or a count outside that list drifted. A
     ledger reads down the column, so tabular is the default here and nothing
     opts back out. */
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: var(--numeric-tabular);
}

/* ================= shell: sidebar + main ================= */

.shell { display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); min-height: 100vh; }

.sidebar {
  background: var(--surface-dark);          /* flat petrol-ink, NO gradient */
  color: var(--nav-fg);
  padding: var(--space-5) var(--nav-inset) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  scrollbar-width: thin;
}

/* The brand stands on the same two columns as the account row at the foot and
   the menu between them: the mark on the avatar's column (the sidebar's inset), the
   product name on the column of every menu label (inset + icon + gap). It has no
   padding of its own, which put the mark 8px and the name 8px to the right. */
.side-brand { display: flex; align-items: center; gap: var(--space-4); padding: 0; }

/* The mark (app/brand.py, direction B1 "Plumb"). It is an inline SVG drawn in
   currentColor, so the surface decides the colour and there is no container:
   the filled rounded square it replaces was the Adobe construction the owner
   spotted. Size is set here, never inside the SVG. */
.brandmark {
  width: 30px;
  height: 30px;
  flex: none;
  display: block;
  color: inherit;
}
.brandmark.big { width: 44px; height: 44px; }
/* On the two dark surfaces the mark carries full white — it is the brand, not
   body text, and --nav-fg is deliberately softened for the menu around it. */
.side-brand .brandmark, .login-brand .brandmark { color: #fff; }

.side-title { color: #fff; font-weight: var(--fw-semibold); font-size: var(--fs-md); letter-spacing: -0.01em; }
.side-sub { font-size: var(--fs-2xs); color: var(--nav-fg-muted); letter-spacing: var(--tracking-label); }

.sidebar nav { display: flex; flex-direction: column; gap: 1px; flex: 1; }

.ngroup {
  font-size: var(--fs-3xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--nav-fg-muted);
  margin: var(--space-4) 0 var(--space-2);
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); background: none; border: 0; box-shadow: none; cursor: pointer;
  padding: var(--space-1) var(--nav-inset); border-radius: var(--radius-sm); font-family: inherit;
}
.ngroup:hover { color: var(--nav-fg); background: rgba(255, 255, 255, 0.05); }
.ngroup-chev { font-size: 9px; opacity: 0.7; transition: transform var(--dur-base) var(--ease-standard); }
.ngroup-sec:not([data-collapsed]) .ngroup-chev { transform: rotate(90deg); }
.ngroup-sec[data-collapsed] .ngroup-items { display: none; }
.ngroup-sec[data-collapsed]:has(a.on) .ngroup-items { display: block; }
@media (prefers-reduced-motion: reduce) { .ngroup-chev { transition: none; } }

.sidebar nav a {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--nav-fg);
  text-decoration: none;
  padding: var(--space-3) var(--nav-inset);
  border-radius: var(--nav-item-radius);
  font-size: var(--fs-sm);
  line-height: var(--lh-dense);
}

.sidebar nav a .ic { width: 18px; display: inline-flex; align-items: center; justify-content: center; opacity: 0.85; flex: none; }
.sidebar nav a .ic svg { width: 16px; height: 16px; }
.sidebar nav a.on .ic { opacity: 1; }

.sidebar nav a:hover { background: var(--nav-hover-bg); color: #fff; }

.sidebar nav a.on {
  background: var(--nav-active-bg);
  color: #fff;
  font-weight: var(--fw-medium);
  box-shadow: inset 3px 0 0 var(--accent-on-dark);
}

.sidebar nav a .chip {          /* nav count — pill radius allowed for counts */
  margin-left: auto;
  background: var(--accent-on-dark);
  color: var(--accent-ink);
  border-radius: var(--radius-pill);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  padding: 1px var(--space-3);
  font-variant-numeric: tabular-nums;
}

.navsearch input {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--border-dark);
  color: #fff;
  width: 100%;
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--fs-xs);
}
.navsearch input::placeholder { color: var(--nav-fg-muted); }
.navsearch input:focus { outline: none; box-shadow: var(--focus-ring-on-dark); background: rgba(255, 255, 255, 0.12); }

/* typeahead suggestions under the sidebar search box (owner finding #5).
   Rendered on the light surface — these are records, not chrome. */
.navsearch { position: relative; }
/* Fixed, not absolute: the sidebar scrolls (overflow-y:auto), which also clips
   the x-axis and cut every suggestion off at the sidebar's right edge
   ("NPF 2021 — Georgia ("). Placed by JS from the input's rect so it can
   overlap the content column to the right. */
.navsearch-results {
  position: fixed; z-index: var(--z-dropdown);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--elev-2); padding: var(--space-3); width: 300px; max-width: 90vw;
  max-height: 60vh; overflow-y: auto;
}
.navsearch-item {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  text-decoration: none; color: var(--text); font-size: var(--fs-sm);
}
.navsearch-item.active, .navsearch-item:hover { background: var(--accent-soft); }
.navsearch-kind {
  flex: 0 0 auto; width: 54px; font-size: var(--fs-3xs); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--text-muted);
}
.navsearch-label { min-width: 0; }
.navsearch-sub { display: block; font-size: var(--fs-2xs); color: var(--text-muted); font-family: var(--font-mono); }
.navsearch-all { padding: var(--space-3) var(--space-4) 0; font-size: var(--fs-2xs); color: var(--text-muted); }

/* The account at the foot of the sidebar is one row the height of a control:
   the avatar, the name and a chevron are the summary of a menu that opens upward
   over the menu above it, and the language stays on the row. The avatar stands on
   the brand mark's column and the name on the column of the menu labels (the
   sidebar's one inset, --nav-inset). */
.side-user {
  position: relative;
  border-top: 1px solid var(--border-dark);
  padding-top: var(--space-4);
  display: flex;
  gap: var(--space-2);
  align-items: center;
  font-size: var(--fs-xs);
}
details.acct { flex: 1; min-width: 0; }
details.acct > summary {
  display: flex; align-items: center; height: var(--control-h);
  color: #fff; border-radius: var(--radius-sm);
}
details.acct > summary::-webkit-details-marker { display: none; }
details.acct > summary::marker { content: ""; }
details.acct > summary:focus-visible { outline: 2px solid var(--accent-on-dark); outline-offset: 0; }
.acct-name { min-width: 0; margin-left: var(--space-4); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-weight: var(--fw-medium); }
/* closed it points the way the menu opens, open it points back */
.acct-chev { flex: none; display: inline-flex; margin-left: var(--space-1); color: var(--nav-fg-muted);
  transform: rotate(180deg); transition: transform var(--dur-base) var(--ease-standard); }
details.acct .acct-chev svg { width: 12px; height: 12px; }   /* past svg:not([class]), which is later */
details.acct[open] .acct-chev { transform: none; }
details.acct > summary:hover .acct-chev { color: #fff; }
@media (prefers-reduced-motion: reduce) { .acct-chev { transition: none; } }
.acct-menu {
  position: absolute; left: 0; right: 0; bottom: calc(100% + var(--space-2)); z-index: var(--z-dropdown);
  display: flex; flex-direction: column; padding: var(--space-2);
  background: var(--nav-active-bg); border-radius: var(--radius-md);
  box-shadow: inset 0 0 0 1px var(--border-dark), var(--elev-2);   /* the hairline is a shadow, so the words of an entry stand on the icon column */
}
/* an entry is one line of the small step, left-aligned, a link or a button alike;
   the class, not the tag, so no rule of a button's own (its fill, its press) shows */
.acct-menu .acct-item {
  display: flex; align-items: center; justify-content: flex-start; width: 100%;
  height: var(--control-h-sm); min-height: 0; padding: 0 var(--space-3);
  border: 0; border-radius: var(--radius-sm); background: none; box-shadow: none;
  color: var(--nav-fg); font: inherit; font-size: var(--fs-xs); text-align: left; text-decoration: none;
  white-space: nowrap; cursor: pointer;
}
.acct-menu .acct-item:hover { background: var(--nav-hover-bg); color: #fff; }
.acct-menu .acct-item:focus-visible { outline: 2px solid var(--accent-on-dark); outline-offset: -2px; }
.acct-menu form { margin: 0; }
.acct-menu hr { border: 0; border-top: 1px solid var(--border-dark); margin: var(--space-2) 0; }
.avatar {
  background: var(--ink-700);
  color: #fff;
  width: 30px; height: 30px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: var(--fw-semibold); font-size: var(--fs-2xs);
  flex: none;
}

/* Content cap: comfortable line lengths without wasting a wide monitor. */
main { padding: var(--space-6) var(--space-7) var(--space-10); max-width: var(--content-max); }

/* ================= top utility bar (§7.5) ================= */
.topbar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5);
  margin-bottom: var(--space-6); }
.topbar-jump { display: inline-flex; align-items: center; gap: var(--space-3); flex: 1; max-width: 420px;
  background: var(--surface); border: 1px solid var(--border-strong); color: var(--text-muted);
  border-radius: var(--radius-md); padding: 0 var(--space-3); min-height: var(--field-height);
  font-size: var(--fs-sm); cursor: text; text-align: left; box-shadow: none; font-weight: var(--fw-regular); }
.topbar-jump:hover { border-color: var(--field-border-hover); background: var(--surface); }
.topbar-jump .ic { display: inline-flex; color: var(--text-muted); }
.topbar-jump svg { width: 16px; height: 16px; }
.topbar-jump-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar kbd { font-family: var(--font-mono); font-size: var(--fs-2xs); background: var(--surface-sunken);
  border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 1px var(--space-3); color: var(--text-secondary); }
/* The right-hand group used to refuse to shrink, and the client-code strip
   inside it refuses to wrap — so a fifth client, or a slightly wider face,
   pushed the whole page sideways by the width of a scrollbar. The group gives
   now, and the strip is the part that gives first: it is a reminder of whose
   books you are in, not a control, and the full list is on its title. */
.topbar-right { display: flex; align-items: center; gap: var(--space-3);
  flex: 0 1 auto; min-width: 0; }
/* Everything in the group is one control tall and one size of type: the period
   was a 16.5px line of 11px text, the reconciliation chip 28px, the three
   places 32px, so five items stood on three heights. None pads itself
   vertically; the height is the control step's. */
.topbar-right > * { min-height: var(--control-h); font-size: var(--fs-xs); }
.topbar-period { display: inline-flex; align-items: center; gap: var(--space-2);
  color: var(--text-secondary); white-space: nowrap; font-variant-numeric: tabular-nums; }
.topbar-period svg { width: 14px; height: 14px; color: var(--text-muted); }
@media (max-width: 760px) { .topbar-period { display: none; } }
.topbar-recon { display: inline-flex; align-items: center; gap: var(--space-2); text-decoration: none;
  font-weight: var(--fw-semibold); padding: 0 var(--space-3);
  border-radius: var(--radius-sm); white-space: nowrap; border: 1px solid transparent; }
.topbar-recon svg { width: 15px; height: 15px; }
.topbar-recon.is-ok { color: var(--ok); background: var(--ok-soft); border-color: var(--ok-border); }
.topbar-recon.is-ok:hover { border-color: var(--ok); }
.topbar-recon.is-tobook { color: var(--warn); background: var(--warn-soft); border-color: var(--warn-border); }
.topbar-recon.is-tobook:hover { border-color: var(--warn); }
/* the three attention places, named (CLARITY-REVIEW P0-1): icon + word from
   1200px up, icon alone below — the title carries the word there */
.topbar-icon { display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2); min-width: var(--field-height); height: var(--field-height);
  padding: 0 var(--space-3); border-radius: var(--radius-md);
  color: var(--text-secondary); text-decoration: none; font-size: var(--fs-xs); white-space: nowrap; }
.topbar-icon:hover { background: var(--surface-sunken); color: var(--accent); }
.topbar-icon svg { width: var(--icon-size-md); height: var(--icon-size-md); flex: none; }
.topbar-icon-label { line-height: 1; }
@media (max-width: 1199px) { .topbar-icon-label { display: none; } .topbar-icon { padding: 0; } }
/* the bell is the dot's positioning context, so the dot rides the bell's top
   right corner (2px over it) at every width, with the label shown or folded
   (it used to be placed against the link, 70px from the bell, on the last
   word). The alignment probe reads the dot against this box. */
.topbar-bell { position: relative; display: inline-flex; flex: none; }
.topbar-dot { position: absolute; top: -2px; right: -2px; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); border: 1.5px solid var(--bg); }
@media (max-width: 920px) { .topbar { margin-bottom: var(--space-5); } .topbar-jump { max-width: none; } }

/* ================= cards & typography ================= */

.card {
  background: var(--card);
  border: var(--card-border);
  border-radius: var(--card-radius);        /* 0 — flat, hairline-defined (no floating) */
  padding: var(--card-pad) var(--card-pad-x);
  margin-bottom: var(--space-5);
  box-shadow: var(--elev-0);
}

/* Vertical rhythm (MAKEOVER-PLAN §3.1): a heading belongs to the block under
   it, so heading→content is the TIGHT gap; block→block is --gap; section→
   section is --gap-section. Three distances, nothing in between. */
h2 { font-size: var(--fs-lg); margin: 0 0 var(--gap-tight); font-weight: var(--fw-semibold); letter-spacing: var(--tracking-tight); color: var(--text-heading); }
h2 + p.muted { margin-top: calc(-1 * var(--space-1)); line-height: var(--lh-body); max-width: 72ch; }
h3 {
  font-size: var(--fs-3xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  margin: 0 0 var(--gap-tight);
  font-weight: var(--fw-semibold);
}

.muted { color: var(--text-muted); font-size: var(--fs-xs); }
/* The one mark for a value that is not there (EMPTY, the blank filter): a quiet
   middle dot that keeps the size of the cell it stands in. */
.nil { color: var(--text-muted); }
.mono { font-family: var(--font-mono); font-size: var(--fs-xs); }
/* Tone is not size. A muted word keeps the size and the weight of what it
   stands in: beside the heading it belongs to (it was 12px beside a 10px
   heading), and in a figure cell, where a muted total was 12px beside a 13px
   column and a mono ratio drew its digits in another face. An explicit .small
   is a size on purpose and stays. */
h3 .muted { font-size: inherit; font-weight: var(--fw-regular); }
/* what qualifies a heading stands beside it, in the same size and the regular
   weight, set apart by space and by tone and never by a dash: a heading carries
   no punctuation (alignment round 27). It sits on the heading's own baseline. */
.h3-sub { margin-left: var(--space-3); color: var(--text-muted); font-size: inherit; font-weight: var(--fw-regular); }
td.amt.muted, td.num.muted, td.amt .muted:not(.small), td.amt.mono { font-size: inherit; font-family: var(--font-figure); }

a { color: var(--accent); }
a:hover { color: var(--accent-hover); }

/* ================= figure bar (was floating stat tiles) =================
   DESIGN-DIRECTION §4C: not 5–8 equal floating cards on grey, but a single
   hairline-ruled strip — segments divided by 1px rules, the first reading as
   the hero metric. This gives first-order hierarchy (kills T2 + T6). */

.tiles {
  display: flex; flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: var(--space-5);
}
.tiles--hero { margin-bottom: var(--space-4); }

.tile {
  flex: 1 1 158px;
  min-width: 0;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--rule);
  border-radius: 0;
  /* the first word of a tile stands one card inset after the rule (or the edge)
     that opens it; what is left of the tile on the right is breathing room, a
     step less, so six money figures still fit one line at 1280 */
  padding: var(--space-4) var(--space-5) var(--space-4) var(--card-pad-x);
}
.tile:first-child { border-left: 0; }

.tile .v {
  font-size: clamp(15px, 1.6vw, 21px);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--lh-tight);
  overflow-wrap: break-word;
  color: var(--text);
}
/* the first segment of a hero row is the screen's primary figure */
.tiles--hero .tile:first-child { flex-grow: 1.5; background: var(--surface-raised); }
.tiles--hero .tile:first-child .v { font-size: clamp(22px, 2.6vw, 30px); }
.tile--hero { }
.tile--hero .v { font-size: clamp(20px, 2.4vw, 28px); }
.tile--hero .l { font-size: var(--fs-xs); }
.tile .l { font-size: var(--fs-2xs); color: var(--text-muted); margin-top: var(--space-2); text-transform: uppercase; letter-spacing: var(--tracking-label); font-weight: var(--fw-medium);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tile-delta { font-size: var(--fs-2xs); color: var(--ok); margin-top: var(--space-2); font-weight: var(--fw-medium); font-variant-numeric: tabular-nums; }
.tile-delta.down { color: var(--danger); }
/* the ONE primary metric carries the single accent — an inset top rule + green figure */
/* amber = a person must act, the same rule the badges follow */
.tile.alert { box-shadow: inset 0 2px 0 var(--warn); }
.tile.alert .v { color: var(--accent); }
.tile a { text-decoration: none; color: inherit; display: block; }
.tile a:hover .v { color: var(--accent); }

/* ================= dashboard section label =================
   Front-loads hierarchy between zones (attention · position · analytics · by
   client) with a serif register heading — the of-record voice, not a card
   header. Reused by the dashboard recomposition. */
/* ================= the compliance dashboard (MAKEOVER-PLAN §5.1) =================
   Owner finding #4: the dashboard read as a cashflow report, but the filings
   are done and the cheques arrive years later — the desk carries dozens of open
   filings as its normal state. One headline, four cards in a reading order,
   the money in grey, and everything else one disclosure down. */

.complead { margin: 0 0 var(--gap-section); }
.complead-line {
  font-family: var(--font-serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-regular);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--text-heading);
  margin: 0;
}
.complead-line a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  white-space: nowrap;
}
.complead-line a:hover { border-bottom-color: var(--accent); color: var(--accent); }
.complead-line a.is-attention .n { color: var(--warn); }
.complead-line .n { font-family: var(--font-figure); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums lining-nums; }
.complead-line .sep { color: var(--text-muted); margin: 0 0.45em; }
.complead-sub { color: var(--text-muted); font-size: var(--fs-sm); margin: var(--space-3) 0 0; }
/* scope chips on the left, the "Browse all cases" door on the right — the
   summary points at the workplace (REDESIGN-CASE-CENTRIC P7) */
.complead-scope { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; margin-top: var(--space-4); }
.complead-browse { white-space: nowrap; }

.dashgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap);
  margin-bottom: var(--gap-section); }
.dashgrid > .span2 { grid-column: 1 / -1; }
@media (max-width: 1080px) { .dashgrid { grid-template-columns: 1fr; } }

.dashcard { margin: 0; }
.dashcard-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4); margin-bottom: var(--gap-tight); }
.dashcard-head h2 { margin: 0; font-family: var(--font-serif); font-weight: var(--fw-semibold);
  font-size: var(--fs-lg); color: var(--text-heading); }
/* One card head. The title is a 10px label and stood on whatever line its
   neighbour made: the base h3 margin (8px, in a flex row it does not collapse)
   put 16px between the title and its content instead of the head's 8; a 13px
   count made the head 4.5px taller and its baseline 3px lower than the head of
   the card beside it. A title is one 18px line, with no margin of its own, and a
   count inside it is the title's size, so heads in one row share a baseline and
   every head is 8px from what it heads. 18px is the line of the 12px link or
   figure that stands beside a title (.dashcard-more, .matchcol-ref). */
.dashcard-head h3, .matchcol-head h3 { margin: 0; line-height: 18px; }
.dashcard-head h3 .fu-count, .matchcol-head h3 .fu-count { font-size: inherit; }
.dashcard-more { font-size: var(--fs-xs); text-decoration: none; white-space: nowrap; }
.dashcard-more:hover { text-decoration: underline; }
.dashcard-foot { margin: var(--gap-tight) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }
.dashcard-figure { margin: 0 0 var(--gap-tight); font-family: var(--font-figure);
  font-size: var(--fs-lg); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }
.dashcard-figure .muted { font-family: var(--font-sans); font-size: var(--fs-xs); font-weight: var(--fw-regular); }
/* Position is context, not the task: its title steps back a tone (plan C3). */
.dashcard.is-secondary .dashcard-head h2 { color: var(--text-secondary); font-family: var(--font-sans);
  font-size: var(--fs-md); }

/* the hero: open cases by age, and per client. Two lists on one set of lines:
   a heading of one style over each, rows of one height, and the figures on
   fixed tracks named once here from tokens, so a column's right edge is the
   same in every row, the bar is as long in every row, and the two lists'
   rows stand on the same lines. (Measured before: rows of 38 and 34px, the
   clients starting 24px below the ages, a bar 40px shorter in the row with the
   widest amount.) */
.openfilings { --of-row: var(--row-h-default); --of-label: calc(var(--colw-key) + var(--space-6));
  --of-n: var(--space-7); --of-age: calc(var(--space-7) + var(--space-2)); --of-amt: var(--colw-amt);
  display: grid; grid-template-columns: 1.6fr 1fr; gap: var(--gap-section); }
@media (max-width: 900px) { .openfilings { grid-template-columns: 1fr; gap: var(--gap); } }
.openfilings h3 { margin: 0 0 var(--space-3); font-size: var(--fs-2xs); line-height: var(--lh-dense);
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-muted); }

/* A list of rows owns its columns (.rowgrid): the list is the grid and declares
   the tracks once, each row is a subgrid of it, so the nth cell of every row
   stands on one left edge and one right edge whatever the row holds. A track
   that takes the slack is minmax(0, 1fr); a figure track is max-content, so
   the widest figure of the list sets it for every row. */
.rowgrid { display: grid; column-gap: var(--space-4); align-content: start; }
.rowgrid > :is(.agerow, .clientrow, .hbar-row, li),
.rowgrid > li > :is(a, .duerow) {
  display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; }
.rowgrid > :is(h3, p) { grid-column: 1 / -1; }
/* the hero's two lists name their tracks once, from the card's tokens, so a
   figure column ends on one edge in every row of both lists; their gap is the
   card's, not a row's */
.openfilings-age, .openfilings-clients { column-gap: var(--space-3); }
.openfilings-age { grid-template-columns: var(--of-label) minmax(0, 1fr) var(--of-n) var(--of-amt); }
.openfilings-clients { grid-template-columns: minmax(0, 1fr) var(--of-n) var(--of-amt) var(--of-age); }
/* one height for every row of both lists; the cells of a row share a
   baseline, and the line they make is centred in the row */
.agerow, .clientrow { align-items: baseline; align-content: center;
  height: var(--of-row); text-decoration: none; color: inherit;
  border-bottom: 1px solid var(--rule); }
.agerow:last-of-type, .clientrow:last-of-type { border-bottom: 0; }
.agerow:hover, .clientrow:hover { color: var(--accent); }
.agerow-label, .clientrow-code { font-size: var(--fs-xs); white-space: nowrap; }
.agerow-label { color: var(--text-secondary); }
.clientrow-code { font-weight: var(--fw-semibold); }
.agerow-bar { align-self: center; background: var(--surface-sunken); height: var(--space-3); border-radius: 2px; overflow: hidden; }
.agerow-bar > span { display: block; height: 100%; background: var(--series-1); }
.agerow.is-risk .agerow-bar > span { background: var(--warn); }
/* the figures: one face, tabular, right-aligned on their track; a count and an
   age are small and quiet, an amount is the figure of the row */
.agerow-n, .agerow-amt, .clientrow-n, .clientrow-amt, .clientrow-age { font-family: var(--font-figure);
  font-variant-numeric: tabular-nums lining-nums; text-align: right; white-space: nowrap; }
.agerow-n, .clientrow-n, .clientrow-age { font-size: var(--fs-xs); color: var(--text-secondary); }
.clientrow-age { color: var(--text-muted); }
.agerow-amt, .clientrow-amt { font-size: var(--fs-sm); font-weight: var(--fw-medium); }
.openfilings-risk { margin: var(--gap-tight) 0 0; font-size: var(--fs-xs); }
.openfilings-risk a { color: var(--warn); text-decoration: none; }
.openfilings-risk a:hover { text-decoration: underline; }

/* in control: the paper trail (INTAKE-COMPLETENESS §3), a compact card after
   the day's work: one figure with its share bar, the way into the incomplete
   files, what is missing most, and where "complete" is still undefined. */
.dashcard.incontrol .dashcard-figure { font-size: var(--fs-2xl); margin-bottom: var(--space-3); }
.ic-bar { background: var(--surface-sunken); height: 8px; border-radius: 2px; overflow: hidden;
  max-width: 320px; }
.ic-bar > span { display: block; height: 100%; background: var(--accent); }
.ic-sub { margin: var(--space-3) 0 var(--gap-tight); font-size: var(--fs-xs); }
.ic-sub a { text-decoration: none; color: inherit; }
.ic-sub a:hover { color: var(--accent); text-decoration: underline; }
.ic-sub b { font-family: var(--font-figure); font-variant-numeric: tabular-nums; }
.dashcard.incontrol .dashcard-foot .badge { margin-right: var(--space-2); }
/* Position spans the row under the work as a low strip of four figures */
.dashcard.span2 .position { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .dashcard.span2 .position { grid-template-columns: repeat(2, 1fr); } }

/* due next */
.duelist { list-style: none; margin: 0; padding: 0;
  grid-template-columns: 82px minmax(0, 3fr) minmax(0, 2fr) max-content; }
/* the history lists of a cheque and of a case have three cells a row: the
   words take the slack and the actor ends on the edge, in full */
.duelist.duelist-log { grid-template-columns: 82px minmax(0, 1fr) max-content; }
.duelist li { border-bottom: 1px solid var(--rule); }
.duelist li:last-child { border-bottom: 0; }
/* a history entry (.duerow) is a deadline row that is not a link */
.duelist :is(a, .duerow) { align-items: baseline; padding: var(--space-3) 0;
  text-decoration: none; color: inherit; }
.duelist a:hover { color: var(--accent); }
.due-when { font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs); color: var(--text-secondary); }
.due-what { font-size: var(--fs-xs); min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* one word, not a sentence: which kind of deadline this is. A task's note says
   who has it and on which case, so it can be long: it gives way with an
   ellipsis inside its track, and the track is the same on every row. */
.due-note { color: var(--text-muted); font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-label); white-space: nowrap;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.due-in { font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xs); color: var(--text-muted); white-space: nowrap;
  min-width: 52px; text-align: right; }
.duelist li.is-overdue .due-when, .duelist li.is-overdue .due-in { color: var(--warn); font-weight: var(--fw-medium); }

/* cheques to process — five counts, one glyphless strip */
.stagestrip { display: flex; gap: 1px; background: var(--rule); border: 1px solid var(--rule);
  border-radius: var(--radius-md); overflow: hidden; }
.stage { flex: 1; background: var(--surface); padding: var(--space-4) var(--space-3);
  text-align: center; text-decoration: none; color: var(--text-muted); min-width: 0; }
.stage:hover { background: var(--surface-raised); color: var(--accent); }
.stage-n { display: block; font-family: var(--font-figure); font-size: var(--fs-lg);
  font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; color: var(--text-muted); }
.stage.has-work .stage-n { color: var(--text); }
.stage-l { display: block; font-size: var(--fs-2xs); letter-spacing: var(--tracking-label); }

/* a table that is a supporting detail, not the point of the card */
.minitbl { margin-top: var(--gap-tight); font-size: var(--fs-xs); }
.minitbl td, .minitbl th { padding: var(--space-3) var(--space-3) var(--space-3) 0; }
/* the last cell ends on the card's inset, like the rows above it */
.minitbl tr > :last-child { padding-right: 0; }
.minitbl tr:last-child td { border-bottom: 0; }

.position { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-4) var(--space-6);
  margin: 0; }
.position > div { display: flex; flex-direction: column; gap: var(--space-1); }
.position dt { font-size: var(--fs-2xs); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--text-muted); }
.position dd { margin: 0; font-family: var(--font-figure); font-size: var(--fs-md);
  font-weight: var(--fw-medium); font-variant-numeric: tabular-nums lining-nums; }

/* Everything that used to fight for the first screen. Present and complete —
   one click down instead of gone. */
.dashmore { border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface); margin-bottom: var(--gap); overflow: hidden; }
.dashmore > summary { display: flex; align-items: baseline; gap: var(--space-4);
  padding: var(--space-4) var(--space-6); cursor: pointer; color: var(--text);
  font-weight: var(--fw-medium); list-style: none; }
.dashmore > summary::-webkit-details-marker { display: none; }
.dashmore > summary::before { content: "›"; color: var(--text-muted); font-size: var(--fs-2xs);
  transition: transform var(--dur-fast) var(--ease-standard); }
.dashmore[open] > summary::before { transform: rotate(90deg); }
.dashmore > summary .muted { font-size: var(--fs-xs); }
.dashmore > summary:hover { background: var(--surface-raised); }
.dashmore > .panel { border: 0; border-top: 1px solid var(--border);
  border-radius: 0; margin: 0; }
/* the by-client tables have a two-row head: the heads that span both rows
   (the year and the carry-forward) stand on the bottom of the band */
table.aggmatrix th[rowspan] { vertical-align: bottom; }
/* eighteen columns of figures: a head that is longer than its column ("CHEQUE
   AMOUNT" is 99px over the 96 of money, "TAX YEAR" 55px over the 48 of a year)
   wraps over two lines, so every column keeps the width of its type */
table.grid.aggmatrix > * > tr > th { white-space: normal; }

.dash-sec { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
  margin: var(--space-7) 0 var(--space-4); }
.dash-sec:first-of-type { margin-top: 0; }
.dash-sec h2 { margin: 0; font-family: var(--font-serif); font-weight: var(--fw-semibold);
  font-size: var(--fs-lg); letter-spacing: 0; color: var(--text-heading); }
.dash-sec-meta { color: var(--text-muted); font-size: var(--fs-xs); }
.dash-sec-meta a { text-decoration: none; }
.dash-sec-meta a:hover { text-decoration: underline; }

/* Contiguous hairline panels (P2-4): one bordered surface per zone, its
   sub-sections divided by a single hairline — no gaps, no field of floating
   card islands. Figure bars sit flush at the panel top; charts become a
   hairline-gap grid. Calm and layered. */
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  margin-bottom: var(--gap-section); overflow: hidden; }
.panel.is-primary { border-top: 2px solid var(--accent); }   /* one calm accent marks the money zone */
.panel > .tiles, .panel > .tiles--hero { border: 0; margin: 0; }
.panel > * + * { border-top: 1px solid var(--border); }       /* the only divider between sections */
.panel-sec { padding: var(--space-5) var(--space-6); }
.panel-sec > h3:first-child { margin-top: 0; }
.panel-sec > .page-head { margin-bottom: var(--space-4); }
/* charts as a contiguous 2-up grid: the 1px gap IS the hairline (panel bg shows through) */
.panel-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: var(--border); }
.panel-grid > * { background: var(--surface); padding: var(--space-5) var(--space-6); min-width: 0; }
.panel-grid > * > h3:first-child { margin-top: 0; }
.panel-grid > .wide { grid-column: 1 / -1; }
@media (max-width: 900px) { .panel-grid { grid-template-columns: 1fr; } }

/* ================= charts (flat, brand-derived series) ================= */

.chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: var(--space-5); margin-bottom: var(--space-5); }
.chart-grid .card { margin-bottom: 0; }

.legend { display: flex; gap: var(--space-5); margin-bottom: var(--space-4); font-size: var(--fs-xs); color: var(--text-secondary); }
.legend .key { display: inline-flex; align-items: center; gap: var(--space-3); }
.swatch { width: 11px; height: 11px; border-radius: var(--radius-xs); display: inline-block; }
.swatch.s1 { background: var(--series-1); }
.swatch.s2 { background: var(--series-2); }   /* second series = ink-slate */

/* A chart is a list of rows that owns its columns (.hbars): the label, the
   track and the value. The label column sizes to the widest label (so a label
   can never print through the bar beside it), the value column to the widest
   value, and the track takes the rest, so every bar of a chart starts on one
   edge and a full bar is a full track, with no hand-set scale. */
.hbars { grid-template-columns: minmax(52px, max-content) minmax(0, 1fr) max-content;
  row-gap: var(--gap-tight); }
.hbars > .hbar-row { align-items: center; }
.hbar-row .ylab { font-size: var(--fs-xs); color: var(--text-secondary);
  text-align: right; white-space: nowrap; }
/* two series in one row: both bars in the track, both values in the value column */
.hbar-pair, .hbar-vals { display: grid; gap: var(--space-1); }
.hbar-vals .bar-val { line-height: 12px; }

.bar {
  height: 12px;
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  min-width: 2px;
  transition: opacity 0.1s;
}
.bar.s1 { background: var(--series-1); }
.bar.s2 { background: var(--series-2); }
.bar.risk { background: var(--danger); }
.bar:hover { opacity: 0.82; }
.bar-val { font-size: var(--fs-2xs); color: var(--text-secondary); white-space: nowrap; font-variant-numeric: tabular-nums; }
.bar-val a { text-decoration: none; }
.bar-val a:hover { text-decoration: underline; }

.meter {
  /* The track is the chart's own column, the same width on every row, so the
     fill length is the recovery rate and nothing else (a long value gives the
     value column its width, never the track a different one). */
  width: 100%;
  height: 12px;
  background: var(--surface-sunken);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.meter-fill { height: 100%; background: var(--series-1); border-radius: var(--radius-pill); }
.meter-fill.over { background: var(--accent); }

#tooltip {
  position: fixed;
  display: none;
  background: var(--ink-900);
  color: var(--text-on-dark);
  font-size: var(--fs-xs);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  pointer-events: none;
  z-index: var(--z-tooltip);
  max-width: 300px;
  box-shadow: var(--elev-1);
}

/* ================= tables (ledger grid) ================= */

/* A table that scrolls sideways must SAY so. The two inner gradients are
   painted with `background-attachment: local` (they travel with the content, so
   they vanish at the ends); the two outer shadows are `scroll` (pinned to the
   box), so a shadow shows at an edge exactly while content continues past it.
   No JS, no layout cost — and a hidden column is never silent again
   (DESIGN-CRITIQUE-CASE-CENTRIC P1-1). */
.tbl-wrap {
  max-height: var(--tbl-box-max); scroll-padding-top: var(--thead-clear);
  overflow: auto; border-radius: var(--radius-lg);
  background:
    linear-gradient(to right, var(--surface) 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to right, rgba(255, 255, 255, 0), var(--surface) 70%) 100% 0,
    radial-gradient(farthest-side at 0 50%, rgba(23, 33, 29, 0.14), rgba(23, 33, 29, 0)),
    radial-gradient(farthest-side at 100% 50%, rgba(23, 33, 29, 0.14), rgba(23, 33, 29, 0)) 100% 0;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }

th, td {
  text-align: left;
  padding: var(--cell-pad-y) var(--cell-pad-x);
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
  line-height: 1.35;
}

/* The cells of a row stand on one text baseline, whatever their size or what
   they hold: a 12px status, a 13px name and a 28px field in one row put their
   words on one line (a row of Settings measured 16.5 to 25.0px apart when each
   cell hung from the top). A column head hangs from the top of its band. */
td, tbody th { vertical-align: baseline; }

th {
  color: var(--thead-fg);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-3xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  background: var(--thead-bg);
}

/* ================= the frozen head ================= */
/* A column head stays in view while its rows pass under it. A table in a
   scroller scrolls in that box and its head stands at the top of the box; a
   table with no scroller scrolls with the page and its head stands at the top
   of the window (--sticky-top is what stands frozen above it: nothing today).
   The selector is the head's own meaning, a column head (scope=col), so the row
   head of a fact list is never caught. Screen only: on paper the head repeats
   on every page by being a <thead>, and nothing is positioned. The head runs
   under the details panel (40) and under every menu (60). Two exclusions: a
   table inside a cell has no head band, and the trend table is a chart drawn in
   a table, left exactly as it is (top: auto keeps its first column parked to
   the left through the pivot rule, which is more specific).
   No border and no shadow on a head: in a collapsed table a sticky cell's
   border does not travel, so the band is what sets the head apart. */
@media screen {
  th[scope="col"] { position: sticky; top: var(--sticky-top); z-index: var(--z-sticky); }
  :is(.tbl-wrap, .xscroll, .tablewrap, .candwrap) th[scope="col"] { top: 0; }
  table.grid:not(:has(> * > tr > .ct-text)) > * > tr:has(> th[scope="col"])::after {
    position: sticky; top: var(--sticky-top); z-index: var(--z-sticky);
  }
  :is(.tbl-wrap, .xscroll, .tablewrap, .candwrap) table.grid:not(:has(> * > tr > .ct-text)) > * > tr:has(> th[scope="col"])::after { top: 0; }
  td table th[scope="col"], table.trendtbl th[scope="col"] { position: static; top: auto; }
  /* a row reached by its #id lands clear below the head */
  tr:target { scroll-margin-top: calc(var(--sticky-top) + var(--thead-clear) + var(--space-2)); }
  /* the Overview pivot's corner cell stays over the heads that travel under it
     when the table scrolls sideways (it was 3, under their 20). The bar tables
     are charts and keep what they have. */
  table.grid.pivot[data-colset="overview"] thead th:first-child { z-index: calc(var(--z-sticky) + 1); }
}

/* ================= typed columns ================= */
/* A column has a TYPE, and the type fixes its width, its alignment, whether it
   wraps and how its figures are set, for the head and for every cell under it:

     ct-text   words that may wrap: the ONE column that takes the slack
     ct-label  the row label of a summary table     ct-tag   a status, a type
     ct-note   a note, one line                     ct-key   a year, a code
     ct-code   a cheque number, a reference         ct-date  dd-mm-yyyy
     ct-amt    money                                ct-num   a count, a percent
     ct-act    the row's action                     ct-chk   the selection tick

   `table.grid` with one type on every th and td. Each width is a token
   (--colw-*, tokens.css) for the content, plus the cell padding. The layout
   stays automatic, so a value that outgrows its token widens its column
   instead of running into the next one. Two tables with the same kinds of
   column in the same order share every edge, because nothing in a cell sizes
   its column any more: on the Overview five stacked tables had five sets of
   edges (the same column up to 70.8px apart at 1920) and the pivot's year
   columns moved up to 187.6px when the measure changed. Tables that must
   agree carry the same data-colset, and scripts/align_probe.py holds them to
   it (colDrift).
   The width selector is an explicit list on purpose: [class*="ct-"] also
   matches the existing class reject-inline. */
table.grid { --ct-pad: calc(2 * var(--cell-pad-x)); }
table.grid > * > tr > :is(.ct-label, .ct-tag, .ct-note, .ct-key, .ct-code, .ct-date, .ct-amt, .ct-num, .ct-act, .ct-chk) {
  width: var(--ct-w); min-width: var(--ct-min, var(--ct-w));
}
table.grid > * > tr > .ct-label { --ct-w: calc(var(--colw-label) + var(--ct-pad)); }
table.grid > * > tr > .ct-tag   { --ct-w: calc(var(--colw-tag)   + var(--ct-pad)); }
table.grid > * > tr > .ct-note  { --ct-w: calc(var(--colw-note)  + var(--ct-pad)); }
table.grid > * > tr > .ct-key   { --ct-w: calc(var(--colw-key)   + var(--ct-pad)); }
table.grid > * > tr > .ct-code  { --ct-w: calc(var(--colw-code)  + var(--ct-pad)); }
table.grid > * > tr > .ct-date  { --ct-w: calc(var(--colw-date)  + var(--ct-pad)); }
table.grid > * > tr > .ct-amt   { --ct-w: calc(var(--colw-amt)   + var(--ct-pad)); }
table.grid > * > tr > .ct-num   { --ct-w: calc(var(--colw-num)   + var(--ct-pad)); }
table.grid > * > tr > .ct-act   { --ct-w: calc(var(--colw-act)   + var(--ct-pad)); text-align: right; }
table.grid > * > tr > .ct-chk   { --ct-w: var(--colw-chk); text-align: center; padding-right: 0; }
/* the column that takes the slack: it never gets narrower than this */
table.grid > * > tr > .ct-text  { min-width: var(--colw-text); }

/* a text column with nothing in it (the spacer that keeps the action of a
   group at the edge) asks for no room: it only takes the slack */
table.grid > * > tr > .ct-text:empty { min-width: 0; }

/* a head is one line, and so is every cell but the text column's */
table.grid > * > tr > th, table.grid > * > tr > td:not(.ct-text) { white-space: nowrap; }
table.grid > * > tr > :is(.ct-amt, .ct-num) { text-align: right; }
/* money reads as of-record. Low specificity (:where), so a total row still
   sets its own weight, as it does over td.amt. A quieter figure changes its
   colour, never its size: a "muted" running total was 12px beside 13px. */
:where(table.grid) td.ct-amt { font-family: var(--font-figure); font-weight: var(--fw-medium); }
:where(table.grid) td.muted { font-size: inherit; }
/* a label or a note that outgrows its width ends in an ellipsis */
table.grid > * > tr > :is(.ct-label, .ct-note) { max-width: var(--ct-w); overflow: hidden; text-overflow: ellipsis; }

/* A table with no text column would stretch its typed columns to fill the
   card. It gets a generated last cell that takes the slack instead: the typed
   columns keep their widths and pack left, next to their labels, and the head
   band, the hairlines, the total rule and the hover still run to the edge. A
   row that is one spanning cell gets none. */
table.grid:not(:has(> * > tr > .ct-text)) > * > tr::after {
  content: ""; display: table-cell; border-bottom: 1px solid var(--rule);
}
table.grid:not(:has(> * > tr > .ct-text)) > * > tr:has(> th)::after { background: var(--thead-bg); }
table.grid:not(:has(> * > tr > .ct-text)) > * > tr.total::after {
  border-top: var(--row-total-rule); background: var(--row-total-bg);
}
table.grid:not(:has(> * > tr > .ct-text)) > tbody > tr:hover::after { background: var(--row-hover); }
table.grid:not(:has(> * > tr > .ct-text)) > * > tr:has(> td[colspan])::after { content: none; }
/* Below the laptop width a set of typed columns can be wider than its card
   (the Overview's set is 920px; its card has 925 at 1280 and 669 at 1024).
   There a column may give width back, down to its content, as every column
   did before, instead of putting a quarter of the table under a scroll:
   measured at 1024, 247px of sideways scroll held rigid, 47px giving back
   (what it was). From 1280 up the widths are held. */
@media (max-width: 1279px) { table.grid { --ct-min: 0px; } }

/* A sortable column head is a link, and it looks the same on every list: the
   column that orders the table carries its direction in full ink; the others
   underline when pointed at and otherwise stay quiet. Only the sorted head
   carries a mark, so the heads cost the table no width they did not already
   have (eleven Cases columns fit 1280 with nothing to spare). A number is
   flush right, so its mark goes before the label. */
th.sortable .sort-link {
  display: inline-flex; align-items: center; gap: var(--space-1);
  color: inherit; text-decoration: none;
}
th.sortable .sort-link:hover { color: var(--text); }
th.sortable .sort-link:hover .sort-label { text-decoration: underline; text-underline-offset: 3px; }
/* a keyboard user must see which head they are on; the ring sits tight to the
   label rather than the whole cell */
th.sortable .sort-link:focus-visible {
  outline: none; color: var(--text);
  box-shadow: var(--focus-ring); border-radius: var(--radius-xs);
}
th.sortable .sort-link:focus-visible .sort-ind { display: inline-flex; }
th.sortable .sort-ind { display: none; flex: none; }
th.sortable .sort-ind svg { width: 11px; height: 11px; stroke-width: 2.5; }
th.sortable.is-sorted { color: var(--text); }
th.sortable.is-sorted .sort-ind { display: inline-flex; }
th.sortable .sort-ind.is-asc svg { transform: rotate(180deg); }
th.amt.sortable .sort-link { flex-direction: row-reverse; }
th:is(.ct-amt, .ct-num).sortable .sort-link { flex-direction: row-reverse; }
/* ledger density for the widest operational lists (the filings register):
   the base-unit gutter instead of the default, so eleven columns still sit
   within a 1280 viewport without a sideways scroll */
.tbl-dense th, .tbl-dense td { padding-left: var(--space-3); padding-right: var(--space-3); }
/* A dense table that is typed names its columns by the same tokens, and the
   padding they add is the dense padding, so a column is as wide as its type
   says and no wider (the default 12px a side would have made every column of
   twelve 8px too wide). */
table.grid.tbl-dense { --ct-pad: calc(2 * var(--space-3)); }
/* A dense REGISTER (Cases, Cheques, Documents), and a list with a long text
   (the Archive, the notifications), has to fit the laptop: its columns give
   width back, down to what they hold, as every column did before they were
   typed. The tick and the action stay rigid. */
table.grid.tbl-flex { --ct-min: 0px; }
table.grid.tbl-flex > * > tr > :is(.ct-chk, .ct-act) { --ct-min: var(--ct-w); }
/* ... and so does the text column, at the laptop width and below (it keeps
   144px above it), and a long head ("JURISDICTION (FORM)") wraps over two lines
   instead of holding its column open: the edit row of Cases is 959px of
   natural width, and the card has 925 at 1280 */
@media (max-width: 1280px) {
  /* the text column keeps no least width and takes whatever the others leave:
     as one auto column among autos it asked for nothing (a name line is
     width 0) and a table with room to spare gave all of it to the dates */
  table.grid.tbl-flex > * > tr > .ct-text { min-width: 0; width: 100%; }
  table.grid.tbl-flex > * > tr > th { white-space: normal; }
  /* a name that is words (a state and its form) may wrap here too */
  table.grid.tbl-flex > * > tr > .col-wrap { white-space: normal; }
  /* the action is as wide as its word here: it was 54px before it was typed,
     and the 72px of the type left the Dutch case with two counts in its File
     cell 10px past its card at 1024 */
  table.grid.tbl-flex > * > tr > .ct-act { width: 1%; --ct-min: 0px; }
  /* and the columns are as wide as what they hold, so the text column keeps
     the room (the type's width is a hint a table over its card cannot keep:
     the Archive's document had 21px of 669 left, and the notifications' message
     233px where it had 349 before the columns were typed) */
  table.grid.tbl-flex > * > tr > :is(.ct-label, .ct-tag, .ct-note, .ct-key, .ct-code, .ct-date, .ct-amt, .ct-num) { width: auto; }
}

/* Column priority on the Cases browser. Eleven columns do not fit a 1280
   viewport (measured: 128px over at 1280, 384px at 1024), and what fell off the
   right was the FILE verdict — the missing-documents link the case-centric
   redesign is built around. So the two lowest-load columns step aside first and
   their exception signals fold into Status (.foldin above). Nothing is lost;
   the widest money triad keeps its place. */
/* a free-text note is the last thing a row needs and the first thing that
   inflates it — one line, the rest in the tooltip (P2-9) */
/* ---- tasks on a case, and one person's list of them ----
   A task table is a work list, so it reads like the other work lists: the row
   is quiet, and only the date earns colour. A finished task stays visible but
   steps back, because "what did we already do" is part of the answer. */
.tasktable:not(.grid) th:first-child { width: 42%; }
.tasktable:not(.grid) td:last-child { white-space: nowrap; width: 1%; }
/* a person's name is one thing; breaking it over two lines makes a column of
   them unreadable at a glance */
.tasktable:not(.grid) td:nth-child(2) { white-space: nowrap; width: 1%; }
.tasktable tr.tk-is-done td { color: var(--text-secondary); }
.tasktable tr.tk-is-done td b { font-weight: var(--fw-regular); }
/* the one row that is actually late gets a left edge, nothing more. The row's
   band is tkb-*, never tk-*: tk-done is the Done form inside the row, and a
   finished row that carried it was laid out as that form (inline-flex) */
.tasktable tr.tkb-overdue td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
.tk-act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
/* Finish in one click, with an optional note in the same submit (base.html
   task_done): the note field sits beside the Done button, never required. */
.tk-done { display: inline-flex; align-items: center; gap: var(--space-2); }
.tk-note { height: var(--control-h-sm); min-height: 0; width: 118px; flex: none;
  padding: 0 var(--space-3); border: 1px solid var(--field-border);
  border-radius: var(--field-radius); background: var(--field-bg); color: var(--text);
  font-size: var(--fs-xs); transition: width var(--dur-base) var(--ease-standard); }
.tk-note:focus { width: 200px; }
.tk-start { margin-left: calc(-1 * var(--space-1)); }
/* the deadline/note controls must not squeeze the title to one letter per line
   in the narrow filing-detail column */
.tasktable:not(.grid) td:first-child { min-width: 150px; }
/* The case's tasks are typed columns on the common cell padding (data-colset
   casetasks), like every other table: the person, the date and the status are
   as wide as the words they hold and never break, the title takes the rest,
   and the actions stand flush right. On the case screen the table shares a
   narrow two-column layout, so the note stacks UNDER the Done button (both
   visible, no horizontal scroll); on the full-width My tasks it stays inline
   beside Done. */
table.grid[data-colset="casetasks"] { --colw-tag: 5.5rem; --colw-date: 5rem; --colw-act: 6.875rem; }
/* five columns do not fit the 589px card at 1280 on the common padding (the
   title kept 99px), so the table is dense, and its first column keeps the
   inset of every table */
table.grid[data-colset="casetasks"] > * > tr > :first-child { padding-left: var(--cell-pad-x); }
#tasks .tk-act { justify-content: flex-end; }
#tasks .tk-done { flex-direction: column; align-items: stretch; gap: var(--space-1); }
#tasks .tk-note, #tasks .tk-note:focus { width: 110px; }
.tk-form { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  margin-top: var(--space-3); }
.tk-form select, .tk-form input { max-width: 190px; }
/* the task's own words take what Save leaves of the line */
.tk-form input[name="title"] { flex: 1 1 140px; min-width: 0; max-width: none; }
/* one participant, with the two things you can do to them on one line */
.tk-watchrow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  margin-top: var(--space-2); }
.tk-watchrow > span { min-width: 90px; }

/* an audited bulk action is a button with a verb, not an inline ✓ (P2-8) */
.btn.btn-chip { padding: 2px var(--space-3); font-size: var(--fs-xs); }

/* The file name of a document is the one elastic column of the Documents
   register and of the Archive. The name ends in an ellipsis (its full name is
   in the title) and
   what is said about it (a newer version, Restricted) is never the part that
   is cut. The line asks for no width (0, with a minimum of the whole cell), so
   a long name can never widen the column: it takes what the other columns
   leave. */
.fname-line { display: flex; align-items: baseline; gap: var(--space-2); width: 0; min-width: 100%; }
.fname-line > a { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fname-line > :not(a) { flex: none; white-space: nowrap; }
/* what is said beside the name stays whole: a version link is a chip, not the
   part that is cut; and a name that is not a link (a fund in the booking
   lines) takes the name's rule */
.fname-line > a.rowlink { flex: none; }
.fname-line > .fname { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The note is a ct-note (one line, then an ellipsis: the type does that), and it
   is the first column of Documents to go. With the widths the columns are
   honest about (a file name keeps 144px, a case 240, a note 240) the table
   needs about 1170px, and the card offers 925 at 1280 and 1086 at 1441. */
@media (max-width: 1440px) { .doc-notes { display: none; } }

.foldin { display: none; }
.foldin.is-shown { display: inline; }   /* its own column is left out: the mark rides in Status at every width */
/* Twelve columns need 1019px with the widths they are honest about, and stop
   fitting at about 1374: Age steps aside from 1380 down. It is a figure and
   nothing else; the statute flag it used to carry stands in Status at every
   width. Since the review is a status control (its lamp opens the menu of its
   moves) the Review column asks 16px more, the twelve need 1032px and stop
   fitting at about 1388 (6px too wide at 1381), so Age steps aside from 1400. */
@media (max-width: 1400px) { .col-age { display: none; } }
@media (max-width: 1280px) {
  /* one notch tighter on the gutters so the remaining columns fit exactly —
     with no residual overflow the sticky Open column overlays nothing */
  .tbl-dense th, .tbl-dense td { padding-left: var(--space-2); padding-right: var(--space-2); }
  table.grid.tbl-dense { --ct-pad: calc(2 * var(--space-2)); }
  /* the Cases edit row is eleven columns now and fits the card at 1280 once
     its reason box gives way: 140px left it 38px too wide with the longer
     state names, and 96px left the Dutch row 37px too wide (its Review, File
     and Save are the wider words), so the box is the 64px of the token (its
     head wraps over two lines to the width of the box) and the Save beside
     it is 8px narrower on each side */
  .tbl-dense tr.editing input.w-note-sm { width: var(--field-w-note-xs); }
  .tbl-dense tr.editing button.cell-save { padding: 0 var(--space-3); }
}
/* Review and Cheques step aside from 1200 down: with the widths the columns
   are honest about, the register needs 839px for its eleven columns (measured
   with the widest state and form names of the sample data), and the card has
   846 at 1201. */
@media (max-width: 1200px) {
  .col-review, .col-cheques { display: none; }
  .foldin-review { display: inline; }
}
/* The way into the case stays reachable however far the table is scrolled: the
   last column parks against the right edge instead of sliding away. Its cell is
   opaque, because it rides over the cells scrolled under it; its head keeps the
   fill of every other head, so the head band runs to the table's edge (it
   stopped 66px short on Cases with a white head over the last column). The
   shadow that hung on it was permanent: in the band, and beside every row of a
   table that was not scrolling at all. */
.tbl-dense td.col-open, .tbl-dense th.col-open { position: sticky; right: 0; }
.tbl-dense td.col-open { background: var(--surface); }
.tbl-dense th.col-open { z-index: calc(var(--z-sticky) + 1); }

tbody tr:hover td, tr:hover td { background: var(--row-hover); }

/* ================= the states of a row, in the order they win =================
   A row is a record (_lists.html row_handle and row_open, lists.js): a click
   on it shows its details in the side panel, and Open at its end is its page.
   Pointed at: the hover wash above, and a pointer everywhere but over the tick.
   The handle is the row's own link, drawn as the ink of its cell; it keeps the
   house focus ring. */
tr:has(a.rowpeek) { cursor: pointer; }
tr:has(a.rowpeek) > :is(td.chk, td.ct-chk) { cursor: default; }
a.rowpeek, a.rowpeek:hover { color: inherit; text-decoration: none; }
/* Open stands flush right, and alone in its cell it IS the cell: the link is
   stretched over it, so a press anywhere in the cell goes to the page and a
   ctrl or middle click opens it in a new tab, as on any link. (The parked cell
   of .tbl-dense is sticky, which anchors the stretch just as well.) The one verb
   of a register without a page or a panel (Map on Funds, row_verb) is stretched
   the same way, so it is a target of at least 24 by 24. */
/* ONE action column, on every list: the cell that holds the row's action stands
   flush right and as narrow as its action, whatever the table (typed or not,
   dense or not, a link or a form's button). The ink of the action then ends at
   the table's edge less the cell padding, on Cases and Cheques as on a report
   table, where it stopped 69 to 112px short, and on Notifications, 156px. A
   typed table names the width of ct-act itself, which wins over this. */
th.ct-act, td.ct-act, th.col-open, td.col-open, th.rowacts, td.rowacts { text-align: right; white-space: nowrap; width: 1%; }
td.ct-act form.inline, td.col-open form.inline { justify-content: flex-end; }
:is(td.col-open, td.ct-act) { position: relative; }
:is(td.col-open, td.ct-act) > :is(a.rowopen, a.rowverb):only-child::after { content: ""; position: absolute; inset: 0; }
/* a link of its own inside such a row keeps a target 24px high, so a near
   miss does not show the details instead (padding above and below an inline
   link widens what it catches and moves nothing) */
tr:has(a.rowpeek) a.file-verdict { padding: 4px 0; }
/* pressed: the button is down on the row itself, not on a control in it */
tr:has(a.rowpeek):active:not(:has(:is(a:not(.rowpeek), button, input, select, label, summary):active, :is(td.chk, td.ct-chk):active)) > td { background: var(--row-selected); }
/* ticked, also while the pointer is on it */
tbody tr.selected > td { background: var(--row-selected); }
/* in view: the keyboard is here (the j / k cursor, or Tab on the handle), or
   the open panel shows this row. One wash, and one bar on the first cell. */
tbody tr.key-cursor > td, tbody tr:has(a.rowpeek:focus-visible) > td, tbody tr.drawer-source > td { background: var(--accent-soft); }
tr.key-cursor > td:first-child, tr:has(a.rowpeek:focus-visible) > td:first-child, tr.drawer-source > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
/* The parked Open cell is opaque, because it rides over the cells scrolled
   under it. So it wears each state of its row itself. */
.tbl-dense tr:hover > td.col-open { background: linear-gradient(var(--row-hover), var(--row-hover)) var(--surface); }
.tbl-dense tr.selected > td.col-open { background: var(--row-selected); }
.tbl-dense tr.key-cursor > td.col-open, .tbl-dense tr:has(a.rowpeek:focus-visible) > td.col-open, .tbl-dense tr.drawer-source > td.col-open { background: var(--accent-soft); }

/* a row whose work has slipped past its date: amber, the app's "someone should
   act" tone, never the oxblood reserved for a reconciliation break
   (CLARITY-REVIEW P1-1). */
tr.is-late > td { background: var(--warn-soft); }
/* the "what happens next" sub-line under a case's status (Cases browser): a
   quiet second line, clipped so a long typed next-action never widens the column */
.nextstep-cell { margin-top: 2px; width: 0; min-width: 100%; max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* Status is the one text column of the register, so it takes what the others
   leave, and the line takes the room its cell has: it asks for none (width 0,
   a minimum of the whole cell), so the typed next step can never push the File
   verdict under the parked Open column ("3 missi", measured 29-09), and it is
   no longer held to 230, 190 and 118px in a cell that has room (two rows of
   seven were clipped for it). The full words stay in its tooltip. */
/* Its lamps may break between words when the column is at its narrowest: the
   sign-off lamp of four eyes ("Declared complete", "Compleet verklaard") is the
   widest thing the column holds, and as one unbreakable line it made the
   Dutch edit row 12px too wide at 1280 and the Dutch list 9px too wide at
   1024. With room to spare a lamp stays one line. Only the lamps that stand
   in the cell itself, and only in a keyboard register (the access matrix and
   the Archive keep theirs on one line, a document's Restricted tag stays whole). */
table.grid.tbl-flex[data-keynav] > * > tr > td.ct-text > .statusdot { white-space: normal; }
/* a "missing" click lands on a case section (Expected documents / Documents):
   give the anchor breathing room and flash a fading accent ring so you see
   exactly where you arrived (REDESIGN-CASE-CENTRIC P2). */
.filing-layout [id] { scroll-margin-top: 16px; }
.filing-layout .card:target { animation: case-land 1.8s ease-out; }
@keyframes case-land {
  0%   { box-shadow: 0 0 0 3px var(--accent-soft), inset 0 0 0 1px var(--accent); }
  100% { box-shadow: 0 0 0 3px transparent, inset 0 0 0 1px transparent; }
}

/* A case section folds. The page used to render eleven cards at once — the
   whole story of a refund, open, every visit. The sections the desk reads on
   a given visit stay open; the rest cost one line until you want them. The
   summary IS the card's heading row, so an open section looks exactly as it
   did and a folded one is a title and a count. */
.foldcard > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap;
  /* every folded section is one control tall: a row that held a button was
     32px and one that held only words 19.5px (53.5, 54.5 and 66 as cards), so
     the closed sections of a case stepped. The words stay on their line, which
     is centred in the row. */
  min-height: var(--control-h); align-content: center;
}
.foldcard > summary::-webkit-details-marker { display: none; }
.foldcard > summary::after {
  /* the one mark the product uses for "onward" — and one Inter actually has:
     the small solid triangle U+25B8 is missing from it, which pulled a whole
     fallback face down the wire for three chevrons */
  content: "\203A"; margin-left: var(--space-4); font-size: 13px; line-height: 1;
  color: var(--text-muted); flex: none; align-self: center;
  transition: transform var(--dur-base) var(--ease-standard);
}
/* the meta takes the slack, so whatever follows it sits against the right edge */
.fold-meta { margin-right: auto; }
.fold-act { display: inline-flex; align-items: baseline; gap: var(--space-3); flex: none; }
.foldcard[open] > summary::after { transform: rotate(90deg); }
.foldcard > summary > h3 { margin: 0; }
.foldcard[open] > summary { margin-bottom: var(--gap-tight); }
.foldcard > summary:hover > h3 { color: var(--accent); }
.foldcard > summary:focus-visible { box-shadow: var(--focus-ring); outline: none; }
/* what a folded section is worth knowing without opening it */
.fold-meta { font-size: var(--fs-sm); color: var(--text-secondary); font-weight: var(--fw-regular);
  text-transform: none; letter-spacing: 0; }
@media (prefers-reduced-motion: reduce) { .foldcard > summary::after { transition: none; } }
/* the small "back to Cases" affordance beside a landed section's heading */
.back-to-cases { font-size: var(--fs-xs); text-decoration: none; color: var(--text-secondary);
  white-space: nowrap; }
.back-to-cases:hover { color: var(--accent); text-decoration: underline; }

/* The case in two lines at the top (owner 11-10, item 6): the next step the
   product reads out, and the latest comment with its one-field box, so a case
   is still updated on the spot in a meeting (REDESIGN-CASE-CENTRIC P4). One
   grid declares the three tracks of both rows: the label, what it says, and
   the one place to act. The action track is one length per width band, so
   the two rows end on one edge whatever they hold, and the inset is the
   card's own, so the labels stand on the edge of the card headings below.
   Labels and bodies share their first baseline; the actions stand at the top
   of their row. The step and the comment stop at two lines (the full words
   are in their title) and the meta line never wraps: the name gives way. */
.case-quickbar { display: grid;
  grid-template-columns: var(--colw-label) minmax(0, 1fr) var(--strip-act-lg);
  column-gap: var(--space-4); row-gap: var(--space-3); align-items: first baseline;
  margin: 0 0 var(--gap); padding: var(--space-3) var(--space-6);
  background: var(--surface-raised); border: 1px solid var(--rule);
  border-radius: var(--radius-md); font-size: var(--fs-sm); }
@media (max-width: 1280px) {
  .case-quickbar { grid-template-columns: var(--colw-label) minmax(0, 1fr) var(--strip-act-md); } }
@media (max-width: 1140px) {
  .case-quickbar { grid-template-columns: var(--strip-label-sm) minmax(0, 1fr) var(--strip-act-sm); } }
.cqb-label { color: var(--text-secondary); font-size: var(--fs-sm); }
.cqb-body { min-width: 0; }
/* a row with nothing to act on: its body runs to the right edge */
.cqb-wide { grid-column: 2 / -1; }
.cqb-line { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-clamp: 2; overflow: hidden; }
.cqb-step { color: var(--text); font-weight: var(--fw-semibold); text-decoration: none; }
a.cqb-step:hover { text-decoration: underline; }
.cqb-meta { display: flex; align-items: baseline; gap: var(--space-3); min-width: 0;
  white-space: nowrap; font-size: var(--fs-xs); color: var(--text-secondary); }
.cqb-meta > * { flex: none; }
.cqb-meta > :is(.cqb-who, .cqb-hint) { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* under "No next action planned": the task that lacks its person or date, or
   the step the stage implies, quiet and linked to where it is carried out */
.cqb-hint { color: var(--text-secondary); text-decoration: none; }
a.cqb-hint:hover { text-decoration: underline; }
.cqb-date { color: var(--text); font-variant-numeric: tabular-nums lining-nums; }
.cqb-more, .cqb-all { text-decoration: none; white-space: nowrap; }
.cqb-more:hover, .cqb-all:hover { text-decoration: underline; }
.cqb-act { align-self: start; display: flex; justify-content: flex-end; gap: var(--space-3);
  min-width: 0; }
.cqb-last { display: flex; align-items: baseline; gap: var(--space-3); }
.cqb-quote { min-width: 0; display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; color: var(--text); }
.cqb-when { color: var(--text-secondary); }
/* an empty row is muted, at the size of the label beside it */
.cqb-none { color: var(--text-muted); }
.cqb-all { flex: none; }
/* at the narrowest width the comment's two lines need the room; the Comments
   card below is the way to the rest */
@media (max-width: 1140px) { .cqb-all { display: none; } }
.cqb-form input { flex: 1; min-width: 0; }
/* "+ Task" beside Done: the task menu (.tk-menu, 320px) with three fields,
   what has to happen on the first row, who is on point and by when on the
   second, Add task at the right of the third. It hangs from the button's
   right edge, over the strip, so it stays clear of the sidebar at 1024. */
.quicktask { display: grid; grid-template-columns: minmax(0, 1fr) var(--field-w-date);
  gap: var(--space-3) var(--space-2); }
.quicktask :is(input, select) { width: 100%; min-width: 0; }
.quicktask input[name="title"], .quicktask button { grid-column: 1 / -1; }
.quicktask button { justify-self: end; }
/* the same three fields in the Cases bulk bar ("Add task" on every ticked
   case): the popover is this grid, as wide as the menu on a case, and the
   bar's own one-line popover rules give way to it */
.bulk-na-pop.quicktask { display: grid; width: 320px; white-space: normal;
  gap: var(--space-3) var(--space-2); }
.bulk-na-pop.quicktask :is(input, select, input.date) { width: 100%; }

td.amt, th.amt, td.num, th.num { text-align: right; font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }
/* Figures are set in Plex Sans with tabular lining numerals (B3 / owner
   finding #3). Mono's wide typewriter texture is gone; the medium weight keeps
   the money column reading as of-record rather than as body text, and tnum
   keeps the digits in a straight column. */
td.amt { font-family: var(--font-figure); font-weight: var(--fw-medium); }

tr.total td { font-weight: var(--fw-semibold); border-top: var(--row-total-rule); background: var(--row-total-bg); }

tr.struck td { color: var(--text-muted); }
tr.struck td.strike-val { text-decoration: line-through; }

/* Column hierarchy (UX-AUDIT P1-9): the first column is the row's anchor —
   client, cheque no., fund, filing — so give it a touch more weight and
   full-strength ink. The eye then scans one column down the page in a single
   pass, which is how dense record grids are actually read. Deliberately subtle
   (medium, not bold) to stay within the quiet-premium restraint. Not applied to
   the selection checkbox cell or to nested tables, which aren't row labels. */
tbody td:first-child { font-weight: var(--fw-medium); color: var(--text); }
td.chk:first-child, td table td:first-child { font-weight: var(--fw-regular); color: inherit; }
/* With a tick in front (an editor on Cases and Cheques) the cell after it is
   the row's anchor, the client or the case: the weight the first cell has on
   every list without a tick. */
td:is(.chk, .ct-chk):first-child + td { font-weight: var(--fw-medium); color: var(--text); }

/* nested tables (allocation preview inside a cell) */
td table { font-size: var(--fs-xs); }
td table th { background: transparent; }

/* ================= badges (squared 4px tags) ================= */

.badge {
  display: inline-flex;                      /* so the shape marker aligns with the label */
  align-items: center;
  gap: var(--space-2);
  vertical-align: baseline;
  padding: var(--badge-pad-y) var(--badge-pad-x);
  border-radius: var(--badge-radius);       /* 4px, not pill */
  font-size: var(--badge-font-size);
  font-weight: var(--badge-font-weight);
  white-space: nowrap;
  background: var(--neutral-soft);
  color: var(--neutral);
  letter-spacing: 0.01em;
}
/* ---- status dots: the traffic light (owner choice 2026-10-04, variant 4 of
   the style page status-indicator-keuze.html). ONE form for every status: a
   small glossy lamp + the label as plain text — status text is never boxed or
   filled. green = settled · amber = a person must act · red = broken or
   missing · grey = in flight / deliberately parked · hollow ring = inert.
   The label (always present) is the non-colour cue; attention and error also
   tint their label, every other label keeps the row's ink. ---- */
.statusdot { display: inline-flex; align-items: baseline; gap: var(--space-2);
  font-size: var(--fs-xs); white-space: nowrap; vertical-align: baseline; }
/* the label stands on the row's baseline, so the lamp is the one that centres
   on it (a lamp as the first item would otherwise set the baseline itself) */
.statusdot > :is(.sdot, .sdot-ic) { align-self: center; }
.sdot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; flex: none;
  vertical-align: -1px;
  box-shadow: inset 0 -2px 3px rgba(16,30,25,.18), inset 0 1.5px 2px rgba(255,255,255,.55); }
.statusdot.is-ok .sdot        { background: radial-gradient(circle at 34% 30%, #3d9268 0%, var(--green-600) 55%, #1d6442 100%); }
.statusdot.is-attention .sdot,
.sdot.sdot-attention          { background: radial-gradient(circle at 34% 30%, #d09a2e 0%, var(--amber-500) 55%, #96690b 100%); }
.statusdot.is-error .sdot     { background: radial-gradient(circle at 34% 30%, #cf5560 0%, var(--red-600) 55%, #9e2a36 100%); }
.statusdot.is-wait .sdot,
.statusdot.is-carry .sdot     { background: radial-gradient(circle at 34% 30%, #b8b0a1 0%, #999180 55%, #7f786a 100%); }
.statusdot.is-neutral .sdot   { background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--border-strong); }
.statusdot.is-attention { color: var(--warn); font-weight: var(--fw-medium); }
.statusdot.is-error     { color: var(--danger); font-weight: var(--fw-medium); }
/* a deadline wears the clock instead of the dot — same form, same colours */
.statusdot .sdot-ic { display: inline-flex; flex: none; }
.statusdot .sdot-ic svg { width: 12px; height: 12px; }
.statusdot.deadline.is-neutral { color: var(--text-secondary); }

/* an unread notification row is bold — the row itself is the marker, so the
   list needs no "new" pill (perfection sweep §1) */
tr.is-unread td { font-weight: var(--fw-semibold); }

/* the shared status key — same badges a screen uses, as a decodable legend */
.status-legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4);
  padding: var(--space-3) var(--space-4); margin: 0 0 var(--space-5);
  background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--radius-lg);
  font-size: var(--fs-2xs); }
.status-legend-item { display: inline-flex; }

/* ---- the status key as an on-demand popover (app/status.py status_key) -----
   Same component on every screen that shows statuses: a quiet "?" that takes no
   room until asked, then explains each badge in plain words. Replaces per-page
   legends that were either always-on clutter or a mystery (owner finding #12). */
.statuskey { position: relative; display: inline-block; }
.statuskey > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center;
  gap: var(--space-3); color: var(--text-muted); font-size: var(--fs-xs); }
.statuskey > summary::-webkit-details-marker { display: none; }
.statuskey > summary:hover { color: var(--accent); }
.statuskey-mark { display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border: 1px solid var(--border-strong); border-radius: 50%;
  font-size: var(--fs-3xs); font-weight: var(--fw-semibold); line-height: 1; }
.statuskey[open] > summary .statuskey-mark { border-color: var(--accent); color: var(--accent); }
.statuskey-body { position: absolute; left: 0; top: calc(100% + 6px); z-index: var(--z-dropdown);
  width: 420px; max-width: 80vw; padding: var(--space-4) var(--space-5);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--elev-2); display: flex; flex-direction: column; gap: var(--space-3); }
.statuskey-row { display: grid; grid-template-columns: 132px 1fr; gap: var(--space-4); align-items: baseline; }
.statuskey-text { font-size: var(--fs-xs); color: var(--text-secondary); line-height: var(--lh-body); }
@media (max-width: 720px) { .statuskey-body { width: 300px; } .statuskey-row { grid-template-columns: 1fr; gap: var(--space-1); } }

/* ---- the Views menu (perfection sweep §3): the predefined quick views, the
   user's saved views and saving the current slice, behind one control on the
   filter row — where a twelve-chip wall used to sit. Same popover mechanics
   as the status key (base.html closes it on Escape / outside click). ---- */
/* (the bar's GET form gives its fields to the bar's row: .filterbar > form,
   written once after form.inline in the forms section) */
/* the line above a table: what it holds on the left (a count), and at its right
   end the Columns control; alone, the control stands at the right */
.listtools { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  margin-bottom: var(--space-3); }
.listtools > p { margin: 0; }
.listtools > .colsel { margin-left: auto; }
/* The head of a register (list_head, _lists.html): one card, its lines a step
   apart (the phase chips, the filter row, the filters in force, the count), so
   the table that follows in a card of its own starts the same distance from
   its card's top on every list. The count line is the last of them and takes no
   margin; at its right end the controls of how the list is shown (Views,
   Columns, a ZIP, the status key) stand in one group, which gives the filter
   row back to the filters. */
.listhead > * + * { margin-top: var(--space-4); }
.listhead > .listtools { margin-bottom: 0; flex-wrap: wrap; row-gap: var(--space-3); }
.listtools-end { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; }
.viewsmenu { position: relative; display: inline-block; }
.viewsmenu > summary { list-style: none; cursor: pointer; }
.viewsmenu > summary::-webkit-details-marker { display: none; }
.viewsmenu > summary .viewsmenu-caret { display: inline-flex; margin-left: var(--space-2); color: var(--text-muted); }
.viewsmenu > summary .viewsmenu-caret svg { width: 12px; height: 12px; }
.viewsmenu[open] > summary { border-color: var(--accent); color: var(--accent); }
.viewsmenu-body { position: absolute; left: 0; top: calc(100% + 6px); z-index: var(--z-dropdown);
  min-width: 250px; padding: var(--space-3); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--elev-2);
  display: flex; flex-direction: column; gap: 1px; }
.viewsmenu-item { display: block; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; font-size: var(--fs-xs); white-space: nowrap; }
.viewsmenu-item:hover { background: var(--neutral-soft); color: var(--accent); }
.viewsmenu-item.on { background: var(--accent-soft); color: var(--text); font-weight: var(--fw-semibold); }
.viewsmenu-head, .statusmenu-head { margin: var(--space-3) var(--space-3) var(--space-1); padding-top: var(--space-3);
  border-top: 1px solid var(--rule); font-size: var(--fs-3xs); letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted); }
.viewsmenu-saved { display: flex; align-items: center; gap: var(--space-2); }
.viewsmenu-saved .viewsmenu-item { flex: 1 1 auto; }
.viewsmenu-save { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 var(--space-3) var(--space-2); }
.viewsmenu-save input[name="name"] { flex: 1 1 150px; min-width: 130px;
  font-size: var(--fs-xs); padding: 0 var(--space-3); }
.viewsmenu-save-btns { display: inline-flex; gap: var(--space-2); }
/* a muted line in the menu: the rule for a name that is already in use, the hint on a
   list that has nothing to save yet */
.viewsmenu-note { flex: 1 1 100%; margin: 0; padding: 0 var(--space-3) var(--space-2); max-width: 260px;
  font-size: var(--fs-2xs); color: var(--text-muted); white-space: normal; }
.viewsmenu-save .viewsmenu-note { padding: 0; } /* the form already insets its rows */
.viewsmenu-note.inline { display: inline; flex: none; padding: 0 0 0 var(--space-2); max-width: none; } /* after a view's name */

/* ---- the task-status menu: the lamp + label IS the control (owner 04-10).
   Opens a small card with the other statuses; same popover manners as the
   views menu. The assign/participants "change" panel gets the same card so a
   table cell never stretches around its forms. ---- */
.statusmenu { position: relative; display: inline-block; }
.statusmenu > summary { list-style: none; cursor: pointer; display: inline-flex;
  align-items: center; gap: var(--space-2); border-radius: var(--radius-sm);
  min-height: var(--control-h-xs); }
.statusmenu > summary::-webkit-details-marker { display: none; }
.statusmenu > summary:hover .statusmenu-caret { color: var(--accent); }
.statusmenu > summary:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.statusmenu-caret { display: inline-flex; color: var(--text-muted); }
.statusmenu-caret svg { width: 11px; height: 11px; }
.statusmenu-body { position: absolute; left: 0; top: calc(100% + 4px); z-index: var(--z-dropdown);
  min-width: 170px; padding: var(--space-2); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--elev-2);
  display: flex; flex-direction: column; gap: 2px; }
/* a menu that stands in a scroller is placed by base.html's place(): fixed to
   the lamp, so the table's box neither clips it nor grows a scrollbar for it */
.statusmenu-body.is-fixed { position: fixed; left: var(--pop-x, 0); top: var(--pop-y, 0); }
.statusmenu-moves { display: flex; flex-direction: column; gap: 2px; margin: 0; }
.statusmenu-item { display: flex; width: 100%; text-align: left; border: 0; background: none;
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); cursor: pointer;
  font: inherit; color: var(--text); justify-content: flex-start; }
.statusmenu-item:hover { background: var(--neutral-soft); }
.statusmenu-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.statusmenu-item:active { background: var(--neutral-soft); box-shadow: inset 0 0 0 1px var(--border-strong); }
.statusmenu-body .statusmenu-moves input.statusmenu-note:not([type="hidden"]) {
  height: var(--control-h-sm); min-height: var(--control-h-sm); font-size: var(--fs-xs); }
/* the last moves, read from the audit chain: date, move and who on one line, the
   note on a second muted line; the dates share one column across the lines */
.statusmenu.has-hist .statusmenu-body { min-width: 17rem; width: max-content; max-width: 90vw; }
.statusmenu-hist { list-style: none; margin: 0; padding: 0 var(--space-3); font-size: var(--fs-xs);
  display: grid; grid-template-columns: auto auto auto;
  column-gap: var(--space-3); row-gap: var(--space-2); }
.statusmenu-hist li { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1;
  align-items: baseline; }
.statusmenu-hist .hist-date { color: var(--text-secondary); white-space: nowrap; }
.statusmenu-hist .hist-move { white-space: nowrap; }
.statusmenu-hist .hist-who { max-width: 9rem; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.statusmenu-hist .hist-note { grid-column: 1 / -1; width: 0; min-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.statusmenu-all { display: block; padding: var(--space-2) var(--space-3) 0; font-size: var(--fs-xs); }
/* the badges of a case head and the state of a compliance case stand in a div:
   a details element (the menu on a lamp) may not stand inside a paragraph, and
   the badges keep the paragraph's air above and below */
.filing-badges { margin: 1em 0; }
details.tkmenu { position: relative; }
.addexp { margin-top: var(--space-3); }
/* ---- Funds: one typed register, and a fund is mapped in its own row ----
   At rest the register is text, one row height. The one open row (?row=) holds
   a closed list of portfolios, one Save and a way out; the columns keep their
   widths, so the register and the portfolios under it keep their shared
   edges. Tokens only. */
/* the columns marked col-extra, and the portfolio's description, step aside
   below the laptop width, so the name keeps its room. An administrator repairing
   an identifier that cannot be read keeps the EIN column: it is where that is
   typed again. */
@media (max-width: 1279px) { table.grid:not([data-repair]) .col-extra, table.grid .col-desc { display: none; } }
/* the title block of a head that carries a state line takes what the actions leave and
   wraps its line, so the title and both buttons stay on one line */
.page-head > .head-main { flex: 1 1 0; min-width: 0; }
/* below the laptop width the portfolio's description ends in an ellipsis (its text is
   the title), so every portfolio row keeps one height */
@media (max-width: 1279px) {
  table.grid td.ct-text.col-clip { max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
/* the slice in force is the first line of the register, not a bar under a filter box */
#funds > .active-filters { margin-bottom: var(--space-4); }
/* the name of the uploaded file in the head of its preview stays as it was typed */
.dashcard-head h3 .filename { text-transform: none; letter-spacing: 0; }
table.grid tr.is-open > td { background: var(--surface-raised); }
table.grid tr.is-open > td.ct-note, table.grid tr.rowstrip > td.ct-note, table.grid tr.editing > td.ct-note { max-width: none; overflow: visible; }
.openrow { display: flex; align-items: center; gap: var(--space-3); margin: 0; }
/* the select takes the slack of the cell and never sizes it: its longest
   option would otherwise widen the column and move every edge */
.openrow select.cell { flex: 1 1 0; min-width: 0; width: 0; }
/* the way out of an open row: a 28px square, quiet until pointed at */
.rowcancel { display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--control-h-sm); height: var(--control-h-sm); border-radius: var(--radius-sm);
  color: var(--text-secondary); text-decoration: none; }
.rowcancel:hover { color: var(--accent); }
.rowcancel:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.rowcancel svg { width: var(--icon-size-sm); height: var(--icon-size-sm); }
/* the open row of a portfolio: its description is a field of the dense step
   (table input.cell) and takes the slack of its cell, which is the text column */
table.grid tr.is-open > td.ct-text > input.cell { width: 100%; }
/* a Save that fits the action column in the language with the longest word */
table.grid tr.is-open > td.ct-act button.cell-save { padding: 0 calc(var(--space-2) + var(--space-1)); }
/* the strip under an open row: the code and the description of a new portfolio */
table.grid tr.is-open:has(+ tr.rowstrip) > td { border-bottom-color: transparent; }
table.grid tr.rowstrip > td { background: var(--surface-raised); white-space: normal;
  padding-top: 0; padding-bottom: var(--space-3); }
/* THE INLINE FORM ROW. Fields, selects and a button on one line, wrapping only
   when the card is too narrow: the status check, the withholding evidence, the
   hold, an expected document, an adviser's access. Every control in it is one
   height (32, or 28 with .sm, the step of a field inside a table row), every
   field a width token (a w-* class, .date, .amt), and at most one field takes
   the slack (.formrow-grow). A bare field would be the full width of its card
   and push the button to a line of its own. */
.formrow { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; margin: 0; }
.formrow label { display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-xs); color: var(--text-secondary); white-space: nowrap; }
.formrow > :where(input, select) { width: auto; flex: 0 1 auto; min-width: 0; max-width: 100%; }
.formrow > input[type="file"] { width: var(--field-w-file); }
.formrow > .formrow-grow { flex: 1 1 var(--field-w-code-sm); min-width: 0; }
.formrow > .formrow-grow > input { flex: 1 1 0; min-width: 0; width: 0; }
/* a picker that takes the slack starts at a state's width and stops at a full name's */
.formrow > select.formrow-grow { flex: 1 1 var(--field-w-state); max-width: var(--field-w-name-lg); }
/* one line at every width: the description gives room before anything wraps */
.formrow.sm { flex-wrap: nowrap; }
.formrow.sm input.cell, .formrow.sm > :where(input, select, button) { height: var(--control-h-sm);
  min-height: var(--control-h-sm); font-size: var(--fs-xs); }
/* in a narrow rail: the field the width of the rail, the button under it as wide as its word */
.formrow.stack { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: var(--space-2); }
.formrow.stack > :where(input, select) { width: 100%; max-width: var(--field-w-name-lg); }
.formrow.stack > button { align-self: flex-start; }
/* the row a save has just landed on: a wash that fades */
@keyframes row-land { from { background-color: var(--accent-soft); } to { background-color: transparent; } }
table.grid tr.is-landed > td { animation: row-land 1.8s ease-out; }
@media (prefers-reduced-motion: reduce) { table.grid tr.is-landed > td { animation: none; } }
/* the per-state tolerance: two short figures and Set */
.tolform { display: flex; gap: var(--space-2); align-items: center; }
.tolform input { width: 7ch; }
.w8add { margin: 0 0 var(--space-4); display: block; }

.tk-menu { position: absolute; right: 0; top: calc(100% + 4px); z-index: var(--z-dropdown);
  width: 320px; padding: var(--space-4); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--elev-2);
  display: flex; flex-direction: column; gap: var(--space-3); }
.tk-menu .tk-form { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }

/* The crop evidence on a scan review enlarges on click (owner 04-10): the
   shared preview opens on the Scan view at this field, Escape closes it. */
.cropzoom { border: 0; background: none; padding: 0; display: block; cursor: zoom-in;
  width: 100%; height: auto; }
/* The evidence is a drawing, not the button's icon: `button svg` would size it
   at 16px, which is how the whole fragment once shrank to a dash. */
.cropzoom .cropimg { width: 100%; max-width: 340px; height: auto; }
.cropzoom:hover svg, .cropzoom:hover img { outline: 2px solid var(--green-100); }


/* A popover that would leave the viewport opens the other way (base.html's
   place()): anchored to its icon's right edge, or — when even that would not
   fit — nudged left by exactly the overshoot. Never a horizontal scrollbar. */
.statuskey-body, .viewsmenu-body, .statusmenu-body { transform: translateX(var(--pop-shift, 0px)); }
.statuskey-body.is-flipped, .viewsmenu-body.is-flipped, .statusmenu-body.is-flipped { left: auto; right: 0; }

/* Status hues now live on the dot (.statusdot above); .badge remains for
   category TAGS only (a role, a document type, "retired", "example") — one
   quiet neutral chip, the label does the telling. */
.badge[data-tone="neutral"] { background: var(--neutral-soft); color: var(--neutral); }

/* A document's type is a category, not a status, so it takes no status hue —
   a coloured "Filed return" read as an error (§3.2). One quiet neutral chip
   for every type; the label does the telling. */
.badge.doc-K1_FEDERAL, .badge.doc-K1_STATE,
.badge.doc-FILED_RETURN, .badge.doc-REFUND_CHEQUE,
.badge.doc-CORRESPONDENCE, .badge.doc-OTHER { background: var(--neutral-soft); color: var(--neutral); }

/* ================= forms & buttons ================= */

form.inline { display: inline; }
/* The GET form inside a filter bar gives its fields to the bar's flex row, so they
   share it with the popovers that hold forms of their own (and cannot nest inside
   it). It stands AFTER form.inline on purpose: the specificity is the same, and a
   form that carried `inline` as well used to turn the bar into a line of text, with
   a 3.8px space between its fields where the gap is 8. No form carries both any
   more (tests/test_alignment_system.py). `form.filterbar { display: flex }` is not
   written: at (0,1,1) it would beat the Matching desk's `.matchfilters` grid. */
.filterbar > form { display: contents; }

.form-grid {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: var(--gap) var(--space-5);
  /* the top of the row, not its middle: a field that is taller than one line
     (a note, a hint under a figure) does not pull its label down with it */
  align-items: start;
  max-width: 640px;
}

.form-grid label { font-size: var(--fs-xs); color: var(--text-secondary); }
/* a label always stands on the first control line: as tall as a control and
   centred in that height, whatever the field beside it grows to */
.form-grid > label { min-height: var(--control-h); display: flex; flex-wrap: wrap; align-items: center;
  column-gap: var(--space-2); }
.form-grid > label:has(> input) { flex-wrap: nowrap; }
/* a label that is hidden (the new account's client and reason, until its role
   asks for them) stays gone: the display above beat the hidden attribute and
   left two empty rows, 32px each, above Team */
.form-grid > label[hidden] { display: none; }
/* a button is as wide as its word, never the width of the field column */
.form-grid > button { justify-self: start; }
/* a field fills its column and never pushes past it */
.form-grid > :is(input, select, textarea) { min-width: 0; max-width: 100%; }
.form-grid .msel, .form-grid .msel-btn { width: 100%; }
.form-grid .msel-btn { max-width: none; }
/* a checkbox with its meaning beside it, in the field column */
.form-grid label.check { display: flex; flex-wrap: nowrap; align-items: center; gap: var(--space-3); color: var(--text); }
.form-grid label.check input { width: auto; margin: 0; }

/* the token widths in an ordinary two-column form */
.form-grid > input.amt, form input.amt:not(.cell) { width: var(--field-w-amount-form); }
.form-grid > input.code, form input.code:not(.cell) { width: var(--field-w-code); }
.form-grid input.amt, .form-grid input.code { justify-self: start; }

/* ================= boxes in a row ================= */
/* Sibling boxes stand in tracks their CONTAINER declares: equal columns, as
   many as fit, and every box in a row as tall as the tallest. The case's
   Actions panel was a wrapping flex row, where each wrapped row sized itself:
   measured at 1920, four boxes of 263.5px over an orphan of 340px, and
   heights of 190 and 124 side by side. No box sets a width of its own here. */
.boxgrid {
  display: grid;
  gap: var(--gap);
  align-items: stretch;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--box-min)), 1fr));
}
/* One box: label, field, label, field, and its action on a line of its own.
   A column first, so it holds where a row cannot be shared. */
.box {
  display: flex; flex-direction: column; gap: var(--space-2);
  min-width: 0; margin: 0;
  padding: var(--space-4) var(--panel-pad-x);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.box > label { font-size: var(--fs-xs); color: var(--text-secondary); }
/* a label that follows a field starts the next pair */
.box > :is(input:not([type="hidden"]), select, textarea) + label { margin-top: var(--space-3); }
/* the action keeps its own line, under everything else; a button in it is as
   wide as its label, never as wide as the box */
.box > .box-act {
  display: flex; align-items: center; gap: var(--space-3);
  margin-top: auto; padding-top: var(--space-3);
}
/* In a grid the rows are shared as well: every box takes its five lines from
   the row it stands in, so the labels, the fields and the buttons of
   neighbouring boxes sit on the same lines, whatever each box holds. A label
   rests on its field (a longer lead in one box moves the row, not the field),
   and a box with one field still has its button on the row's button line. */
@supports (grid-template-rows: subgrid) {
  /* The space between two rows of boxes is a gutter under each box, not a row
     gap: a row gap also falls between the shared lines INSIDE a box, so a box
     with one field, alone in its row, stood 156px tall with 44px of air above
     its button. As a gutter: 124px, and the 12px every other box has. */
  .boxgrid { row-gap: 0; margin-bottom: calc(-1 * var(--gap)); }
  .boxgrid > .box {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: subgrid;
    grid-row: span 5;
    row-gap: var(--space-2);
    margin-bottom: var(--gap);
  }
  .boxgrid > .box > label { align-self: end; }
  .boxgrid > .box > .box-act { grid-row: -2 / -1; align-self: start; margin-top: 0; }
}

/* A small uppercase field-group label. It was only ever defined scoped to the
   bundle form and the user-edit panel, so on the archive, document detail,
   extraction review, cheque assist and template try-it it rendered as plain
   14px body text. One base definition; the scoped copies still match it. */
.fieldlabel {
  font-size: var(--fs-3xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

input, select, textarea {
  padding: var(--space-3) var(--field-pad-x);
  border: 1px solid var(--field-border);
  border-radius: var(--field-radius);
  font-size: var(--fs-sm);
  font-family: inherit;
  /* A form control does NOT inherit figure settings on its own — the UA sheet
     resets them — so an amount typed into a cell came out proportional beside
     a column that was tabular. */
  font-variant-numeric: inherit;
  font-feature-settings: inherit;
  width: 100%;
  background: var(--field-bg);
  color: var(--text);
  min-height: var(--control-h);
}

/* A single-line control is exactly one step tall — the same step its button
   is. Height came out of padding plus the font's line box before, which is
   why one row could hold a 32px button, a 33px input and a 40px select and
   still look, to the code, like it agreed. The padding goes horizontal only;
   the line box centres itself in the height.
   The selector is :where() on purpose: it carries NO specificity, so the dense
   step of a field in a row (table input.cell, 28px) and every other rule that
   sizes one field simply wins by being written, instead of each of them
   outranking this rule with a :not() of its own. */
input:where(:not([type="checkbox"]):not([type="radio"]):not([type="file"])), select {
  height: var(--control-h);
  padding-top: 0;
  padding-bottom: 0;
  line-height: normal;
}
/* multi-line keeps its padding and grows */
textarea { height: auto; min-height: calc(var(--control-h) * 2); }

input:hover, select:hover, textarea:hover { border-color: var(--field-border-hover); }

/* A date is ten characters and a year is four; a full-width box for either
   reads as a mistake next to the field beside it. */
input.date { width: var(--field-w-date); }
input.date.with-label { width: var(--field-w-date-lg); }
/* A field that opens a calendar says so. The mark is drawn in the field's own
   ink at low weight — an affordance, not a second control competing with the
   value; the whole field is the target, so it needs no hit area of its own. */
input.date {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236B7280' stroke-width='1.3'%3E%3Crect x='2' y='3.2' width='12' height='11' rx='1.5'/%3E%3Cpath d='M2 6.6h12M5.4 1.8v2.6M10.6 1.8v2.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 14px 14px;
  padding-right: calc(var(--field-pad-x) + var(--space-6));
}
input.date:hover { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%231B5E44' stroke-width='1.3'%3E%3Crect x='2' y='3.2' width='12' height='11' rx='1.5'/%3E%3Cpath d='M2 6.6h12M5.4 1.8v2.6M10.6 1.8v2.6'/%3E%3C/svg%3E"); }
input[type="number"].year, input.year { width: var(--field-w-year); }
/* a select carries its option text, not just its value, so it gets its own
   step — sized to the value alone it clipped its own placeholder */
select.year { width: var(--field-w-year-sel); }
.form-grid input.date, .form-grid input.year { justify-self: start; }



input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--field-border-focus);
  box-shadow: var(--focus-ring);
}

textarea { resize: vertical; }

/* Reset the generic field padding/border for the native tick controls: the
   base input{} rule would otherwise pad and border them for a single focus
   layout pass, briefly inflating a row checkbox to ~39x31px (N1b). */
input[type="checkbox"], input[type="radio"] { width: auto; min-height: 0; padding: 0; border: 0; accent-color: var(--accent); }
input[type="file"] { padding: var(--space-2); background: var(--surface-sunken); }

/* ---------------------------------------------------------------------------
   Button hierarchy (MAKEOVER-PLAN §3.2). Five kinds, one job each, and every
   kind states all five of its states — rest · hover · focus-visible · active ·
   disabled (plus loading). The hierarchy is what lets a screen carry many
   options while only ONE of them looks like the next step:

     primary    the one next step of the view      filled ledger green
     secondary  supporting actions                 outlined, ink text
     ghost      row-level / tertiary actions       text only
     danger     destructive, always confirmed      outlined oxblood
     link       inline navigation inside text      accent text

   Sizes: sm 28px (inside table rows) · md 32px (default) · lg 36px (dialogs).
   --------------------------------------------------------------------------- */

button, .btn {
  background: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
  border: 1px solid transparent;
  padding: 0 var(--btn-pad-x);
  min-height: var(--btn-height);
  border-radius: var(--btn-radius);
  font-size: var(--btn-font-size);
  font-weight: var(--btn-font-weight);
  font-family: inherit;
  font-variant-numeric: inherit;
  font-feature-settings: inherit;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  box-shadow: none;
  /* A button is one line. Left to wrap, the same control was 32px in a wide
     column and 44px in a narrow one, which is most of what "the buttons are
     not the same size" looked like. If the label cannot fit, the row wraps
     around the button, not the words inside it. */
  white-space: nowrap;
}
button:hover, .btn:hover { background: var(--btn-primary-bg-hover); }
button:active, .btn:active { background: var(--btn-primary-bg-active); }

button.secondary, .btn.secondary {
  background: var(--btn-secondary-bg);
  color: var(--btn-secondary-fg);
  border-color: var(--btn-secondary-border);
}
button.secondary:hover, .btn.secondary:hover { border-color: var(--btn-secondary-border-hover); color: var(--accent); background: var(--surface-raised); }
button.secondary:active, .btn.secondary:active { background: var(--accent-soft); }

button.danger, .btn.danger { background: var(--surface); color: var(--danger); border-color: var(--danger-border); }
button.danger:hover, .btn.danger:hover { background: var(--danger-soft); border-color: var(--danger); }
button.danger:active, .btn.danger:active { background: var(--red-100); }
button.danger:focus-visible, .btn.danger:focus-visible { box-shadow: var(--focus-ring-danger); }

button.accent, .btn.accent { background: var(--accent); }
button.accent:hover, .btn.accent:hover { background: var(--accent-hover); }

/* inline icons inside buttons/controls inherit currentColor at the sm size */
/* An inline icon is icon-sized wherever it lands — see app/icons.py, which
   emits a bare <svg viewBox="0 0 24 24">. Anything with a class of its own
   (the brand mark, the process schematic) keeps its own size. */
svg:not([class]) { width: var(--icon-size-sm); height: var(--icon-size-sm);
  vertical-align: -0.15em; flex: none; }

.btn svg, button svg { width: var(--icon-size-sm); height: var(--icon-size-sm); flex: none; }

/* ghost / tertiary text action */
button.ghost, .btn.ghost { background: transparent; color: var(--accent); border-color: transparent; box-shadow: none; }
button.ghost:hover, .btn.ghost:hover { background: var(--accent-soft); }
button.ghost:active, .btn.ghost:active { background: var(--accent-border); }
/* A ghost button is a word. Where it starts a line its word stands on the
   line's edge, not a padding and a border in from it, so it reads with the
   text above and below; the wash on hover reaches back over what it gave up. */
button.ghost.flush-start, .btn.ghost.flush-start { margin-left: calc(-1 * (var(--btn-pad-x) + 1px)); }

/* sizes */
button.sm, .btn.sm { min-height: var(--btn-height-sm); padding: 0 var(--space-4); font-size: var(--fs-xs); }
button.lg, .btn.lg { min-height: var(--btn-height-lg); padding: 0 var(--space-5); }

/* One text button: a word on a line that happens to submit or toggle (Undo in
   the toast, "sign out everywhere" in the headers of the portal, the row verbs).
   It gives the control step back (height, padding, border, fill, also while
   hovered) and takes the type of the line it stands in, so it is as tall as its
   words: the toast was 58px with a 32px Undo hanging 6px below its message. Each
   use then sets only its colour and size, in the rule that follows. */
button.linklike, .toast-undo button, form.signout-all button {
  min-height: 0; height: auto; padding: 0; border: 0; background: none; box-shadow: none;
  display: inline; font: inherit; color: inherit; }

/* icon-only control: square, always paired with a title/aria-label */
.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; min-height: 0; padding: 0;
  background: var(--surface); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer;
}
.btn-icon:hover { border-color: var(--accent); color: var(--accent); background: var(--surface); }
.btn-icon svg { width: 16px; height: 16px; }

/* disabled — same for every kind: it is visibly inert and says why on hover */
button:disabled, .btn:disabled, button.is-disabled, .btn.is-disabled,
button[aria-disabled="true"], .btn[aria-disabled="true"] {
  background: var(--stone-400);
  color: var(--surface);
  border-color: transparent;
  cursor: not-allowed;
  box-shadow: none;
}
button.secondary:disabled, .btn.secondary:disabled,
button.ghost:disabled, .btn.ghost:disabled,
button.danger:disabled, .btn.danger:disabled,
button.secondary.is-disabled, .btn.secondary.is-disabled {
  background: var(--surface);
  color: var(--text-500);
  border-color: var(--border);
}
button:disabled:hover, .btn:disabled:hover,
button.is-disabled:hover, .btn.is-disabled:hover { background: var(--stone-400); }
button.secondary:disabled:hover, .btn.secondary:disabled:hover { background: var(--surface); color: var(--text-500); border-color: var(--border); }

.actions { margin-top: var(--space-5); display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }

/* ================= message strips (one severity vocabulary) =================
   MAKEOVER-PLAN §3.3: every "something happened / something is off" surface —
   flash messages, readiness blocks, watchdog findings, the example-catalogue
   banner — uses ONE component with four severities, so a coloured rule always
   means severity and never decoration. Colour + a left rule + the text; no
   competing borders, no red used for information.

     .msg           information  (ink hairline)
     .msg.ok        confirmed / done
     .msg.warn      advisory, refusal, held back
     .msg.error     blocking, broken, refused
*/
.msg {
  display: flex; align-items: flex-start; gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-left: 3px solid var(--text-muted);
  border-radius: var(--radius-lg);
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
}
.msg-body { flex: 1; min-width: 0; }
.msg-actions { flex: none; display: flex; gap: var(--space-3); align-items: center; }
.msg svg { width: 16px; height: 16px; flex: none; margin-top: var(--space-1); }
.msg.ok    { background: var(--ok-soft);     border-color: var(--ok-border);     border-left-color: var(--ok);     color: var(--ok); }
.msg.warn  { background: var(--warn-soft);   border-color: var(--warn-border);   border-left-color: var(--warn);   color: var(--warn); }
.msg.error { background: var(--danger-soft); border-color: var(--danger-border); border-left-color: var(--danger); color: var(--danger); }
.msg.ok b, .msg.warn b, .msg.error b { color: inherit; }

/* the action flash is the same strip: success confirms, a refusal warns —
   refusals must never masquerade as successes */
.flash {
  display: flex; align-items: flex-start; gap: var(--space-4);
  background: var(--ok-soft);
  border: 1px solid var(--ok-border);
  box-shadow: inset 3px 0 0 var(--ok);
  color: var(--ok);
  padding: var(--space-4) var(--card-pad-x);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-5);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  transition: opacity var(--dur-slow) var(--ease-standard);
}
.flash.warn {
  background: var(--warn-soft);
  border-color: var(--warn-border);
  box-shadow: inset 3px 0 0 var(--warn);
  color: var(--warn);
}
/* a confirmation fades once it has been read; a warning stays put */
.flash.is-fading { opacity: 0; }

/* A toast — a quiet confirmation that a background reading finished, or a neat
   note when it did not. It sits out of the way (bottom-right), auto-dismisses,
   and carries the same tone vocabulary as .flash: ledger-green for a success,
   red for a failure. Nothing here mutates; it only reports what just happened. */
/* ============ the calendar under a date field (static/datepicker.js) ========
   Typing stays the fast path; this is for the dates you do not know the number
   of. It borrows the card's surface and hairline so it reads as part of the
   page, and the one filled green in it is the day you already chose. */
.dp {
  position: absolute; z-index: 70;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--elev-2);
  padding: var(--space-3); width: 244px;
  font-size: var(--fs-sm);
}
.dp[hidden] { display: none; }
.dp-head { display: flex; align-items: center; gap: var(--space-1); margin-bottom: var(--space-2); }
.dp-title {
  flex: 1; text-align: center; font-weight: var(--fw-semibold);
  color: var(--text-heading); text-transform: capitalize; white-space: nowrap;
}
.dp-nav {
  background: none; border: 0; color: var(--text-secondary); cursor: pointer;
  width: var(--control-h-xs); height: var(--control-h-xs); min-height: 0;
  padding: 0; border-radius: var(--radius-sm); line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.dp-nav:hover { background: var(--surface-sunken); color: var(--accent); }
.dp-wd, .dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.dp-wd span {
  text-align: center; font-size: var(--fs-3xs); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: var(--tracking-caps);
  padding-bottom: var(--space-1);
}
.dp-day {
  background: none; border: 0; cursor: pointer; padding: 0;
  height: 28px; min-height: 0; border-radius: var(--radius-sm);
  font-size: var(--fs-sm); font-variant-numeric: tabular-nums;
  color: var(--text); display: inline-flex; align-items: center; justify-content: center;
}
.dp-day:hover { background: var(--surface-sunken); color: var(--accent); }
.dp-day.is-other { color: var(--text-muted); }
.dp-day.is-today { box-shadow: inset 0 0 0 1px var(--border-strong); font-weight: var(--fw-medium); }
.dp-day.is-cursor { box-shadow: inset 0 0 0 1px var(--accent); }
.dp-day.is-on, .dp-day.is-on:hover {
  background: var(--accent); color: var(--text-on-accent); font-weight: var(--fw-semibold);
}
.dp-foot {
  display: flex; justify-content: space-between; gap: var(--space-3);
  margin-top: var(--space-2); padding-top: var(--space-2);
  border-top: 1px solid var(--border);
}
.dp-foot button {
  background: none; border: 0; padding: 0; min-height: 0; cursor: pointer;
  color: var(--text-secondary); font-size: var(--fs-xs); font-family: inherit;
}
.dp-foot button:hover { color: var(--accent); text-decoration: underline; }

.toasts {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: min(24rem, calc(100vw - 2 * var(--space-5)));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  background: var(--ok-soft);
  border: 1px solid var(--ok-border);
  color: var(--ok);
  padding: var(--space-4) var(--card-pad-x);
  border-radius: var(--radius-lg);
  box-shadow: inset 3px 0 0 var(--ok), var(--elev-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  cursor: pointer;
  transform: translateY(0);
  opacity: 1;
  transition: opacity var(--dur-slow) var(--ease-standard),
              transform var(--dur-slow) var(--ease-standard);
  animation: toast-in var(--dur-slow) var(--ease-standard);
}
.toast.error {
  background: var(--danger-soft);
  border-color: var(--danger-border);
  box-shadow: inset 3px 0 0 var(--danger), var(--elev-2);
  color: var(--danger);
}
.toast .toast-ico { flex: none; display: inline-flex; margin-top: 1px; }
.toast .toast-ico svg { width: 16px; height: 16px; }
/* the offer to put it back, inside the confirmation that it happened */
.toast-undo { flex: none; margin-left: auto; display: inline-flex; }
.toast-undo button {
  font-weight: var(--fw-semibold);
  text-decoration: underline; text-underline-offset: 2px;
}
.toast-undo button:hover { text-decoration: none; }
.toast.is-leaving { opacity: 0; transform: translateY(6px); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

/* Inbox · Identified — the link cell. When the exact case does not exist yet,
   creating it is the primary act and linking to another of the fund's cases is
   the deliberate second one: its button steps back to a secondary one so the
   two do not compete (DESIGN-CRITIQUE-CASE-CENTRIC P0-1). Every control of the
   cell is the 28px step of a row, and they stand on one line that wraps as a
   whole: the create action, the picker with its button, and the two quiet ways
   out (re-tag, and "not a cheque"). */
.linkform { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }
.casecell { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4); }
.casecell-quiet { display: inline-flex; align-items: center; gap: var(--space-4);
  min-height: var(--control-h-sm); font-size: var(--fs-xs); }
.casecell-quiet > form > button.linklike { display: inline-flex; align-items: center;
  min-height: var(--control-h-sm); }
/* the one place a link is re-asked: the chosen case disagrees with the tags */
.linkconfirm {
  border: 1px solid var(--warn-border); border-left: 3px solid var(--warn);
  background: var(--warn-soft); border-radius: var(--radius-md);
  padding: var(--space-4); max-width: 34rem;
}
.linkconfirm p { margin: 0 0 var(--space-3); font-size: var(--fs-sm); line-height: var(--lh-body); }
.linkconfirm-acts { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }

/* A summary strip of counts, tiles wearing their tone so an at-risk or
   unconfirmed count reads at a glance. Shared by the W-8BEN-E status screen and
   the expected-documents catalogue; a detail table sits below it. */
.statstrip { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.stat-tile {
  flex: 1 1 150px; min-width: 150px;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); background: var(--surface);
  display: flex; flex-direction: column; gap: var(--gap-tight);
}
/* the effective-date control stays on one line: at 1024 the Set button used to
   drop under its own field, which read as two separate controls (P2-6) */
.validfrom-form { display: flex; flex-wrap: nowrap; gap: var(--space-2); align-items: center; }
.validfrom-form .cell.date { width: var(--field-w-date-sm); flex: none; }
.validfrom-form button { white-space: nowrap; flex: none; }

.stat-tile.is-risk { border-color: var(--danger-border); background: var(--danger-soft); }
.stat-tile.is-warn { border-color: var(--warn-border); background: var(--warn-soft); }
.stat-tile.is-ok { border-color: var(--ok-border); background: var(--ok-soft); }
.stat-n {
  font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xl); font-weight: var(--fw-semibold); line-height: 1;
}
.stat-lab { font-size: var(--fs-sm); color: var(--ink-2); }
.stat-note { font-size: var(--fs-xs); color: var(--danger); font-weight: var(--fw-semibold); }
ul.plain { list-style: none; margin: 0; padding: 0; }
ul.plain li { padding: var(--gap-tight) 0; }

/* A generated file, on the page you land on — the end of "I somehow can't
   create the CSV" (owner finding #17). The download is the primary action
   here; everything else on the screen is preparation for it. */
.exportdone {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-5); flex-wrap: wrap;
  border-left: 3px solid var(--ok);
}
.exportdone-body { display: flex; flex-direction: column; gap: var(--space-2); }

/* A line that is not going out in this export reads as waiting, not as an
   error — nothing is wrong with it, it is simply not its turn yet. */
tr.is-held > td { background: var(--neutral-soft); }

.empty { color: var(--text-muted); font-style: normal; padding: var(--space-4) 0; }

details > summary { cursor: pointer; color: var(--text-secondary); }

.paperclip { text-decoration: none; font-size: var(--fs-md); }

/* ================= one export control (MAKEOVER-PLAN §3.4) =================
   Downloads used to sit in five different spots per screen and competed with
   the page's real action. One secondary "Export" menu per screen collects every
   format; row-level downloads stay ghost icons in their row. A <details>, so it
   works without JavaScript. */
.exportmenu { position: relative; display: inline-block; }
.exportmenu > summary { list-style: none; cursor: pointer; }
.exportmenu > summary::-webkit-details-marker { display: none; }
.exportmenu > summary::marker { content: ""; }
.exportmenu-list {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: var(--z-dropdown);
  min-width: 232px; padding: var(--space-3);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--elev-2);
  display: flex; flex-direction: column; gap: var(--space-1);
}
.exportmenu-list a {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; font-size: var(--fs-sm); white-space: nowrap;
}
.exportmenu-list a:hover { background: var(--accent-soft); color: var(--accent); }
.exportmenu-list a .ic { color: var(--text-muted); display: inline-flex; }
.exportmenu-list a:hover .ic { color: var(--accent); }
.exportmenu-list .exportmenu-note { padding: var(--space-3) var(--space-4) 0; color: var(--text-muted); font-size: var(--fs-2xs); }
.exportmenu-list hr { border: 0; border-top: 1px solid var(--rule); margin: var(--space-3) 0; }

/* ================= quick chips & filter bar ================= */

/* Two ways of looking at the same list — a segmented control in the page head,
   not two entries in the sidebar (plan §5.3: the board is a view of Cheques). */
.viewtoggle { display: inline-flex; height: var(--control-h); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); overflow: hidden; }
.viewtoggle a { display: inline-flex; align-items: center; padding: 0 var(--space-4); font-size: var(--fs-sm);
  text-decoration: none; color: var(--text-secondary); background: var(--surface); }
.viewtoggle a + a { border-left: 1px solid var(--border-strong); }
.viewtoggle a:hover { color: var(--accent); background: var(--surface-raised); }
/* The lit segment is a quiet sunken fill with the ink label, not a filled
   green: the one filled green of a page is its real primary (+ New case), and
   a switch between two renderings of the same rows is not an act. */
.viewtoggle a.on { background: var(--surface-sunken); color: var(--text);
  font-weight: var(--fw-medium); }

.quickchips { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }
.chiplink {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);          /* squared tag */
  padding: var(--space-2) var(--space-4);
  font-size: var(--fs-xs);
  text-decoration: none;
  color: var(--text-secondary);
  background: var(--surface);
}
.chiplink:hover { border-color: var(--accent); color: var(--accent); }
/* a chip in a toolbar is one control tall, like the button beside it */
.page-actions .chiplink, .complead-scope .chiplink { display: inline-flex; align-items: center; height: var(--control-h); }
.chiplink.on { background: var(--accent); color: var(--text-on-accent); border-color: var(--accent); font-weight: var(--fw-medium); }
/* the one chip that is amber: a band with something overdue in it */
.chiplink.is-attention:not(.on) { color: var(--warn); }

/* Follow-ups groups: one calm heading shape and one table rhythm across groups
   that carry different columns. The count is a quiet tabular figure beside the
   title; every table's trailing action column (ct-act, the one rule at the Open
   cell below) sits hard right, so the eye finds "Open →" in the same place down
   every group (owner: strak, uitgelijnd). */
.fu-head { margin: 0 0 var(--space-3); }
/* The action of a follow-up is a word ("Open slip →", "Review →", "Open case"),
   not the one word Open, so the five groups that start with a case give their
   action column the width of the widest row verb (--colw-act-word, the Dutch
   "Open strook →" fits). Every action then ends the row on one edge and the
   columns before it stand on the same edges in all five. The count columns
   hold the Dutch head "OPEN SINDS" (67px), which the 4rem of a count does not. */
table.grid[data-colset="followups"] { --colw-act: var(--colw-act-word); --colw-num: 4.5rem; }
.fu-count { margin-left: var(--space-2); color: var(--text-muted);
  font-family: var(--font-figure); font-variant-numeric: tabular-nums lining-nums;
  font-weight: var(--fw-regular); font-size: var(--fs-sm); }

.filterbar { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }
.filterbar select, .filterbar input { width: auto; }
/* A date field is a fixed shape; `.filterbar input`(0,1,1) at auto made the
   archive from/to fields ~175px and broke the filter row over three lines.
   `.filterbar input.date`(0,2,1) wins — wide enough for "from dd-mm-yyyy". */
.filterbar input.date { width: var(--field-w-date-lg); }
/* a range is one thing to the row: its two dates wrap to the next line together */
.filterbar .daterange { display: inline-flex; gap: var(--space-3); }

/* A filter that takes several values (UX-AFFORDANCES A1). Closed, it is a
   select in all but name — the same height, border, radius and type — so a
   bar of them reads as one row of controls. The words say what is chosen:
   "all clients" in the quiet ink of a placeholder, a choice in full ink.
   Open, it is the same small panel as the export menu, tick boxes in a list
   that scrolls inside the panel, never the page. */
.msel { position: relative; display: inline-block; }
/* The gap between a box's words and its chevron is the small step: with the bar's gap
   at 8px between neighbours, the six boxes of the Cases row, Views and the key need
   937px at 1280 and the card holds 925; at 4px they need 913 and stay one row. */
.msel-btn {
  display: inline-flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  height: var(--control-h); max-width: var(--field-w-state);
  padding: 0 var(--space-3) 0 var(--field-pad-x);
  border: 1px solid var(--field-border); border-radius: var(--field-radius);
  background: var(--field-bg); color: var(--text-muted); font-size: var(--fs-sm);
  cursor: pointer; list-style: none; user-select: none; white-space: nowrap;
}
.msel-btn::-webkit-details-marker { display: none; }
.msel-btn::marker { content: ""; }
.msel-btn:hover { border-color: var(--field-border-hover); }
.msel-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.msel.is-on .msel-btn { color: var(--text); }
.msel[open] .msel-btn { border-color: var(--accent); }
.msel-text { overflow: hidden; text-overflow: ellipsis; }
.msel-btn svg { width: 14px; height: 14px; flex: none; color: var(--text-muted);
  transition: transform var(--dur-base) var(--ease-standard); }
.msel[open] .msel-btn svg { transform: rotate(180deg); }
.colsel .msel-btn { max-width: none; }   /* it says how many are left out: never clipped, in Dutch either */
.msel-pop {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: var(--z-dropdown);
  min-width: 100%; width: max-content; max-width: 300px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--elev-2);
  padding-top: var(--space-2);
}
.msel-pop.is-flipped { left: auto; right: 0; }
.msel-find { display: block; width: calc(100% - 2 * var(--space-3)); margin: 0 var(--space-3) var(--space-2);
  height: var(--control-h-sm); min-height: 0; }
.msel-opts { max-height: 264px; overflow-y: auto; padding: 0 var(--space-2); }
.msel-opt {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  font-size: var(--fs-sm); color: var(--text); cursor: pointer; white-space: nowrap;
}
.msel-opt:hover { background: var(--accent-soft); }
.msel-opt[hidden] { display: none; }
.msel-opt input { width: auto; min-height: 0; margin: 0; flex: none; accent-color: var(--accent); }
.msel-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  margin-top: var(--space-2); padding: var(--space-3); border-top: 1px solid var(--rule);
}
/* a muted line inside a panel: the columns that always show */
.msel-note { margin: var(--space-2) var(--space-3) 0; max-width: 260px; font-size: var(--fs-2xs);
  color: var(--text-muted); white-space: normal; }

/* Owner finding #14: "the filter button should have a colour also or be more
   intuitive". The button answers two questions about itself, and the answers
   never overlap — a filter is either in force, or waiting to be applied.
   .has-filters : this list is filtered right now (the button also counts them)
   .is-dirty    : you changed a control and the rows below are still the old
                  ones, so pressing it is the next thing to do -> primary. */
.filterbar button.has-filters, .filterbar .btn.has-filters {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}
.filterbar button.is-dirty, .filterbar .btn.is-dirty {
  background: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
  border-color: transparent;
}
.filterbar button.is-dirty:hover, .filterbar .btn.is-dirty:hover {
  background: var(--btn-primary-bg-hover);
  color: var(--btn-primary-fg);
}

/* ================= row selection + bulk action bar (P2-5) ================= */
th.chk, td.chk { width: 34px; text-align: center; padding-right: 0; }
th.chk input, td.chk input { width: auto; min-height: 0; margin: 0; accent-color: var(--accent); cursor: pointer; }
/* A dark "selection mode" bar — appears only when something is selected. It
   floats at the foot of the list (position:sticky) and sits AFTER the table in
   the DOM, so revealing it never nudges the rows a person is ticking (N1). */
.bulkbar { position: sticky; bottom: var(--space-4); z-index: var(--z-header);
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4); margin-top: var(--space-3);
  background: var(--ink-900); color: var(--text-on-dark); border-radius: var(--radius-md);
  box-shadow: var(--elev-2);
  animation: bulkbar-in var(--dur-base) var(--ease-standard); }
@keyframes bulkbar-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .bulkbar { animation: none; } }
.bulkbar-count { font-weight: var(--fw-semibold); color: #fff; }
.bulkbar-count b { font-variant-numeric: tabular-nums; }
.bulkbar-actions { display: flex; gap: var(--space-3); align-items: center; margin-left: auto; flex-wrap: wrap; }
.bulkbar .btn { min-height: 28px; }
.bulkbar .btn.ghost { color: var(--accent-on-dark); border-color: transparent; }
.bulkbar .btn.ghost:hover { background: var(--ink-700); color: #fff; }
/* "37 columns ?" next to the export button: the count reads on the dark bar,
   the popover opens leftward so a right-aligned bar cannot push it off-screen */
.bulkbar-cols { display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-xs); color: var(--accent-on-dark); }

.bulk-na { position: relative; }
.bulk-na > summary { list-style: none; cursor: pointer; }
.bulk-na > summary::-webkit-details-marker { display: none; }
.bulk-na-pop { position: absolute; right: 0; bottom: calc(100% + 6px); z-index: var(--z-dropdown);
  background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--elev-2); padding: var(--space-4); display: flex; gap: var(--space-3); align-items: center; white-space: nowrap; }
.bulk-na-pop input { width: 150px; }
.bulk-na-pop input.date { width: var(--field-w-date-sm); }
.bulk-na-pop select { width: 140px; }
.bulk-na-pop input[name="comment"] { width: 210px; }
/* While the details panel is open the bar ends where the panel begins
   (lists.js), so its actions stay in reach beside it. A popover then hangs
   from the bar itself: above it, at its right end and no wider than it, its
   fields wrapping instead of running under the panel or off the page. */
.bulkbar.is-beside .bulk-na { position: static; }
.bulkbar.is-beside .bulk-na-pop { max-width: 100%; flex-wrap: wrap; }
.bulkbar.is-beside .bulk-na-pop :is(input, select) { max-width: 100%; }
/* Where the list beside the panel cannot hold the bar's widest control, the
   bar lies over the panel instead, one step above it. */
.bulkbar.is-over { z-index: calc(var(--z-header) + 1); }

/* ================= cheque board ================= */

/* Six stages fit a 1440 screen: 6*165 + gaps clears the ~1135px the board gets
   there (§3.10), and 1fr lets them grow on wider screens. Below 1200 the
   pipeline wraps to three, and overflow-x carries the narrow middle band. */
.board { display: grid; grid-template-columns: repeat(6, minmax(165px, 1fr)); gap: var(--space-4); overflow-x: auto; }
@media (max-width: 1200px) { .board { grid-template-columns: repeat(3, 1fr); } }

.board-col { background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-4); min-height: 120px; }
.board-col-head {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: var(--tracking-label);
  margin-bottom: var(--space-4); display: flex; justify-content: space-between; align-items: center;
}
.chip-count { background: var(--accent); color: var(--text-on-accent); border-radius: var(--radius-pill); padding: 1px var(--space-3); font-size: var(--fs-2xs); font-variant-numeric: tabular-nums; }

.board-card {
  display: block;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-4);
  margin-bottom: var(--space-3);
  text-decoration: none;
  color: var(--text);
  box-shadow: none;
}
.board-card:hover { border-color: var(--accent); box-shadow: var(--elev-1); }
.bc-top { font-size: var(--fs-xs); display: flex; justify-content: space-between; gap: var(--space-3); color: var(--text-secondary); }
.bc-amt { font-size: var(--fs-md); font-weight: var(--fw-bold); margin: var(--space-2) 0; letter-spacing: var(--tracking-tight); }
.bc-meta { font-size: var(--fs-2xs); color: var(--text-muted); }

/* ================= filing timeline ================= */

.timeline { position: relative; margin-left: var(--space-3); }
.tl-row { display: grid; grid-template-columns: 86px 34px 1fr; gap: var(--space-3); padding: var(--space-3) 0; }
.tl-when { font-size: var(--fs-xs); color: var(--text-secondary); text-align: right; font-variant-numeric: tabular-nums; }
.tl-dot { text-align: center; font-size: var(--fs-md); line-height: 24px; position: relative; color: var(--accent); }
.tl-dot svg { width: 15px; height: 15px; vertical-align: -3px; }
.tl-dot::after {
  content: ""; position: absolute; left: 50%; top: 26px; bottom: -14px;
  width: 2px; background: var(--rule); transform: translateX(-50%);
}
.tl-row:last-child .tl-dot::after { display: none; }
.tl-body { font-size: var(--fs-sm); }

/* ================= allocation preview ================= */

.preview {
  background: var(--surface-sunken);
  box-shadow: inset 0 0 0 1px var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4) 0;
}
/* The table of the preview is one of the case's money tables (data-colset
   "casemoney"), so it stands on the card's own edges, not 17px inside them:
   the panel's line is an inset shadow and its padding is vertical only, and
   what is not the table (the note, the button) keeps one table inset. */
.preview > :not(.chq-scroll) { margin-left: var(--cell-pad-x); margin-right: var(--cell-pad-x); }

/* ================= one cheque, one block (P1-5) =================
   A cheque used to be one row of a seven-column table whose sixth cell held a
   whole second table. The nesting made the row 261px wider than the card at
   1280, so the booked amounts and the Custody-slip button sat off-screen with
   nothing to say they were there. The cheque's facts now read as a line and
   the allocation sits under them with the card's full width. */
.chq + .chq { margin-top: var(--space-5); }
.chq-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-2) var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--rule);
}
.chq-amt {
  font-family: var(--font-figure); font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text);
}
.chq-facts { font-size: var(--fs-sm); }
/* the two small controls sit together at the right, out of the reading line */
.chq-scan { margin-left: auto; }
.chq-scan, .chq-acts { display: inline-flex; align-items: baseline; gap: var(--space-3);
  font-size: var(--fs-sm); }
/* the three small actions of the line (Preview, audit, edit / delete) are one
   size: a summary and the preview link take the line's, as audit does */
.chq-scan summary, .chq-acts summary { font-size: inherit; }
.chq-alloc { margin-top: var(--space-4); }
/* a last-resort scroller: the allocation now has the whole card, but a very
   long fund name must still never push the card open */
.chq-scroll { overflow-x: auto; }
.alloc-table { width: 100%; }

/* ================= print (allocation slip) ================= */

@media print {
  .sidebar, .topbar, .actions, form, #tooltip, .page-actions, .controls,
  .edit-hint, .quickchips, .pager, .editlock, .active-filters { display: none !important; }
  /* A work list prints its table. For an account that may edit, Cases and
     Cheques stand inside the bulk form, which the rule above hides with every
     other form; the form is shown again (the forms inside its rows stay
     hidden), and what is no use on paper is not printed: the selection bar,
     the ticks and the way into the case. A viewer's print loses only the
     last. (If the Open cell is renamed from col-open to ct-act, that class
     joins this rule.) */
  form[data-bulk-form] { display: block !important; }
  .bulkbar, table[data-keynav] :is(th, td):is(.chk, .ct-chk, .col-open) { display: none !important; }
  /* A PDF's text layer is built from the glyphs, and Inter's tabular and
     contextual alternates carry private-use code points. Printed with them,
     a pack's "2023" extracted as four unreadable characters — an auditor's
     search found nothing and a copied amount pasted as noise (measured
     29-09-2026, every PDF the app printed). Text prints with the alternates
     off; figures print in Plex Sans, whose own digits are already tabular,
     so a column of amounts still lines up. */
  :root { --font-figure: "IBM Plex Sans", "Segoe UI", system-ui, sans-serif; }
  * { font-variant-numeric: normal !important; font-feature-settings: "calt" 0 !important; }
  td.amt, th.amt, td.num, th.num, .num, .amt, .ct-amt, .ct-num { font-family: var(--font-figure); }
  .shell { display: block; }
  main { max-width: none; margin: 0; padding: 0; }
  .card { border: 1px solid var(--border); box-shadow: none; break-inside: avoid; }
  .tbl-wrap { max-height: none; overflow: visible; }
  /* no printed table is cut at the height a box gets on screen (the quarter
     comparison is a PDF page inside .xscroll) */
  .xscroll, .tablewrap, .candwrap { max-height: none; }
  /* nothing is positioned on paper: the parked Open head and the pivot's
     corner stand where the table puts them. A table that runs over a page
     repeats its <thead> on each, and no row is split across two. */
  th { position: static !important; }
  tr { break-inside: avoid; }
  .tile { break-inside: avoid; }
  a { color: inherit; text-decoration: none; }
  .insight-grid { display: block; }
  .insight-grid .card { margin-bottom: var(--space-4); }
}

/* ================= pager ================= */

.pager { display: flex; gap: var(--space-4); align-items: center; justify-content: center; margin: var(--space-3) 0 var(--space-6); }

/* ================= page header ================= */

.page-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-6); margin-bottom: var(--space-5);
  /* the two heights the actions are measured against: the title's line, and the
     crumb line (its 18px and the 2px under it) that stands over it on a page
     with a breadcrumb */
  --title-lh: calc(var(--fs-2xl) * var(--lh-body));
  --crumb-lh: calc(var(--fs-xs) * var(--lh-body) + var(--space-1)); }
/* the title block takes what the actions leave and wraps its own lines, so the
   title and its buttons stay on one line (it used to ask for its whole width and
   push the second button of a head under the first). Its minimum stays its longest
   word: at 0 the Allocation dataset's title was 90px wide at 1024 and its words
   spilled out of the block. */
.page-head > div:first-child { flex: 1 1 0; }
/* Primary page title in serif — the of-record "register" voice, the type
   contrast (serif title · grotesk UI · mono figures) that breaks the generic
   single-grotesk look (DESIGN-DIRECTION §4B). */
.page-head h2 { margin-bottom: var(--space-2); font-family: var(--font-serif); font-weight: var(--fw-semibold);
  font-size: var(--fs-2xl); letter-spacing: 0; color: var(--text); }
/* a file name is data: the title keeps it whole on one line, so the actions beside
   it wrap before the name does, and a name longer than the measure is cut with an
   ellipsis (the full name is in its title attribute). The measure is a length, not
   a percentage, so a long name cannot widen the title block past the page. */
.page-head h2.fname { max-width: 30rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.page-head .lede { max-width: 720px; margin: 0; line-height: var(--lh-body); color: var(--text-secondary); }
/* the state of the page, under its name — never inside the heading, so the
   page keeps calling itself what the menu calls it */
.page-state { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-5);
  margin: 0 0 var(--space-3); }
/* The header actions wrap onto their own lines rather than run off the edge
   when several sit beside the title (e.g. Filings: List/Investigations · New ·
   Bulk entry · Import) — flex:none held them at max-content width and overflowed
   the page at ~1024px before the 920px column-stack could catch it. */
/* align-items: a flex row stretches its children by default, so a button
   beside a two-line heading grew to 44px while the same button elsewhere
   was 32px. A control keeps its own height; only the row grows. */
.page-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; flex: 0 1 auto;
  min-width: 0; justify-content: flex-end; align-items: center; }
/* The actions stand on the line of the title: their centre is the centre of its
   first line, with or without a breadcrumb over it. No page sets a margin of its
   own. The head stacks under 920px and the margin goes with it. */
.page-head .page-actions { margin-top: calc((var(--title-lh) - var(--control-h)) / 2); }
.page-head:has(.crumbs) .page-actions {
  margin-top: calc(var(--crumb-lh) + (var(--title-lh) - var(--control-h)) / 2); }
/* a form or a menu among the actions is a row of controls, not a line of text: one
   height (the control step) whatever sits inside, and the 8px gap in a form. The menu
   has no gap: its closed list is a second box of the same row, and a gap there put 8px
   of nothing after its button (the Export of the Overview ended 8px short of the edge). */
.page-actions form, .page-actions .exportmenu { display: inline-flex; align-items: center; }
.page-actions form { gap: var(--space-3); }
.small { font-size: var(--fs-2xs); }
.card.slim { padding: var(--space-4) var(--card-pad-x); }

/* ================= ledger explorer ================= */

.ledger-kpi { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

.controls { padding: var(--space-4) var(--card-pad-x); }
/* A grid, not a wrap: fifteen filters in even columns read as one block
   instead of four ragged rows that happen to end where they end. */
.ctrl-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-4) var(--space-5); align-items: end; }
.ctrl-group { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.ctrl-group select, .ctrl-group input { width: 100%; }
.ctrl-group label { font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--text-muted); font-weight: var(--fw-semibold); }
.ctrl-group.check { justify-content: flex-end; }
.ctrl-group.check label { text-transform: none; letter-spacing: 0; font-size: var(--fs-xs);
  color: var(--text-secondary); font-weight: var(--fw-regular);
  display: flex; align-items: center; gap: var(--space-3); white-space: nowrap;
  min-height: var(--btn-height); }
.ctrl-group.check input { width: auto; flex: none; }
/* An inline form inside a table cell is one control, not a paragraph: it
   lays out in a row and wraps as a whole rather than dropping its button
   onto a line of its own. */
td form.inline, td .inline-actions {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
}
td form.inline > * { flex: none; }
/* A row's own small actions (Rotate, Revoke on an API token), in a cell
   marked nowrap: side by side, because a table gives a cell that may wrap no
   more than its widest button and the two would stack on every row. Below
   the narrow breakpoint they stack again, so the table asks for less width
   before it scrolls inside its card. */
td.nowrap .inline-actions { flex-wrap: nowrap; }
@media (max-width: 1140px) { td.nowrap .inline-actions { flex-wrap: wrap; } }
/* a select in a cell sizes to its content, not to the column — full width
   pushed the button that belongs beside it onto a line of its own */
td form.inline select { width: auto; max-width: 180px; }
td form.inline input.cell.date { width: var(--field-w-date-sm); }

/* An action link in a table never breaks before its arrow. */
td a.rowlink, .nowrap { white-space: nowrap; }
/* a hyphenated term in running text stays on one line (the nb filter, app/prose.py) */
.nb { white-space: nowrap; }

/* the old flex row separated its groups with a hairline; a grid separates them
   by being a grid, and the rule was left standing in a cell of its own */
.ctrl-sep { display: none; }
/* Apply and Reset stand in the last cell of the last row of the filter grid, at its
   right edge, instead of in a row of their own under it (a grid whose last row is
   full still gives them one: the line is placed, not forced) */
.ctrl-actions { display: flex; gap: var(--space-3); align-items: center; justify-content: flex-end;
  grid-column: -2 / -1; }

/* One disclosure affordance, app-wide. */
summary { cursor: pointer; list-style: none; }
summary::-webkit-details-marker { display: none; }
/* a bare summary — "edit", "history", "log" in a table cell — still has to
   look like it opens something, so it gets the chevron the app draws itself */
details > summary.muted { display: inline-flex; align-items: center; gap: var(--space-2); }
details > summary.muted::before {
  content: "\203A"; font-weight: var(--fw-semibold); line-height: 1;
  transition: transform var(--dur-fast) var(--ease-standard);
}
details[open] > summary.muted::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  details > summary.muted::before { transition: none; }
}

.active-filters { display: flex; flex-wrap: wrap; gap: var(--gap-tight); align-items: center; }
/* the rule is the line between the boxes and what is in force: with nothing above
   it (a card of its own, the first line of a register) there is nothing to divide */
:is(.ctrl-form, .filterbar) + .active-filters { margin-top: var(--gap); padding-top: var(--gap); border-top: 1px solid var(--rule); }

/* removable filter chips (P2-5b) — each active filter as a chip with an ✕ that
   drops just that one and keeps the rest */
.fchip { display: inline-flex; align-items: center; gap: var(--space-3); padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
  background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: var(--fs-xs); color: var(--text-secondary); }
.fchip b { color: var(--text); font-weight: var(--fw-medium); }
.fchip .x { color: var(--text-muted); text-decoration: none; font-size: var(--fs-sm); line-height: 1;
  padding: 1px var(--space-2); border-radius: var(--radius-xs); }
.fchip .x:hover { color: var(--danger); background: var(--danger-soft); }
.fchip-clear { font-size: var(--fs-xs); color: var(--accent); text-decoration: none; margin-left: var(--space-1); }
.fchip-clear:hover { text-decoration: underline; }

table.pivot td a { text-decoration: none; font-weight: var(--fw-medium); }
table.pivot td a:hover { text-decoration: underline; }
table.pivot th.total-col, table.pivot td.total-col { border-left: 1px solid var(--border); background: var(--row-total-bg); }

.pivot-bar-wrap { display: flex; align-items: center; gap: var(--space-3); }
.pivot-bar { height: 10px; background: var(--series-1); border-radius: 0 var(--radius-xs) var(--radius-xs) 0; min-width: 2px; }
.pivot-bar-pct { font-size: var(--fs-2xs); color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ================= insights ================= */

/* the grid keeps the block rhythm of a card: a card that followed one stood
   against it, border on border (measured: 0px where every other pair is 16) */
.insight-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-5);
  margin-bottom: var(--space-5); }
.insight-grid .card { margin-bottom: 0; }
.insight-grid .card.wide { grid-column: 1 / -1; }
@media (max-width: 980px) { .insight-grid { grid-template-columns: 1fr; } }

/* Sixteen quarters across a half-width card gives each column ~24px, and the
   label "21-Q3" is wider than that. It used to be rotated -30°, which does not
   make it narrower — it overflowed the column, then the card, and printed over
   the panel beside it. Set upright instead: a vertical label is exactly as
   wide as one character and cannot leave its column. */
.qtrend { display: flex; align-items: flex-end; gap: var(--space-2); height: 168px;
  padding-top: var(--space-3); overflow: hidden; }
.qtrend-col { flex: 1; min-width: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end; height: 100%; gap: var(--space-2); }
.qtrend-bar { width: 100%; max-width: 34px; background: var(--series-1); border-radius: var(--radius-xs) var(--radius-xs) 0 0; min-height: 3px; transition: opacity 0.1s; }
.qtrend-col:hover .qtrend-bar { opacity: 0.8; }
.qtrend-lab { font-size: var(--fs-3xs); color: var(--text-muted); white-space: nowrap;
  writing-mode: vertical-rl; transform: rotate(180deg); line-height: 1; flex: none; }

.pipeline-bar { display: flex; height: 26px; border-radius: var(--radius-md); overflow: hidden; background: var(--surface-sunken); }
.pipe { height: 100%; }
.pipe.pending { background: var(--series-2); }        /* awaiting = ink-slate */
.pipe.exported { background: var(--series-1); }       /* exported = teal */

/* ================= the chosen period and the recovery pipeline =============
   (owner 17-10, mock-ups 05 A and 01 C). Every mark is sized by its SVG
   attributes in the template and coloured here from the tokens: the chosen
   quarter and the step in view are the accent, everything else ink and
   greys. A choice wears the two marks of a lit tab, the ink label and the
   green line, and never a fill. */
.qstrip { display: flex; align-items: stretch; gap: var(--space-1); }
.qcol { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center;
  gap: var(--space-1); padding: var(--space-1) 0; text-decoration: none;
  color: var(--text-muted); border-bottom: 2px solid transparent; }
.qcol:hover { color: var(--text); }
.qcol:hover .qcol-bar rect { fill: var(--chart-axis); }
.qcol.on { color: var(--text); border-bottom-color: var(--accent); }
.qcol-v { min-height: 1.4em; font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  color: var(--text); white-space: nowrap; font-variant-numeric: tabular-nums; }
.qcol-bar { display: block; width: 70%; max-width: 40px; height: 64px;
  border-bottom: 1px solid var(--chart-axis); }
.qcol-bar rect { fill: var(--border); }
.qcol.on .qcol-bar rect { fill: var(--accent); }
.qcol-l { font-size: var(--fs-2xs); font-weight: var(--fw-medium); line-height: 1.2; }
.qcol-y { min-height: 1.2em; font-size: var(--fs-3xs); line-height: 1.2; color: var(--text-muted); }

/* the one sentence that makes the point, over a chart; the source line under it */
.rp-note { margin: 0 0 var(--space-4); color: var(--text); }
.rp-src { margin: var(--space-4) 0 0; }
.rp-more { margin: var(--space-3) 0 0; }
.rp-head { margin: var(--gap) 0 var(--space-3); }
.rpcard { scroll-margin-top: var(--space-5); }

/* The strip: one box per step from claim to cash, each a link. The line
   under a step is the share of the chain that reached it, in ink. */
.rpipe { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr)); border: 1px solid var(--border); }
.rpipe > li { min-width: 0; }
.rpipe > li + li { border-left: 1px solid var(--border); }
.rpstep { display: flex; flex-direction: column; gap: var(--space-1); height: 100%;
  box-sizing: border-box; padding: var(--space-4) var(--space-4) var(--space-3);
  text-decoration: none; color: var(--text-secondary); border-bottom: 2px solid transparent; }
.rpstep:hover { color: var(--text); background: var(--surface-raised); }
.rpstep.on { color: var(--text); border-bottom-color: var(--accent); }
.rp-l, .rp-v, .rp-n, .rp-r { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rp-l { font-size: var(--fs-2xs); font-weight: var(--fw-semibold); text-transform: uppercase;
  letter-spacing: var(--tracking-label); }
.rp-v { font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text);
  font-variant-numeric: tabular-nums; }
.rp-n { font-size: var(--fs-xs); }
.rp-r { font-size: var(--fs-2xs); color: var(--text-muted); }
.rp-reach { display: block; width: 100%; height: 4px; margin-top: var(--space-2); }
.rp-track { fill: var(--surface-sunken); }
.rp-fill { fill: var(--series-2); }
.rp-next { margin-top: var(--space-1); }
/* Below the laptop width a step is about 100px: its name may take two lines
   (every name keeps two, so the figures of the six steps stay on one line)
   and the figure steps down one size. */
@media (max-width: 1279px) {
  .rpstep { padding: var(--space-3); }
  .rp-l { white-space: normal; letter-spacing: 0; line-height: 1.25; min-height: 2.5em; }
  .rp-v { font-size: var(--fs-md); }
}

/* Reconciliation over time, on the client's report (mock-up 02 B). Expected
   is the dashed ink line, received the solid green one, the gap between them
   a grey hatch: shape and words carry the difference, not colour alone. */
.rcchart { display: block; width: 100%; max-width: 900px; height: auto; margin: 0 0 var(--space-4); }
.rc-grid { stroke: var(--chart-grid); stroke-width: 1; }
.rc-ax { fill: var(--chart-label); font-size: var(--fs-sm); font-family: var(--font-figure);
  font-variant-numeric: tabular-nums; }
.rc-hatch-bg { fill: var(--surface-raised); }
.rc-hatch-ln { stroke: var(--border); stroke-width: 2; }
.rc-exp { fill: none; stroke: var(--series-2); stroke-width: 2; stroke-dasharray: 6 4; }
.rc-rec { fill: none; stroke: var(--series-1); stroke-width: 2.5; }
.rc-end-exp { fill: var(--series-2); }
.rc-end-rec { fill: var(--series-1); }
.rc-mark { stroke: var(--text); stroke-width: 1.5; }
.rc-dot { fill: var(--surface); stroke: var(--text); stroke-width: 1.5; }
.rc-lab { font-size: var(--fs-base); font-weight: var(--fw-semibold); fill: var(--text); }
.rc-lab-rec { fill: var(--series-1); }
.rc-lab-v { font-weight: var(--fw-regular); fill: var(--text-secondary);
  font-family: var(--font-figure); font-variant-numeric: tabular-nums; }
@media print { .rcchart { max-width: 100%; } }

/* pinned saved views in the sidebar */
.sidebar nav a.pinned-view { font-size: var(--fs-xs); color: var(--nav-fg-muted); padding: var(--space-2) var(--nav-inset) var(--space-2) var(--space-6); }
.sidebar nav a.pinned-view:hover { color: #fff; }

/* form.saveview (0,1,1) after form.inline (0,1,1): the save form is a flex row
   like the controls beside it. `.saveview` alone lost to form.inline's
   `display: inline`, which sat the field and its button on the text baseline
   and put them 0.5 to 1px off the Export control beside them (11-10). The name
   field gives way before the title line wraps. */
form.saveview { display: inline-flex; gap: var(--space-3); align-items: center; }
.saveview input[name="name"] { min-width: 0; flex: 0 1 var(--field-w-name); font-size: var(--fs-xs); }

/* ================= overview: one slice, three views =================
   The row of filter boxes under the tabs is the Cases filter row, written once
   for Totals, Tie-out and Insights (_overview_views.html). On paper the boxes
   are hidden and the filters in force print as plain text, so a sliced sheet
   says what it is sliced to. */

.ovslice { margin-bottom: var(--gap); }
/* the first line of a view, right under the filters: a count, a state or the
   switch between its sections. One control high, so the first block of every
   view starts on the same line. */
.viewbar { display: flex; align-items: center; gap: var(--space-4);
  min-height: var(--control-h); margin: 0 0 var(--gap); }
.viewbar > .page-state { margin: 0; }
.viewbar > .rowlink { margin-left: auto; white-space: nowrap; }
td.neg { color: var(--figure-negative); }
/* A SHORTFALL is one thing wherever it appears: a minus sign and the attention
   tone. The case page used to show the same missing money three ways — red
   "-10.032,30" in the rail, amber unsigned "SHORT 10.032,30" in the settlement
   tile, ink "-10.032,30" in the Discrepancy heading — and red belongs to
   errors here, not to money someone still has to chase
   (DESIGN-CRITIQUE-CASE-CENTRIC P2-3). */
.amt.short, td.amt.short, td.ct-amt.short, .rail-facts dd.short, .discrepancy h3 .amt.short { color: var(--warn); }

/* ================= edit mode (unlock → edit → lock) ================= */

.btn.editlock.on {
  background: var(--accent);
  color: var(--text-on-accent);
  border-color: transparent;
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Read-only-by-role affordance (P0-3/P2-7): a quiet, non-interactive chip shown
   to viewers/auditors in place of the editors' unlock control, so read-only is
   stated, not merely a missing button. Dashed + muted = "inactive by design". */
.editlock-readonly {
  display: inline-block;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-4);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  cursor: default;
  white-space: nowrap;
}

.edit-hint {
  background: var(--info-soft);
  border: 1px solid var(--info-border);
  color: var(--info);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-4);
  font-size: var(--fs-xs);
}
.edit-hint.is-warn { background: var(--warn-soft); border-color: var(--warn-border); color: var(--warn); }

tr.editing td { background: var(--accent-soft); }
/* An unlocked register is wider than its card on a narrow window and scrolls
   in its own box. The row's Save parks against the right edge instead of
   sliding away with the columns, as the Open column of the Cases table does. */
tr.editing td.col-save { position: sticky; right: 0;
  box-shadow: -8px 0 8px -8px rgba(23, 33, 29, 0.18); }
/* What the parked Save stands over is not in view. A field that takes the
   focus is scrolled clear of it: the padding is the width of the Save cell,
   which is at its widest in Dutch. */
.xscroll.parks-save { scroll-padding-right: 7rem; }
/* On a narrow window the full name gives up a third of its width, so the
   identifier field beside it stands clear of the parked Save before anything
   is scrolled: it is where an identifier that cannot be read is typed again. */
@media (max-width: 1140px) { tr.editing input.w-name-lg { width: var(--field-w-name); } }
/* Inline editing inside a row: the field and the Save beside it are one step,
   the dense one. The field used to take the ROW height (40px) while the
   button took the dense button height (28px), so every editable row had a
   12px step in it. */
table input.cell, table select.cell {
  padding: 0 var(--space-3);
  font-size: var(--fs-xs);
  border-radius: var(--radius-sm);
  min-width: 0;
  height: var(--control-h-sm);
  min-height: var(--control-h-sm);
}
table textarea.cell { height: auto; min-height: var(--control-h-sm);
  padding: var(--space-2) var(--space-3); }
table input.cell.amt { text-align: right; font-variant-numeric: tabular-nums;
  width: var(--field-w-amount); }
table input.cell.date { width: var(--field-w-date-sm); }
table input.cell.why { width: 100%; }
button.cell-save {
  padding: 0 var(--space-4);
  font-size: var(--fs-xs);
  border-radius: var(--radius-sm);
  height: var(--control-h-sm);
  min-height: var(--control-h-sm);
}
/* Stated at this weight on purpose. The base button:hover carries the same
   specificity as button.cell-save and loses to it on order, so the Save that
   ends every inline edit — the most-pressed control here — answered to
   nothing. */
button.cell-save:hover { background: var(--btn-primary-bg-hover); }
button.cell-save:active { background: var(--btn-primary-bg-active); }
button.secondary.cell-save:hover { background: var(--surface-raised);
  border-color: var(--btn-secondary-border-hover); color: var(--accent); }
button.secondary.cell-save:active { background: var(--accent-soft); }
button.danger.cell-save:hover { background: var(--danger-soft); border-color: var(--danger); }

/* dashboard dataset card */
.dataset-card { border-top: 2px solid var(--accent); }
.dataset-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-7); }
@media (max-width: 900px) { .dataset-split { grid-template-columns: 1fr; } }
.ds-figure { font-size: var(--fs-3xl); font-weight: var(--fw-bold); letter-spacing: var(--tracking-tight); color: var(--text); }
.ds-pf { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-2) 0; text-decoration: none; color: var(--text); font-size: var(--fs-xs); }
.ds-pf:hover .ds-pf-name { color: var(--accent); text-decoration: underline; }
.ds-pf-name { width: 118px; flex: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-pf-amt { flex: none; font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); }

/* ================= accessibility & responsiveness ================= */

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}
.sidebar a:focus-visible, .navsearch input:focus-visible { box-shadow: var(--focus-ring-on-dark); }

/* Forced-colors / Windows High Contrast strips box-shadow, so the ring above
   vanishes there (WCAG 2.4.7). Paint a real outline that only shows in that
   mode — it uses the system Highlight colour, so it is visible whatever theme
   the user has forced. Belt-and-suspenders: the box-shadow ring still renders
   in normal mode. */
@media (forced-colors: active) {
  :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
}

/* honeypot: off-screen (not display:none, which bots skip) — humans never see it */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* the account a password step is for, as a field the browser's password
   manager can read (login_password.html): the heading already says the name
   to a person, so this one sits off screen too, in a wrapper of its own */
.pm-account { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.skiplink {
  position: absolute; left: -9999px; top: 8px; z-index: 100;
  background: var(--ink-900); color: #fff; padding: var(--space-3) var(--space-5); border-radius: var(--radius-md);
}
.skiplink:focus { left: 8px; }

/* narrow screens: sidebar becomes a top bar, nav wraps */
@media (max-width: 920px) {
  .shell { grid-template-columns: 1fr; }
  .sidebar { position: static; height: auto; overflow: visible; flex-direction: row; flex-wrap: wrap;
             align-items: center; gap: var(--space-3) var(--space-5); padding: var(--space-4) var(--space-5); }
  .sidebar nav { flex-direction: row; flex-wrap: wrap; gap: var(--space-1) var(--space-2); flex-basis: 100%; }
  .ngroup { display: none; }
  .ngroup-sec, .ngroup-items, .ngroup-sec[data-collapsed] .ngroup-items { display: contents; }
  .sidebar nav a { padding: var(--space-2) var(--space-3); font-size: var(--fs-xs); }
  .side-user { border-top: none; padding-top: 0; margin-left: auto; }
  .acct-menu { bottom: auto; top: calc(100% + var(--space-2)); left: auto; right: 0; width: 220px; }
  .navsearch { flex: 1; min-width: 160px; }
  main { padding: var(--space-5) var(--space-4) var(--space-8); }
  .page-head { flex-direction: column; gap: var(--space-4); }
  .page-head > div:first-child { flex: none; }
  .page-head .page-actions, .page-head:has(.crumbs) .page-actions { margin-top: 0; }
  /* stacked, the title has the line to itself, so a long name may wrap there */
  .page-head h2.fname { max-width: none; white-space: normal; overflow-wrap: anywhere; }
}

/* horizontal-scroll wrapper for wide tables: the page body never scrolls sideways.
   Past the height of a table box it scrolls in the box too, so its head can
   stay in view (a box that scrolls one way is a scroll box both ways). */
.xscroll { overflow-x: auto; max-height: var(--tbl-box-max); scroll-padding-top: var(--thead-clear); }

/* the carry-forward offset on a case: a box like its neighbours in the Actions
   panel (same track, same inset, same lines), told apart by its tint alone */
.box.cf-offset-box { border-color: var(--ok-border); background: var(--ok-soft); }

/* ================= command palette ================= */

/* "sign out everywhere" (security G3): a POST, because it ends sessions, but
   dressed as the sign-out link beside it, in the portal, consultant and pending
   headers (in the sidebar it is an entry of the account menu). Never a
   button-shaped control. */
form.signout-all { display: inline; margin: 0; }
.portal-nav form.signout-all button { color: var(--text-secondary); font-size: var(--fs-sm); }
.portal-nav form.signout-all button:hover { color: var(--text); }

/* The idle sign-out's warning (app/static/session.js), a minute before an
   unattended screen signs itself out. Amber: it asks for something. It sets
   no display of its own, so the [hidden] rule keeps it out of sight until
   the script shows it. */
.session-notice { position: fixed; top: var(--space-4); left: 50%; transform: translateX(-50%);
  z-index: var(--z-toast); max-width: calc(100vw - 2 * var(--space-5));
  background: var(--warn-soft); border: 1px solid var(--warn-border);
  border-left: 3px solid var(--warn); color: var(--warn); border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-5); font-size: var(--fs-sm); box-shadow: var(--elev-2); }

/* The shortcuts sheet — a small centred card, the same modal vocabulary as the
   palette. */
.shortcuts-overlay { position: fixed; inset: 0; z-index: var(--z-palette); display: flex;
  align-items: flex-start; justify-content: center; padding-top: 12vh; }
.shortcuts-overlay[hidden] { display: none; }
.shortcuts-backdrop { position: absolute; inset: 0; background: var(--scrim); }
.shortcuts-panel { position: relative; width: 420px; max-width: 92vw; background: var(--card);
  border: 1px solid var(--border); border-radius: var(--modal-radius); box-shadow: var(--modal-shadow);
  overflow: hidden; }
.shortcuts-head { display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border); }
.shortcuts-head h2 { margin: 0; font-size: var(--fs-md); }
.shortcuts-list { margin: 0; padding: var(--space-3) var(--space-5) var(--space-5); }
.shortcuts-row { display: grid; grid-template-columns: 96px 1fr; align-items: center;
  gap: var(--space-4); padding: var(--space-3) 0; }
.shortcuts-row + .shortcuts-row { border-top: 1px solid var(--rule); }
.shortcuts-row dt { margin: 0; display: flex; align-items: center; gap: var(--space-2); }
.shortcuts-row dd { margin: 0; color: var(--text-secondary); font-size: var(--fs-sm); }
.shortcuts-list kbd { font-family: var(--font-mono); font-size: var(--fs-2xs);
  background: var(--surface-sunken); border: 1px solid var(--border); border-radius: var(--radius-xs);
  padding: 2px var(--space-3); color: var(--text-secondary); white-space: nowrap; }
.shortcuts-or { color: var(--text-muted); font-size: var(--fs-2xs); }

.palette-overlay { position: fixed; inset: 0; z-index: var(--z-palette); display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; }
.palette-overlay[hidden] { display: none; }
.palette-backdrop { position: absolute; inset: 0; background: var(--scrim); }   /* solid scrim, no blur */
.palette-panel { position: relative; width: 560px; max-width: 92vw; background: var(--card); border-radius: var(--modal-radius); box-shadow: var(--modal-shadow); overflow: hidden; border: 1px solid var(--border); }
.palette-input {
  width: 100%; border: none; padding: 0 var(--space-5); font-size: var(--fs-md); outline: none;
  border-bottom: 1px solid var(--border); background: transparent; color: inherit;
  box-sizing: border-box;
}
.palette-results { max-height: 60vh; overflow-y: auto; padding: var(--space-3); }
.palette-item {
  display: flex; align-items: center; gap: var(--space-4); padding: var(--space-4) var(--space-4); border-radius: var(--radius-md);
  cursor: pointer; text-decoration: none; color: inherit;
}
.palette-item.active { background: var(--row-selected); }
.palette-kind { font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--text-muted); flex: 0 0 auto; width: 52px; }
.palette-label { font-weight: var(--fw-semibold); font-size: var(--fs-sm); }
.palette-sub { color: var(--text-muted); font-size: var(--fs-2xs); margin-top: 1px; font-family: var(--font-mono); }
.palette-empty, .palette-hint { padding: var(--space-6) var(--space-5); text-align: center; color: var(--text-muted); font-size: var(--fs-xs); }

/* ---- in-app attachment preview (modal) ---- */
.preview-modal { position: fixed; inset: 0; z-index: var(--z-modal); display: flex; align-items: center; justify-content: center; }
.preview-modal[hidden] { display: none; }
.preview-backdrop { position: absolute; inset: 0; background: var(--scrim); }
.preview-panel { position: relative; background: var(--card); border-radius: var(--modal-radius); box-shadow: var(--modal-shadow);
  width: min(1000px, 94vw); height: min(88vh, 900px); display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--border); }
.preview-bar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); background: var(--surface-raised); }
.preview-name { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.preview-tools { display: flex; gap: var(--space-2); flex: none; }
.pv-btn { display: inline-flex; align-items: center; justify-content: center; min-width: 30px; height: 30px;
  border: 1px solid var(--border); background: var(--surface); border-radius: var(--radius-sm); cursor: pointer; color: var(--text-secondary);
  font-size: var(--fs-md); text-decoration: none; box-shadow: none; padding: 0; min-height: 0; }
.pv-btn:hover { border-color: var(--accent); color: var(--accent); }
.pv-btn svg { width: 16px; height: 16px; }
.preview-body { flex: 1; overflow: auto; background: var(--surface-sunken); display: flex; align-items: center; justify-content: center; }
.pv-img { max-width: 100%; max-height: 100%; transition: transform .12s ease; transform-origin: center; }
.pv-frame { width: 100%; height: 100%; border: 0; background: #fff; }
.pv-fallback { padding: var(--space-8); text-align: center; color: var(--text-secondary); font-size: var(--fs-base); }
.preview-trigger { cursor: zoom-in; }
/* The overlay's second view, Scan: what a reader took from the document, drawn
   on the page. The switch is there only for a document that has one. */
.pv-switch { display: inline-flex; margin-right: var(--space-3); border: 1px solid var(--border);
  border-radius: var(--radius-sm); overflow: hidden; }
.pv-switch[hidden] { display: none; }
.pv-tab { height: var(--control-h-sm); min-height: 0; padding: 0 var(--space-4); border: 0; border-radius: 0;
  background: var(--surface); color: var(--text-secondary); box-shadow: none;
  font-size: var(--fs-xs); font-weight: var(--fw-semibold); }
.pv-tab + .pv-tab { border-left: 1px solid var(--border); }
.pv-tab:hover { background: var(--surface-raised); color: var(--accent); }
.pv-tab[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }
.preview-modal.is-scan .preview-panel { width: min(1280px, 94vw); }
.preview-body.is-scan { display: block; overflow: hidden; }
.preview-body.is-scan .pv-fallback { padding: var(--space-8); }

/* ================= details panel (reusable side-panel) =================
   A panel at the right that shows a record (cheque → filing → client →
   portfolio) beside the list it came from. It is not a modal: nothing is
   dimmed, and its frame lets every click through to the page behind, so the
   list keeps its scroll and stays usable. One shell, fed an HTML fragment per
   entity from /drawer/<kind>/<id>. Esc or ✕ closes it; a back stack supports
   drilling deeper. It is not a place to type: a fragment holds one form at
   most, only in an edit view the reader asked for with Edit, and while that
   form holds changes not saved nothing replaces or closes the panel. The
   left hairline and the shadow are all that sets it off from the page. It
   lies over the page's own sticky heads and bars and under
   everything that pops up: a menu, the calendar, a tooltip, a toast, and the
   modal layers (the document preview, the palette, the shortcuts sheet). */
.drawer-overlay { position: fixed; inset: 0; z-index: var(--z-header); display: flex; justify-content: flex-end;
  pointer-events: none; }
.drawer-overlay[hidden] { display: none; }
.drawer-panel { position: relative; width: min(480px, 94vw); height: 100%; background: var(--surface);
  border-left: 1px solid var(--border-strong); box-shadow: var(--elev-2); display: flex; flex-direction: column;
  overflow: hidden; transform: translateX(100%); transition: transform var(--dur-slow) var(--ease-standard);
  pointer-events: auto; }
.drawer-overlay.open .drawer-panel { transform: translateX(0); }
/* focus lands on the panel itself when the keyboard opened it: it keeps its
   own shadow and square corners instead of the ring of a control */
.drawer-panel:focus-visible { outline: none; box-shadow: var(--elev-2); border-radius: 0; }
@media (prefers-reduced-motion: reduce) { .drawer-panel { transition: none; } }
.drawer-head { display: flex; align-items: center; gap: var(--space-3); flex: none;
  padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border); background: var(--surface-raised); }
.drawer-title { flex: 1; min-width: 0; margin: 0; font-family: var(--font-serif); font-weight: var(--fw-semibold);
  font-size: var(--fs-lg); letter-spacing: 0; color: var(--text-heading); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the one way from the panel to the record's page: a full-height button at a
   fixed place in the head, secondary so the page keeps its one filled green */
.drawer-open { flex: none; }
.drawer-iconbtn { display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h); height: var(--control-h);
  border: 1px solid var(--border); background: var(--surface); border-radius: var(--radius-sm); color: var(--text-secondary);
  cursor: pointer; padding: 0; min-height: 0; box-shadow: none; flex: none; }
.drawer-iconbtn:hover { border-color: var(--accent); color: var(--accent); }
.drawer-iconbtn svg { width: 16px; height: 16px; }
/* reading the panel to its end does not start scrolling the list behind it */
/* --bar-over: the height a selection bar takes when it lies over the foot of
   the panel (lists.js), so the last lines can be scrolled clear of it */
.drawer-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: var(--space-5);
  padding-bottom: calc(var(--space-5) + var(--bar-over, 0px)); }
.drawer-loading { padding: var(--space-3) 0; }
.drawer-loading .skeleton-cell { margin-bottom: var(--space-4); }

/* drawer content vocabulary (reused by every entity fragment) */
.dw-context { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); font-size: var(--fs-xs);
  color: var(--text-muted); margin-bottom: var(--space-5); }
.dw-context a { text-decoration: none; }
.dw-context a:hover { text-decoration: underline; }
.dw-context .sep { color: var(--border-strong); }
.dw-sec { margin: 0 0 var(--space-5); }
.dw-sec > h4 { font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--tracking-label);
  color: var(--text-muted); font-weight: var(--fw-semibold); margin: 0 0 var(--space-3);
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
/* the source/expected/actual reconciliation strip */
.dw-figs { display: flex; border: 1px solid var(--border); }
.dw-fig { flex: 1; min-width: 0; padding: var(--space-3) var(--space-4); border-left: 1px solid var(--rule); }
.dw-fig:first-child { border-left: 0; }
.dw-fig .l { font-size: var(--fs-3xs); text-transform: uppercase; letter-spacing: var(--tracking-label);
  color: var(--text-muted); font-weight: var(--fw-medium); }
.dw-fig .v { font-family: var(--font-figure); font-size: var(--fs-md); font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight); margin-top: var(--space-1); font-variant-numeric: tabular-nums lining-nums; }
.dw-fig.exp .v { color: var(--series-2); }        /* expected = ink-slate */
.dw-fig.act .v { color: var(--accent); }          /* actual/received = ledger green */
.dw-diff { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-md);
  font-size: var(--fs-sm); }
.dw-diff.ok { background: var(--ok-soft); border-color: var(--ok-border); color: var(--ok); }
.dw-diff.break { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger); }
.dw-diff .n { font-family: var(--font-figure); font-weight: var(--fw-semibold); margin-left: auto; font-variant-numeric: tabular-nums; }
.dw-diff svg { width: 16px; height: 16px; flex: none; }
/* facts / responsible list */
.dw-facts { margin: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.dw-facts > div { display: flex; justify-content: space-between; gap: var(--space-4); align-items: baseline; }
.dw-facts dt { color: var(--text-muted); font-size: var(--fs-xs); margin: 0; white-space: nowrap; }
.dw-facts dd { margin: 0; font-weight: var(--fw-medium); text-align: right; }
/* the quiet second line of a fact: who has the next step, and by when */
.dw-facts dd .dw-sub { color: var(--text-muted); font-size: var(--fs-xs); font-weight: var(--fw-regular); }
.dw-facts dd.amt { font-family: var(--font-figure); font-variant-numeric: tabular-nums lining-nums; }
/* related links */
.dw-links { display: flex; flex-direction: column; gap: var(--space-1); }
.dw-link { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); text-decoration: none;
  color: var(--text); border-radius: var(--radius-sm); font-size: var(--fs-sm); }
.dw-link:hover { background: var(--accent-soft); color: var(--accent); }
.dw-link .ic { color: var(--text-muted); display: inline-flex; flex: none; }
.dw-link:hover .ic { color: var(--accent); }
.dw-link .meta { margin-left: auto; color: var(--text-muted); font-size: var(--fs-xs); }
/* the expected documents of a case: the verdict, then one line per paper */
.dw-verdict { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-3); font-size: var(--fs-sm); }
.dw-docs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.dw-doc { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-1) var(--space-4);
  padding: var(--space-2) 0; font-size: var(--fs-sm); }
.dw-doc + .dw-doc { border-top: 1px solid var(--rule); }
.dw-doc-note { grid-column: 1 / -1; font-size: var(--fs-xs); }
/* compact audit/action timeline inside the drawer */
.dw-tl { display: flex; flex-direction: column; }
.dw-tl-row { display: grid; grid-template-columns: 20px 1fr; gap: var(--space-3); padding: var(--space-2) 0; border-top: 1px solid var(--rule); }
.dw-tl-row:first-child { border-top: 0; }
.dw-tl-ic { color: var(--accent); line-height: 1; padding-top: var(--space-1); }
.dw-tl-ic svg { width: 14px; height: 14px; }
.dw-tl-t { font-size: var(--fs-sm); }
.dw-tl-d { font-size: var(--fs-xs); color: var(--text-muted); }
.dw-tl-m { font-size: var(--fs-2xs); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.dw-tl-m b { color: var(--text-secondary); font-weight: var(--fw-medium); }

/* editing in the panel. A fragment types nothing except in an edit view the
   reader asked for with Edit: one form, ended by Save or Cancel. A dropdown is
   28px in a row of the list, Add and Save 32px. A wrong field says so beside
   itself, in red; a field that is right says nothing. The foot stays in view
   with the notice that a move would drop what was entered. */
.dw-h-act { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.dw-h-act .dw-edit { font-size: var(--fs-xs); font-weight: var(--fw-medium); text-transform: none; letter-spacing: 0;
  text-decoration: none; }
.dw-h-act .dw-edit:hover { text-decoration: underline; }
.dw-form select, .dw-form input:not([type="hidden"]) { width: 100%; max-width: var(--field-w-name-lg); }
.dw-doc select { grid-column: 1 / -1; height: var(--control-h-sm); min-height: var(--control-h-sm); }
.dw-doc label { min-width: 0; }
.dw-doc .field-msg { grid-column: 1 / -1; }
.dw-field { display: grid; gap: var(--space-1); margin: var(--space-4) 0; }
.dw-field label { font-size: var(--fs-xs); color: var(--text-secondary); }
.field-msg { margin: var(--space-1) 0 0; font-size: var(--fs-xs); }
.field-msg:empty { display: none; }
.field-msg .statusdot, .dw-unsaved .statusdot { white-space: normal; align-items: flex-start; }
.dw-form [aria-invalid="true"] { border-color: var(--danger); }
.dw-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4);
  position: sticky; bottom: 0; z-index: 1; margin-top: var(--space-4); padding: var(--space-3) 0;
  background: var(--surface); border-top: 1px solid var(--rule); }
/* Cancel is a text button (it draws no box), but in the foot of a panel form it
   stands on the line of Save as a control of the row: one height, its word on
   the row's centre line, a target as tall as the button beside it */
.dw-actions > button.linklike { display: inline-flex; align-items: center; min-height: var(--control-h); }
.dw-unsaved, .dw-formmsg { flex: 1 0 100%; margin: 0; font-size: var(--fs-xs); }
.dw-unsaved[hidden], .dw-formmsg[hidden] { display: none; }
.dw-formmsg { color: var(--danger); }
.dw-form.is-asking .dw-actions { background: var(--warn-soft); }
/* a row the save took out of the view stays, quiet, until the page is loaded again */
tr.row-left > td { color: var(--text-muted); }

/* ================= the pipeline strip (app/pipeline.py, plan §6.3) =================
   Owner finding #15: "It is pretty hard to see how this process works and where
   to be on the page." Five vocabularies described one journey; this is the one
   map, on every cheque, filing and drawer. Steps behind are ticked and quiet,
   the step you are on is stated, the ones ahead are visible but grey. */
.pipestrip { display: flex; flex-wrap: wrap; align-items: center; gap: 0;
  list-style: none; margin: 0 0 0 calc(-1 * var(--space-3)); padding: 0; }
.pipestrip > li { display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-xs); color: var(--text-muted); white-space: nowrap;
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-3); }
.pipestrip > li + li::before { content: ""; width: 10px; height: 1px;
  background: var(--rule); margin-right: var(--space-3); flex: none; }
.pipestrip-mark { font-size: var(--fs-2xs); line-height: 1; }
.pipestrip > li.is-done { color: var(--text-secondary); }
.pipestrip > li.is-done .pipestrip-mark { color: var(--ok); }
.pipestrip > li.is-now {
  color: var(--text); font-weight: var(--fw-semibold);
  background: var(--accent-soft); border-radius: var(--radius-sm);
}
.pipestrip > li.is-now .pipestrip-mark { color: var(--accent); }
.pipestrip > li.is-now.is-trouble { background: var(--warn-soft); color: var(--warn); }
.pipestrip > li.is-now.is-trouble .pipestrip-mark { color: var(--warn); }
.pipestrip > li.is-todo { color: var(--text-muted); opacity: 0.72; }
/* in a row or a drawer the words are too much — the marks alone still show
   how far along a thing is, and every step keeps its tooltip */
.pipestrip.is-compact { margin-left: calc(-1 * var(--space-2)); }
.pipestrip.is-compact > li { padding: 0 var(--space-2); }
.pipestrip.is-compact > li + li::before { width: 6px; margin-right: var(--space-2); }
.pipestrip.is-compact .pipestrip-text { display: none; }
.pipestrip.is-compact > li.is-now .pipestrip-text { display: inline; }

/* ================= the Matching workspace (plan §6.6) =================
   Three columns in the order the decision is made: the cheques waiting, the
   filings each could pay, the pair being connected. */
/* The process backbone (IA-DRILLDOWN P0-1): a slim, always-there map of the
   cheque→filing journey, carried under the topbar on every core work screen so
   "where does the work stand" is answered before any row is read. Calm by
   default — one ink weight, tabular counts, hairline separators; ledger green
   only on the phase this screen is about and on hover. Reads left to right as
   the journey; a phase with a backlog shows its number in ink, an empty phase
   stays muted so nothing shouts. */
/* The seven steps are one grid: equal tracks that grow past their words (a long
   label like "Collected & booked" widens its own step and nothing is cut), so
   the strip fills the card and the first count stands on the card's text edge
   (1px border + the card inset), where the title above it begins. The inset is
   the LEFT padding of every step, so each count keeps one offset in its track;
   the right padding is only the least air before the next rule: with 24px on
   both sides the Dutch strip, and the English one beside the Matching note, no
   longer fit a 1280px screen and scrolled out of sight (measured 90 and 95px).
   The eighth track holds the optional note and is empty without one. A narrower
   screen scrolls the strip inside itself, as before. */
.backbone { display: grid; grid-template-columns: repeat(7, minmax(max-content, 1fr)) auto;
  align-items: stretch; margin: 0 0 var(--gap);
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--card-radius); overflow-x: auto; scrollbar-width: none; }
.backbone::-webkit-scrollbar { display: none; }
.backbone-step { display: inline-flex; align-items: baseline; gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-6); text-decoration: none;
  color: var(--text-secondary); white-space: nowrap; }
.backbone-step + .backbone-step { border-left: 1px solid var(--rule); }
.backbone-step .n { font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold); color: var(--text); }
.backbone-step .n.is-zero { color: var(--text-muted); font-weight: var(--fw-regular); }
.backbone-step .l { font-size: var(--fs-xs); letter-spacing: var(--tracking-label); }
.backbone-step:hover { background: var(--surface-raised); color: var(--accent); }
.backbone-step:hover .n { color: var(--accent); }
.backbone-step.is-here { background: var(--accent-soft); }
.backbone-step.is-here .l,
.backbone-step.is-here .n { color: var(--accent); }
.backbone-note { margin-left: auto; align-self: center; padding: 0 var(--space-4);
  font-size: var(--fs-2xs); color: var(--text-muted); white-space: nowrap; }

.processbar { display: flex; align-items: stretch; gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--radius-md); overflow: hidden;
  margin-bottom: var(--gap); }
.processbar-step { flex: 1; background: var(--surface); padding: var(--space-4) var(--space-3);
  text-align: center; text-decoration: none; color: var(--text-muted); min-width: 0; }
.processbar-step .n { display: block; font-family: var(--font-figure); font-size: var(--fs-lg);
  font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; color: var(--text); }
.processbar-step .l { display: block; font-size: var(--fs-2xs); letter-spacing: var(--tracking-label); }
.processbar-step:hover { background: var(--surface-raised); color: var(--accent); }
.processbar-step.on { background: var(--accent-soft); color: var(--accent); }
.processbar-step.is-next { color: var(--text-muted); }
.processbar-step.is-next .n { color: var(--text-muted); font-weight: var(--fw-regular); }
.processbar-since { align-self: center; padding: 0 var(--space-5); background: var(--surface);
  font-size: var(--fs-2xs); color: var(--text-muted); white-space: nowrap; }

/* ---- Matching: decision first (owner 06-10) ----
   Three areas: the queue of cheques, the desk where the ONE decision on the
   picked cheque is made, and the paper (quiet facts about the physical
   cheque). The route decides the mode; the grid carries it as data-mode. */
.matchgrid { display: grid; grid-template-columns: 264px minmax(0, 1fr) 300px;
  gap: var(--gap); align-items: start; }
.matchgrid.no-paper { grid-template-columns: 264px minmax(0, 1fr); }
.matchdesk { display: flex; flex-direction: column; gap: var(--gap); min-width: 0;
  container-type: inline-size; }
@media (max-width: 1439.98px) {
  .matchgrid, .matchgrid.no-paper { grid-template-columns: 248px minmax(0, 1fr);
    grid-template-rows: auto 1fr; }
  .matchgrid > .matchqueue { grid-row: 1 / span 2; }
  .matchgrid > .matchpaper { grid-column: 2; } }
@media (min-width: 1440px) and (max-width: 1539.98px) {
  .matchgrid[data-mode="spread"] { grid-template-columns: 264px minmax(0, 1fr);
    grid-template-rows: auto 1fr; }
  .matchgrid[data-mode="spread"] > .matchqueue { grid-row: 1 / span 2; }
  .matchgrid[data-mode="spread"] > .matchpaper { grid-column: 2; } }
@media (max-width: 1180px) {
  .matchgrid[data-mode="spread"] { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; }
  .matchgrid[data-mode="spread"] > .matchdesk { order: 1; }
  .matchgrid[data-mode="spread"] > .matchpaper { grid-column: auto; order: 2; }
  .matchgrid[data-mode="spread"] > .matchqueue { grid-row: auto; order: 3; } }
@media (max-width: 860px) {
  .matchgrid, .matchgrid.no-paper { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; }
  .matchgrid > .matchqueue { grid-row: auto; }
  .matchgrid > .matchpaper { grid-column: auto; } }
@media (min-width: 1440px) { .paper-quick { display: none !important; } }
.matchcol { margin: 0; }
.matchcol-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4); margin-bottom: var(--gap-tight); }
.matchcol-ref { font-size: var(--fs-xs); color: var(--text-muted); }
.matchcol-ref b { color: var(--text-heading); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums lining-nums; }

/* the queue: filters as three short rows, then the cheques */
.matchfilters { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-3); margin-bottom: var(--gap-tight); }
.matchfilters select { max-width: 100%; width: 100%; }
.matchfilters select[name="client"], .matchfilters select[name="source"] { grid-column: 1 / -1; }
.mf-clear { align-self: center; font-size: var(--fs-xs); }
/* the two scrolling lists never bury the screen (owner 04-10) */
.matchlist { list-style: none; margin: 0; padding: 0; max-height: 62vh; overflow-y: auto; }
.candwrap { max-height: 62vh; overflow: auto; scroll-padding-top: var(--thead-clear); }
.matchlist > li { border-bottom: 1px solid var(--rule); }
.matchlist > li:last-child { border-bottom: 0; }
/* amount and flag share a line when they fit; in a narrow queue the flag
   wraps under the amount instead of breaking "amount unknown" in two */
.matchlist a { display: flex; flex-wrap: wrap; align-items: baseline;
  column-gap: var(--space-4); row-gap: var(--space-1);
  padding: var(--space-3) var(--space-4); text-decoration: none; color: inherit; }
.matchlist a:hover { background: var(--surface-raised); }
.matchlist > li.on a { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }
/* the two groups of the queue: ready to confirm, then to decide. The head is
   the table head's own look, and the group name stays in view while the list
   scrolls. */
.matchlist > li.ml-group { position: sticky; top: 0; z-index: var(--z-sticky);
  background: var(--thead-bg); color: var(--thead-fg); font-size: var(--fs-3xs);
  font-weight: var(--fw-semibold); text-transform: uppercase;
  letter-spacing: var(--tracking-label); border-bottom: 1px solid var(--rule);
  padding: var(--space-2) var(--space-4); }
.matchlist > li.ml-group .fu-count { font-size: inherit; }
.ml-amt { font-family: var(--font-figure); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }
.ml-amt .muted { font-weight: var(--fw-regular); }
.ml-flag:empty { display: none; }
/* the reason is the one word that may wrap: the longest in Dutch is wider than the queue */
.ml-flag { min-width: 0; }
.ml-flag .statusdot { white-space: normal; }
.ml-who, .ml-meta { flex-basis: 100%; }
.ml-who { font-size: var(--fs-xs); min-width: 0; overflow-wrap: anywhere; }
.ml-meta { font-size: var(--fs-2xs); color: var(--text-muted); }

/* the tables on the desk: typed columns (table.grid) on a flush table. The
   desk's cards declare the width of the Difference column (--colw-verdict)
   and of the Expected, Difference and action columns together (--match-sum),
   and the decision sum above takes that for its second track, so it starts on
   the Expected edge and ends on the table's right edge, and no state (a clean
   fit, an amber one, "Exact") can move a column.
   A flush table has no band: its first cell stands on the card's inset and its
   last on the other edge, with a hairline under the head. Band means inset,
   flush means no band (a band over a cell with no padding put the first
   letter on the band's edge). */
.matchdesk > :is(.decide, .others, .spread) {
  --colw-verdict: calc(var(--dv-lamp) + var(--dv-fig) + var(--dv-word) + 2 * var(--space-2));
  --match-sum: calc(var(--colw-amt) + var(--colw-verdict) + var(--colw-act) + 5 * var(--cell-pad-x));
}
table.grid:is([data-colset="matchcand"], [data-colset="matchspread"], [data-colset="matchdone"]) {
  --colw-tag: var(--colw-verdict); }
table.grid[data-colset="matchdone"] { --colw-act: 7rem; }
.candidates { font-size: var(--fs-xs); }
.candidates th, .candidates td { padding-top: var(--space-3); padding-bottom: var(--space-3); }
.candidates > * > tr > :first-child { padding-left: 0; }
.candidates > * > tr > :last-child { padding-right: 0; }
/* the action column ends on the table's edge, so it has one padding, not two */
.candidates > * > tr > .ct-act:last-child { --ct-w: calc(var(--colw-act) + var(--cell-pad-x)); }
.candidates tfoot th { vertical-align: baseline; }
.candidates td.ct-text a { white-space: nowrap; }
:is(.candidates, .doclist, .minitbl) th { background: var(--surface); }
/* the last row before a foot has no rule of its own: the foot draws the one */
.candidates:has(> tfoot) > tbody > tr:last-child > td { border-bottom: 0; }
/* A column that holds a field (the Amount of a spread): the typed figure ends
   one field padding and border inside the field's edge, so the head and the
   plain figures of the column (the Total) are inset by the same, and a figure
   stands on one edge wherever it is. The field fills its cell; it never sizes
   the column. */
table.grid > * > tr > :is(th, td).col-field { padding-right: calc(var(--cell-pad-x) + var(--space-3) + 1px);
  --ct-w: calc(var(--colw-amt) + var(--ct-pad) + var(--space-3) + 1px); }
.spread-table td.ct-amt > input.cell.amt { width: 100%; }
/* the people on a task who are not the one on point */
.tk-watch { font-size: var(--fs-2xs); color: var(--text-muted); margin-top: var(--space-1); white-space: normal; }
/* a person's name in a typed cell is one line, and a very long one ends in an
   ellipsis with the whole name in its title (it is a block, so the baseline of
   its row is still the baseline of its first line) */
.who { display: block; max-width: 8em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* a comment keeps the line breaks it was typed with */
.pre-wrap { white-space: pre-wrap; }
/* a quiet tag in a row (state filings: "special case", "from an example row") */
.cand-tag { display: inline-block; font-size: var(--fs-3xs); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--text-muted); border: 1px solid var(--rule);
  border-radius: var(--radius-sm); padding: 0 var(--space-2); margin-right: var(--space-1);
  white-space: nowrap; }
.cand-sub { display: block; margin-top: var(--space-1); font-size: var(--fs-2xs);
  color: var(--text-muted); font-weight: var(--fw-regular); }

/* the decision card: one case, its sum, one Link button. The left rule
   follows the verdict: ledger green when the fit is clean, amber when not. */
.decide { display: grid; grid-template-columns: minmax(0, 1fr) var(--match-sum);
  gap: var(--space-4) var(--space-6); padding: var(--space-5) var(--card-pad-x) var(--space-4);
  margin: 0; box-shadow: inset 3px 0 0 var(--accent); }
.decide.is-loose { box-shadow: inset 3px 0 0 var(--warn); }
.cand-suggest { display: block; margin: 0 0 var(--space-2); padding: 0; background: none;
  border-radius: 0; font-size: var(--fs-3xs); text-transform: uppercase;
  letter-spacing: var(--tracking-label); font-weight: var(--fw-medium); color: var(--accent); }
.decide.is-loose .cand-suggest, .decide.is-create .cand-suggest { color: var(--text-muted); }
.decide-case { margin: 0; font-size: var(--fs-lg); font-weight: var(--fw-semibold); line-height: 1.3;
  text-transform: none; letter-spacing: normal; color: var(--text-heading); }
.decide-case a { color: var(--text-heading); text-decoration: none; }
.decide-case a:hover { text-decoration: underline; }
.decide-flag { margin: var(--space-2) 0 0; font-size: var(--fs-xs); color: var(--warn); }
.decide-sum .is-total { border-top: 1px solid var(--rule); padding-top: var(--space-2); }
.decide-sum dd .statusdot { font-variant-numeric: tabular-nums lining-nums; }
/* The sum is as wide as the Expected, Difference and action columns of the
   table below it, on that table's right edge even when the card stacks. Every
   figure in it ends on the edge the Difference's figure ends on: a plain
   amount leaves the word's track free, as the verdict beside it reserves it. */
.decide-sum { justify-self: end; width: var(--match-sum); max-width: 100%; }
.decide-sum:has(.dv) > div:not(.is-total) > dd { padding-right: calc(var(--dv-word) + var(--space-2)); }
.decide-act, .decide-book, .decide > .candwrap { grid-column: 1 / -1; }
.decide-act { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4);
  margin: 0; padding-top: var(--space-4); border-top: 1px solid var(--rule); }
.paper-quick { margin-left: auto; display: flex; gap: var(--space-4); font-size: var(--fs-xs); }
.decide-book .book-preview-head { margin: 0 0 var(--space-3); font-weight: var(--fw-medium); }
.decide-book .rail-facts { margin-bottom: var(--space-4); }
.decide-book .rail-facts > div.is-total { border-top: 1px solid var(--rule);
  padding-top: var(--space-2); margin-top: var(--space-1); }
.decide-book .rail-facts > div.is-excluded dt,
.decide-book .rail-facts > div.is-excluded dd { color: var(--text-muted); font-weight: var(--fw-regular); }

/* the other cases: reference rows, smaller and untinted, no buttons. The
   amber lamp stays; the amber WORDS do not shout in a reference row. */
.others { margin: 0; }
.others-table td { font-size: var(--fs-xs); }
.others-table tbody tr:hover td { background: var(--surface-raised); }
.others-table .statusdot.is-attention { color: var(--text-secondary); font-weight: var(--fw-regular); }
/* A difference is three tracks, the lamp, the figure and the word, declared by
   the verdict itself from three tokens (--dv-*): every row's figure ends on
   the same edge in tabular figures, and every lamp and word stands under the
   one above ("Exact" has no figure and keeps its track). The figure is ink in
   a table, and takes the verdict's colour in the sum. */
.statusdot.dv { display: inline-grid; grid-template-columns: var(--dv-lamp) var(--dv-fig) var(--dv-word);
  column-gap: var(--space-2); align-items: baseline; white-space: nowrap; }
.dv-fig { text-align: right; font-variant-numeric: tabular-nums lining-nums; }
.candidates .dv .dv-fig { color: var(--text); font-weight: var(--fw-regular); }
/* the other cases in the narrow card: the case name may wrap, so the tracks
   keep their width and the table never scrolls sideways */
@media (max-width: 1140px) { .others-table td.ct-text a { white-space: normal; } }
.others-foot { margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--rule); display: flex; gap: var(--space-4); align-items: center; }

/* spreading one cheque over several cases: the ledger and its live total */
.spread { margin: 0; }
.spread-table input[aria-invalid="true"] { border-color: var(--danger); }
/* the foot's status is a sentence ("2.000,00 left to spread"): it may wrap in
   the one column it has, and the column keeps its width as it is typed into */
table.grid.spread-table > tfoot > tr > td.ct-tag { white-space: normal; }
.spread-table tfoot .statusdot { white-space: normal; align-items: flex-start; }
.spread-table tr.has-part td { background: var(--surface-raised); }
.spread-table { border-collapse: separate; border-spacing: 0; }
.spread .candwrap { scroll-padding-bottom: 5.5rem; }
.spread-table tfoot { position: sticky; bottom: 0; }
.spread-table tfoot th, .spread-table tfoot td { background: var(--surface); }
.spread-table tfoot tr.is-total th, .spread-table tfoot tr.is-total td {
  border-top: 1px solid var(--text-heading); font-weight: var(--fw-semibold); }
.spread-done tfoot th, .spread-done tfoot td { background: var(--surface); }
.spread-done tfoot tr.is-total th, .spread-done tfoot tr.is-total td {
  border-top: 1px solid var(--rule); font-weight: var(--fw-semibold); }

/* identify: the form IS the step, in the centre, with the scan beside it */
.idcard { margin: 0; }
.idcard-body { display: grid; grid-template-columns: minmax(0, 1fr) 15rem; gap: var(--space-6); }
/* The grid's two tracks size its fields: the first is a code's width, the
   second a fund's, and every field fills its track, so the fields stacked in
   a column share both edges (they were 18 to 20px apart, each at the width of
   what it holds). */
.idcard .tag-grid { grid-template-columns: minmax(0, var(--field-w-code)) minmax(0, var(--field-w-fund));
  gap: var(--space-4) var(--space-5); }
.idcard .tag-grid label > :is(input, select) { width: 100%; }
.idcard-discard { margin-top: var(--space-4); }
.matchshelf { margin: 0; }
/* the amount of a waiting cheque on the sheet is a link of its own in a row
   that opens a panel: 24px high, so a near miss does not show the case instead */
tr:has(a.rowpeek) a.tolink { padding: 4px 0; }
/* Below the laptop width the sheet's seven typed columns are 731px (746 in
   Dutch) in a card of 669 at 1024, and the shelf's four are 447 in a desk of
   405: Open would sit under a sideways scroll. A tighter gutter of eight pixels
   brings both inside (the shelf's case name may also narrow, as Dutch is the
   wider); from 1280 up the widths are the tokens'. */
@media (max-width: 1279px) {
  table.grid:is([data-colset="matchcases"], [data-colset="matchshelf"]) { --cell-pad-x: var(--space-2); }
  table.grid[data-colset="matchshelf"] { --colw-text: 7.5rem; }
}
/* narrow desks, after the base rules so they win: the scan drops under
   the identify form below 800px, the tables' figure columns take their
   compact widths below 700px, and everything stacks below 620px */
@container (max-width: 800px) {
  .idcard-body { grid-template-columns: minmax(0, 1fr); }
  .idcard-paper .cheque-thumb:not(.pdf) { max-width: 220px; } }
@container (max-width: 700px) {
  .matchdesk > :is(.decide, .others, .spread) {
    --cell-pad-x: var(--space-3); --colw-amt: 5.5rem; --dv-fig: 4.5rem; --dv-word: 3rem; }
  /* the desk of a cheque at 1024 has 405px in its card for four columns: the
     figures in the 12px type of the desk take 72px, the case keeps the rest */
  .matchdesk > :is(.decide, .others) { --colw-amt: 4.5rem; --colw-act: 3rem; --colw-text: 6rem; } }
@container (max-width: 620px) {
  .decide { grid-template-columns: minmax(0, 1fr); }
  .idcard .tag-grid { grid-template-columns: minmax(0, 1fr); } }

/* the paper column */
.matchpaper { container-type: inline-size; }
.matchpaper .cheque-thumb { margin-bottom: var(--gap-tight); }
.matchpaper .paper-facts dd { text-align: right; overflow-wrap: anywhere; font-weight: var(--fw-regular); }
/* Under the desk (or beside it in a wide card) the facts are a label and its
   value, not two ends of a long line: the label on the page's label column,
   the value beside it on its left edge. In the narrow column to the right they
   stay as they were, label and value at either end. */
@container (min-width: 22rem) {
  .matchpaper .paper-facts > div { display: grid; grid-template-columns: var(--colw-label) minmax(0, 1fr);
    column-gap: var(--space-4); justify-content: start; }
  .matchpaper .paper-facts dd { text-align: left; } }
.matchpaper .actions { margin-top: var(--gap-tight); }
.pairform { margin-top: var(--gap-tight); }

/* ---- the cheque's own page (plan §6.5) ---- */
.chequegrid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: var(--gap); align-items: start; }
@media (max-width: 1000px) { .chequegrid { grid-template-columns: 1fr; } }
.chequegrid .card { margin-bottom: var(--gap); }
.chequefiling { margin: 0 0 var(--gap-tight); font-size: var(--fs-md); }
.chequefiling .muted { margin-left: var(--space-3); font-size: var(--fs-xs); }

/* Comments with an author (owner finding #11). The composer is one field
   because the name and the date are not the writer's to type — they come from
   the session, like every other action in the app. The cell holds the open
   composer (a field of 120px and Post) and no more, so the variance queue
   keeps Open in view at 1280 in Dutch, where it reads Openen. */
.notecell { min-width: 240px; }
.note-last { display: block; font-size: var(--fs-xs); color: var(--text-secondary);
  line-height: var(--lh-body); }
.note-who { display: block; font-size: var(--fs-2xs); color: var(--text-muted); }
.notecompose { display: flex; gap: var(--space-2); align-items: center; margin-top: var(--space-2); }
.notecompose input { flex: 1; min-width: 120px; }
.notecompose button { flex: none; }

/* Closing the loop in Matching (IA-DRILLDOWN P0-2): after a link, the right
   panel confirms the connection and offers the booking step in place. Quiet by
   the brief — the confirmation is a single green tick, the split preview reuses
   the aligned .rail-facts list, colour stays rationed. */
.linked-ok { display: flex; align-items: baseline; gap: var(--space-2);
  margin: 0 0 var(--space-4); }
.linked-ok-mark { color: var(--accent); font-weight: var(--fw-semibold); }
.book-preview { margin: var(--space-4) 0; }
.book-preview-head { font-size: var(--fs-2xs); color: var(--text-muted);
  letter-spacing: var(--tracking-label); margin-bottom: var(--space-2); }
.book-preview .rail-facts > div.is-total { border-top: 1px solid var(--rule);
  padding-top: var(--space-2); margin-top: var(--space-1); }
.book-preview .rail-facts > div.is-excluded dt,
.book-preview .rail-facts > div.is-excluded dd { color: var(--text-muted);
  font-weight: var(--fw-regular); }

/* Settings, split into Personal (yours) and Administration & rules (the team's)
   — owner decision 2026-09-21. Two clear group headings; a non-admin sees the
   rules read-only under a calm neutral permission note, never a blank screen. */
.setgroup { margin: var(--space-6) 0 var(--space-1); font-size: var(--fs-lg); }
.setgroup:first-of-type { margin-top: var(--space-2); }
.setgroup-sub { margin: 0 0 var(--space-4); color: var(--text-secondary); font-size: var(--fs-sm); }
.setgroup-lock { margin-left: var(--space-2); font-weight: var(--fw-regular); }
.prefs { margin: 0; display: flex; flex-direction: column; }
.pref { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); padding: var(--space-4) 0; }
.pref:first-child { padding-top: 0; }
.pref:last-child { padding-bottom: 0; }
.pref + .pref { border-top: 1px solid var(--rule); }
.pref-what dt { margin: 0; font-weight: var(--fw-semibold); }
.pref-what dd { margin: var(--space-1) 0 0; color: var(--text-muted); font-size: var(--fs-xs); }
.pref-control { flex: none; }
.permnote { display: flex; align-items: baseline; gap: var(--space-2);
  margin: 0 0 var(--space-4); color: var(--text-secondary); font-size: var(--fs-sm); }
.permnote-mark { color: var(--text-muted); }
/* The rules, and one confidentiality level per document type (security G7,
   folded under them): typed columns (data-colset "rules" and "classrules").
   The rule or the type is the text column and takes what is left; a value is a
   short tag; the change is ONE grid of three tracks, the same in every row
   (.ruleform: the value, the reason taking what is left, Apply), so the
   reason field starts on one x down the table and Apply stands on one x. It
   was a fixed layout of percentages with a flex row in each cell: the reason
   started on six x positions, a select's Apply wrapped onto a line of its own
   and the rows were 41 and 77px. Reset is a column of its own (a changed
   type's Reset is a second button of the type's form, bound to it by its
   form attribute, so it still posts the reason typed there). A figure keeps
   the figure face, and Current and Default read from the left like their
   heads. */
table.grid[data-colset="rules"] { --colw-tag: 5rem; --colw-note: 27rem; --colw-act: 3.25rem; }
table.grid[data-colset="classrules"] { --colw-note: 27rem; --colw-act: 3.25rem; }
@media (min-width: 1536px) { table.grid[data-colset="rules"] { --colw-tag: 9rem; } }
table.grid:is([data-colset="rules"], [data-colset="classrules"]) > * > tr > td.ct-note { max-width: none; overflow: visible; }
table.grid[data-colset="rules"] > * > tr > td.ct-tag { white-space: normal; }
table.grid[data-colset="rules"] > * > tr > td.rule-fig { font-family: var(--font-figure); }
.ruleform { display: grid; grid-template-columns: var(--field-w-note) minmax(7.25rem, 1fr) auto;
  gap: var(--space-3); align-items: center; margin: 0; }
.ruleform > :is(input, select) { width: 100%; max-width: none; min-width: 0; }
/* at 1180 and below the value stands over the reason and its Apply, in every
   row alike, so the change keeps its three controls inside a 248px column (the
   Dutch fold was 51px past its card at 1024 with the one line) */
@media (max-width: 1180px) {
  table.grid:is([data-colset="rules"], [data-colset="classrules"]) { --colw-note: 15.5rem; }
  table.grid:is([data-colset="rules"], [data-colset="classrules"]) > * > tr > .ct-text { min-width: 0; }
  :is([data-colset="rules"], [data-colset="classrules"]) .ruleform { grid-template-columns: minmax(0, 1fr) auto; }
  :is([data-colset="rules"], [data-colset="classrules"]) .ruleform > :is(input, select):first-child { grid-column: 1 / -1; grid-row: 1; }
  :is([data-colset="rules"], [data-colset="classrules"]) .ruleform > input[name="reason"] { grid-column: 1; grid-row: 2; }
  :is([data-colset="rules"], [data-colset="classrules"]) .ruleform > button { grid-column: 2; grid-row: 2; }
}

/* Aging made explicit on Investigations (owner decision 2026-09-21): a headline
   for what has been open too long, and a per-row mark for a case over a year —
   both wear the attention △ so long-running work stands out; only aged rows get
   it, so amber stays rationed. */
.aging-headline { display: flex; align-items: baseline; gap: var(--space-2);
  margin: 0 0 var(--gap); font-size: var(--fs-sm); }
.aging-headline b { font-variant-numeric: tabular-nums; }
.aging-long { display: inline-flex; align-items: baseline; gap: var(--space-1);
  font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Documents ↔ Archive, kept separate but connected (owner decision 2026-09-21):
   an Active piece is quiet; an Archived one wears a small preservation marker
   and links to its record. One search over both marks every hit. */
.archived-inline { display: inline-flex; align-items: baseline;
  font-size: var(--fs-2xs); color: var(--text-secondary); letter-spacing: var(--tracking-label);
  border: 1px solid var(--border); border-radius: 3px; padding: 0 var(--space-2);
  white-space: nowrap; }
.active-inline { font-size: var(--fs-2xs); color: var(--text-muted); letter-spacing: var(--tracking-label); }
.archived-tag { text-decoration: none; white-space: nowrap; display: inline-flex;
  align-items: baseline; gap: var(--space-2); }
.archived-tag .meta { color: var(--text-muted); font-size: var(--fs-2xs); }
.archived-tag:hover .meta { color: var(--accent); }

/* ================= deadline & compliance calendar (owner feature) =========
   One time-ordered agenda of every dated obligation, so a desk where refunds
   can wait years is never surprised. The bands are the chips of the list head
   (.quickchips, .chiplink) and pivot the whole view; colour stays rationed to
   the amber attention mark, as everywhere else. */
.cal-band-head { margin: 0 0 var(--space-3); }
.cal-in { font-family: var(--font-figure); font-variant-numeric: tabular-nums; white-space: nowrap;
  display: inline-flex; align-items: baseline; gap: var(--space-1); color: var(--text-secondary); }
.cal-in > .sdot { align-self: center; }
.cal-in .is-attention { color: var(--warn); font-weight: var(--fw-medium); }
/* who has a task and on which case, a step beside what it is (no dash) */
.cal-who { margin-left: var(--space-3); }
.cal-caveat { display: inline-block; margin-left: var(--space-2); font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-label); color: var(--text-muted);
  border: 1px dashed var(--border); border-radius: 3px; padding: 0 var(--space-2); }
.cal-lane-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2);
  font-size: var(--fs-sm); margin: 0 0 var(--space-2); }
.cal-lane-head b { font-variant-numeric: tabular-nums; }
.cal-lane-head .rowlink { margin-left: auto; }
.cal-foot { margin-top: var(--gap); }

/* ================= reconciliation health matrix (owner feature) ===========
   Year × client received-of-expected. Ledger-green (--ledger-600 = 27,94,68) at
   rising strength IS the intensity; the overlay is translucent so each cell
   keeps the mode's luminance and --text stays readable in light and dark. */
.health-matrix { width: 100%; border-collapse: collapse; }
.health-matrix th, .health-matrix td { padding: var(--space-1) var(--space-2); white-space: nowrap; }
.health-matrix td.hc { text-align: right; font-variant-numeric: tabular-nums; }
.health-matrix td.hc a { text-decoration: none; color: inherit; display: block; }
.health-matrix td.hc a:hover { text-decoration: underline; }
.health-matrix .pctsign { font-size: var(--fs-2xs); color: var(--text-muted); margin-left: 1px; }
.health-matrix td.hc-empty { color: var(--text-muted); }
.health-matrix tr.total td { border-top: 1px solid var(--rule); font-weight: var(--fw-medium); }
.hc.heat-1 { background: rgba(27, 94, 68, .08); }
.hc.heat-2 { background: rgba(27, 94, 68, .18); }
.hc.heat-3 { background: rgba(27, 94, 68, .32); }
.hc.heat-4 { background: rgba(27, 94, 68, .46); }
.hc.heat-5 { background: rgba(27, 94, 68, .60); }
.hc.heat-4 .pctsign, .hc.heat-5 .pctsign { color: inherit; }
.health-aging { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4);
  margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--rule); }
.health-aging-label { font-size: var(--fs-2xs); letter-spacing: var(--tracking-label); text-transform: uppercase; }
.agebucket { display: grid; gap: 2px; text-decoration: none; flex: 1 1 140px; min-width: 120px; }
.agebucket-head { font-size: var(--fs-sm); color: var(--text-secondary); }
.agebucket-bar { height: 5px; background: var(--rule); border-radius: 3px; overflow: hidden; }
.agebucket-bar span { display: block; height: 100%; background: var(--accent); }
.agebucket-amt { font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-weight: var(--fw-medium); color: var(--text); }
.agebucket-n { font-size: var(--fs-2xs); }
.agebucket:hover .agebucket-head { color: var(--accent); }

/* ================= saved views (owner feature) ============================
   Per-user named filter slices on a list screen. They live in the Views menu
   (_lists.html views_menu, .viewsmenu above); this is the cross that removes
   one of them. Colour is spent nowhere else here. */
.savedview-x { border: 0; border-left: 1px solid var(--border); background: transparent;
  color: var(--text-muted); cursor: pointer; padding: 0 var(--space-2); line-height: 1; font-size: var(--fs-md); }
.savedview-x:hover { color: var(--danger); }

/* ================= structured discrepancy handling (owner feature B2) ======
   One quiet toolbar: state the difference, record why, choose an outcome in
   three short verbs. No colour on the controls — writing off a rounding
   difference is routine, not an error; the amber mark is on the summary only,
   where "needs handling" belongs. */
.card.discrepancy h3 { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.disc-form { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; margin-top: var(--space-3); }
.disc-form input[name="reason"] { flex: 1 1 240px; min-width: 190px; height: var(--control-h);
  padding: 0 var(--field-pad-x); border: 1px solid var(--field-border);
  border-radius: var(--field-radius); background: var(--field-bg); color: var(--text); }
.disc-btns { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

/* ================= template letters on a compliance case (owner feature B4) =
   Drafted from the case, copied or downloaded and then logged. Nothing sent. */
.lettertpls { margin: var(--space-3) 0 0; }
.lettertpls-note { margin-bottom: var(--space-2); }
.lettertpl { border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3); margin-bottom: var(--space-2); }
.lettertpl > summary { cursor: pointer; }
.lettertpl-body { width: 100%; margin-top: var(--space-2); }

/* bulk import (owner feature B5): the upload form, calm and small */
.importform { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }

/* ================= the expected-documents checklist (INTAKE-COMPLETENESS §3)
   What a file must hold, what is in, what is missing. The card wears the
   accent rule the settlement card wears — it is the page's first card — and
   the verdict line carries the tone; the rows stay quiet except the missing
   one, which is the only thing amber means here. */
.card.filecheck { border-top: 2px solid var(--accent); }
.filecheck-verdict { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3);
  margin: 0 0 var(--space-3); }
.filecheck-verdict .num { font-family: var(--font-figure); font-variant-numeric: tabular-nums lining-nums;
  font-weight: var(--fw-semibold); }
.filecheck-note { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2);
  margin: 0 0 var(--space-3); }
/* the "not needed" (n.a.) controls on a missing row (REDESIGN-CASE-CENTRIC P3):
   a folded reason form, and an "Undo" that reads as a link */
.na-set { margin-top: 2px; }
.na-set > summary { cursor: pointer; list-style: none; }
.na-set > summary::-webkit-details-marker { display: none; }
.na-set > summary:hover { color: var(--accent); }
/* the reason fills the popover (the task menu's pattern); Mark not needed under it */
.na-form select { flex: 1 1 100%; max-width: none; }
button.linklike { color: var(--text-secondary); font-size: var(--fs-xs); text-decoration: underline; }
button.linklike:hover { color: var(--accent); }

/* "+ Add …" (DESIGN-CRITIQUE-CASE-CENTRIC P1-4)
   Three forms used to stand permanently open at the foot of the case —
   withholding, cheque, document — each with its own filled green button, so a
   case at rest showed four primaries competing for the same eye. They now wait
   behind one quiet opener, with the rule above it doing the separating that
   three <h3>s used to do. A section that holds nothing yet renders open, so an
   empty case still shows you the way in. */
.addform { margin-top: var(--space-5); padding-top: var(--space-4);
  border-top: 1px solid var(--rule); }
.addform > summary { cursor: pointer; list-style: none; display: inline-block;
  color: var(--accent); font-weight: var(--fw-semibold); font-size: var(--fs-sm); }
.addform > summary::-webkit-details-marker { display: none; }
.addform > summary:hover { text-decoration: underline; }
.addform > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: var(--radius-sm); }
.addform[open] > summary { margin-bottom: var(--space-4); }
.addform > form { margin-top: 0; }
/* The rule above an opener separates it from what is above it. With nothing
   above it (the first thing in its card, right under a heading, or the first
   thing in a form's grid) it has nothing to separate and floats alone. */
.card > .addform:first-child, h3 + .addform { padding-top: 0; border-top: 0; }
.card > .addform:first-child { margin-top: 0; }
/* The "+ New task" scope choice — Personal vs on a case — as one calm row of
   radios; choosing Personal hides the case-only fields (JS toggles is-personal,
   the server ignores them without it). */
.radio-row { display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-5); align-items: baseline; }
.radio-row > label { display: inline-flex; align-items: baseline; gap: var(--space-2);
  font-weight: var(--fw-regular); cursor: pointer; }
.form-grid > .radio-row { row-gap: 0; align-items: center; }
.form-grid > .radio-row > label { min-height: var(--control-h); align-items: center; }
.form-grid.is-personal .case-only { display: none; }
.filecheck-table tr.fc-na td:first-child b { color: var(--text-secondary); font-weight: var(--fw-regular); }
/* The case's typed tables, each its own set of columns (data-colset).
   The card beside the 320px rail is 589px wide at 1280, so a set names the
   widths its words need, not the page's: the checklist's status and evidence
   are narrower than the tokens, and its action is as wide as "not needed?". */
table.grid[data-colset="casefile"] { --colw-tag: 6.25rem; --colw-note: 8rem; --colw-act: 5.5rem; }
/* the evidence is a name line, which asks for no width of its own: the column
   keeps its token so the name has a line to end in */
table.grid[data-colset="casefile"] > * > tr > .ct-note { min-width: var(--ct-w); }
table.grid[data-colset="casedocs"] { --colw-note: 9rem; }
/* a comment's moment is a date and, for one written today, its time: a tag's
   width, the person beside it the same */
table.grid[data-colset="casenotes"] { --colw-tag: 7.5rem; }
.docreqs td:first-child { white-space: nowrap; }
.docreqs tr.is-retired td { color: var(--text-muted); }
.file-verdict { text-decoration: none; }
/* a file with papers missing AND a paper to confirm: two lamps, one per line */
.verdictstack { display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: var(--space-1); vertical-align: top; }
.file-verdict:hover .badge { outline: 1px solid var(--accent); outline-offset: 1px; }

/* ================= compliance cases (plan §7) =================
   Owner finding #16: "I find it hard to assess what needs to be done here, how
   compliance would easily switch between things." Every case row carries the
   four facts the finding names — progress, papers, deadline, owner. */
.progbar { display: inline-flex; align-items: center; gap: var(--space-3); white-space: nowrap; }
.progbar-track { width: 54px; height: 6px; border-radius: 3px;
  background: var(--surface-sunken); overflow: hidden; flex: none; }
.progbar-fill { display: block; height: 100%; background: var(--series-1); }
.progbar.is-full .progbar-fill { background: var(--ok); }
.progbar-n { font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xs); color: var(--text-secondary); }

.caselist tr.is-overdue td:first-child { box-shadow: inset 2px 0 0 var(--warn); }
/* the row in view (the keyboard is on it, or the panel shows it) wins over the
   overdue mark: one bar, the accent one, as on every other list */
.caselist tr.is-overdue.key-cursor > td:first-child, .caselist tr.is-overdue.drawer-source > td:first-child {
  box-shadow: inset 3px 0 0 var(--accent); }
.casemeta { display: block; font-size: var(--fs-2xs); color: var(--text-muted); margin-top: 1px; }
.casedue { font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xs); color: var(--text-muted); margin-left: var(--space-2); }
.casedue.is-late { color: var(--warn); font-weight: var(--fw-medium); }

.casegrid { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: var(--gap); align-items: start; }
@media (max-width: 1040px) { .casegrid { grid-template-columns: 1fr; } }
.casegrid .card { margin-bottom: var(--gap); }
.nextstep { font-size: var(--fs-xs); color: var(--text-secondary); align-self: center; }

.tasklist td { padding: var(--space-3) var(--space-3) var(--space-3) 0; vertical-align: middle; }
.tasklist tr.is-done .task-label { color: var(--text-muted); }
.task-tick { width: 30px; }
.task-tick button { min-height: 0; padding: 0 var(--space-2); font-size: var(--fs-sm); }
.task-label .muted { display: block; }
.task-assign { width: 260px; }
/* Out-specify `td form.inline select{width:auto;max-width:180px}` (0,1,3): a
   180px select forced the date onto its own line, 102px per task row. This
   selector is (0,2,1) — two classes beat that rule's one. */
.tasklist .task-assign select { width: 130px; max-width: 130px; }
.task-assign input.date { width: var(--field-w-date-sm); }
.doclist td, .doclist th { padding: var(--space-3) var(--space-3) var(--space-3) 0; }
.doclist select { width: auto; }
/* The cells of a list are one size: a quiet cell is quiet in its colour, not in
   its size (the Replaced by and Reason cells were 11px, the hash 12px, the rest
   13px: three sizes in one row). A note inside a cell, marked small, stays small. */
.doclist td.muted:not(.small), .doclist .mono:not(.small) { font-size: inherit; }
/* the group row of a flush table names the group and nothing more: no band,
   the table's own left edge */
.doclist tr.group-row td { background: none; padding-left: 0; text-transform: none;
  letter-spacing: normal; font-size: inherit; color: var(--text-heading); position: static; }
/* a document's "What changed" under its versions table: the same rule above
   it as the forms that follow, so the card reads as one list of parts */
.vchanges { margin-top: var(--space-5); padding-top: var(--space-4);
  border-top: 1px solid var(--rule); }
.vchanges .fieldlabel { margin-bottom: var(--space-3); }

/* one row that adds a thing — the same shape under tasks, documents and the log */
.addrow { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap;
  margin-top: var(--gap-tight); }
.addrow input { flex: 1 1 180px; min-width: 120px; }
.addrow input.date { flex: 0 0 var(--field-w-date-sm); }
.addrow select { width: auto; flex: none; }

.corrlist { list-style: none; margin: var(--gap-tight) 0 0; padding: 0; }
.corrlist > li { display: grid; grid-template-columns: 82px 1fr; gap: var(--space-4);
  padding: var(--space-3) 0; border-bottom: 1px solid var(--rule); }
.corrlist > li:last-child { border-bottom: 0; }
.corr-when { font-family: var(--font-figure); font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs); color: var(--text-secondary); }
.corr-body { font-size: var(--fs-xs); min-width: 0; }
.corr-body .muted { display: block; }
.caseowner { display: flex; align-items: center; gap: var(--space-3);
  font-size: var(--fs-xs); color: var(--text-muted); margin-bottom: var(--gap-tight); }
.caseowner select { width: auto; }

/* ---- cheque inbox / triage ---- */
/* (the filled green count pill that used to sit beside these headings is gone:
   every other screen counts in parentheses, and a section heading is not an
   action — DESIGN-CRITIQUE-CASE-CENTRIC P2-10) */
.intake-sources { display: grid; grid-template-columns: minmax(0,2fr) minmax(0,1fr); gap: var(--space-6); align-items: stretch; }
@media (max-width: 820px) { .intake-sources { grid-template-columns: 1fr; } }
/* ---- the file field (templates/_filepick.html, static/filepick.js) ----------
   One line, the width of a file picker, so a table column does not reflow when
   a file is chosen. A chosen file takes the button's place as a row with its
   name, size and a way to take it off. Without the script the real input stays
   in view and none of this shows. */
.filepick { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); min-width: 0; }
.filepick:not(.zone) { flex: none; width: var(--field-w-file); max-width: 100%; }
.form-grid > .filepick { justify-self: start; }
.filepick:not(.is-enhanced) .filepick-btn, .filepick:not(.is-enhanced) .filepick-state,
.filepick:not(.is-enhanced) .filepick-list { display: none; }
/* the input stays in the layout (1px, invisible): a required field that is
   display:none blocks the submit without a word */
.filepick.is-enhanced input[type="file"] { position: absolute; top: 0; left: 0; width: 1px; height: 1px;
  min-height: 0; margin: 0; padding: 0; border: 0; opacity: 0; pointer-events: none; }
.filepick-state { font-size: var(--fs-sm); color: var(--text-muted); }
.filepick-state[hidden], .filepick.has-files .filepick-state { display: none; }
.filepick:not(.zone).has-files .filepick-btn { display: none; }
.filepick-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column;
  gap: var(--space-2); min-width: 0; flex: 1 1 100%; }
.filepick-list:empty { display: none; }
.filepick-file { display: flex; align-items: center; gap: var(--space-3); min-width: 0;
  min-height: var(--control-h); padding: 0 var(--space-3) 0 var(--space-4);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md); background: var(--surface); }
.filepick-name { display: flex; flex: 1 1 auto; min-width: 0; font-size: var(--fs-sm);
  font-weight: var(--fw-semibold); color: var(--text); }
/* the front of a long name gives way; its end, the extension, stays */
.filepick-stem { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: pre; }
.filepick-tail { flex: none; white-space: pre; }
.filepick-meta { flex: none; font-size: var(--fs-xs); color: var(--text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.filepick-x { flex: none; width: var(--control-h-xs); height: var(--control-h-xs); min-height: 0; padding: 0;
  border: 0; border-radius: var(--radius-sm); background: none; color: var(--text-muted);
  font-size: var(--fs-md); line-height: 1; }
.filepick-x:hover, .filepick-x:active { background: var(--surface-sunken); color: var(--text); }
/* a refused file is shown, with the reason, and never sent: red is the error colour */
.filepick-file.is-refused { border-color: var(--danger-border); background: var(--danger-soft); }
.filepick-file.is-refused .filepick-name { font-weight: var(--fw-regular); color: var(--text-secondary); }
.filepick-file.is-refused .filepick-meta { color: var(--danger); font-weight: var(--fw-semibold); }
/* inside a table row: the dense step, like the Save beside it */
.filepick.dense .filepick-file { min-height: var(--control-h-sm); padding-left: var(--space-3); }
.filepick.dense .filepick-name, .filepick.dense .filepick-state { font-size: var(--fs-xs); }
/* a file is being dragged over the window: every field that takes one says so,
   and the one under the pointer is filled */
.is-dragging-files .filepick { outline: 2px dashed var(--border-strong); outline-offset: 4px; border-radius: var(--radius-md); }
.is-dragging-files .filepick.is-over { outline-color: var(--accent); background: var(--accent-soft); }

/* the zone: the Inbox, several files at once */
.filepick.zone { flex-direction: column; justify-content: center; gap: var(--space-3);
  border: 2px dashed var(--border-strong); border-radius: var(--radius-lg); background: var(--surface-raised);
  padding: var(--space-6) var(--panel-pad-x); text-align: center; }
.filepick.zone .filepick-list { flex: none; width: min(560px, 100%); text-align: left; }
.filepick.zone .filepick-hint { font-size: var(--fs-xs); color: var(--text-muted); }
.filepick.zone.has-files, .filepick.zone.has-refused { border-style: solid; background: var(--surface); }
.filepick.zone.has-files .filepick-state { display: inline; font-size: var(--fs-sm);
  font-weight: var(--fw-semibold); color: var(--text); }
.filepick.zone.has-files .filepick-hint { display: none; }
.filepick .dz-icon svg { width: 26px; height: 26px; color: var(--accent); }
/* dragging over the zone: colour and one label over it; nothing moves, so the
   pointer keeps its target */
.is-dragging-files .filepick.zone { outline: 0; border-color: var(--accent); }
.is-dragging-files .filepick.zone.is-over { border-style: solid; }
.is-dragging-files .filepick.zone.is-over::after { content: attr(data-l-drop); position: absolute; inset: 0;
  display: grid; place-items: center; border-radius: inherit; background: var(--accent-soft); color: var(--accent);
  font-size: var(--fs-sm); font-weight: var(--fw-semibold); pointer-events: none; }
/* the type a whole upload is declared as, beside the upload button */
.door-type { display: inline-flex; align-items: center; gap: var(--space-3);
  font-size: var(--fs-xs); color: var(--text-secondary); }
.mailbox-poll { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-4) var(--panel-pad-x); background: var(--surface-raised); }
.cheque-thumb { display: block; width: 100%; max-width: 190px; border: 1px solid var(--border);
  border-radius: var(--radius-md); overflow: hidden; }
.cheque-thumb img { display: block; width: 100%; height: auto; }
.cheque-thumb.pdf, .cheque-thumb.none { display: inline-flex; align-items: center; gap: var(--space-3); padding: var(--space-4);
  text-decoration: none; color: var(--text-secondary); background: var(--surface-sunken); font-size: var(--fs-xs); }
/* a chip is a file name, and it takes the whole of its column (a picture keeps
   its 190px): the name ends in an ellipsis only where the column ends */
.cheque-thumb.pdf { max-width: 100%; }
.cheque-thumb.pdf svg { width: 18px; height: 18px; flex: none; }
/* the filename beside the icon truncates rather than being hard-clipped by the
   thumb's own overflow:hidden (a long name in a narrow thumb) */
.cheque-thumb.pdf span, .cheque-thumb.none span {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* In a 56px table cell the name ellipsed down to a single letter ("c…") —
   noise where the filename is already on the row and in the title. The thumb
   there is the icon alone (P2-10). */
td .cheque-thumb { max-width: 56px; }
td .cheque-thumb.pdf { justify-content: center; padding: var(--space-3); max-width: 56px; }
td .cheque-thumb.pdf span { display: none; }
.triage-row { display: grid; grid-template-columns: 210px minmax(0,1fr) auto; gap: var(--space-6); align-items: start;
  padding: var(--space-5) 0; border-top: 1px solid var(--rule); }
.triage-row:first-of-type { border-top: none; }
@media (max-width: 820px) { .triage-row { grid-template-columns: 1fr; } }
/* the cheques awaiting a word from Custody: the action of a row is a reference
   field and its Confirm, so the action column is as wide as the two */
table.grid[data-colset="settle"] { --colw-act: 18rem; }
table.grid[data-colset="settle"] > * > tr > .ct-act .formrow { justify-content: flex-end; }
/* the field and its Confirm give up their one line where the card is narrow, so
   the case keeps the room the two would have taken */
@media (max-width: 1140px) { table.grid[data-colset="settle"] .formrow.sm { flex-wrap: wrap; } }
/* the copy of a cheque, attached from its row on the case page */
.attach-scan { display: flex; gap: var(--space-3); margin-top: var(--space-3); }
.triage-preview { display: flex; flex-direction: column; gap: var(--space-3); }
/* the thumbnail column of a table of identified cheques */
td.w-thumb { width: 64px; }
/* the ways out of a row: a quiet move to the other side of the line, and the discard */
.triage-side { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); }
.tag-grid { display: grid; gap: var(--space-4) var(--space-5);
  /* columns from the form's OWN width, not the viewport's: at 1024 the
     triage row leaves it ~180px, and three columns of that were 50px
     fields — too narrow for an amount. Three where there is room. */
  grid-template-columns: repeat(auto-fill,
    minmax(max(100px, calc((100% - 2 * var(--space-5)) / 3 - 1px)), 1fr)); }
  /* (the 1px keeps three columns at three: exactly a third lands on the
     edge of auto-fill's floor() and rounding can take one away) */
.tag-grid label { display: flex; flex-direction: column; gap: var(--space-2); font-size: var(--fs-xs); color: var(--text-secondary); }
.tag-grid input, .tag-grid select { width: 100%; }
/* a waiting paper asks two things, the type and the case: two columns that
   hold a type's whole name, not the third of a form that asks six */
.tag-grid.paper-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 18rem)); }
/* and a new case made on the row asks for its four answers in the room the row
   has, not in the 210px label column of a full-width form */
.triage-form .form-grid { grid-template-columns: minmax(0, 6rem) minmax(0, 1fr); }
.triage-form .form-grid select, .triage-form .form-grid input { width: 100%; }
.reject-inline { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-top: var(--space-3);
  /* the Matching pane is narrow, and "Verwerpen" is longer than "Discard":
     the button goes under the reason rather than past the card */
  max-width: 100%; }
/* the reason beside its Discard is the step of the button, not a taller field */
.reject-inline > input { height: var(--control-h-sm); min-height: var(--control-h-sm); font-size: var(--fs-xs); }

/* the head of an assist block (the Inbox's intake mailbox) */
.assist-head { display: flex; align-items: center; gap: var(--space-3); color: var(--info); }
.assist-head svg { width: 16px; height: 16px; }
/* The case's own K-1s in the fund breakdown: the paper, what it is, where it
   stands and the way to its read-out. A quiet list at the width of its
   content, where a dashed box used to ask for the K-1 a second time. */
.k1papers { width: auto; max-width: 100%; margin-top: var(--space-4); }
.k1papers td { padding-left: 0; padding-right: var(--gap-section); }
/* a long file name with no space in it breaks inside the card, not out of it;
   the type, the state and the link stay whole */
.k1papers td:first-child { overflow-wrap: anywhere; }
.k1papers td:not(:first-child) { white-space: nowrap; }
.k1papers tr:last-child td { border-bottom: 0; }
.k1papers td.k1papers-act { padding-right: 0; white-space: nowrap; }

/* quarter-over-quarter comparison */
.qcompare td, .qcompare th { padding: var(--space-4) var(--space-4); }
.qbig { font-size: var(--fs-xl); font-weight: var(--fw-bold); letter-spacing: var(--tracking-tight); }
.qdelta { font-weight: var(--fw-semibold); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.qdelta.up { color: var(--ok); }
.qdelta.down { color: var(--danger); }
.qpct { font-weight: var(--fw-regular); font-size: 0.9em; }

/* grouped table rows (documents by client, etc.) */
tr.group-row td { background: var(--surface-raised); font-weight: var(--fw-semibold); color: var(--text-heading);
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label); padding: var(--space-3) var(--space-4); position: sticky; top: 0; }

/* ================= health =================
   One verdict under the page's name, then one typed table (table.grid): the
   areas, each with a state of its own, and under an area the checks that need
   a person. Nothing here sizes a column: the types do. The verdict is the
   page's one statement, so it is a step larger than a state in a row, and
   only its first part wears a tone. */
.verdict { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-5); font-size: var(--fs-md); }
.verdict .statusdot { font-size: inherit; }
.verdict .num { font-family: var(--font-figure); font-variant-numeric: tabular-nums lining-nums; font-weight: var(--fw-semibold); }
.verdict .muted { font-size: var(--fs-xs); }
/* an area's name is the head of its rows, set as body type: no capitals, no
   head band. Its weight is what tells it from the checks under it. */
table.grid > tbody > tr > th.hc-area { background: none; color: var(--text); font-size: inherit;
  font-weight: var(--fw-semibold); text-transform: none; letter-spacing: 0; }
/* what an area adds up to and what was verified read a step back; what a
   person has to do stays in ink */
table.grid > tbody > tr > .hc-sum,
table.grid[data-colset="health"] tr:is([data-state="ok"], [data-state="off"]) > .ct-text { color: var(--text-secondary); }
/* the line under a table: what else there is to see, and its one quiet action */
.tbl-foot { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: var(--space-2) var(--space-5); margin: var(--space-4) 0 0; font-size: var(--fs-xs); }
/* Column priority (the Cases idiom). Below 1440 the count and the date step
   aside and fold into the cells that stay, so the sentence keeps its width:
   the count rides on the state, the date ends the sentence. Folding only at
   1140 left the sentence 162px wide at 1141. */
@media (max-width: 1439px) {
  table.grid[data-colset="health"] :is(.col-count, .col-since) { display: none; }
  .foldin-count, .foldin-since { display: inline; }
}
/* Below the laptop width a typed column may give width back (typed columns,
   above). Here the name, the state and the action are as wide as what they
   hold and no wider, and the sentence gets the rest: 245px became 293px at
   1024, which is what keeps a Dutch sentence on two lines. */
@media (max-width: 1279px) {
  table.grid[data-colset="health"] > * > tr > :is(.ct-label, .ct-tag, .ct-act) { width: 1%; }
}
/* The backup is taken from its own row. Its form is a word in the action
   column like Open: inline, so it ends on the column's edge (a form in a cell
   is a flex row, which packed the button short of it), and as tall as a line
   of the row, not a 32px control. It keeps the house colour of a row
   mutation, which is what tells it from a way to another page. */
table.grid[data-colset="health"] > * > tr > .ct-act form.inline { display: inline; }
table.grid[data-colset="health"] > * > tr > .ct-act button.linklike { font-size: inherit; }
/* the foot's right side: when the team last heard, then Send now */
.tbl-foot-end { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-4); margin-left: auto; }

/* designed empty states */
/* the empty state lives in one place — see .emptystate further down */

/* K-1 ratio confirmation gate */
.ratio-confirm { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4);
  margin-top: var(--space-4); padding: var(--space-4) var(--panel-pad-x); border-radius: var(--radius-lg); border: 1px solid var(--border); }
.ratio-confirm.pending { background: var(--warn-soft); border-color: var(--warn-border); }
.ratio-confirm.ok { background: var(--ok-soft); border-color: var(--ok-border); }

/* period-close outstanding-items disclosure */
.disclosure { border: 1px solid var(--warn-border); background: var(--warn-soft); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); margin: var(--space-2) 0 var(--space-5); }
.disclosure-head { font-weight: var(--fw-semibold); color: var(--warn); display: flex; align-items: center; gap: var(--space-4); }
.disclosure table { background: var(--surface); border-radius: var(--radius-md); margin-top: var(--space-2); }

/* fund breakdown: aggregate-first, constituent lines always traceable */
.breakdown .fund-agg > td { border-top: 1.5px solid var(--border); background: var(--surface-raised); }
.breakdown .wl-line > td { border-bottom: 1px dashed var(--rule); }
/* the generated last cell of a typed table is part of its row: the aggregate
   row's fill and rule, and a line's dashed hairline, run through it to the edge */
table.grid.breakdown > * > tr.fund-agg::after { border-top: 1.5px solid var(--border); background: var(--surface-raised); }
table.grid.breakdown > * > tr.wl-line::after { border-bottom: 1px dashed var(--rule); }
.breakdown .wl-line.collapsed { display: none; }
.breakdown .wl-label { padding-left: var(--space-6); }
.lines-toggle { background: none; border: 1px solid var(--border); box-shadow: none; color: var(--accent);
  cursor: pointer; font-size: var(--fs-2xs); font-weight: var(--fw-semibold); padding: 1px var(--space-3); border-radius: var(--radius-sm); margin-left: var(--space-3); min-height: 0; }
.lines-toggle:hover { border-color: var(--accent); background: var(--accent-soft); }

/* ---- the case's three money tables are one set of columns (ALIGN 17) ----
   The settlement, the fund breakdown and the allocation of a cheque stand one
   above the other in one card, so they carry one data-colset: the fund (the
   portfolio, in the settlement), its model portfolio, its ratio, then the
   money, in that order in all three. The Ratio and the first amount stand on
   the same edges in every table and on every case (scripts/align_probe.py
   colDrift); the columns pack left against their labels and the generated last
   cell takes the slack.
   The card is narrow. The case keeps a 320px rail beside it, so the card is
   589px at 1280, 675 at 1366, 845 at 1536 and 1150 from 1840 up, and the
   typed widths of the system (840px for the settlement, the table with the
   most columns) would not fit it before 1536. So the set has three widths by
   viewport, each held rigid in the card it stands in: the settlement is 586px
   at 1280 (a six-figure amount with cents, 74.4px, is what the money columns
   are cut to), 648 from 1366 and 784 from 1536. Below 1280 the rail leaves a card
   of 409 to 588px, and the columns give width back as the system says.
   A name is words, so it wraps in its column instead of ending in an ellipsis
   (it also holds what is to be said about its row: a badge, a note), and a
   head may take two lines instead of holding its column open. */
table.grid[data-colset="casemoney"] { --colw-note: 6.25rem; --colw-code: 4rem; --colw-num: 3.25rem; --colw-amt: 4.7rem; --colw-tag: 4.5rem; }
@media (min-width: 1366px) {
  table.grid[data-colset="casemoney"] { --colw-note: 8.5rem; --colw-code: 4.5rem; --colw-num: 3.5rem; --colw-amt: 5rem; --colw-tag: 5rem; }
}
@media (min-width: 1536px) {
  table.grid[data-colset="casemoney"] { --colw-note: 12rem; --colw-code: 7.5rem; --colw-num: 4rem; --colw-amt: 5.5rem; --colw-tag: 6rem; }
}
table.grid[data-colset="casemoney"] > * > tr > th { white-space: normal; overflow-wrap: anywhere; hyphens: auto; }
table.grid[data-colset="casemoney"] > * > tr > td.ct-note { white-space: normal; max-width: none; overflow: visible; }
table.grid[data-colset="casemoney"] > * > tr > td.ct-code { white-space: normal; }
table.grid[data-colset="casemoney"] td:is(.ct-note, .ct-tag) .statusdot { white-space: normal; }
table.grid[data-colset="casemoney"] td:is(.ct-note, .ct-tag) .statusdot > :is(.sdot, .sdot-ic) { align-self: auto; }
/* the count under the booking column runs over that column and the slack, and
   wraps there: it asks the money columns for no room */
table.grid[data-colset="casemoney"] > * > tr.total > td[colspan] { white-space: normal; }
/* a row being edited: the amount keeps its field width (a figure is never cut
   in its own field) and its reason stands under it, filling that width (side
   by side they made the column 274px); Save and delete wrap in the action
   column. At 1280 that row is 17px wider than the card, where it was 100px */
table.grid[data-colset="casemoney"] > * > tr.editing > td.ct-amt > input.cell { display: block; }
table.grid[data-colset="casemoney"] > * > tr.editing > td.ct-amt > input.cell + input.cell { width: 100%; margin-top: var(--space-1); }
table.grid[data-colset="casemoney"] > * > tr.editing > td.ct-act { white-space: normal; }

/* model-portfolio settlement (filed → received → variance, the hero view) */
.settle-card { border-top: 2px solid var(--accent); }
.settle-hero { display: flex; align-items: stretch; gap: var(--space-4); flex-wrap: wrap; margin: var(--space-4) 0 var(--space-2); }
.settle-fig { flex: 1; min-width: 150px; background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-4) var(--panel-pad-x); }
.settle-fig .l { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--text-muted); font-weight: var(--fw-semibold); }
.settle-fig .v { font-size: var(--fs-2xl); font-weight: var(--fw-bold); letter-spacing: var(--tracking-tight); margin-top: var(--space-2); }
.settle-fig.filed .v { color: var(--series-2); }         /* filed/expected = ink-slate */
.settle-fig.recv .v { color: var(--accent); }             /* received/actual = teal */
.settle-fig.var.short { border-color: var(--warn-border); background: var(--warn-soft); }
.settle-fig.var.short .v { color: var(--warn); }
.settle-fig.var.over { border-color: var(--info-border); background: var(--info-soft); }
.settle-fig.var.over .v { color: var(--info); }
.settle-op { display: flex; align-items: center; font-size: var(--fs-xl); color: var(--text-muted); font-weight: var(--fw-regular); font-family: var(--font-figure); }
@media (max-width: 620px) { .settle-op { display: none; } }

/* demo access gate */
.access-form { max-width: 360px; }
.access-form label { display: block; font-size: var(--fs-xs); color: var(--text-secondary); font-weight: var(--fw-semibold); margin-bottom: var(--space-3); }
.access-form button { width: 100%; justify-content: center; }

/* error page */
.errorpage { max-width: 620px; margin: 8vh auto 0; text-align: center; padding: var(--space-8) var(--space-7); }

/* ================= the Trust page (public, outside the app shell) =================
   These rules lived in a <style> block of the page, in pixel sizes of their
   own; they stand here on the tokens. The page keeps ONE measure: the rows, the
   lists and the paragraphs of every section are as wide as the product's
   readable line (a row was 832px and a paragraph 545px). A row is a grid with a
   fixed first track, so the names of the items start on one left edge whether
   the badge says Live or Planned (they were 22.6px apart). */
.trust-wrap { max-width: calc(var(--measure) + 2 * var(--space-6)); margin: 0 auto;
  padding: var(--space-9) var(--space-6) var(--space-10); }
.trust-hero { margin-bottom: var(--space-7); }
.trust-hero h1 { font-size: var(--fs-2xl); margin-bottom: var(--space-3); }
.trust-section { margin-bottom: var(--space-7); }
.trust-section h2 { font-size: var(--fs-lg); margin-bottom: var(--space-2); }
.trust-section > p.muted { max-width: none; margin-bottom: var(--space-5); }
.trust-row { display: grid; grid-template-columns: var(--trust-label-w) minmax(0, 1fr);
  align-items: start; gap: var(--space-5); padding: var(--space-4) 0; border-bottom: 1px solid var(--border); }
.trust-row:last-child { border-bottom: none; }
.trust-row .badge, .trust-row .statusdot { margin-top: var(--space-1); }
.trust-row .t-name { font-weight: var(--fw-semibold); font-size: var(--fs-sm); }
.trust-row .t-note { color: var(--muted); font-size: var(--fs-xs); margin-top: var(--space-1); line-height: var(--lh-body); }
.trust-note-list { margin: 0; padding-left: var(--space-5); color: var(--muted); font-size: var(--fs-xs); line-height: var(--lh-body); }
.trust-cta { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5);
  background: var(--card); border: 1px solid var(--border); border-radius: var(--card-radius);
  padding: var(--space-5) var(--space-6); margin-top: var(--space-7); }
.trust-cta-title { font-weight: var(--fw-semibold); font-size: var(--fs-base); margin-bottom: var(--space-1); }
.trust-foot { margin-top: var(--space-8); color: var(--muted); font-size: var(--fs-xs); }
.trust-back { margin-bottom: var(--space-5); display: inline-block; }
.errorcode { font-size: var(--fs-4xl); font-weight: var(--fw-bold); color: var(--text);
             letter-spacing: 0.04em; font-family: var(--font-serif); }
.errorcode .glyph { color: var(--premium); }
.errorpage .actions { justify-content: center; }

/* ================= filing detail: two-column working layout (§9.4) ======== */

.crumbs { display: flex; align-items: center; gap: var(--space-3); font-size: var(--fs-xs); color: var(--text-muted); margin-bottom: var(--space-1); }
.crumbs a { color: var(--text-muted); text-decoration: none; }
.crumbs a:hover { color: var(--accent); }
.crumbs span { color: var(--border-strong); }

.filing-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--space-5); align-items: start; }
@media (max-width: 1100px) { .filing-layout { grid-template-columns: 1fr; } }
.filing-main { min-width: 0; }

.filing-rail { position: sticky; top: var(--space-4); display: flex; flex-direction: column; gap: var(--space-4); }
@media (max-width: 1100px) { .filing-rail { position: static; } }
.rail-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-5); }
.rail-card h3 { margin-bottom: var(--space-4); }
.rail-facts { margin: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.rail-facts > div { display: flex; justify-content: space-between; gap: var(--space-3); align-items: baseline; }
.rail-facts dt { color: var(--text-muted); font-size: var(--fs-xs); margin: 0; }
.rail-facts dd { margin: 0; font-weight: var(--fw-semibold); text-align: right; }
/* a fact with a note: its value stays one short phrase on the row's edge and the
   note stands on a line of its own under it */
.rail-facts > div.has-note { flex-wrap: wrap; }
.rail-facts dd.muted { font-weight: var(--fw-regular); }
.rail-facts dd.rail-fact-note { flex: 1 0 100%; text-align: left; }
.rail-facts dd.amt { font-variant-numeric: tabular-nums lining-nums; }
.rail-facts dd.neg { color: var(--figure-negative); }
.rail-note { margin: var(--space-4) 0 0; padding: var(--space-3) var(--space-4); background: var(--warn-soft); color: var(--warn); border-radius: var(--radius-md); font-size: var(--fs-xs); }
.rail-meta { margin: var(--space-3) 0 0; }
.rail-links { display: flex; flex-direction: column; gap: var(--space-3); }
.rail-links a { display: flex; align-items: center; gap: var(--space-3); text-decoration: none; color: var(--accent); font-size: var(--fs-sm); }
.rail-links a svg { width: 16px; height: 16px; }
.rail-links a:hover { text-decoration: underline; }

/* The sticky section tab strip is gone with the strip itself: a contents
   list for a page whose every section is now one line when folded, and its
   tenth entry never fit beside the rail at 1280 anyway. */

/* entry forms sit on a sunken sub-panel — visually distinct from the record (§9.4) */
.filing-main form.form-grid { background: var(--surface-sunken); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: var(--space-4) var(--panel-pad-x); margin-top: var(--space-3); }

/* ================= filing sign-off ================= */

/* the two signatures side by side (four eyes): who declared the case
   complete and who signed it off, each with a name and a date, or the open
   slot saying who may still sign. A rule on the left of each slot: ink while
   open, green when signed, brass when both are on (the certificate). */
.signoff { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); margin: 0; }
.signoff-slot { min-width: 0; padding-left: var(--space-3); border-left: 2px solid var(--rule); overflow-wrap: anywhere; }
.signoff-slot.done { border-left-color: var(--ok); }
.signoff.sealed .signoff-slot { border-left-color: var(--premium); }
.signoff-slot dt { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-secondary); }
.signoff-slot dd { margin: 0; }
.signoff-who { font-weight: var(--fw-semibold); }
.signoff-act { margin-top: var(--space-4); }
/* the readiness gate's reason under a disabled Declare complete, or the quiet
   note that the state has no checklist (owner 17-10) */
.signoff-gate { margin: var(--space-2) 0 0; }
.signoff-lock { margin: var(--space-3) 0 0; }
.signoff-revoke { margin-top: var(--space-2); }
/* its form is one inline form row that stacks (.formrow.sm.stack): the field
   the width of the rail, the button under it as wide as its word, in the 28px
   step of Declare complete and Sign off; a local flex row would wrap it
   differently in each language */
/* the header's sign-off mark is a way to the block, not a link to read */
.signoff-mark { text-decoration: none; color: inherit; }

/* ================= reconciliation center ================= */

.recon-funnel { display: flex; align-items: flex-end; gap: var(--space-4); padding: var(--space-3) var(--space-2) 0; }
.funnel-step { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); min-width: 84px; }
.funnel-bar { width: 54px; background: var(--series-1); border-radius: var(--radius-sm) var(--radius-sm) 0 0; min-height: 4px; }
.funnel-n { font-size: var(--fs-md); font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; }
.funnel-l { font-size: var(--fs-2xs); color: var(--text-muted); text-align: center; }
.funnel-arrow { color: var(--text-muted); font-size: var(--fs-md); padding-bottom: var(--space-7); }

.recon-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
  background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--radius-lg);
  font-size: var(--fs-2xs); color: var(--text-muted);
}
.recon-legend-item { display: flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.recon-legend-item .recon-tick { margin-right: 0; cursor: default; }
.recon-ticks { white-space: nowrap; }
/* in the tie-out's chain table the ticks are a typed column as wide as its six
   marks (6 x 28px) and its Dutch head, and no wider: a fixed 230px took the
   cheque number's room at 1024 in Dutch */
table.grid[data-colset="tieout"] { --colw-tag: 11.75rem; }
.recon-tick { display: inline-block; width: 26px; text-align: center; font-size: var(--fs-sm); border-radius: var(--radius-sm); padding: 1px 0; margin-right: var(--space-1); }
.recon-tick.ok { color: var(--ok); background: var(--ok-soft); }
.recon-tick.todo { color: var(--warn); background: var(--warn-soft); }
.recon-tick.na { color: var(--text-muted); }

/* ================= ledger trend view ================= */
/* categorical series: teal, steel, brass, plum, ink-slate, sage (§8) */
.swatch.t0, .stackseg.t0 { background: var(--series-1); }
.swatch.t1, .stackseg.t1 { background: var(--series-2); }
.swatch.t2, .stackseg.t2 { background: var(--series-3); }
.swatch.t3, .stackseg.t3 { background: var(--series-4); }
.swatch.t4, .stackseg.t4 { background: var(--series-5); }
.swatch.t5, .stackseg.t5 { background: var(--series-6); }

.stackbar {
  display: flex;
  height: 18px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface-sunken);
}
.stackseg { height: 100%; min-width: 1px; transition: opacity 0.1s; }
.stackseg:hover { opacity: 0.8; }
.trendtbl td { vertical-align: middle; }

/* ================= language switch ================= */

.langswitch { display: flex; gap: var(--space-2); margin-top: var(--space-2); }
.side-user .langswitch { flex: none; margin: 0; }
.langswitch .lang {
  background: rgba(255, 255, 255, 0.06);
  color: var(--nav-fg-muted);
  border: 1px solid var(--border-dark);
  border-radius: var(--radius-sm);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-semibold);
  padding: 0 var(--space-3);
  cursor: pointer;
  box-shadow: none;
  /* the tertiary step: a micro toggle, named rather than left to fall out of
     padding (it measured 17px, which was on no scale at all) */
  height: var(--control-h-xs);
  min-height: var(--control-h-xs);
}
.langswitch .lang.on { background: var(--accent-on-dark); color: var(--accent-ink); border-color: transparent; }
/* It had neither: the base button:hover lost to this rule's own background,
   so the one control in the sidebar that a person clicks to change the whole
   app's language gave no sign it could be clicked at all. */
.langswitch .lang:hover { background: rgba(255, 255, 255, 0.14); color: var(--nav-fg); }
.langswitch .lang.on:hover { background: var(--accent-on-dark); color: var(--accent-ink);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18); }
.langswitch .lang:active { transform: translateY(1px); }
.langswitch .lang:focus-visible { outline: 2px solid var(--accent-on-dark);
  outline-offset: 2px; }


/* ================= login (branded split panel) ================= */

.login-body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  background: var(--bg);
}

.login-panel {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);          /* 0 — flat panel, no floating card */
  box-shadow: none;                          /* P6: no drop-shadow */
  overflow: hidden;
  max-width: 720px;
  width: 100%;
  /* <main class="login-panel"> otherwise inherits main{padding} (24/32/64px),
     which drew an off-centre white frame between border and panels. */
  padding: 0;
  margin: 0;
}

.login-brand {
  background: var(--surface-dark);          /* flat petrol-ink, no gradient */
  color: var(--nav-fg);
  padding: var(--space-7) var(--space-6) var(--space-6);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-6);
  border-right: 2px solid var(--premium-rule);   /* brass register hairline */
}
.login-brand h1 { color: #fff; font-size: var(--fs-xl); margin: var(--space-4) 0 var(--space-3); letter-spacing: -0.01em; font-family: var(--font-serif); }
.login-tag { font-size: var(--fs-xs); line-height: 1.6; color: var(--nav-fg-muted); margin: 0; }
.login-foot { font-size: var(--fs-2xs); color: var(--nav-fg-muted); margin: 0; }

.login-form { padding: var(--space-6) var(--space-6) var(--space-6); }
.login-form h2 { margin-bottom: var(--space-4); }

/* Login/2FA/set-password forms: the shared .form-grid keeps a 210px label
   column, which in the ~400px login column collapsed every field to ~80px and
   forced the submit button to wrap. Here the label sits ABOVE a full-width
   field, and the button spans the column. Robust down to phone width. */
.login-form .form-grid {
  display: block;
  max-width: none;
}
.login-form .form-grid > label {
  display: block;
  min-height: 0;
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}
.login-form .form-grid > label:first-of-type { margin-top: 0; }
.login-form .form-grid > input,
.login-form .form-grid > select { width: 100%; }
/* the empty grid spacer before the button is not needed in a block layout */
.login-form .form-grid > div:empty { display: none; }
.login-form .form-grid > button[type="submit"] {
  margin-top: var(--space-5);
  width: 100%;
  justify-content: center;
}

.login-users { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-5); }
.login-user {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  padding: var(--space-4) var(--space-4);
  text-align: left;
  box-shadow: none;
}
.login-user:hover { border-color: var(--accent); box-shadow: var(--elev-1); }
.login-user-name { font-weight: var(--fw-semibold); flex: 1; }
/* the bar column of a pivot table — wide enough to read, never wider: a bar is
   a picture of the figure beside it, and the name keeps the room (at 40% a
   fund's name broke over two lines in the half-width card of Insights at 1280,
   and Top funds was taller than Top model portfolios; at 24% it keeps one) */
.pivot .barcell { width: 24%; min-width: 90px; }
/* the dataset's booking lines: eleven columns in a card, so the cheque number,
   the jurisdiction and the portfolio are as wide as their words need */
table.grid[data-colset="bookinglines"] { --colw-code: 5.5rem; --colw-note: 7.5rem; --colw-tag: 6.5rem; }
/* and they give width back at every width, not only at the laptop's (the rows of
   this register are long: a fund, a portfolio with the one it was redirected
   from, a status like "Booked (pending export)"): the fund takes what the rest
   leave and ends in an ellipsis, and the table scrolled by up to 158px between
   1281 and 1440 when the types held their widths */
table.grid[data-colset="bookinglines"] > * > tr > .ct-text { min-width: 0; width: 100%; }
table.grid[data-colset="bookinglines"] > * > tr > th { white-space: normal; }
table.grid[data-colset="bookinglines"] > * > tr > :is(.ct-code, .ct-date, .ct-key, .ct-note, .ct-tag, .ct-num, .ct-amt) { width: auto; }

/* ================= the archive (§10.a) ================= */

.archivelist tr.is-off td { opacity: 0.6; }
.archivelist tr.is-off a { text-decoration: line-through; }

/* what one search found that is not a document */
.alsogrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--gap); }
.hitlist { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.hitlist li { padding: var(--space-2) 0; border-bottom: 1px solid var(--rule);
  font-size: var(--fs-xs); display: flex; gap: var(--space-3);
  justify-content: space-between; align-items: baseline; }
.hitlist li:last-child { border-bottom: 0; }

/* The administration panel sits in the narrow column of the two-column page,
   so its forms stack: label over field, and a button on its own full-width
   line rather than squeezed into a 120px grid cell. */
.holdform, .deleteblock .form-grid {
  display: flex; flex-direction: column; align-items: stretch;
  gap: var(--space-3); max-width: 100%; }
.holdform label, .deleteblock label {
  display: block; min-height: 0;
  font-size: var(--fs-3xs); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--text-muted);
  font-weight: var(--fw-semibold); }
.holdform > div:empty, .deleteblock .form-grid > div:empty { display: none; }
/* A button is as wide as its label, at the start of its line; the long ones
   of the narrow column wrap inside it rather than leave it. */
.holdform button, .deleteblock button { width: auto; max-width: 100%; align-self: flex-start;
  white-space: normal; }
/* the forms of the panel are told apart by one rule: the one the delete block
   draws above itself, between every two of them */
.holdform + .holdform, .deleteblock { margin-top: var(--gap); padding-top: var(--gap);
  border-top: 1px solid var(--border); }

/* a hash is 64 characters and belongs on one line, wrapping where it must */
.cover-facts td.hash-cell { font-family: var(--font-mono); font-size: var(--fs-3xs);
  word-break: break-all; line-height: var(--lh-tight); }

/* ================= report bundle (§10.d) =================
   The cover is a page somebody reads on paper a year later, so it is set
   wider and quieter than a working screen. */

.cover { max-width: 760px; }
.cover-head { display: flex; align-items: center; gap: var(--space-5);
  padding-bottom: var(--space-5); border-bottom: 2px solid var(--accent); }
.cover-head h1 { margin: 0; font-size: var(--fs-2xl); }
.cover-org { font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--text-muted);
  font-weight: var(--fw-semibold); }
.cover-for { margin: var(--space-6) 0 var(--space-2); font-size: var(--fs-md); }
.cover-what { margin: 0 0 var(--space-6); color: var(--text-secondary);
  max-width: 62ch; line-height: var(--lh-body); }
.cover h2 { font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--text-muted);
  margin: var(--space-6) 0 var(--space-3); }
.cover-facts { width: 100%; border-collapse: collapse; }
/* The head of a row is the name of the fact, not a column head: it has none of
   the head's band, capitals and tracking, a gutter between it and its value,
   and the value's baseline (a row head hung from the top stood 3px off it). */
.cover-facts th { width: 190px; text-align: left; font-weight: var(--fw-medium);
  color: var(--text-secondary); background: none; font-size: inherit;
  text-transform: none; letter-spacing: normal;
  padding: var(--space-2) var(--space-5) var(--space-2) 0; }
.cover-facts td { padding: var(--space-2) 0; }
.cover-list { width: 100%; font-size: var(--fs-xs); }
.cover .caveat { margin-top: var(--space-6); }

/* the state of the file (§8.5): the cover's setting, and on paper one case
   per page, so each page can be filed with its case */
.sof-count { margin: var(--space-5) 0 0; color: var(--text-secondary); }
.sof-case { margin-top: var(--space-6); padding-top: var(--space-5);
  border-top: 1px solid var(--border); }
.cover .sof-case-name { margin: 0 0 var(--space-3); font-size: var(--fs-md);
  text-transform: none; letter-spacing: 0; color: var(--text); }
.sof-verdict { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-3); margin: 0 0 var(--space-4); }
.sof-file { font-family: var(--font-mono); font-size: var(--fs-3xs); word-break: break-all; }
@media print {
  .sof-case { break-before: page; margin-top: 0; padding-top: 0; border-top: 0; }
}

/* the "building…" strip: one line that replaces itself with the download */
.bundlejob { display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--gap);
  border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background: var(--surface); font-size: var(--fs-xs); }
.bundlejob .spin { width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 2px solid var(--accent-border); border-top-color: var(--accent);
  animation: bundlespin 0.9s linear infinite; }
@keyframes bundlespin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .bundlejob .spin { animation: none; } }

/* the preset picker: three recipients, each saying what it sends */
.presetpick { display: grid; gap: var(--space-3); margin: var(--space-4) 0; }
.presetpick label { display: grid; grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4); align-items: start;
  padding: var(--space-4); border: 1px solid var(--border);
  border-radius: var(--radius-md); cursor: pointer; }
.presetpick label:hover { border-color: var(--accent-border); background: var(--surface-raised); }
.presetpick input { width: auto; margin-top: 2px; }
.presetpick b { font-size: var(--fs-sm); }
.presetpick .what { grid-column: 2; margin: 0; color: var(--text-muted);
  font-size: var(--fs-xs); line-height: var(--lh-body); }
/* The bundle panel is a small form, not a list of links: it needs real width,
   and it must not run off the right edge of the window on a narrow screen. */
.exportmenu-list.bundleform { width: 430px; max-width: calc(100vw - var(--space-7));
  padding: var(--space-5); gap: var(--space-3); }
.bundleform .fieldlabel { font-size: var(--fs-3xs); font-weight: var(--fw-semibold);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted);
  margin-top: var(--space-2); }
.bundleform .row { display: flex; gap: var(--space-4); align-items: flex-end;
  flex-wrap: wrap; margin-bottom: var(--space-4); }
.bundleform .row .fixed { font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  text-transform: none; letter-spacing: 0; color: var(--text);
  padding: var(--space-3) 0; }
.bundleform .row label { display: flex; flex-direction: column; gap: var(--space-2);
  font-size: var(--fs-3xs); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--text-muted);
  font-weight: var(--fw-semibold); }
.bundleform .row select, .bundleform .row input { width: auto; min-width: 120px; }
/* The library's ZIP panel: the three filters of the list, what the file will
   hold, and the one button. It hangs from the right end of the count line and
   keeps inside the window, as the bundle panel does. */
.exportmenu-list.zipform { width: 352px; max-width: calc(100vw - var(--space-7));
  padding: var(--space-5); gap: var(--space-3); }
.zipmenu > summary .zipmenu-caret { display: inline-flex; margin-left: var(--space-2); color: var(--text-muted); }
.zipmenu > summary .zipmenu-caret svg { width: 12px; height: 12px; }
.zipmenu[open] > summary { border-color: var(--accent); color: var(--accent); }
.zipform-filters { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-4); }
.zipform-note { margin: 0 0 var(--space-3); }

/* ================= page sections (Periods & reports §5.4) =================
   A screen that carries three separate jobs shows one of them at a time. Each
   is a real URL, so the tabs are links and the back button works. */

.pagetabs { display: flex; flex-wrap: wrap; gap: var(--space-1);
  margin: 0 0 var(--gap); border-bottom: 1px solid var(--border); }
/* A tab is a 32px control (the house height), so the title line, the tabs and
   the first bar under them are three rows of one height. The tab in view
   carries two marks: the ink label and the green line. Its weight does not
   change, so a tab is as wide lit as unlit and the row never shifts when the
   view does. Focus keeps the house ring. */
.pagetabs a { display: inline-flex; align-items: center; box-sizing: border-box;
  height: var(--control-h); padding: 0 var(--space-5); white-space: nowrap;
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  text-decoration: none; color: var(--text-secondary);
  border-bottom: 2px solid transparent; margin-bottom: -1px; }
.pagetabs a:hover { color: var(--text); }
.pagetabs a:active { color: var(--accent-active); }
.pagetabs a.active { color: var(--text); border-bottom-color: var(--accent); }

/* On paper the tab in view is the title of the sheet (Reports, not Periods &
   reports) and the page's own name is a small line above it. This block stands
   directly after the rules above: inside the general print block it would lose
   to the lit rule. */
@media print {
  .pagetabs { border-bottom: 0; margin-bottom: var(--space-4); }
  .pagetabs a:not(.active) { display: none; }
  .pagetabs a.active { height: auto; padding: 0; margin: 0; border: 0;
    font-family: var(--font-serif); font-size: var(--fs-2xl);
    font-weight: var(--fw-semibold); color: var(--text); }
  .page-head:has(+ .pagetabs) { margin-bottom: var(--space-2); }
  .page-head:has(+ .pagetabs) h2 { margin-bottom: 0; font-family: var(--font-sans);
    font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-secondary); }
}

/* The Overview's slice on paper (the screen rules are with the Overview's):
   after the general print block, which hides every form and every list of
   filters in force. The boxes stay hidden; the filters in force print as plain
   text, and a sheet that is not sliced prints no card at all. The switch of a
   view's first line prints only the section in view, and its link to another
   page is not printed. */
@media print {
  .ovslice { display: block !important; }
  .ovslice:not(:has(.active-filters)) { display: none !important; }
  .ovslice .filterbar { display: none !important; }
  .ovslice .active-filters { display: flex !important; margin-top: 0; padding-top: 0; border-top: 0; }
  .ovslice .active-filters a { display: none !important; }
  .viewbar { min-height: 0; margin-bottom: var(--space-3); }
  .viewbar .viewtoggle { border: 0; }
  .viewbar .viewtoggle a:not(.on) { display: none; }
  .viewbar .viewtoggle a.on { padding: 0; background: none; font-weight: var(--fw-medium); }
  .viewbar > .rowlink { display: none; }
}

/* The close form stands folded: quarter-end is a deliberate act, not a set of
   fields you scroll past forty times between closes. */
.closeform { margin-top: var(--gap); }
.closeform > summary { display: inline-flex; }
.closeform[open] > summary { margin-bottom: var(--space-4); }
.closeform .form-grid { max-width: 560px; }

/* Add / register forms fold the same way: the list is the page and the form
   opens on a click, so a screen is not tailed by an open form every visit
   (§3.4). The summary is a normal-width button, not a full-row block. */
.addform > summary { display: inline-flex; width: auto; }
.addform .form-grid { max-width: 560px; }
/* A fold inside a form (the new-case details of an upload) spans both columns
   and its own grid is the same two tracks at the same width, so its labels and
   its fields stand on the parent's two edges instead of a second pair. The
   empty cell that held its place beside the case choice is not needed. */
.form-grid > .addform { grid-column: 1 / -1; margin-top: 0; padding-top: 0; border-top: 0; }
.form-grid .form-grid { max-width: none; }
.form-grid > div:empty:has(+ .addform) { display: none; }

/* A list of things you open or export: a hairline per row, no table chrome. */
.hairlist td { padding: var(--space-4) 0; vertical-align: middle; }
.hairlist tr:last-child td { border-bottom: 0; }

/* ================= users (admin only) =================
   One row per account: who they are, what their role is, whether they can get
   in, and when they were last here. Everything you can DO to an account lives
   behind one "Manage" disclosure, so the list stays a list. */

/* a deactivated account is dimmed in its text cells, not in the cell that
   holds Manage: opacity on that cell made the open panel see-through */
.userlist tr.is-off > td:not(.rowacts) { opacity: 0.62; }
.userlist tr.is-off td b { font-weight: var(--fw-medium); }

/* The panel floats over the rows below it. That only works because this table
   is NOT wrapped in .tbl-wrap (overflow: auto), which clips a popover — five
   narrow columns never need to scroll anyway. */
.useredit { position: relative; }
.useredit > summary { list-style: none; cursor: pointer; display: inline-flex; }
.useredit > summary::-webkit-details-marker { display: none; }
.useredit-body {
  position: absolute; right: 0; top: calc(100% + var(--space-2)); z-index: var(--z-dropdown);
  /* the panel lives inside td.rowacts, which is right-aligned and nowrap —
     both inherit into it and both are wrong here */
  width: 470px; max-width: 86vw; text-align: left; white-space: normal;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  column-gap: var(--space-3); row-gap: var(--space-5); align-items: start;
  padding: var(--space-5); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--elev-2);
}
/* The panel is ONE grid of three tracks (label, field, Save) and every block
   in it spans them: a form is a row of that grid (a subgrid), so the labels
   start on one edge, the fields on one edge and every Save on one edge, where
   the role, team and expiry forms each laid themselves out (the Save stood on
   three x positions, and a select was capped at 180px by the table cell the
   panel lives in). Fields and buttons are one control step, the 28px of a
   field inside a row. */
.useredit-body > * { grid-column: 1 / -1; }
.useredit-body > form.inline { display: grid; grid-template-columns: subgrid;
  row-gap: var(--space-3); align-items: center; }
.useredit-body > form.inline > :is(input, select):not([type="hidden"]) {
  width: 100%; min-width: 0; max-width: none;
  height: var(--control-h-sm); min-height: var(--control-h-sm);
  padding: 0 var(--space-3); font-size: var(--fs-xs); border-radius: var(--radius-sm); }
/* the role takes the field and Save tracks; its reason is the row under it */
.useredit-body > .roleform > select { grid-column: 2 / -1; }
.useredit-body .fieldlabel {
  font-size: var(--fs-3xs); font-weight: var(--fw-semibold); letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-muted); }
/* min-width:0 or a long sentence refuses to shrink and runs out of the panel */
.useredit-body .rolewhat { grid-column: 1 / -1; min-width: 0; margin: 0; }
.useredit-body input.date { width: var(--field-w-date); }
.useredit-body .formrow > input { height: var(--control-h-sm); min-height: var(--control-h-sm);
  padding-top: 0; padding-bottom: 0; font-size: var(--fs-xs); border-radius: var(--radius-sm); }
/* the reason for a role change (security G5) is the row under the select,
   with the Save it belongs to at its end */

/* the panel is exempt from the cap a select has in a table cell (180px): it
   cut "Tax consultant (external)" and the Dutch Pending, and it is why the
   linked client and the role were two widths. */
.useredit-body select { max-width: none; }

/* The permission list (templates/_perms.html): an account's row of the access
   matrix stood upright. The list names its two fixed tracks once, from
   tokens; between them the permission takes the slack and a note is as wide
   as the widest in the list, so every mark, name, note and action stands on
   one edge. A line is one small control high; the hairline between lines is
   an inset shadow, so it costs no height. A tick is ink and a cross the quiet
   grey: no red and no green at rest, so the safest accounts do not read as a
   list of errors. */
.permlist { --pm-mark: var(--colw-mark); --pm-act: var(--colw-act-word);
  display: flex; flex-direction: column; }
.pm-head { display: flex; align-items: center; gap: var(--space-3);
  min-height: var(--control-h-sm); }
.pm-sum { margin-left: auto; font-size: var(--fs-xs); color: var(--text-muted); }
/* line-height normal, the one a button has by itself: a name, its mark and
   the word on its button then share one centre line (at 1.4 the names stood
   0.9px above their buttons) */
.pm-row { position: relative; display: flex; align-items: center; gap: var(--space-3);
  height: var(--control-h-sm); font-size: var(--fs-xs); line-height: normal;
  box-shadow: inset 0 1px 0 var(--border); }
.pm-mark { text-align: center; font-weight: var(--fw-semibold); }
.pm-row > .pm-mark { flex: none; width: var(--pm-mark); }
.pm-what { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pm-sub { font-size: var(--fs-2xs); color: var(--text-muted); }
.pm-note { flex: none; display: inline-flex; align-items: center; }
/* an action's box ends on the list's right edge, the edge Save ends on */
.pm-act { flex: none; width: var(--pm-act); display: flex; justify-content: flex-end; align-items: center; }
.pm-scope > .pm-what { color: var(--text-muted); }
.pm-val { flex: none; color: var(--text-secondary); }
.pm-none { color: var(--text-500); }
.pm-row.is-yes .pm-mark { color: var(--ink); }
.pm-row.is-no .pm-mark { color: var(--text-500); }
.pm-row.is-no .pm-what { color: var(--text-secondary); }
/* Where the browser can, the lines are rows of one grid: each takes the
   list's four tracks, so a note in one line moves no name in another. Without
   subgrid a line is the flex row above, on the same two fixed tracks. */
@supports (grid-template-columns: subgrid) {
  .pm-rows { display: grid; column-gap: var(--space-3);
    grid-template-columns: var(--pm-mark) minmax(0, 1fr) auto var(--pm-act); }
  .pm-row { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; }
  .pm-row > .pm-mark, .pm-row > .pm-act { width: auto; }
  .pm-scope > .pm-val { grid-column: 3 / -1; justify-self: end; }
}
/* Revoke is one click. Grant is a fold: a grant says why (security G5), so
   it opens its reason on the line's second row, from the name's edge to the
   list's, and its summary then reads Cancel. The line grows downwards by its
   padding, so the first row stays where it was. */
.pm-grant > summary { list-style: none; cursor: pointer; }
.pm-grant > summary::-webkit-details-marker { display: none; }
.pm-grant > summary .when-open, .pm-grant[open] > summary .when-closed { display: none; }
.pm-grant[open] > summary .when-open { display: inline; }
.pm-row:has(.pm-grant[open]) { height: calc(2 * var(--control-h-sm) + var(--space-3));
  padding-bottom: calc(var(--control-h-sm) + var(--space-3)); }
.pm-grant[open] > .pm-why { position: absolute; left: calc(var(--pm-mark) + var(--space-3)); right: 0;
  bottom: var(--space-3); display: flex; align-items: center; gap: var(--space-3); }
/* the reason is one small control high, like the lines of the list */
.permlist .pm-why input[name="reason"] { flex: 1 1 auto; min-width: 0; width: auto;
  height: var(--control-h-sm); min-height: 0; font-size: var(--fs-xs); }
/* While another role is chosen in the fold's role form, the list is what Save
   would give (static/rolepreview.js paints it from the chosen option). A line
   that changes says so where its control stood: the controls belong to the
   saved role and stand aside, and the word ends on the edge they ended on,
   so nothing moves. Colour appears now and only now: every cross of the
   chosen role is red, and so are the words for what is taken away; a tick
   and "new" stay ink. At rest the list has no red, so the safest account is
   not a list of errors. The note of an exception stays only where the
   exception survives the move. */
.pm-change { white-space: nowrap; }
.permlist.is-preview .pm-act > :not(.pm-change) { display: none; }
.permlist.is-preview .pm-row.is-no .pm-mark,
.permlist.is-preview .pm-row.is-loss .pm-change { color: var(--danger); }
.permlist.is-preview .pm-row.is-gain .pm-what,
.permlist.is-preview .pm-row.is-loss .pm-what,
.permlist.is-preview .pm-scope.is-gain > .pm-val,
.permlist.is-preview .pm-scope.is-loss > .pm-val { color: var(--text); font-weight: var(--fw-medium); }
.permlist.is-preview .pm-row.is-dropped .pm-note > * { display: none; }
/* the status in the head counts what the choice changes. When it is longer
   than the head offers it takes a second line, set tight so the head keeps
   its height, with its lamp on the first; the label beside it never breaks */
.pm-label { flex: none; }
.pm-head > .pm-sum { min-width: 0; text-align: right; line-height: var(--lh-tight); text-wrap: balance; }
.pm-sum .statusdot { white-space: normal; align-items: baseline; text-align: left; text-wrap: balance; }
/* a summary that wraps keeps its lamp on its first line, not centred on all of them */
.pm-sum .statusdot > :is(.sdot, .sdot-ic) { align-self: auto; }
/* where the access of a role that is no permission is given: one quiet line
   under Scope, there only while such a role is chosen */
.pm-hint { margin: 0; font-size: var(--fs-xs); color: var(--text-muted); text-align: right; }
.pm-hint:empty { display: none; }
/* a choice the last-administrator rule would refuse: the list stays on the
   saved role, and the rule's sentence takes the width of the list under the
   head, with Cancel where it always stands */
.permlist.is-refused .pm-head { flex-wrap: wrap; }
.permlist.is-refused .pm-sum { order: 1; flex-grow: 1; margin-left: 0; text-align: left;
  line-height: var(--lh-dense); }
.permlist.is-refused .pm-undo { margin-left: auto; }
/* A ghost button showed no keyboard focus anywhere in the product: its own
   rule (box-shadow: none) outranked the page's :focus-visible ring. Written
   here, beside the list whose Grant, Revoke and Cancel made it count; it
   holds for every ghost button. */
button.ghost:focus-visible, .btn.ghost:focus-visible { box-shadow: var(--focus-ring); }
/* Below the laptop width the list of accounts takes the gutters of the dense
   registers, so its seven columns stay inside their card in Dutch as well. */
@media (max-width: 1140px) {
  .userlist th, .userlist td { padding-left: var(--space-2); padding-right: var(--space-2); }
}

.useracts { display: flex; flex-wrap: wrap; gap: var(--space-3);
  border-top: 1px solid var(--border); padding-top: var(--space-4); }

/* the one-time first-password link: shown once, never stored */
.invitecard .caveat { margin-bottom: var(--space-4); }
.invitelink { display: flex; gap: var(--space-3); align-items: center; }
.invitelink input { flex: 1; font-family: var(--font-mono); font-size: var(--fs-2xs); }

.adduser { max-width: 560px; }

/* the access matrix (security G5): account types and people against every
   permission. A held right is a tick in ink, a missing one a cross in the
   quiet grey (the marks of the permission list above, templates/_perms.html);
   colour only where a grant has no recorded reason. A permission column is
   as narrow as its head allows, and the privileged one sits on a sunken
   ground so the line between running the work and running the organisation
   reads at a glance.
   ONE table for both groups (data-colset "matrix"), so the permission columns
   are shared by construction; they are the tick type (ct-chk), one width from
   one rule, with their marks centred and no padding of their own. Two tables
   had these columns at 54 to 146px wide and their marks 88 to 139px apart. A
   person is one line: the role has its own column, an exception is a ring
   round the mark (its words are the mark's label and its tooltip, and a word
   would widen the column), and the reason and who set it follow on the line. */
table.grid[data-colset="matrix"] { --colw-chk: 2rem; --colw-label: 12rem; --colw-tag: 9rem; --colw-note: 7rem; }
.matrix th.mx-c, .matrix td.mx-c { text-align: center; white-space: nowrap; padding-left: 0; padding-right: 0; }
.matrix thead th { vertical-align: bottom; }
.matrix tr.mx-group > th { vertical-align: baseline; }
/* the whole matrix is the page: the people stand in the table as the sign-off
   below confirms them, so it does not scroll inside a box of 72vh (twenty rows
   and a head of 144px are taller than that at 1280x800) */
.tbl-wrap:has(> table.matrix) { max-height: none; }
/* a scope may be wider than its token (a count with its badge) and a name may take
   a second line (a name with its team): they are not held to the one line of their type */
table.grid[data-colset="matrix"] > * > tr > td.mx-scope { max-width: none; overflow: visible; text-overflow: clip; }
table.grid[data-colset="matrix"] > * > tr > td.mx-who { white-space: normal; max-width: none; overflow: visible; text-overflow: clip; }
/* (an action link in a table never breaks, and a person's name is one: a long name takes a second line) */
.matrix td.mx-who a.rowlink { white-space: normal; }
/* "external" after a person's role is said by the type row above and by the
   tooltip: at the laptop width the cell holds the role alone */
@media (max-width: 1360px) { .matrix td.ct-tag .mx-ext { display: none; } }
/* at the laptop width the words take the gutters of the dense registers (4px a
   side): with the Dutch names the table was 4px past its card at 1280 */
@media (max-width: 1280px) {
  table.grid.matrix { --ct-pad: calc(2 * var(--space-2)); }
  .matrix th:not(.mx-c), .matrix td:not(.mx-c) { padding-left: var(--space-2); padding-right: var(--space-2); }
}
/* A permission head stands upright along its column, reading upwards from
   the ticks (security G7). A flag column then costs the width of a tick, not
   of its longest word, so the grid fits its card in Dutch as in English and
   a permission added later costs one more narrow column. Set flat, two more
   columns put the Dutch grid 192px past its card at 1280; with the shortest
   honest Dutch heads it was still 57px over, and breaking a word ("LE-ZEN")
   is not an option. A head of several words takes a second or third line
   beside the first, at a space only: nothing here hyphenates. The cap is the
   room two words need side by side ("STRIKT VERTROUWELIJKE"): a head is never
   more than two lines, so a column is 32px (at 6.5rem the longest Dutch head
   took three lines and the column 36px). */
.matrix thead th.mx-c .mx-h { display: inline-block; writing-mode: vertical-rl;
  transform: rotate(180deg); max-height: 9rem; white-space: normal;
  text-align: start; line-height: 1.2; hyphens: manual; overflow-wrap: normal;
  word-break: normal; }
.matrix .mx-sub { display: block; margin-block-start: var(--space-1); font-weight: var(--fw-regular);
  text-transform: none; letter-spacing: 0; color: var(--text-muted); }
.matrix .mx-priv { background: var(--surface-sunken); }
.matrix .mx-on { color: var(--ink); font-weight: var(--fw-semibold); }
.matrix .mx-off { color: var(--text-500); }
/* an exception: the ring is drawn outside the box, so a row stays the height of its line */
.matrix .mx-ring { display: inline-block; line-height: 1; outline: 1px solid currentColor;
  outline-offset: 1px; border-radius: var(--radius-xs); }
.matrix .mx-ring.is-unexplained, .matrix .mx-ring.is-unexplained > * { color: var(--warn); }
.matrix .mx-why { overflow-wrap: anywhere; }
.matrix td .badge + .statusdot { margin-left: var(--space-1); }
.matrix .mx-exc { margin-top: var(--space-1); }
/* the sign-off: an optional note, the one primary, and what it signs */
.mxconfirm { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.mxconfirm input[name="note"] { flex: 1 1 24ch; min-width: 0; max-width: 48ch; width: auto; }

/* role badges are category tags, not states: one neutral chip for every role
   (the per-role hues — admin purple, aic blue, client green — said nothing and
   broke the one-accent palette; perfection sweep §1). The default .badge grey
   does the work; the role-<name> class stays for tests and tooling. */

.login-new summary { cursor: pointer; }
.login-note { font-size: var(--fs-2xs); color: var(--text-muted); margin: var(--space-5) 0 0; }
/* the authorised-use notice under every sign-in form (security G4): said, not
   asked. Ink only, no tint and no icon; a hairline makes it the form's footing */
.login-notice {
  font-size: var(--fs-2xs);
  line-height: 1.5;
  color: var(--text-secondary);
  margin: var(--space-5) 0 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

@media (max-width: 700px) {
  .login-panel { grid-template-columns: 1fr; max-width: 420px; }
  .login-brand { padding: var(--space-6) var(--space-6); gap: var(--space-4); border-right: none; border-bottom: 2px solid var(--premium-rule); }
}

/* ================= sticky first column on wide pivots ================= */

table.pivot thead th:first-child,
table.pivot tbody td:first-child,
table.pivot tfoot td:first-child {
  position: sticky;
  left: 0;
  background: var(--card);
  z-index: 1;
  box-shadow: 1px 0 0 var(--border);
}
table.pivot thead th:first-child { z-index: 3; background: var(--thead-bg); }
table.pivot tr:hover td:first-child { background: var(--row-hover); }
table.pivot tr.total td:first-child { background: var(--row-total-bg); }

.onpage-nav { margin-top: var(--space-4); }
:target { scroll-margin-top: var(--space-4); }

/* ---- period-close readiness gate + integrity certificate ---- */
.ok-text { color: var(--ok); }
.warn-text { color: var(--warn); }
.hash { font-family: var(--font-mono); font-size: .85em; color: var(--text-muted); }

.readiness {
  border: 1px solid var(--border); border-left: 3px solid var(--ok);
  border-radius: var(--radius-lg); padding: var(--space-4) var(--space-5); margin: var(--space-3) 0 var(--space-5);
  background: var(--ok-soft);
}
.readiness--warn { border-left-color: var(--warn); background: var(--warn-soft); }
.readiness--block { border-left-color: var(--danger); background: var(--danger-soft); }
.readiness-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }
.readiness-list { margin: var(--space-4) 0 0; padding-left: var(--space-6); }
.readiness-list li { margin: var(--space-2) 0; }
.readiness-list.block li { color: var(--danger); }
/* A CAVEAT stays on the page (app/help.py): a rule that changes what the
   figures below MEAN is not "explanation" and never goes behind the "?".
   One component for both shapes it takes — a line under a title, and a card
   that leads a screen — instead of the hand-written border-left each screen
   used to carry. */
.caveat { margin: var(--space-3) 0 0; padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--warn); background: var(--warn-soft); color: var(--warn);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  font-size: var(--fs-xs); line-height: var(--lh-body); max-width: 78ch; }
.card.is-caveat { box-shadow: inset 3px 0 0 var(--danger); }
.card.is-note { box-shadow: inset 3px 0 0 var(--accent); }

.check-row { display: flex; align-items: center; gap: var(--space-3); font-weight: var(--fw-medium); cursor: pointer; }
.check-row input { width: auto; }

/* the closed-period certificate — engraved register (serif + brass + mono) */
.cert {
  border: 1px solid var(--premium-rule); border-radius: var(--radius-lg);
  padding: var(--space-5) var(--space-5); margin: var(--space-2) 0 var(--space-5); background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--premium-soft);
}
.cert-title { font-family: var(--font-serif); font-size: var(--fs-lg); font-weight: var(--fw-semibold); margin-bottom: var(--space-4); letter-spacing: .01em; color: var(--text-heading); border-bottom: 1px solid var(--premium-rule); padding-bottom: var(--space-3); }
.cert-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: var(--space-5); }
.cert-item .l { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--text-muted); }
.cert-item .v { font-size: var(--fs-md); font-weight: var(--fw-semibold); margin: var(--space-1) 0 var(--space-2); font-variant-numeric: tabular-nums; }
.cert-ack { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px dashed var(--premium-rule); color: var(--warn); font-size: var(--fs-sm); }
.cert-rules { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--premium-rule); }
.cert-rules > summary { cursor: pointer; font-size: var(--fs-sm); color: var(--text-muted); list-style: none; user-select: none; }
.cert-rules > summary::-webkit-details-marker { display: none; }
.cert-rules > summary::before { content: "+"; display: inline-block; margin-right: var(--space-2); color: var(--text-muted); }
.cert-rules[open] > summary::before { content: "\2212"; }
.cert-rules > summary:hover { color: var(--ink); }
.cert-rules .ruleset { width: auto; margin-top: var(--space-3); border-collapse: collapse; }
.cert-rules .ruleset td { padding: var(--space-1) var(--space-6) var(--space-1) 0; font-size: var(--fs-sm); border: 0; }
.cert-rules .ruleset td.amt { padding-right: 0; color: var(--text-heading); }
@media (max-width: 760px) { .cert-grid { grid-template-columns: 1fr; } }

/* Case-level on hold (59): a deliberate pause, stated calmly. Neutral, not
   amber — amber is for what is late; a held case is waiting on purpose. */
.hold-banner { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-5); flex-wrap: wrap; margin: var(--space-4) 0;
  padding: var(--space-4) var(--space-5); background: var(--surface-sunken);
  border: 1px solid var(--premium-rule); border-left: 3px solid var(--text-muted);
  border-radius: var(--radius-md); }
.hold-banner .hold-line { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
.hold-banner .hold-reason { color: var(--ink); font-size: var(--fs-sm); margin-top: var(--space-2); }
.hold-resume { display: flex; align-items: center; gap: var(--space-3); }
.hold-resume input { width: 22ch; }
.hold-set { margin: var(--space-3) 0; }
.hold-set > summary { cursor: pointer; font-size: var(--fs-sm); color: var(--text-muted);
  list-style: none; user-select: none; width: fit-content; }
.hold-set > summary::-webkit-details-marker { display: none; }
.hold-set > summary::before { content: "+"; margin-right: var(--space-2); color: var(--text-muted); }
.hold-set[open] > summary::before { content: "\2212"; }
.hold-set > summary:hover { color: var(--ink); }

/* access review (10): the dormant queue banner and the per-row marker */
.reviewbanner { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.dormant-line { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-2); }
/* the access matrix's banner (security G5): the sentence takes the room, so
   its one button stays on the line at the right instead of wrapping under */
.matrixbanner > span:not(.statusdot) { flex: 1 1 30ch; min-width: 0; }

/* document health (46): three plain signals in a row, not one score */
.dochealth { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin: 0 0 var(--space-5); }
.dochealth .dh-label { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-muted); }

/* activity trail filter (58): a compact GET filter row */
.activity-filter { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin: 0 0 var(--space-5); }
.activity-filter input[name="actor"] { width: 18ch; }
.activity-filter select { width: auto; min-width: 12ch; max-width: 22ch; }

/* tax-consultant surface (29): a comment bar + log on the assigned case */
.consultant-commentbar { display: flex; gap: var(--space-3); margin-bottom: var(--space-4); }
.consultant-commentbar input { flex: 1; min-width: 0; }
/* a document/information request bar (47/70) */
.consultant-requestbar { display: flex; gap: var(--space-3); margin-bottom: var(--space-4); flex-wrap: wrap; }
.consultant-requestbar input[name="text"] { flex: 1; min-width: 12ch; }
.consultant-requestbar input.date { width: 16ch; }
.consultant-comments { list-style: none; padding: 0; margin: 0; }
.consultant-comments li { padding: var(--space-3) 0; border-top: 1px solid var(--premium-rule); }
.consultant-comments li:first-child { border-top: 0; }
.consultant-comments li > div { margin-top: var(--space-1); }
/* the admin's "external consultants on this case" control (29) */
.consultants-body { margin-top: var(--space-3); }
.consultants-list { list-style: none; padding: 0; margin: 0 0 var(--space-3); }
.consultants-list li { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-1) 0; }

/* ---- designed empty states + download feedback ---- */
.empty {
  color: var(--text-muted);
  font-style: normal;
  font-size: var(--fs-xs);
  padding: var(--space-4) 0;
}
td.empty { text-align: center; padding: var(--space-5) 0; }
.empty.bare { text-align: center; padding: var(--space-3) 0; }

/* "Nothing here yet" is a small fact, not an announcement. One definition
   (it used to have two, ~400 lines apart, with different sizes), and calm:
   a small mark, one line of title, one line of hint. */
.emptystate {
  text-align: center;
  padding: var(--space-6) var(--space-5);
  color: var(--text-muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}
.emptystate .es-icon {
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  margin-bottom: var(--space-2);
  opacity: 1;
}
.emptystate .es-icon svg { width: 17px; height: 17px; }
.emptystate.good .es-icon { background: var(--ok-soft); color: var(--ok); }
.emptystate .es-title { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-secondary); }
.emptystate .es-hint { font-size: var(--fs-xs); max-width: 420px; line-height: var(--lh-body); text-wrap: balance; }
.emptystate .es-action { margin-top: var(--space-4); }

/* A chart is bounded by its card — a rotated axis label that runs past the
   edge lands on top of whatever is next to it. */
.card .chart, .card .chart-wrap, .chart-grid > .card { min-width: 0; overflow: hidden; }

/* loading skeleton rows for tables (data-tool idiom, not a spinner).
   QA-10: the "no gradients" rule targets DECORATIVE gradients (hero fills,
   glow). This is a functional loading shimmer between two neutral stone tints
   — a deliberate, documented exception; it also honours prefers-reduced-motion
   below. */
@keyframes rd-shimmer { 0% { background-position: -280px 0; } 100% { background-position: 280px 0; } }
.skeleton-row td { padding: var(--cell-pad-y) var(--cell-pad-x); }
.skeleton-cell {
  display: block; height: 10px; border-radius: var(--radius-xs);
  background: linear-gradient(90deg, var(--stone-100) 25%, var(--stone-150) 37%, var(--stone-100) 63%);
  background-size: 560px 100%; animation: rd-shimmer 1.2s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .skeleton-cell { animation: none; } }

/* download feedback: PDF/Excel/ZIP generation can take a few seconds server-side */
@keyframes rd-spin { to { transform: rotate(360deg); } }
.btn.is-loading, button.is-loading, .chiplink.is-loading { position: relative; color: transparent !important; pointer-events: none; }
.btn.is-loading::after, button.is-loading::after, .chiplink.is-loading::after {
  content: "";
  position: absolute;
  width: 16px; height: 16px;
  top: 50%; left: 50%;
  margin: -8px 0 0 -8px;                     /* centre the 16px spinner (half of its box) */
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: #fff;
  animation: rd-spin 0.6s linear infinite;
}
.btn.secondary.is-loading::after, .btn.accent.is-loading::after,
.chiplink.is-loading::after { color: var(--accent); }
/* a small standalone spinner (e.g. the Inbox loading state), same motion */
.spinner { display: inline-block; width: 16px; height: 16px; vertical-align: middle;
  border: 2px solid var(--accent); border-top-color: transparent; border-radius: 50%;
  animation: rd-spin 0.6s linear infinite; }

/* ==========================================================================
   Template extraction (MAKEOVER-PLAN §10.b): the field reading and its evidence
   ==========================================================================
   One field per row: what the reader is sure of, what it proposes, and the
   piece of the page it read it from. The crop earns its width — a confidence
   without evidence asks the reviewer to trust a number. */

.reads { display: grid; gap: var(--gap); }
.readrow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 340px);
  gap: var(--gap);
  align-items: start;
  padding: var(--gap) 0;
  border-top: 1px solid var(--border);
}
.reads > .readrow:first-child { border-top: 0; padding-top: 0; }
/* the tick sits on the label's line — it reads as "confirm <this field>", and
   it needs no off-scale nudge to line up with the input below */
.readrow .read-tick { padding-top: var(--space-1); }
.readrow .read-field input { width: 100%; }
.readrow .readvalue { margin: var(--gap-tight) 0 0; font-variant-numeric: tabular-nums; }
.readrow .read-why { display: flex; align-items: center; gap: var(--gap-tight);
                     margin: var(--gap-tight) 0 0; flex-wrap: wrap; }
/* a field the reader is unsure of arrives unticked; the rule says so quietly
   rather than shouting — nothing is wrong yet, somebody just has to look */
.readrow.is-check { border-left: 2px solid var(--warn-border); padding-left: var(--gap); }
.readrow .read-crop { min-width: 0; }
/* the studio's try-it list shows the same rows with nothing to tick */
.readrow.no-tick { grid-template-columns: minmax(0, 1fr) minmax(0, 340px); }

/* The read-out's other two sections. The type is one select at the width of
   its longest option. A withholding line is two controls on one row: the fund,
   and its figure at the amount width; where the column is too narrow for both
   the figure goes under the fund. */
.readtype { display: flex; align-items: center; flex-wrap: wrap; gap: var(--gap-tight) var(--gap); }
.readtype select { width: auto; min-width: var(--field-w-name); max-width: 100%; }
.linefields { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--gap-tight) var(--gap); }
.linefields .linefund { flex: 1 1 var(--field-w-name); min-width: 0; max-width: var(--field-w-name-lg); }
.linefields .linefund select { width: 100%; }
.linefields .lineamount { flex: 0 0 var(--field-w-amount-form); }
.linefields .read-why { flex: 1 0 100%; margin: 0; }
/* The rows typed by hand stand on the columns of a proposed line: the same
   grid, with no piece of the page and an empty cell where a proposal has its
   tick. So the tick's cell has one width, with a tick in it or not. They are
   blank rows, not readings, so no rule runs between them. */
.readrow .read-tick { width: var(--control-h-xs); }
.handrows > .readrow { border-top: 0; padding: 0; }
/* the lines this paper already added: fund, figure, and that it is done; the
   first line still offered keeps the distance one row keeps from the next */
.linesdone { width: auto; }
.linesdone + .reads { margin-top: var(--gap); }
.linesdone td { padding-left: 0; padding-right: var(--gap-section); }
.linesdone tr:last-child td { border-bottom: 0; }
.linesdone td.amt { text-align: right; font-variant-numeric: tabular-nums; }
.linesdone td .statusdot + .badge { margin-left: var(--space-2); }
/* the offer to take the lines' total as the case's expected refund: one tick
   and its words, under the lines */
.expected-offer { display: flex; align-items: center; gap: var(--gap-tight);
  margin: var(--gap) 0 0; font-weight: var(--fw-regular); }
/* the one confirmation, under the last section */
.readactions { margin-top: 0; }

.cropimg {
  max-width: 100%; height: auto; display: block;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface);
}
.cropimg .crop-word, .scanimg .crop-word { fill: var(--text-500); }
.cropimg .crop-hit, .scanimg .crop-hit { fill: var(--text); font-weight: 600; }
/* the value that was read is marked in the picture itself (owner 11-10) */
.cropimg .crop-mark, .scanimg .crop-mark { fill: var(--mark); }
/* the zone the reader looked in: outlined only when it found nothing there */
.cropimg .crop-zone, .scanimg .crop-zone { fill: none; stroke: var(--accent); stroke-width: 1; stroke-dasharray: 3 2; }
.cropimg .crop-zone[data-found], .scanimg .crop-zone[data-found] { stroke: none; }

/* The Scan view: the list of what was read beside the pages it was read from.
   Embedded in the preview overlay the two columns scroll on their own; on its
   own page it is a plain two-column block. A row of the list brings its mark
   into view and rings it. */
.scanview { display: grid; grid-template-columns: minmax(180px, 260px) minmax(0, 1fr);
  gap: var(--gap); align-items: start; }
.scanview.is-embedded { height: 100%; align-items: stretch; padding: var(--gap); box-sizing: border-box; }
.scanview.is-embedded .scan-side, .scanview.is-embedded .scan-pages { min-height: 0; overflow: auto; }
.scan-side { display: flex; flex-direction: column; gap: var(--gap-tight); min-width: 0; }
.scan-fields { list-style: none; margin: 0; padding: 0; }
.scan-fields > li { border-top: 1px solid var(--border); }
.scan-fields > li:first-child { border-top: 0; }
.scan-fields > li.is-check { border-left: 2px solid var(--warn-border); padding-left: var(--gap-tight); }
.scan-field { display: grid; grid-template-columns: minmax(0, 1fr); justify-content: start; justify-items: start;
  gap: var(--space-2); width: 100%; min-width: 0; padding: var(--gap-tight) 0; text-align: left; }
button.scan-field { min-height: 0; border: 0; border-radius: 0; background: none; box-shadow: none;
  color: inherit; font-weight: var(--fw-regular); }
button.scan-field:hover { background: var(--surface-raised); }
button.scan-field[aria-current="true"] { box-shadow: inset 2px 0 0 var(--accent); padding-left: var(--gap-tight); }
.scan-label { color: var(--text-secondary); font-size: var(--fs-xs); }
.scan-value { font-size: var(--fs-sm); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.scan-pages { min-width: 0; overflow: auto; }
.scan-page { margin: 0 0 var(--gap); }
.scan-page figcaption { margin-top: var(--space-2); }
.scanimg { display: block; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); }
/* the zoom buttons set --scan-zoom on the pages; a page is as wide as the pane at 1 */
.scan-pages .scanimg { width: calc(var(--scan-zoom, 1) * 100%); max-width: none; height: auto; }
.scanimg .crop-mark.is-focus, .scanimg .crop-zone:not([data-found]).is-focus { stroke: var(--accent); stroke-width: 2; stroke-dasharray: none; }
@media (max-width: 700px) {
  .scanview, .scanview.is-embedded { grid-template-columns: minmax(0, 1fr); height: auto; }
  .preview-body.is-scan { overflow: auto; }
}

/* the studio's field table: many narrow numeric columns, so it scrolls in its
   own box rather than pushing the page sideways */
.tablewrap { overflow-x: auto; max-height: var(--tbl-box-max); scroll-padding-top: var(--thead-clear); }
.fieldtable { min-width: 820px; table-layout: fixed; }
.fieldtable input, .fieldtable select { width: 100%; }
.fieldtable input.num { text-align: right; }
.fieldtable .newrow input, .fieldtable .newrow select { background: var(--surface-raised); }
.tryresult { margin-top: var(--gap-section); border-top: 1px solid var(--border); padding-top: var(--gap); }

@media (max-width: 900px) {
  .readrow { grid-template-columns: auto minmax(0, 1fr); }
  .readrow .read-crop { grid-column: 2; }
}

/* ==========================================================================
   The client portal (MAKEOVER-PLAN §10.e): its own bare, calm chrome
   ==========================================================================
   Deliberately not the workbench. A client sees a small window onto their own
   dossier, on the same foundation (mark, tokens, ledger green) so it reads as
   one product, but with none of the internal navigation. */

.portalshell { min-height: 100vh; display: flex; flex-direction: column;
  background: var(--bg); }

.portal-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-7);
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.portal-brand { display: flex; align-items: center; gap: var(--space-3);
  text-decoration: none; color: var(--text); font-weight: 600; }
.portal-mark { display: inline-flex; color: var(--accent); }
.portal-mark svg { width: var(--icon-size-md); height: var(--icon-size-md); }
.portal-brand-name { font-size: var(--fs-md); }
.portal-nav { display: flex; align-items: center; gap: var(--space-5); }
.portal-nav a { text-decoration: none; color: var(--text-secondary);
  font-size: var(--fs-sm); }
.portal-nav a:hover { color: var(--text); }
.portal-nav a.on { color: var(--accent); font-weight: 600; }
.portal-signout { color: var(--text-secondary); }

.portal-main { flex: 1 0 auto; width: 100%; max-width: 920px;
  margin: 0 auto; padding: var(--space-8) var(--space-7); }
.portal-flashwrap { max-width: 920px; margin: var(--space-5) auto 0;
  padding: 0 var(--space-7); }

.portal-foot {
  display: flex; flex-direction: column; gap: var(--space-1);
  padding: var(--space-6) var(--space-7);
  border-top: 1px solid var(--border); color: var(--text-secondary);
  font-size: var(--fs-xs);
}

.portal-hero { margin-bottom: var(--space-7); }
.portal-hero h1 { font-size: var(--fs-xl); margin: 0 0 var(--space-2); }
.portal-hero p { margin: 0; }
.portal-crumbs { font-size: var(--fs-xs); color: var(--text-secondary);
  margin-bottom: var(--space-4); }
.portal-crumbs a { color: var(--text-secondary); }
.portal-crumbs span { margin: 0 var(--space-2); }

.portal-stats { display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4); margin-bottom: var(--space-7); }
.portal-stat { background: var(--surface); border: 1px solid var(--border);
  padding: var(--space-5); }
.portal-stat-n { font-size: var(--fs-lg); font-weight: 600;
  font-variant-numeric: tabular-nums; }
.portal-stat-l { font-size: var(--fs-xs); color: var(--text-secondary);
  margin-top: var(--space-1); }

.portal-cols { display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--space-5); align-items: start; }
.portal-table { width: 100%; }
.portal-narrow { max-width: 520px; }

.portal-steps { margin: 0 0 var(--space-5); padding-left: var(--space-6);
  display: grid; gap: var(--space-3); }
.totp-key { display: inline-block; margin-top: var(--space-2);
  font-family: var(--font-mono, monospace); font-size: var(--fs-md);
  letter-spacing: 0.12em; background: var(--surface-sunken);
  padding: var(--space-2) var(--space-3); border: 1px solid var(--border); }

@media (max-width: 720px) {
  .portal-stats { grid-template-columns: repeat(2, 1fr); }
  .portal-cols { grid-template-columns: 1fr; }
  .portal-top, .portal-main, .portal-flashwrap, .portal-foot {
    padding-left: var(--space-5); padding-right: var(--space-5); }
}


/* ============ spacing steps, said once ====================================
   Ninety-one of the inline style= attributes in the templates were one of six
   spacing values from the scale, written out by hand. A value repeated by hand
   is a value that drifts: these are the same six, named. Nothing else moved —
   the remaining inline styles are one-offs and layout, and they go when the
   thing they style gets a component of its own.

   A second pass (28-09) added the four steps below and `.ml-auto`, and
   collapsed the aliases: --gap-tight IS --space-3 and --gap IS --space-5, so
   "margin-top:var(--gap-tight)" and "margin-top:var(--space-3)" were the same
   8px written two ways. `margin: X 0 0` composes as `class="m-0 mt-X"` — the
   later rule wins for the top edge, so no extra class is needed for it.

   WHAT A UTILITY CANNOT DO: beat a more specific rule. An inline style is
   (1,0,0,0) and always wins; `.mt-3` is (0,1,0) and loses to `h2 + p.muted`
   (0,1,2) and to `form.inline` (0,1,1). Five inline styles stayed for exactly
   that reason — see tests/test_master_todo_finishing. The conversion was
   checked by shooting all 56 screens before and after and diffing the pixels;
   every screen is identical apart from the app's own clocks and counters. */
.m-0  { margin: 0; }
.mt-0 { margin-top: 0; }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mb-0 { margin-bottom: 0; }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-5 { margin-bottom: var(--space-5); }
.ml-auto { margin-left: auto; }

/* A row of things side by side — the shape seventeen inline styles were
   spelling out one property at a time. `.row-between` pushes the ends apart. */
.row { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }
/* a picker beside its button is as wide as its token and shares the line */
.row > select, .row > input { width: auto; flex: 0 1 auto; }
.row-4 { gap: var(--space-4); }
.row-between { justify-content: space-between; }

/* A quiet vertical rule before a cell — the dashboard's column separator. */
.rule-l { border-left: 2px solid var(--grid); }

/* A figure that is below zero, wherever it is shown. */
.neg { color: var(--figure-negative); }

/* ============ width by what the field holds ==============================
   tokens.css names a width per kind of value; these apply it to the fields
   that hold words. Written as element.class, and placed LAST, so they win a
   tie with a container's default — `.filterbar input { width: auto }` is
   (0,1,1) too, and sat later in the file until they moved here. A rule that
   is more specific still wins; see tests/test_master_todo_finishing. */
input.w-note, select.w-note, textarea.w-note         { width: var(--field-w-note); }
input.w-note-sm, select.w-note-sm                    { width: var(--field-w-note-sm); }
input.w-name, select.w-name                          { width: var(--field-w-name); }
input.w-name-lg, select.w-name-lg                    { width: var(--field-w-name-lg); }
input.w-search                                       { width: var(--field-w-search); }
input.w-url                                          { width: var(--field-w-url); }
input.w-file                                         { width: var(--field-w-file); }
input.w-code-sm                                      { width: var(--field-w-code-sm); }
select.w-case                                        { min-width: var(--field-w-case); }
select.w-state                                       { max-width: var(--field-w-state); }
input.w-auto, select.w-auto                          { width: auto; }
.form-grid input.w-note, .form-grid input.w-name, .form-grid input.w-url,
.form-grid input.w-file, .form-grid input.w-search   { justify-self: start; }
/* a readable line: a paste box, a table of a few narrow columns */
.measure { max-width: var(--measure); }
