/* Legacy component geometry. Current visual system is in modern.css. */
@layer legacy {
/* Billfish design system - docs/DESIGN-BRIEF.md (authoritative), documented for builders in docs/NOTES-CHASSIS.md.
   2026-09-06 CHASSIS: rewritten around the brief's tokens. Light only. Borders, not shadows. Radius 4px on
   controls and panels, 0 on table cells and full-width bands. Table rows are 40px. Tabular figures on every
   number. Motion only in response to a user action; prefers-reduced-motion turns it all off.
   Nothing from the Stockholm pass (its translucent rail, KPI bento and large-radius panels) survives; the legacy
   class names other pages still use (.card, .stats, .chip, .status, .seg, .toolbar, .lines …) are kept but
   restyled to these tokens so nothing renders in the old vernacular. */

/* ---------- type: Public Sans, self-hosted (billfish/web/static/fonts, OFL). No CDN, no Google Fonts. ---------- */
@font-face { font-family: "Public Sans"; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/fonts/PublicSans-Regular.woff2") format("woff2"); }
@font-face { font-family: "Public Sans"; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/fonts/PublicSans-Medium.woff2") format("woff2"); }
@font-face { font-family: "Public Sans"; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/fonts/PublicSans-SemiBold.woff2") format("woff2"); }

/* ---------- tokens (docs/DESIGN-BRIEF.md §2) ---------- */
:root {
  /* Surfaces. The shell/canvas relationship is INVERTED from the brief: the sidebar is the tinted surface and
     the work area is white, the way Attio and Notion do it. A white rail floating on grey is the single strongest
     "admin panel" tell in the old build. --paper keeps its name so every legacy rule that reaches for it lands on
     the shell tint it was always standing in for. */
  --paper: #FBFBFA; --surface: #FFFFFF; --rule: #E9E9E7;
  --shell: #FBFBFA;                     /* sidebar, table headers, inset fields */
  --hover: rgba(31, 31, 30, .045);      /* the neutral wash every hover uses -- never the accent */
  --hover-strong: rgba(31, 31, 30, .075);
  --ink: #1F1F1E; --ink-muted: #78766F;
  /* Capitol Axis blue, taken from the website's own primary button (website/modern/static/css/site.css).
     --clear stays green: it means "paid, filed, complete", which is a different job from "this is interactive". */
  --accent: #24508F; --accent-hi: #2C5FA8; --accent-weak: #EAF0F8;
  /* status colours, used only for status: a small filled dot plus a text label, never colour alone */
  --due-soon: #9A6B10; --overdue: #8C2F2A; --clear: #2F5D50; --neutral: #6B706D;
  /* type scale */
  --font: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --fs-title: 24px; --fs-h2: 16px; --fs-body: 14px; --fs-dense: 13px; --fs-label: 12px;
  /* structure */
  --radius: 6px; --radius-lg: 10px; --radius-pill: 999px; --row: 40px; --side-w: 236px;
  /* Delta badge roles. Named for what they MEAN (a movement in the good/bad direction), not for a colour, so a
     call site never writes a hex and the two states can be retuned in one place. Greens are pulled toward lime
     per the owner, then darkened until the text clears 4.5:1 on its own tint -- a badge nobody can read is a
     decoration. `--delta-*-bg` is the tint the text sits on, never a border colour. */
  --delta-up: #3F7D20; --delta-up-bg: #EFF8E4;
  --delta-down: #A82F26; --delta-down-bg: #FBEBE9;
  /* Concentric-radius inputs. A nested shape must keep its parent's radius minus the gap between them, floored
     so a small inner box never goes square: concentric(outer, inset) = max(outer - inset, 6px). */
  --nav-inset: 6px;
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px;
  /* Shadows are back, but only the two an Attio-class interface actually uses: a 1px lift on raised controls,
     and a real float for things that leave the page. Nothing "hover-lifts". */
  --shadow-xs: 0 1px 2px rgba(15, 15, 15, .05);
  --shadow-float: 0 12px 32px rgba(15, 15, 15, .10), 0 3px 8px rgba(15, 15, 15, .05), 0 0 0 1px rgba(15, 15, 15, .06);
  --t-panel: 150ms; --t-row: 120ms;
  /* aliases so templates written against the earlier token names keep rendering in the new palette */
  --bg: var(--surface); --card: var(--surface); --card-2: var(--shell); --tile: var(--shell); --line: var(--rule); --line-soft: var(--rule);
  --code: var(--paper); --muted: var(--ink-muted); --faint: var(--ink-muted); --focus: var(--accent);
  --btn: var(--accent); --btn-ink: #fff; --accent-soft: var(--accent-weak); --accent-soft-ink: var(--accent);
  --good: var(--clear); --good-soft: var(--accent-weak); --warn: var(--due-soon); --warn-soft: #F7F1E4; --bad: var(--overdue); --bad-soft: #F6ECEB;
  --radius-sm: var(--radius); --radius-inner: var(--radius); --shadow: var(--shadow-xs); --shadow-soft: var(--shadow-xs);
  --display: var(--fs-title); --title: var(--fs-title); --heading: var(--fs-h2); --body: var(--fs-body); --caption: var(--fs-label);
  --top-h: 0px;
}

/* ---------- base ---------- */
* { box-sizing: border-box; }
html { color-scheme: light; -webkit-text-size-adjust: 100%; }
body { margin: 0; background: var(--surface); color: var(--ink); font: 400 var(--fs-body)/1.5 var(--font); font-variant-numeric: tabular-nums; overflow-x: hidden; }
a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; }
h1, h2, h3, h4 { margin: 0; line-height: 1.25; font-weight: 600; letter-spacing: 0; }
h1 { font-size: var(--fs-title); letter-spacing: -0.01em; }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-body); }
p { margin: 0 0 var(--s2); max-width: 72ch; }
code, pre { font-family: var(--mono); font-size: var(--fs-label); }
pre { background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s2) var(--s3); overflow: auto; max-height: 420px; white-space: pre-wrap; margin: 0; }
pre.mail { max-height: none; font-family: inherit; font-size: var(--fs-body); }
hr { border: 0; border-top: 1px solid var(--rule); margin: var(--s4) 0; }
svg { max-width: 100%; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
kbd.key { display: inline-block; font: 11px/1 var(--mono); color: var(--ink-muted); background: var(--surface); border: 1px solid var(--rule); border-radius: 3px; padding: 3px 5px; min-width: 18px; text-align: center; }
[hidden] { display: none !important; }

/* ---------- icons: inline sprite, monoline, follow the text colour ---------- */
.icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; flex: 0 0 auto; vertical-align: -3px; }
.icon.chev { width: 14px; height: 14px; margin-left: auto; }
.tile, .doc-icon { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: var(--radius); border: 1px solid var(--rule); color: var(--ink-muted); flex: 0 0 auto; vertical-align: middle; }
.avatar { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: var(--radius); background: var(--accent-weak); color: var(--accent); font-size: 11px; font-weight: 600; flex: 0 0 auto; vertical-align: middle; }
.avatar.sm { width: 20px; height: 20px; font-size: 10px; }
.avatar.ink { background: var(--ink); color: var(--surface); }
.avatars { display: inline-flex; align-items: center; } .avatars .avatar + .avatar { margin-left: -6px; border: 1px solid var(--surface); }
.item { display: flex; align-items: center; gap: var(--s2); min-width: 0; } .item > div { min-width: 0; }

/* ---------- app shell: fixed 220px sidebar on --surface with one rule on its right (brief §3) ---------- */
.app { display: flex; min-height: 100vh; }
.sidebar {
  position: sticky; top: 0; height: 100vh; width: var(--side-w); flex: 0 0 var(--side-w);
  background: var(--surface); border-right: 1px solid var(--rule);
  display: flex; flex-direction: column; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin;
}
.side-firm { display: flex; align-items: center; gap: var(--s2); min-height: 52px; padding: 0 var(--s4); border-bottom: 1px solid var(--rule); color: var(--ink); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.side-firm a { color: inherit; } .side-firm a:hover { text-decoration: none; }
.side-firm .logo { height: 18px; width: auto; }
.side-nav { display: flex; flex-direction: column; padding: var(--s2) 0; }
.side-link { position: relative; display: flex; align-items: center; gap: 10px; min-height: 36px; padding: 0 var(--s4); color: var(--ink); font-weight: 500; white-space: nowrap; border-left: 2px solid transparent; }
.side-link .icon { color: var(--ink-muted); }
.side-link:hover { background: var(--paper); text-decoration: none; }
.side-link[aria-current="page"] { background: var(--accent-weak); color: var(--accent); border-left-color: var(--accent); }
.side-link[aria-current="page"] .icon { color: var(--accent); }
.side-link .label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* overdue count on Today and Filings: a bare --overdue numeral, no badge background */
.side-link .count { margin-left: auto; font-size: var(--fs-label); font-weight: 600; color: var(--overdue); font-variant-numeric: tabular-nums; }
.side-user { margin-top: auto; border-top: 1px solid var(--rule); }
.side-user > summary { display: flex; align-items: center; gap: 10px; min-height: 52px; padding: 0 var(--s4); cursor: pointer; list-style: none; }
.side-user > summary::-webkit-details-marker { display: none; }
.side-user > summary:hover { background: var(--paper); }
.side-user .who { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.side-user .name, .side-user .caption { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-user .name { font-weight: 500; }
/* `.side-user.menu`, not `.side-user`: this element carries BOTH classes, and the generic `.menu .sheet`
   rule below has equal specificity but comes later in the file, so its `top: calc(100% + 4px)` won. With
   a top AND a bottom both applied the sheet collapsed to an 8px sliver pinned to the bottom edge of the
   viewport -- the menu opened, invisibly, and clicking the user button looked like it did nothing. The
   extra class wins the cascade; `top: auto` states the intent so a later edit cannot silently undo it.
   This menu is anchored at the BOTTOM of the sidebar, so it has to open upward. */
.side-user.menu .sheet { position: absolute; left: var(--s2); right: var(--s2); top: auto; bottom: 56px; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); box-shadow: var(--shadow-float); padding: var(--s1); z-index: 30; }
.side-user .sheet a, .side-user .sheet button { display: flex; align-items: center; gap: var(--s2); width: 100%; min-height: 36px; padding: 0 var(--s3); border: 0; background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer; border-radius: var(--radius); }
.side-user .sheet a:hover, .side-user .sheet button:hover { background: var(--paper); text-decoration: none; }
.side-user .sheet .caption { padding: var(--s2) var(--s3) var(--s1); }
.side-user { position: relative; }
.live-badge { display: block; padding: var(--s1) var(--s3); color: var(--ink-muted); font-size: var(--fs-label); }
.live-badge .live-dot { display: none; }
.frame { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.backdrop, .menu-btn, .tabbar { display: none; }
.topbar { display: none; }

/* ---------- page ---------- */
main { width: 100%; max-width: 1280px; margin: 0 auto; padding: var(--s5) var(--s6) var(--s7); }
main.bare { max-width: 440px; }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s4); }
.page-head .sub { color: var(--ink-muted); margin-top: var(--s1); }
.section { margin-top: var(--s5); } .section > h2 { margin-bottom: var(--s3); }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s3); }
.muted { color: var(--ink-muted); } .faint { color: var(--ink-muted); } .caption { font-size: var(--fs-label); color: var(--ink-muted); }
.label { font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); }
.small { font-size: var(--fs-label); } .strong { font-weight: 600; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.nowrap { white-space: nowrap; } .center { text-align: center; } .right { text-align: right; }
/* legacy direction classes render in the status palette; the word beside them always carries the meaning */
.good, .ok { color: var(--clear); } .warn { color: var(--due-soon); } .bad { color: var(--overdue); }
.row { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; } .row.between { justify-content: space-between; }
.stack > * + * { margin-top: var(--s3); } .stack-lg > * + * { margin-top: var(--s5); }
.grid { display: grid; gap: var(--s4); } .grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.detail-grid { display: grid; gap: var(--s4); grid-template-columns: minmax(0, 3fr) minmax(280px, 2fr); align-items: start; }
@media (max-width: 860px) { .detail-grid { grid-template-columns: minmax(0, 1fr); } }
.spacer { flex: 1; }
.flash { display: flex; align-items: center; gap: var(--s3); background: var(--surface); border: 1px solid var(--rule); border-left: 2px solid var(--accent); color: var(--ink); padding: var(--s2) var(--s3); border-radius: var(--radius); margin-bottom: var(--s4); }
.flash.err, .flash.warn { border-left-color: var(--due-soon); }
.flash form { margin: 0; }
.lede { font-size: var(--fs-h2); font-weight: 400; line-height: 1.45; max-width: 72ch; margin: var(--s1) 0 var(--s2); }

/* ---------- status: a filled dot plus a word (brief §2). tone-* maps the legacy names onto the four status colours ---------- */
/* the chassis marks a column's kind on its <th>/<td> (class="status"), so the dot is scoped to the inline element */
.status:not(th):not(td), .dot-label { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; vertical-align: middle; color: var(--ink); }
.status:not(th):not(td)::before, .dot-label::before, .sdot { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--neutral); flex: 0 0 auto; }
.tone-good::before, .tone-clear::before, .sdot.clear { background: var(--clear); }
.tone-warn::before, .tone-due-soon::before, .sdot.due-soon { background: var(--due-soon); }
.tone-bad::before, .tone-overdue::before, .sdot.overdue { background: var(--overdue); }
.tone-accent::before, .sdot.accent { background: var(--accent); }
.tone-neutral::before, .sdot.neutral { background: var(--neutral); }
.status.paid::before, .status.filed::before, .status.won::before { background: var(--clear); }
.status.overdue::before, .status.collections::before, .status.lost::before { background: var(--overdue); }
.status.partially_paid::before, .status.partial::before, .status.awaiting_approval::before, .status.pending_approval::before { background: var(--due-soon); }
.chip { display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--rule); background: var(--surface); color: var(--ink-muted); border-radius: var(--radius); padding: 1px 8px; font-size: var(--fs-label); font-weight: 500; line-height: 1.5; white-space: nowrap; vertical-align: middle; }
.chip .icon { width: 12px; height: 12px; }
.chip.range { color: var(--ink-muted); }
.score { display: none; }
.star { color: var(--ink); } .flag { color: var(--overdue); font-weight: 600; }
.due-soon { color: var(--due-soon); } .overdue { color: var(--overdue); } .clear { color: var(--clear); }
td.due-soon, td.overdue, td.clear { color: inherit; }

/* ---------- buttons & inputs: 4px radius, 1px rule, 32px tall; primary is --accent ---------- */
button, .btn, input, select, textarea { font: inherit; color: var(--ink); }
input, select, textarea { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); padding: 4px 8px; min-height: 32px; max-width: 100%; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px var(--accent-weak); }
input[type=checkbox], input[type=radio] { display: inline-block; width: 15px; height: 15px; min-height: 0; padding: 0; margin: 0; accent-color: var(--accent); vertical-align: middle; }
input[type=search]::-webkit-search-decoration, input[type=search]::-webkit-search-cancel-button { -webkit-appearance: none; }
textarea { width: 100%; min-height: 80px; }
label { color: var(--ink-muted); font-size: var(--fs-label); font-weight: 500; }
label > input, label > select, label > textarea { display: block; margin-top: 2px; color: var(--ink); font-size: var(--fs-body); font-weight: 400; width: 100%; }
.row > label { min-width: 0; flex: 1 1 140px; } .row > label.row { flex: 0 0 auto; }
select { max-width: 100%; }
button, .btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 32px; padding: 4px 12px; border-radius: var(--radius); border: 1px solid var(--rule); background: var(--surface); color: var(--ink); cursor: pointer; white-space: nowrap; font-weight: 500; line-height: 1.3; }
button:hover, .btn:hover { background: var(--paper); border-color: var(--ink-muted); text-decoration: none; }
button.primary, .btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
button.primary:hover, .btn.primary:hover { background: #274D42; border-color: #274D42; }
button.quiet, .btn.quiet { background: transparent; border-color: transparent; color: var(--ink-muted); }
button.quiet:hover, .btn.quiet:hover { background: var(--paper); color: var(--ink); border-color: transparent; }
button.danger, .btn.danger { color: var(--overdue); }
button.small, .btn.small { min-height: 28px; padding: 2px 10px; font-size: var(--fs-dense); }
button:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: default; }
.icon-btn { min-width: 32px; padding: 4px; color: var(--ink-muted); }
.icon-btn.quiet { border-color: transparent; background: transparent; }
.actions { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; } .actions form { margin: 0; }
form { margin: 0; }
.filters { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); align-items: flex-end; margin-bottom: var(--s4); }
.filters label { display: flex; flex-direction: column; min-width: 120px; }
.filters .search { flex: 1 1 220px; } .filters .search input { width: 100%; }
.text-link { background: none; border: 0; padding: 0; color: var(--accent); font-weight: 500; cursor: pointer; min-height: 0; }
.text-link:hover { text-decoration: underline; }
/* segmented control: a row of bordered options, the active one on --accent-weak */
.seg { display: inline-flex; border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; background: var(--surface); }
.seg a, .seg button { display: inline-flex; align-items: center; min-height: 30px; padding: 0 12px; color: var(--ink-muted); font-weight: 500; border: 0; border-left: 1px solid var(--rule); background: transparent; border-radius: 0; }
.seg a:first-child, .seg button:first-child { border-left: 0; }
.seg a:hover { text-decoration: none; color: var(--ink); background: var(--paper); }
.seg a[aria-current="true"], .seg button[aria-pressed="true"] { background: var(--accent-weak); color: var(--accent); }
.toolbar { display: flex; gap: var(--s2) var(--s3); align-items: center; flex-wrap: wrap; margin-bottom: var(--s4); }
.toolbar .search { flex: 1 1 200px; min-width: 160px; } .toolbar .search input { width: 100%; }
.toolbar select { min-width: 120px; }

/* disclosure & menus (menus float, so they get the one shadow) */
details.disclosure { border-top: 1px solid var(--rule); }
details.disclosure > summary { cursor: pointer; min-height: 40px; display: flex; align-items: center; gap: var(--s2); color: var(--ink); font-weight: 500; list-style: none; }
details.disclosure > summary::-webkit-details-marker { display: none; }
details.disclosure > summary::before { content: "›"; display: inline-block; width: 12px; color: var(--ink-muted); transition: transform var(--t-row); }
details.disclosure[open] > summary::before { transform: rotate(90deg); }
details.disclosure > .body { padding: 0 0 var(--s3); }
details.card { padding: 0 var(--s4); } details.card > summary { min-height: 44px; } details.card > .body { padding-bottom: var(--s4); }
.menu { position: relative; } .menu > summary { list-style: none; cursor: pointer; }
.menu > summary::-webkit-details-marker { display: none; }
.menu .sheet { position: absolute; right: 0; top: calc(100% + 4px); background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); box-shadow: var(--shadow-float); min-width: 220px; padding: var(--s1); z-index: 30; }
.menu.left .sheet { right: auto; left: 0; }
.menu .sheet a, .menu .sheet button { display: flex; align-items: center; gap: var(--s2); min-height: 34px; padding: 0 var(--s3); border-radius: var(--radius); color: var(--ink); width: 100%; background: none; border: 0; font: inherit; font-weight: 400; text-align: left; cursor: pointer; justify-content: flex-start; }
.menu .sheet a:hover, .menu .sheet button:hover { background: var(--paper); text-decoration: none; }
.menu .sheet a[aria-current="true"] { background: var(--accent-weak); color: var(--accent); }
.menu .sheet .sep { border-top: 1px solid var(--rule); margin: var(--s1) 0; }
.menu .sheet form.inline { padding: var(--s2) var(--s3); display: flex; flex-direction: column; gap: var(--s2); }
.menu .sheet form.inline input { width: 100%; }

/* empty state: one line stating the situation, one next action */
.empty { color: var(--ink-muted); padding: var(--s5) var(--s3); text-align: center; }
.empty strong { display: block; color: var(--ink); font-weight: 500; margin-bottom: 2px; }
td.empty { padding: var(--s5); }

/* ---------- panels: --surface, 1px rule, 4px radius, no shadow ---------- */
.card { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s4); min-width: 0; }
.card > h2 { margin-bottom: var(--s3); } .card > :last-child { margin-bottom: 0; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s3); }
.card-head h2 { margin: 0; } .card-head h2 .caption { font-weight: 400; margin-left: 4px; }
.card-head .search { flex: 0 1 260px; } .card-head .search input { width: 100%; }
.card.flat { padding: 0; } .card.flat > .table-wrap { margin: 0 !important; width: auto !important; }
.card.flat > .card-head { margin: 0; padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule); }
.card.highlight { border-color: var(--accent); }
.card-list { display: flex; flex-direction: column; gap: var(--s3); list-style: none; margin: 0; padding: 0; }
.card > .table-wrap { margin: 0 calc(-1 * var(--s4)); width: calc(100% + 2 * var(--s4)); }
.card > .table-wrap:first-child { margin-top: calc(-1 * var(--s4)); } .card > .table-wrap:last-child { margin-bottom: calc(-1 * var(--s4)); }
.card > .table-wrap th:first-child, .card > .table-wrap td:first-child { padding-left: var(--s4); }
.card > .table-wrap th:last-child, .card > .table-wrap td:last-child { padding-right: var(--s4); }

/* KPI numbers: a plain tabular number over a 12px label, separated by rules - no cards, no boxes (brief §4) */
.stats { display: flex; flex-wrap: wrap; gap: 0; border: 1px solid var(--rule); border-radius: var(--radius); background: var(--surface); }
.stats > .stat { flex: 1 1 160px; border: 0; border-radius: 0; border-left: 1px solid var(--rule); padding: var(--s3) var(--s4); }
.stats > .stat:first-child { border-left: 0; }
.stat .n { display: block; font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.01em; line-height: 1.2; font-variant-numeric: tabular-nums; }
.stat .n a { color: inherit; }
.stat .l, .stat-top .l { display: block; font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); margin-top: 2px; }
.stat .sub { display: block; font-size: var(--fs-label); color: var(--ink-muted); margin-top: var(--s1); }
.stat-top .tile { display: none; }
.stat-row { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s3); }
.stat-main { min-width: 0; }
.delta { display: block; font-size: var(--fs-label); margin-top: var(--s1); color: var(--ink-muted); }
.delta.up { color: var(--clear); } .delta.down { color: var(--overdue); }
.spark-line { flex: 0 0 auto; color: var(--ink-muted); } .spark-line.up { color: var(--clear); } .spark-line.down { color: var(--overdue); }
.spark-line polyline { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.spark-line circle { fill: currentColor; }
.figures { display: flex; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.figures > div { flex: 1 1 0; padding: var(--s3) var(--s4); border-left: 1px solid var(--rule); }
.figures > div:first-child { border-left: 0; padding-left: 0; }
.figures .n { display: block; font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.figures .l { display: block; font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); }

/* one-line lists */
.lines { list-style: none; margin: 0; padding: 0; }
.lines li { display: flex; gap: var(--s3); align-items: baseline; padding: var(--s2) 0; border-top: 1px solid var(--rule); min-height: var(--row); }
.lines li:first-child { border-top: 0; }
.lines .when { flex: 0 0 96px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.lines .what { flex: 1; min-width: 0; }
.lines .amount { margin-left: auto; font-variant-numeric: tabular-nums; white-space: nowrap; }
.plain-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.plain-list > li { padding: var(--s3) 0; border-top: 1px solid var(--rule); } .plain-list > li:first-child { border-top: 0; }
.plain-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s4); }
.plain-row .who { min-width: 0; flex: 1 1 auto; } .plain-row .who .name { font-weight: 500; }
.plain-row .who .why { color: var(--ink-muted); margin-top: 2px; }
.plain-row .aside { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; text-align: right; }
.plain-row .aside .value { font-weight: 500; font-variant-numeric: tabular-nums; } .plain-row .aside .value .to { font-weight: 400; color: var(--ink-muted); }
.plain-row .go { font-size: var(--fs-label); font-weight: 500; }
.facts { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3); list-style: none; padding: 0; margin: var(--s1) 0 0; font-size: var(--fs-label); color: var(--ink-muted); }
.facts li + li::before { content: "·"; margin-right: var(--s3); }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--s1) var(--s4); margin: 0; }
.kv dt { color: var(--ink-muted); font-size: var(--fs-label); font-weight: 500; } .kv dd { margin: 0; overflow-wrap: anywhere; }

/* ---------- tables (generic): 40px rows, 1px rule between rows, no vertical lines, no zebra ---------- */
.table-wrap { overflow-x: clip; max-width: 100%; }
table { border-collapse: collapse; border-spacing: 0; width: 100%; background: var(--surface); }
th, td { height: var(--row); padding: 0 var(--s3); border-bottom: 1px solid var(--rule); text-align: left; vertical-align: middle; border-radius: 0; }
th { font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); white-space: nowrap; background: var(--surface); }
/* The toolbar above is sticky too. Both anchored at 0 meant the header slid underneath it and vanished, so
   the header parks at the toolbar's height. chassis.js measures the visible bar and keeps --top-h honest
   through wrapping, zoom and the bulk-bar swap; the fallback below is the unwrapped toolbar's height. */
thead th { position: sticky; top: var(--top-h, 0px); z-index: 2; box-shadow: inset 0 -1px 0 var(--rule); border-bottom: 0; }
th a { color: var(--ink-muted); } th a:hover { color: var(--ink); text-decoration: none; }
th a.on { color: var(--ink); } th a.on::after { content: " ↓"; } th a.on.asc::after { content: " ↑"; }
td.check, th.check { width: 36px; padding-right: 0; }
/* Row states -- hover, keyboard cursor, selected -- are decided once, in the spreadsheet section below. They
   used to be declared here as well, in the older accent-wash language, and the two sets disagreed. */
/* A click anywhere on these rows opens the record, so a click-drag across one used to leave the browser's
   blue text-selection band over half the table -- selection the user never asked for and cannot act on. */
tbody tr.row-link, tbody tr[data-href] { cursor: pointer; -webkit-user-select: none; user-select: none; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
td .caption { display: block; line-height: 1.3; }
tr.done td { color: var(--ink-muted); text-decoration: line-through; }
.col-wide { min-width: 140px; } td.col-wide { min-width: 300px; }
.truncate { max-width: 36ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
td.wrap2 { max-width: 46ch; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: var(--ink-muted); }
.pager { display: flex; gap: var(--s3); align-items: center; margin-top: var(--s3); color: var(--ink-muted); flex-wrap: wrap; font-variant-numeric: tabular-nums; }
.pager .btn { min-height: 28px; }
.load-more-row td { text-align: center; } tr.load-more-row:hover td { background: none; }

/* ---------- the list chassis (templates/_list.html; brief §5) ---------- */
.list { --list-x: var(--s4); }
.list-top { display: flex; align-items: center; gap: var(--s3); min-height: 40px; margin-bottom: var(--s3); flex-wrap: wrap; }
.list-top h1 { display: flex; align-items: center; gap: 6px; }
.list-top .view-menu > summary { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.01em; color: var(--ink); min-height: 32px; }
.list-top .view-menu > summary .icon { color: var(--ink-muted); width: 18px; height: 18px; }
.list-top .view-menu > summary:hover { color: var(--accent); }
.list-top .search { flex: 0 1 300px; min-width: 180px; margin-left: auto; }
.list-top .search input { width: 100%; }
.list-top .primary { flex: 0 0 auto; }
.list-chips { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; margin: 0 0 var(--s3); }
.list-chips .chip { padding-right: 4px; color: var(--ink); }
.list-chips .chip a { display: inline-flex; color: var(--ink-muted); padding: 2px; border-radius: 2px; }
.list-chips .chip a:hover { color: var(--overdue); text-decoration: none; }
.list-chips .chip .icon { width: 12px; height: 12px; }
.list-chips .clear { color: var(--ink-muted); font-size: var(--fs-label); }
.list-bulk { display: none; position: sticky; top: 0; z-index: 20; align-items: center; gap: var(--s3); min-height: 40px; margin-bottom: var(--s3); padding: 0 var(--s3); background: var(--accent-weak); border: 1px solid var(--rule); border-radius: var(--radius); }
.list-bulk .n { font-weight: 600; color: var(--accent); font-variant-numeric: tabular-nums; }
.list.selecting .list-top, .list.selecting .list-chips { display: none; }
.list.selecting .list-bulk { display: flex; }
.list-table { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); }
.list-table > .table-wrap { border-radius: var(--radius); }
.list-table table { font-size: var(--fs-body); }
.list-table.dense table { font-size: var(--fs-dense); line-height: 1.45; }
.list-table th:first-child, .list-table td:first-child { padding-left: var(--list-x); }
.list-table th:last-child, .list-table td:last-child { padding-right: var(--list-x); }
.list-table tbody tr:last-child td { border-bottom: 0; }
.list-table td.check, .list-table th.check { width: 32px; padding-left: var(--s3); padding-right: 0; }
.list-table td.check input, .list-table th.check input { opacity: 0; transition: opacity var(--t-row); }
.list-table tr:hover td.check input, .list-table tr:focus-within td.check input, .list-table td.check input:checked, .list-table td.check input:focus-visible,
.list.selecting td.check input, .list.selecting th.check input, .list-table th.check input:checked, .list-table tr.key-focused td.check input { opacity: 1; }
.list-table td.text a { color: var(--ink); font-weight: 500; }
.list-table td.text a:hover { color: var(--accent); }
.list-table td.money, .list-table td.date, .list-table td.number, .list-table th.money, .list-table th.date, .list-table th.number { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.list-table td .sub { display: block; font-size: var(--fs-label); color: var(--ink-muted); line-height: 1.3; }
.list-table td.date .due-soon { color: var(--due-soon); } .list-table td.date .overdue { color: var(--overdue); }
.list-foot { display: flex; align-items: center; gap: var(--s3); min-height: 40px; margin-top: var(--s2); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.list-foot .spacer { flex: 1; }
.list-foot .btn { min-height: 28px; }
.list-empty { padding: var(--s6) var(--s4); text-align: center; color: var(--ink-muted); }
.list-empty strong { display: block; color: var(--ink); font-weight: 500; }
.list-empty a { margin-top: var(--s2); display: inline-block; }

/* ---------- record page (brief §6): identity strip, underlined tabs, 65/35 body, rail blocks separated by rules ---------- */
.record { margin: calc(-1 * var(--s5)) calc(-1 * var(--s6)) 0; }
.identity { background: var(--surface); border-bottom: 1px solid var(--rule); padding: var(--s5) var(--s6) 0; border-radius: 0; }
.identity-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }
.identity-head h1 { font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.01em; }
.identity-head .crumb { display: block; font-size: var(--fs-label); color: var(--ink-muted); margin-bottom: 2px; }
.identity-head .crumb a { color: var(--ink-muted); }
.identity-head .actions { flex: 0 0 auto; }
.identity-fields { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s6); margin: var(--s4) 0 0; padding: 0; list-style: none; }
.identity-fields li { min-width: 120px; }
.identity-fields .l { display: block; font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); }
.identity-fields .v { display: block; font-variant-numeric: tabular-nums; }
.identity-fields .v.status { display: inline-flex; }
.tabrow { display: flex; gap: var(--s5); margin: var(--s3) 0 0; overflow-x: auto; }
.tabrow a { display: inline-flex; align-items: center; min-height: 36px; padding: 0 2px; color: var(--ink-muted); border-bottom: 2px solid transparent; white-space: nowrap; font-weight: 500; }
.tabrow a:hover { color: var(--ink); text-decoration: none; }
.tabrow a[aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); }
.record-body { display: grid; grid-template-columns: minmax(0, 65fr) minmax(280px, 35fr); gap: var(--s6); padding: var(--s5) var(--s6) 0; align-items: start; }
.record-main { min-width: 0; }
.record-main > section + section { margin-top: var(--s5); }
.record-main h2 { margin-bottom: var(--s3); }
.rail { min-width: 0; }
.rail-block { padding: var(--s3) 0 var(--s4); border-top: 1px solid var(--rule); }
.rail-block:first-child { border-top: 0; padding-top: 0; }
.rail-block h2 { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s2); font-size: var(--fs-body); margin-bottom: var(--s2); }
.rail-block h2 a { font-size: var(--fs-label); font-weight: 500; }
.rail-block ul { list-style: none; margin: 0; padding: 0; }
.rail-block li { display: flex; justify-content: space-between; gap: var(--s3); align-items: baseline; min-height: 28px; }
.rail-block li .sub { color: var(--ink-muted); font-size: var(--fs-label); }
.rail-block li .r { margin-left: auto; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink-muted); font-size: var(--fs-dense); }
.rail-block .none { color: var(--ink-muted); }

/* ---------- Leads: filters down the left (docs/DESIGN-BRIEF.md §10) ---------- */
.leads-layout { display: grid; grid-template-columns: 168px minmax(0, 1fr); gap: var(--s5); align-items: start; }
.filters-rail { min-width: 0; }
.filters-rail .group { padding: var(--s3) 0; border-top: 1px solid var(--rule); }
.filters-rail .group:first-child { border-top: 0; padding-top: 0; }
.filters-rail h2 { font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); margin: 0 0 var(--s2); }
.filters-rail ul { list-style: none; margin: 0; padding: 0; }
.filters-rail li { border-bottom: 1px solid var(--rule); }
.filters-rail li:last-child { border-bottom: 0; }
.filters-rail a { display: block; padding: var(--s2) 0; color: var(--ink); text-decoration: none; }
.filters-rail a:hover { color: var(--accent); }
.filters-rail a[aria-current="true"] { color: var(--accent); font-weight: 500; }

details.more { border-top: 1px solid var(--rule); margin-top: var(--s5); }
details.more > summary { cursor: pointer; min-height: 40px; display: flex; align-items: center; gap: var(--s2); color: var(--ink-muted); font-weight: 500; list-style: none; }
details.more > summary::-webkit-details-marker { display: none; }
details.more > summary::before { content: "›"; display: inline-block; width: 12px; transition: transform var(--t-row); }
details.more[open] > summary::before { transform: rotate(90deg); }
details.more > .body { padding-bottom: var(--s3); }
.table-plain { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--surface); }
.table-plain tbody tr:last-child td { border-bottom: 0; }
.table-plain th:first-child, .table-plain td:first-child { padding-left: var(--s4); } .table-plain th:last-child, .table-plain td:last-child { padding-right: var(--s4); }
.notice { display: flex; gap: var(--s3); align-items: center; padding: var(--s2) var(--s3); border: 1px solid var(--rule); border-left: 2px solid var(--due-soon); border-radius: var(--radius); background: var(--surface); margin-bottom: var(--s3); }
/* A permanent statement of how the product works is NOT an alert. `.notice` earns its amber edge by
   telling the reader something has gone wrong or needs doing; the filing screens use the same box for a
   sentence that is true forever and can never be acted on or dismissed, which is why it reads as a
   notification nobody can clear (owner, 2026-09-07: "is this a notif or a tutorial message?"). Same
   words, no edge, no box: a quiet caption under the heading, where a standing caveat belongs. */
.notice.standing { border: 0; border-left: 0; padding: 0; background: none; color: var(--ink-muted);
  font-size: var(--fs-dense); margin-bottom: var(--s3); }
/* billfish/web/auth/middleware.py::enforce_open_mode_is_safe -- open auth mode has no login at all, so this stays
   full-width, top of every page (bare pages too), and never auto-dismisses: it is a fact about this deployment,
   not a message to acknowledge and forget. Plain and functional on purpose -- no icon, no dismiss button. */
.open-auth-banner { position: sticky; top: 0; z-index: 40; display: block; padding: var(--s2) var(--s4); background: var(--bad-soft); color: var(--overdue); border-bottom: 1px solid var(--overdue); font-size: var(--fs-dense); text-align: center; }
.open-auth-banner a { color: inherit; text-decoration: underline; }
.record-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--s3) var(--s5); }
.record-summary .l { display: block; font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); }

/* ---------- tabs (legacy) ---------- */
.tabs { display: flex; gap: var(--s4); border-bottom: 1px solid var(--rule); margin: var(--s3) 0 var(--s4); overflow-x: auto; }
.tabs a { display: inline-flex; align-items: center; min-height: 36px; padding: 0 2px; color: var(--ink-muted); border-bottom: 2px solid transparent; white-space: nowrap; font-weight: 500; }
.tabs a:hover { color: var(--ink); text-decoration: none; } .tabs a.on, .tabs a[aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); }

/* ---------- timeline / activity (ACTIVITY) ---------- */
.timeline { list-style: none; padding: 0; margin: 0; }
.timeline li { position: relative; padding: var(--s1) 0 var(--s3) var(--s4); border-left: 1px solid var(--rule); margin-left: 4px; }
.timeline li::before { content: ""; position: absolute; left: -4px; top: 10px; width: 7px; height: 7px; border-radius: 50%; background: var(--rule); }
.timeline li.accent::before, .timeline li.note-status_change::before { background: var(--accent); }
.timeline .when { color: var(--ink-muted); font-size: var(--fs-label); display: block; }
.why-now { margin: 0; padding-left: 0; list-style: none; display: flex; flex-direction: column; gap: var(--s2); } .why-now li { max-width: 72ch; }
.activity-card .composer { border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s3); background: var(--surface); margin-bottom: var(--s4); }
.composer-tabs { display: flex; gap: var(--s3); margin-bottom: var(--s3); border-bottom: 1px solid var(--rule); }
.composer-tabs .tab { min-height: 32px; padding: 0 2px; border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: none; color: var(--ink-muted); }
.composer-tabs .tab.on { color: var(--accent); border-bottom-color: var(--accent); font-weight: 500; }
.composer-pane textarea { min-height: 72px; } .composer-pane[hidden] { display: none; }
.composer-pane .row { flex-wrap: wrap; align-items: center; } .composer-pane .row label.caption { display: flex; align-items: center; gap: 4px; }
.timeline-pinned { margin-bottom: var(--s3); padding-bottom: var(--s3); border-bottom: 1px solid var(--rule); }
.timeline-feed { margin: 0; }
.entry { display: flex; gap: var(--s3); align-items: flex-start; padding: var(--s2) 0; border-top: 1px solid var(--rule); }
.lines .entry:first-child, ul.timeline-feed li.entry:first-child, ul.timeline-pinned li.entry:first-child { border-top: 0; }
.entry-icon { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; color: var(--ink-muted); }
.entry-icon .icon { width: 14px; height: 14px; }
.entry-body { flex: 1; min-width: 0; }
.entry-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; color: var(--ink-muted); font-size: var(--fs-label); margin-bottom: 2px; }
.entry-text { white-space: pre-wrap; word-break: break-word; }
.entry.pinned { background: var(--paper); border-radius: var(--radius); padding: var(--s2) var(--s3); border-top: none; margin-bottom: var(--s2); }
.note-edit { margin-top: 4px; } .note-edit summary { color: var(--ink-muted); cursor: pointer; list-style: none; } .note-edit summary::-webkit-details-marker { display: none; }
.note-edit form { margin-top: var(--s2); } .entry-actions { margin-top: 4px; display: flex; gap: var(--s2); }
.task-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; } .task-row.done { color: var(--ink-muted); text-decoration: line-through; }
/* the task tick button; never the chassis's th/td.check selection column */
button.check { width: 18px; height: 18px; min-width: 18px; min-height: 0; border-radius: var(--radius); border: 1px solid var(--rule); background: var(--surface); color: var(--clear); display: inline-flex; align-items: center; justify-content: center; padding: 0; font-size: 11px; line-height: 1; }
.task-row.done .check, li.done .check { background: var(--accent-weak); border-color: var(--clear); }
li.pinned { background: var(--paper); border-radius: var(--radius); padding: 0 var(--s2); }
.stage-select { display: flex; align-items: center; gap: var(--s2); } .stage-select select { min-width: 160px; font-weight: 500; }

/* stepper (legacy, unused by the current lead page) */
.stepper { display: flex; list-style: none; margin: 0; padding: 0; overflow-x: auto; }
.stepper li { flex: 1 1 0; min-width: 0; position: relative; }
.stepper li + li::before { content: ""; position: absolute; left: -50%; right: 50%; top: 11px; height: 1px; background: var(--rule); }
.stepper li.done + li::before { background: var(--accent); }
.stepper button, .stepper span.step { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; width: 100%; background: none; border: 0; padding: 0; min-height: 40px; color: var(--ink-muted); font-size: var(--fs-label); font-weight: 500; cursor: pointer; }
.stepper .dot { width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--rule); background: var(--surface); display: grid; place-items: center; font-size: 11px; color: var(--ink-muted); }
.stepper li.done .dot { background: var(--accent); border-color: var(--accent); color: #fff; }
.stepper li.current .dot { border-color: var(--accent); color: var(--accent); }
.stepper li.current button, .stepper li.current span.step { color: var(--ink); font-weight: 600; }

/* ---------- charts (competitor/finance pages): one ink, dashed rules ---------- */
.chart-card .card-head { margin-bottom: var(--s2); }
.chart-scroll { overflow-x: auto; }
.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart text { fill: var(--ink-muted); font-size: 11px; }
.chart .grid { stroke: var(--rule); } .chart .axis { stroke: var(--rule); }
.chart .hatch { stroke: var(--ink-muted); stroke-width: 1; }
.chart .bar .hit { fill: transparent; }
.chart .bar .base { fill: var(--paper); stroke: var(--rule); stroke-width: 1; }
.chart .bar .fill { opacity: .55; }
.chart .bar.cur .base { fill: var(--accent); stroke: var(--accent); } .chart .bar.cur .fill { opacity: 0; }
.chart .bar:hover .base, .chart .bar:focus .base { stroke: var(--ink); }
.chart .bar { outline: none; cursor: default; }
.chart .tip { display: none; pointer-events: none; }
.chart .bar:hover .tip, .chart .bar:focus .tip, .chart .bar:focus-within .tip { display: block; }
.chart .tip rect { fill: var(--ink); } .chart .tip text { fill: #fff; font-size: 11px; font-weight: 500; }
.chart .lab { fill: var(--ink-muted); }
.spark { vertical-align: middle; display: inline-block; } .spark rect { fill: var(--rule); } .spark rect.cur { fill: var(--accent); } .spark rect.zero { fill: var(--paper); }
.sparkbar { display: inline-block; height: 8px; background: var(--accent); vertical-align: middle; }
.field-list { margin: 0; display: flex; flex-direction: column; gap: var(--s2); }
.field-list dt, .field-list dd { margin: 0; } .field-list dt { color: var(--ink-muted); font-size: var(--fs-label); font-weight: 500; margin-top: var(--s3); } .field-list dt:first-child { margin-top: 0; }
.field-list code { white-space: normal; overflow-wrap: anywhere; color: var(--ink); }

/* ---------- misc pages ---------- */
.login { max-width: 360px; margin: 10vh auto; text-align: center; } .login input { width: 100%; } .login form { text-align: left; } .login button { width: 100%; }
.htmx-request .htmx-indicator { display: inline; } .htmx-indicator { display: none; }
.digest-html { border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s3) var(--s4); background: var(--surface); overflow: auto; } .digest-html img { max-width: 100%; }
.freshness { font-size: var(--fs-label); color: var(--ink-muted); display: flex; gap: var(--s3); flex-wrap: wrap; margin-top: var(--s6); }
.freshness span + span::before { content: "·"; margin-right: var(--s3); }
.prose h1 { font-size: var(--fs-title); margin: 8px 0 12px; } .prose h2 { font-size: var(--fs-h2); margin: 26px 0 8px; } .prose p, .prose li { max-width: 72ch; margin: 6px 0; }
.prose table { margin: 10px 0 16px; } .prose code { font-size: 12px; }
.today-grid { grid-template-columns: minmax(0, 3fr) minmax(300px, 2fr); align-items: start; }
@media (max-width: 960px) { .today-grid { grid-template-columns: minmax(0, 1fr); } }
#new-api-key, #new-webhook-secret { font-family: var(--mono); font-size: var(--fs-body); }
#leads-table:not(.selecting) .bulk-bar { display: none; }
#leads-table:not(.selecting) th.check, #leads-table:not(.selecting) td.check { display: none; }
.selecting .lead-card .select { display: block; }
.lead-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s2) var(--s4); align-items: start; }
.lead-card .head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; } .lead-card .name { font-size: var(--fs-h2); font-weight: 600; }
.lead-card .why { margin: var(--s1) 0 0; max-width: 80ch; }
.lead-card .aside { display: flex; flex-direction: column; align-items: flex-end; gap: var(--s1); text-align: right; min-width: 92px; }
.lead-card .value { font-size: var(--fs-h2); font-weight: 600; } .lead-card .select { display: none; align-self: center; }
.lead-card .foot { grid-column: 1 / -1; display: flex; justify-content: space-between; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* board (kanban: /board/<object> and the Pipeline; the one place a card may carry a coloured left edge) */
.view-switch a { display: inline-flex; align-items: center; gap: var(--s1); } .view-switch .icon { width: 14px; height: 14px; }
#board-root { margin-top: var(--s3); }
.board-cols { display: flex; align-items: flex-start; gap: var(--s3); overflow-x: auto; padding-bottom: var(--s4); }
.board-col { flex: 0 0 200px; width: 200px; display: flex; flex-direction: column; max-height: calc(100dvh - 140px); background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius); }
.board-col.collapsed { flex-basis: 48px; width: 48px; }
.board-col.collapsed .board-col-body, .board-col.collapsed .board-col-title, .board-col.collapsed .board-col-row2, .board-col.collapsed .board-col-add { display: none; }
.board-col.collapsed .board-col-head { padding: var(--s3) var(--s2); } .board-col.collapsed .board-col-row1 { flex-direction: column; }
.board-col-head { display: flex; flex-direction: column; gap: var(--s2); padding: var(--s2) var(--s3); border-bottom: 1px solid var(--rule); }
.board-col-row1, .board-col-row2 { display: flex; align-items: center; gap: var(--s2); }
.board-col-title { font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); margin: 0; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-col-drag { cursor: grab; color: var(--ink-muted); letter-spacing: -2px; touch-action: none; }
.board-col-sum { white-space: nowrap; font-variant-numeric: tabular-nums; }
.board-col.wip-over .board-col-count { color: var(--overdue); }
.board-wip { display: inline-flex; margin-left: auto; } .board-wip .wip-input { width: 52px; min-height: 26px; padding: 0 6px; font-size: var(--fs-label); }
.board-col-add[disabled] { opacity: .4; cursor: not-allowed; }
.board-col-body { list-style: none; margin: 0; padding: var(--s2); display: flex; flex-direction: column; gap: var(--s2); overflow-y: auto; flex: 1 1 auto; min-height: 60px; }
.board-col.over .board-col-body { background: var(--accent-weak); }
.board-empty-slot { min-height: 8px; }
.board-card { list-style: none; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s2) var(--s3); cursor: grab; touch-action: none; user-select: none; }
.board-card.stale { border-left: 2px solid var(--due-soon); }
.board-card:focus-visible, .board-card.grabbed { outline: 2px solid var(--accent); outline-offset: 1px; }
.board-card.dragging { opacity: .6; cursor: grabbing; }
.bc-head { display: flex; align-items: center; gap: var(--s2); } .bc-title { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bc-sub { margin: 2px 0 0; color: var(--ink-muted); font-size: var(--fs-label); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.bc-foot { margin-top: var(--s2); display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); } .bc-value { margin-left: auto; font-weight: 500; font-variant-numeric: tabular-nums; }
.kanban-fallback { display: flex; gap: var(--s3); overflow-x: auto; padding-bottom: var(--s2); align-items: flex-start; }
.kanban-col { flex: 0 0 260px; background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s3); }
.kanban-col-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s3); } .kanban-col-head h3 { margin: 0; }
.kanban-cards { display: flex; flex-direction: column; gap: var(--s2); }
.kanban-card { display: block; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s2) var(--s3); color: var(--ink); } .kanban-card:hover { text-decoration: none; border-color: var(--ink-muted); }

/* command menu + shortcuts help: floating, so they get the shadow */
dialog.cmdk, dialog.shortcuts-help { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--surface); color: var(--ink); box-shadow: var(--shadow-float); padding: 0; }
dialog.cmdk::backdrop, dialog.shortcuts-help::backdrop { background: rgba(25, 28, 27, .35); }
dialog.cmdk { width: min(560px, 92vw); margin-top: 12vh; }
.cmdk-search-row { display: flex; align-items: center; gap: var(--s2); padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule); }
.cmdk-icon { display: inline-flex; color: var(--ink-muted); }
#cmdk-input { flex: 1 1 auto; border: none; outline: none; background: transparent; font-size: var(--fs-h2); color: var(--ink); min-height: auto; padding: 0; box-shadow: none; }
.cmdk-list { max-height: 50vh; overflow-y: auto; padding: var(--s2) 0; }
.cmdk-group-label { padding: var(--s2) var(--s4) var(--s1); font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); }
.cmdk-item { display: flex; align-items: center; gap: var(--s3); padding: var(--s2) var(--s4); cursor: pointer; }
.cmdk-item .icon { flex: 0 0 auto; color: var(--ink-muted); }
.cmdk-item-text { display: flex; flex-direction: column; min-width: 0; }
.cmdk-item-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk-item-sub { font-size: var(--fs-label); color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk-item.active { background: var(--accent-weak); }
.cmdk-empty { padding: var(--s5) var(--s4); text-align: center; color: var(--ink-muted); }
.cmdk-hints { display: flex; gap: var(--s4); padding: var(--s2) var(--s4); border-top: 1px solid var(--rule); color: var(--ink-muted); font-size: var(--fs-label); }
.cmdk-hints .key + .key { margin-left: 2px; }
dialog.shortcuts-help { width: min(480px, 92vw); padding: var(--s5); }
dialog.shortcuts-help dl { display: flex; flex-direction: column; gap: var(--s3); margin: var(--s4) 0; }
dialog.shortcuts-help dl > div { display: flex; justify-content: space-between; align-items: center; gap: var(--s4); }
dialog.shortcuts-help dt { display: flex; align-items: center; gap: 4px; } dialog.shortcuts-help dd { margin: 0; color: var(--ink-muted); text-align: right; }

/* model: data-model + workflows forms; records: inline field edit */
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3) var(--s4); align-items: start; }
.form-grid .span-2 { grid-column: 1 / -1; } .form-grid label { display: flex; flex-direction: column; gap: 2px; }
label.check { flex-direction: row; align-items: center; gap: 6px; } label.check input { margin-top: 0; width: auto; }
.inline-label { display: inline-flex; align-items: center; gap: 6px; margin-bottom: var(--s3); } .inline-form { display: inline-flex; margin: 0; }
.field-form .opt-group { border-top: 1px dashed var(--rule); padding-top: var(--s3); margin-top: var(--s1); }
.field-preview { border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s3); background: var(--paper); }
.preview .caption { margin-bottom: 4px; }
.wf-form .wf-card { margin-bottom: var(--s4); } .wf-card h2 { margin-bottom: var(--s3); }
.trig-group { margin-top: var(--s3); } .trig-group[hidden] { display: none; }
.rule-row { border-top: 1px solid var(--rule); padding-top: var(--s3); margin-top: var(--s3); } .rule-row:first-of-type { border-top: none; padding-top: 0; margin-top: var(--s2); }
.wf-action-card { border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s4); margin-bottom: var(--s3); background: var(--paper); }
.wf-action-card .act-group[hidden] { display: none; } .wf-action-card .act-group { margin-top: var(--s3); } .wf-action-card > label { max-width: 320px; }
td .icon-btn { padding: 2px; min-height: 26px; min-width: 26px; }
.filter-chips { margin-bottom: var(--s3); } .filter-chips .chip { gap: 6px; } .filter-chips .chip a { display: inline-flex; color: var(--ink-muted); }
.filter-chips .chip a:hover { color: var(--overdue); text-decoration: none; } .filter-chips .chip .icon { width: 12px; height: 12px; }
.field-cell { display: block; } .field-display { display: flex; align-items: center; gap: 6px; min-height: 28px; }
.field-display .icon-btn { color: var(--ink-muted); } .field-display .icon-btn:hover { color: var(--ink); }
.field-edit { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; } .field-edit input, .field-edit select, .field-edit textarea { min-height: 30px; }
.field-edit textarea { min-height: 60px; width: 100%; flex-basis: 100%; }
.records-detail { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1.3fr) minmax(260px, 1fr); gap: var(--s4); align-items: start; }
@media (max-width: 960px) { .records-detail { grid-template-columns: minmax(0, 1fr); } }

/* ---------- Today (docs/DESIGN-BRIEF.md §4): a work queue, not a dashboard. .figures above is the header-strip
   primitive; below it is one continuous list, grouped under quiet 12px headings with a hairline rule -- never
   sections in separate cards. Each row's primary action is revealed on hover/focus, the same opacity-swap
   `.list-table td.check input` already uses. ---------- */
.today-page { max-width: 900px; margin: 0 auto; }
.today-page .figures { margin: var(--s5) 0 var(--s6); }
.today-empty { padding: var(--s7) 0; color: var(--ink-muted); text-align: center; }
.today-group + .today-group { margin-top: var(--s6); }
.today-group-label { font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); margin: 0 0 var(--s1); padding-bottom: var(--s2); border-bottom: 1px solid var(--rule); }
.today-list { list-style: none; margin: 0; padding: 0; }
.today-row { display: flex; align-items: center; gap: var(--s3); min-height: var(--row); border-bottom: 1px solid var(--rule); }
.today-list .today-row:last-child { border-bottom: 0; }
.today-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.today-text .subject { font-weight: 500; }
.today-text .desc { color: var(--ink-muted); }
.today-right { flex: 0 0 auto; display: flex; align-items: center; gap: var(--s3); }
.today-date { font-variant-numeric: tabular-nums; color: var(--ink-muted); white-space: nowrap; font-size: var(--fs-dense); }
.today-date.due-soon { color: var(--due-soon); } .today-date.overdue { color: var(--overdue); }
.today-action { opacity: 0; transition: opacity var(--t-row); }
.today-row:hover .today-action, .today-row:focus-within .today-action, .today-action:focus-visible { opacity: 1; }

/* ---------- FILINGS-UI: the two-column review (docs/DESIGN-BRIEF.md §9, docs/NOTES-FILINGS-UI.md) ---------- */
.filing-problems { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--surface); margin: var(--s4) 0; }
.filing-problems h2 { padding: var(--s3) var(--s4) 0; margin-bottom: var(--s2); }
.filing-problem { display: flex; gap: var(--s3); align-items: flex-start; padding: var(--s3) var(--s4); border-top: 1px solid var(--rule); }
.filing-problem:first-child { border-top: 0; }
.filing-problem .status { flex: 0 0 auto; }
.filing-problem .msg { flex: 1; min-width: 0; }
.filing-problem .fix { display: block; color: var(--ink-muted); font-size: var(--fs-label); margin-top: 2px; }
.filing-part { margin-top: var(--s5); } .filing-part:first-child { margin-top: 0; }
.filing-part h2 { margin-bottom: var(--s2); }
.filing-field { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s4); padding: var(--s3) 0; border-top: 1px solid var(--rule); }
.filing-field:first-child { border-top: 0; }
.fv-label { font-size: var(--fs-label); font-weight: 500; color: var(--ink-muted); }
.fv-value { font-variant-numeric: tabular-nums; font-size: var(--fs-h2); font-weight: 600; margin-top: 2px; }
.fv-value.text { font-size: var(--fs-body); font-weight: 400; font-variant-numeric: normal; }
.fv-value.muted { color: var(--ink-muted); font-weight: 400; font-size: var(--fs-body); }
.fv-note { font-size: var(--fs-label); color: var(--ink-muted); margin-top: var(--s1); }
.fv-missing { margin-top: var(--s2); padding: var(--s2) var(--s3); border: 1px solid var(--due-soon); border-radius: var(--radius); background: var(--warn-soft); font-size: var(--fs-dense); }
.filing-sources { list-style: none; margin: 0; padding: 0; }
.filing-sources li { display: flex; justify-content: space-between; gap: var(--s3); padding: 4px 0; border-top: 1px solid var(--rule); font-size: var(--fs-dense); }
.filing-sources li:first-child { border-top: 0; }
.filing-sources .amt { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink-muted); }
.filing-sources-none { color: var(--ink-muted); font-size: var(--fs-dense); }
@media (max-width: 700px) { .filing-field { grid-template-columns: minmax(0, 1fr); } }

/* ---------- BILLING: the Billing list's one-click Approve column (docs/DESIGN-BRIEF.md §8) ---------- */
.list-table td.action, .list-table th.action { text-align: right; white-space: nowrap; }
.row-action { display: inline-flex; }

/* ---------- BILLING: the invoice document (docs/DESIGN-BRIEF.md §8) -- "a document, not a form; it should look
   like the thing that will be emailed." Firm block, client block, engagement, line items, totals, balance and
   payment terms live inside this card; Send invoice / Record payment / Void sit in the page's own .actions,
   outside it. ---------- */
.invoice-doc { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s6); max-width: 820px; }
.invoice-doc-head, .invoice-doc-bill { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--s5); flex-wrap: wrap; margin-bottom: var(--s5); }
.invoice-doc-block { min-width: 0; display: flex; flex-direction: column; }
.invoice-doc-block .label { margin-bottom: 2px; }
.invoice-doc-block .name { font-weight: 600; }
.invoice-doc-block .line { color: var(--ink-muted); }
.invoice-doc-meta { text-align: right; flex: 0 0 auto; }
.invoice-doc-meta h1 { font-size: var(--fs-h2); font-weight: 500; color: var(--ink-muted); }
.invoice-doc-meta .num { display: block; font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; margin-top: 2px; }
.invoice-doc-kv { display: grid; grid-template-columns: max-content max-content; gap: 2px var(--s4); justify-content: end; align-content: start; margin: 0; }
.invoice-doc-kv dt { color: var(--ink-muted); font-size: var(--fs-label); font-weight: 500; text-align: right; }
.invoice-doc-kv dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.invoice-doc-totals { display: grid; grid-template-columns: max-content max-content; gap: var(--s1) var(--s5); justify-content: end; margin: var(--s4) 0 0; width: 100%; }
.invoice-doc-totals dt { color: var(--ink-muted); margin: 0; }
.invoice-doc-totals dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.invoice-doc-totals dt.total, .invoice-doc-totals dd.total { font-weight: 600; border-top: 1px solid var(--rule); padding-top: var(--s2); }
.invoice-doc-totals dt.balance, .invoice-doc-totals dd.balance { font-weight: 600; color: var(--ink); font-size: var(--fs-h2); }
.invoice-doc-terms { margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--rule); color: var(--ink-muted); font-size: var(--fs-dense); }
.invoice-doc-terms p:last-child { margin-bottom: 0; }
@media (max-width: 640px) { .invoice-doc { padding: var(--s4); } .invoice-doc-kv, .invoice-doc-totals { justify-content: start; } .invoice-doc-meta { text-align: left; } }

/* ---------- narrow screens: the sidebar becomes a drawer behind a menu button; nothing else changes ---------- */
@media (max-width: 900px) {
  .sidebar { position: fixed; left: 0; top: 0; bottom: 0; height: 100dvh; z-index: 40; transform: translateX(-102%); transition: transform var(--t-panel); }
  html[data-drawer="open"] .sidebar { transform: none; box-shadow: var(--shadow-float); }
  html[data-drawer="open"] .backdrop { display: block; position: fixed; inset: 0; background: rgba(25, 28, 27, .35); z-index: 39; }
  .topbar { display: flex; align-items: center; gap: var(--s2); min-height: 44px; padding: 0 var(--s3); border-bottom: 1px solid var(--rule); background: var(--surface); }
  .menu-btn { display: inline-flex; min-height: 36px; min-width: 36px; }
  main { padding: var(--s4) var(--s3) var(--s7); }
  .record { margin: calc(-1 * var(--s4)) calc(-1 * var(--s3)) 0; }
  .identity { padding: var(--s4) var(--s3) 0; }
  .record-body { grid-template-columns: minmax(0, 1fr); padding: var(--s4) var(--s3) 0; gap: var(--s5); }
  .leads-layout { grid-template-columns: minmax(0, 1fr); }
  .table-wrap, .list-table > .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  thead th { position: static; }
  /* ...so the grip has no positioned header cell to hang on, and there is no hover to reveal it with anyway */
  .list-table th .col-grip { display: none; }
  th, td { white-space: nowrap; }
  .list-top .search { margin-left: 0; flex-basis: 100%; }
  .figures { flex-wrap: wrap; } .figures > div { flex-basis: 50%; }
  .today-row { flex-wrap: wrap; height: auto; padding: var(--s2) 0; }
  .today-action { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; animation: none !important; } }

/* --- design pass: density (brief §2) ------------------------------------------------------------------
   "Table rows are 40px. Do not pad this product out to feel airy; these users are scanning thirty rows."
   Text cells were wrapping to two, three and four lines, which is what actually broke the rule -- the row
   height itself was already right. One line, ellipsis, whole value on hover. */
.list-table td.text .cell-text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An auto-layout table sizes a column to its widest cell, so the ellipsis never engages until the column itself is
   capped. `width: 0` lets max-width win without collapsing the cell. */
.list-table td.text { max-width: 30ch; width: 0; }
.list-table td.text:has(.sub) { max-width: 30ch; }
.list-table th { white-space: nowrap; }

/* --- design pass: row actions (brief §2, §5) ----------------------------------------------------------
   "never by a colored pill background on every row (that turns the table into confetti)". A filled accent
   button on every row of Billing did exactly that. Today's pattern is the house style: the action is revealed
   on hover, and on keyboard focus so it is never hover-only. */
.list-table td.action .row-action button { opacity: 0; transition: opacity var(--t-row); }
.list-table tr:hover td.action .row-action button,
.list-table tr:focus-within td.action .row-action button,
.list-table tr.key-focused td.action .row-action button,
.list-table td.action .row-action button:focus-visible { opacity: 1; }

/* ============================================================================================================
   Interaction layer - docs/NOTES-PEEK.md. Added 2026-09-06. No token, palette, type-scale or density rule from
   docs/DESIGN-BRIEF.md §2 changes here: this section only styles behaviour that did not exist before (the peek
   panel, click-to-edit fields, the column menu, toasts, in-flight feedback). Motion stays inside the brief's
   budget - panels 150ms, rows 120ms, nothing on entrance, all of it off under prefers-reduced-motion.
   ============================================================================================================ */

/* ---------- peek: a record in a slide-over, so a list is never lost behind it (brief §5 "click anywhere on the
     row opens the record" - this is that click, without the navigation) ---------- */
html[data-peek="open"] { overflow: hidden; }
.peek { position: fixed; inset: 0; z-index: 60; }
.peek-scrim { position: absolute; inset: 0; background: rgba(25, 28, 27, .18); opacity: 0; transition: opacity var(--t-panel) ease; }
.peek.in .peek-scrim { opacity: 1; }
.peek-panel {
  position: absolute; top: 0; right: 0; bottom: 0; width: min(720px, 88vw);
  display: flex; flex-direction: column; background: var(--surface); border-left: 1px solid var(--rule);
  box-shadow: var(--shadow-float); transform: translateX(16px); opacity: 0;
  transition: transform var(--t-panel) ease, opacity var(--t-panel) ease;
}
.peek.in .peek-panel { transform: none; opacity: 1; }
.peek-panel:focus { outline: none; }
.peek-bar { display: flex; align-items: center; gap: var(--s2); min-height: 48px; padding: 0 var(--s3) 0 var(--s2); border-bottom: 1px solid var(--rule); flex: 0 0 auto; }
.peek-bar .peek-title { flex: 1 1 auto; min-width: 0; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.peek-nav { display: inline-flex; gap: 2px; }
.peek-nav .icon-btn:disabled { opacity: .35; }
.peek-body { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; padding: 0; }
/* the record page paints its own identity band, so it sits flush inside the panel */
.peek-body > .record { margin: 0; }
.peek-body .record-body { grid-template-columns: 1fr; }
.peek-body .rail { border-left: 0; border-top: 1px solid var(--rule); }
.peek-body .peek-error { padding: var(--s5); }
.peek-panel.loading .peek-body { opacity: .5; transition: opacity 90ms linear; }
/* the row the panel is showing keeps its place marked in the list underneath */
tbody tr[data-peeking] td { background: var(--accent-weak); box-shadow: inset 2px 0 0 var(--accent); }

/* ---------- click-to-edit: the value is the control, so a field takes one click, not a pencil then two buttons ---------- */
.field-cell { display: inline-flex; align-items: center; min-width: 0; max-width: 100%; }
.field-display {
  display: inline-flex; align-items: center; gap: var(--s1); min-height: 26px; max-width: 100%;
  margin: -3px -6px; padding: 2px 6px; border: 1px solid transparent; border-radius: var(--radius);
  background: none; color: inherit; font: inherit; text-align: left; cursor: text;
}
.field-display:hover { border-color: var(--rule); background: var(--paper); }
.field-display:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.field-display[data-readonly] { cursor: default; margin: 0; padding: 0; }
.field-display[data-readonly]:hover { border-color: transparent; background: none; }
.field-edit { display: inline-flex; align-items: center; gap: var(--s1); margin: -3px -1px; }
.field-edit input, .field-edit select, .field-edit textarea { min-height: 28px; margin: 0; }
.field-edit textarea { min-height: 72px; }
.field-cell.saving { opacity: .55; }
.field-cell .field-error { display: block; margin-top: 2px; font-size: var(--fs-label); color: var(--overdue); }
/* the definition list on a record page edits in place too */
dl.kv dd .field-cell { max-width: 100%; }

/* ---------- column menu: show, hide, reorder and resize the columns of any list (kept per list in localStorage) ---------- */
.col-menu > .sheet { min-width: 240px; max-height: 60vh; overflow-y: auto; }
.col-menu .sheet .col-item { display: flex; align-items: center; gap: var(--s2); min-height: 32px; padding: 0 var(--s2); border-radius: var(--radius); cursor: grab; }
.col-menu .sheet .col-item:hover { background: var(--paper); }
.col-menu .sheet .col-item.dragging { opacity: .4; }
.col-menu .sheet .col-item.over { box-shadow: inset 0 2px 0 var(--accent); }
.col-menu .sheet .col-item input { margin: 0; }
.col-menu .sheet .col-item .grip { margin-left: auto; color: var(--rule); font-size: 12px; letter-spacing: -1px; }
.col-menu .sheet .col-item:hover .grip { color: var(--ink-muted); }
.col-menu .sheet .col-reset { width: 100%; margin-top: var(--s1); }
th.col-hidden, td.col-hidden { display: none; }
/* The resize grip lives on the header's right edge and only appears once the pointer is in the header row. It is
   absolutely positioned, so it needs a positioned header cell -- which this file used to supply here as
   `.list-table th { position: relative; }`, quietly overriding the sticky header and killing it on every list in
   the product. The header cell is already positioned: `thead th` is `sticky` (see the tables section), and sticky
   establishes a containing block exactly as relative does. So the grip needs nothing here, and the header's
   position stays declared in one place -- including the narrow-window rule that turns it off. */
.list-table th .col-grip { position: absolute; top: 0; right: -3px; bottom: 0; width: 7px; cursor: col-resize; opacity: 0; z-index: 3; }
.list-table thead:hover th .col-grip { opacity: 1; }
.list-table th .col-grip::after { content: ""; position: absolute; top: 8px; bottom: 8px; left: 3px; width: 1px; background: var(--rule); }
.list-table th .col-grip:hover::after, .list-table th .col-grip.dragging::after { top: 0; bottom: 0; background: var(--accent); }
html[data-resizing] { cursor: col-resize; user-select: none; }

/* ---------- toasts: the flash and its undo, floated out of the document flow so nothing reflows under it ---------- */
.toasts { position: fixed; left: var(--s5); bottom: var(--s5); z-index: 70; display: flex; flex-direction: column; gap: var(--s2); max-width: min(420px, calc(100vw - 2 * var(--s5))); }
.toast {
  display: flex; align-items: center; gap: var(--s3); padding: var(--s2) var(--s2) var(--s2) var(--s3);
  background: var(--surface); border: 1px solid var(--rule); border-left: 2px solid var(--accent);
  border-radius: var(--radius); box-shadow: var(--shadow-float); color: var(--ink);
  transition: opacity var(--t-panel) ease, transform var(--t-panel) ease;
}
.toast.err, .toast.warn { border-left-color: var(--due-soon); }
.toast .msg { flex: 1 1 auto; min-width: 0; }
.toast form { margin: 0; }
.toast.out { opacity: 0; transform: translateY(6px); }

/* ---------- in-flight feedback: a hairline progress bar for htmx swaps, a busy cursor for the row acting ---------- */
.htmx-request.row-action button, .htmx-request .btn { opacity: .6; pointer-events: none; }
#htmx-bar { position: fixed; top: 0; left: 0; height: 2px; width: 0; background: var(--accent); z-index: 90; opacity: 0; transition: width 180ms ease, opacity 180ms ease; }
#htmx-bar.on { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .peek-scrim, .peek-panel, .toast, #htmx-bar, .peek-panel.loading .peek-body { transition: none !important; }
  .peek-panel { transform: none; }
}

/* ============================================================================================================
   AESTHETIC PASS - 2026-09-06. Attio / Notion vernacular.

   This section deliberately supersedes parts of docs/DESIGN-BRIEF.md §2 at the owner's direction (see
   docs/NOTES-AESTHETIC.md for what changed and why). What it does NOT touch: the brief's information design.
   Row density is still 40px, status is still a dot plus a word, money is still tabular and right-aligned, the
   column sets are unchanged, and motion is still only ever a response to a user action. This is chrome.

   The four moves that carry the look:
     1. The shell inverts - tinted sidebar, white work area. A white rail on grey is what read as "admin panel".
     2. Nav and menu items become inset rounded pills; the active one is a raised white pill, not a colour wash.
     3. Neutral hover everywhere. The accent stops being chrome and goes back to meaning "this one, right now".
     4. Tables become spreadsheet-like: tinted sticky header, hairline cell dividers, rounded clipped container.
   ============================================================================================================ */

/* ---------- shell ---------- */
.sidebar { background: var(--shell); border-right: 1px solid var(--rule); padding-bottom: var(--s2); }
.side-firm {
  min-height: 44px; margin: var(--s2) var(--s2) var(--s1); padding: 0 var(--s2); border-bottom: 0;
  border-radius: var(--radius); font-size: var(--fs-body); font-weight: 600; letter-spacing: -0.005em;
}
.side-firm:hover { background: var(--hover); }
/* a small ink tile carrying the firm's initial, the way every workspace switcher opens a sidebar */
.side-firm::before {
  content: attr(data-initial); display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 20px; height: 20px; margin-right: 2px; border-radius: 5px;
  background: var(--ink); color: var(--surface); font-size: 11px; font-weight: 600; line-height: 1;
}
.side-nav { padding: var(--s1) var(--s2); gap: 1px; }
.side-link {
  min-height: 30px; padding: 0 var(--s2); gap: 8px; border-left: 0; border-radius: var(--radius);
  color: var(--ink-muted); font-size: var(--fs-dense); font-weight: 500;
}
.side-link .icon { width: 15px; height: 15px; color: var(--ink-muted); }
.side-link:hover { background: var(--hover); color: var(--ink); }
.side-link:hover .icon { color: var(--ink); }
/* the active item is a raised white pill on the tint -- the whole reason the shell inverted */
.side-link[aria-current="page"] {
  background: var(--surface); color: var(--ink); font-weight: 500;
  box-shadow: var(--shadow-xs), 0 0 0 1px rgba(15, 15, 15, .05);
}
.side-link[aria-current="page"] .icon { color: var(--ink); }
.side-link .count { font-size: 11px; }
.side-user { border-top: 1px solid var(--rule); margin: auto var(--s1) 0; }   /* stays pinned to the bottom */
.side-user > summary { min-height: 46px; padding: 0 var(--s2); border-radius: var(--radius); font-size: var(--fs-dense); }
.side-user > summary:hover { background: var(--hover); }
.side-user .caption { font-size: 11px; }
.side-user .sheet { bottom: 52px; }
.frame { background: var(--surface); }
main { max-width: 1320px; padding: var(--s5) var(--s6) var(--s7); }

/* ---------- controls: hairline + 1px lift, dark primary, pill chips ---------- */
button, .btn {
  min-height: 30px; padding: 3px 10px; border-radius: var(--radius);
  border: 1px solid var(--rule); background: var(--surface); box-shadow: var(--shadow-xs);
  font-size: var(--fs-dense); font-weight: 500; color: var(--ink);
}
button:hover, .btn:hover { background: var(--shell); border-color: var(--rule); box-shadow: var(--shadow-xs); }
button:active, .btn:active { background: var(--hover-strong); box-shadow: none; }
/* the primary action is ink, not a colour: one dark button per screen reads as the one thing to do */
button.primary, .btn.primary { background: var(--ink); color: var(--surface); border-color: var(--ink); }
button.primary:hover, .btn.primary:hover { background: #34332F; border-color: #34332F; }
button.quiet, .btn.quiet { background: transparent; border-color: transparent; box-shadow: none; color: var(--ink-muted); }
button.quiet:hover, .btn.quiet:hover { background: var(--hover); color: var(--ink); box-shadow: none; }
button.small, .btn.small { min-height: 26px; padding: 1px 8px; font-size: var(--fs-label); }
.icon-btn { min-width: 30px; padding: 3px; }

input, select, textarea { border-radius: var(--radius); border-color: var(--rule); box-shadow: var(--shadow-xs); font-size: var(--fs-dense); }
input:focus, select:focus, textarea:focus { border-color: var(--ink-muted); box-shadow: 0 0 0 3px var(--hover); }
/* search is an inset field on the tint, not a raised control */
input[type=search] { background: var(--shell); box-shadow: none; }
input[type=search]:focus { background: var(--surface); box-shadow: 0 0 0 3px var(--hover); }

.chip {
  border: 0; background: var(--shell); box-shadow: inset 0 0 0 1px var(--rule);
  border-radius: var(--radius-pill); padding: 1px 9px; font-size: var(--fs-label); color: var(--ink);
}
.seg { border-radius: var(--radius); background: var(--shell); padding: 2px; border-color: var(--rule); gap: 2px; }
.seg a, .seg button { border-left: 0; border-radius: 4px; min-height: 26px; font-size: var(--fs-label); }
.seg a[aria-current="true"], .seg button[aria-pressed="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-xs); }

/* ---------- floating surfaces: bigger radius, a real shadow, pill items ---------- */
.menu .sheet { border-radius: var(--radius-lg); padding: var(--s1); border-color: transparent; }
.menu .sheet a, .menu .sheet button {
  min-height: 30px; padding: 0 var(--s2); border-radius: 5px; font-size: var(--fs-dense); box-shadow: none;
  border: 0; background: none;
}
.menu .sheet a:hover, .menu .sheet button:hover { background: var(--hover); }
.menu .sheet a[aria-current="true"] { background: var(--hover); color: var(--ink); font-weight: 500; }
.card { border-radius: var(--radius-lg); box-shadow: var(--shadow-xs); }
.stats { border-radius: var(--radius-lg); box-shadow: var(--shadow-xs); overflow: hidden; }

/* ---------- tables: the spreadsheet feel -- tinted sticky header, hairline cell dividers, clipped corners ---------- */
th { background: var(--shell); font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-muted); }
thead th { box-shadow: inset 0 -1px 0 var(--rule); }
/* Row states, in one place and in increasing strength. Hover and the keyboard cursor are neutral washes --
   colour means status in this product, and "the pointer is here" is not a status. Selection is different: it is
   a state the user put the row into and the bulk bar acts on, so it takes the same accent the bulk bar uses, and
   it has to survive hover or a selected row loses its state under the cursor. The 2px marker rides the row's
   leading edge -- `td:first-child` only, never redrawn on every cell down the row. */
tbody tr:hover td { background: var(--hover); }
tbody tr.key-focused td { background: var(--hover-strong); box-shadow: none; }
tbody tr.key-focused td:first-child { box-shadow: inset 2px 0 0 var(--ink); }
tbody tr.picked td, tbody tr.picked:hover td { background: var(--accent-weak); }
tbody tr.picked td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
/* `clip`, never `hidden`: both round off the corners, but `hidden` makes this a scroll container and a
   sticky <thead> inside a scroll container sticks to that container, not to the page -- which is exactly
   how the sticky header came to scroll away with the rows. */
.list-table { border-radius: var(--radius-lg); box-shadow: var(--shadow-xs); overflow: clip; }
.list-table > .table-wrap { border-radius: inherit; }
.list-table table { font-size: var(--fs-dense); }
/* vertical hairlines are the one thing that makes a CRM table read as a grid rather than a report */
.list-table th + th, .list-table td + td { border-left: 1px solid var(--rule); }
.list-table td.check, .list-table th.check { border-left: 0; }
.list-table td.text a { font-weight: 500; }
.list-table tbody tr[data-peeking] td { background: var(--hover-strong); box-shadow: inset 2px 0 0 var(--ink); }
.list-top { min-height: 34px; margin-bottom: var(--s3); }
.list-top .view-menu > summary { font-size: 20px; letter-spacing: -0.015em; }
.list-foot { font-size: var(--fs-label); }
.list-chips .chip { background: var(--surface); }

/* ---------- record page ---------- */
.identity { background: var(--surface); }
.identity-fields .l { font-size: 11px; letter-spacing: .01em; }
.tabrow a { font-size: var(--fs-dense); font-weight: 500; color: var(--ink-muted); border-radius: 5px 5px 0 0; }
.tabrow a:hover { background: var(--hover); color: var(--ink); text-decoration: none; }
.tabrow a[aria-current="page"] { color: var(--ink); }
.rail-block h2 { font-size: 11px; letter-spacing: .01em; text-transform: none; }

/* ---------- peek panel, restyled to the new chrome ---------- */
.peek-scrim { background: rgba(15, 15, 15, .22); }
.peek-panel { border-left: 0; border-radius: var(--radius-lg) 0 0 var(--radius-lg); overflow: hidden; }
.peek-bar { background: var(--shell); }
.peek-bar .peek-title { font-size: var(--fs-dense); }

/* ---------- inline edit, matched to the neutral hover ---------- */
.field-display:hover { background: var(--hover); border-color: transparent; }
.field-display:focus-visible { outline: 2px solid var(--ink); }

/* ---------- toasts ---------- */
.toast { border-radius: var(--radius-lg); border-left-width: 1px; border-left-color: var(--rule); padding-left: var(--s3); }
.toast::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }
.toast.err::before, .toast.warn::before { background: var(--due-soon); }

/* the focus ring follows the ink, not the accent, now that the accent is not chrome */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ---------- record identity, second pass: a named object gets a mark, and its tables get the list's chrome ---------- */
.identity { padding-top: var(--s4); }
.identity-name .row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.record-mark {
  display: inline-grid; place-items: center; flex: 0 0 auto; width: 30px; height: 30px;
  border-radius: 7px; background: var(--shell); box-shadow: inset 0 0 0 1px var(--rule);
  color: var(--ink-muted); font-size: 14px; font-weight: 600; line-height: 1;
}
.identity-head h1 { font-size: 22px; letter-spacing: -0.015em; min-width: 0; }
.identity-fields { gap: var(--s2) var(--s5); margin-top: var(--s3); }
.identity-fields .v { font-size: var(--fs-dense); }
.tabrow { gap: var(--s4); }
.tabrow a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--ink); }
/* the small tables inside a record read as the same object as the big one on the list */
.table-plain { border: 1px solid var(--rule); border-radius: var(--radius-lg); box-shadow: var(--shadow-xs); overflow: hidden; }
.table-plain table { font-size: var(--fs-dense); }
.table-plain tbody tr:last-child td { border-bottom: 0; }
.record-main h2, .rail-block h2 { font-size: var(--fs-dense); font-weight: 600; letter-spacing: -0.005em; }

/* ---------- command menu: the one surface every modern CRM is judged on ---------- */
dialog.cmdk, dialog.shortcuts-help { border: 0; border-radius: 14px; box-shadow: var(--shadow-float); }
dialog.cmdk::backdrop, dialog.shortcuts-help::backdrop { background: rgba(15, 15, 15, .30); }
dialog.cmdk { width: min(640px, 92vw); margin-top: 14vh; }
.cmdk-search-row { padding: var(--s3) var(--s4); gap: var(--s3); }
#cmdk-input { font-size: 15px; }
#cmdk-input::placeholder { color: var(--ink-muted); }
.cmdk-list { padding: var(--s1) var(--s1) var(--s2); max-height: 46vh; }
.cmdk-group-label { padding: var(--s2) var(--s3) var(--s1); font-size: 11px; letter-spacing: .01em; }
.cmdk-item { padding: 0 var(--s3); min-height: 38px; border-radius: 7px; gap: var(--s2); }
.cmdk-item .icon { width: 15px; height: 15px; }
.cmdk-item.active { background: var(--hover); }
.cmdk-item.active .cmdk-item-label { font-weight: 500; }
.cmdk-hints { background: var(--shell); padding: var(--s2) var(--s4); }

/* the empty state is three lines, not one run-on sentence: situation, hint, next action (brief §12) */
.list-empty { padding: var(--s7) var(--s4); }
.list-empty strong { font-size: var(--fs-body); margin-bottom: 2px; }
.list-empty span { display: block; }
.list-empty a { display: inline-block; margin-top: var(--s3); font-weight: 500; }

/* ============================================================================================================
   DENSITY - 2026-09-06, second look. The first aesthetic pass took the wrong half of the reference: it read
   Notion (an airy document) when the target was Attio (a dense working grid). Owner: "too much whitespace."

   The table is the page. It runs edge to edge from the sidebar to the window, under a single thin toolbar --
   no page gutter, no rounded card floating in margins, no centred max-width leaving the window half empty.
   ============================================================================================================ */

:root { --row: 36px; --side-w: 216px; --gut: var(--s4); }

/* the work area fills the window and gives up its generous gutter */
main { max-width: none; padding: var(--s3) var(--gut) var(--s6); }

/* ---------- sidebar: same structure, less air ---------- */
.side-firm { min-height: 38px; margin: 6px 6px 2px; font-size: var(--fs-dense); }
.side-nav { padding: 2px 6px; }
.side-link { min-height: 28px; padding: 0 8px; }
.side-link .icon { width: 14px; height: 14px; }
.side-user > summary { min-height: 42px; padding: 0 8px; }
.side-user .avatar { width: 24px; height: 24px; }

/* ---------- list: one thin toolbar, then the grid ---------- */
.list-top { min-height: 30px; margin-bottom: 6px; gap: var(--s2); }
.list-top .view-menu > summary { font-size: 15px; letter-spacing: -0.01em; min-height: 26px; }
.list-top .search { flex: 0 1 240px; }
.list-top .search input { min-height: 26px; font-size: var(--fs-label); padding: 2px 8px; }
.list-top .btn, .list-top button { min-height: 26px; padding: 1px 8px; font-size: var(--fs-label); }
.list-chips { margin-bottom: 6px; gap: 6px; }
.list-bulk { min-height: 30px; margin-bottom: 6px; }

/* full-bleed: cancel main's gutter, drop the card chrome, keep the hairlines */
.list-table {
  margin: 0 calc(-1 * var(--gut)); border-radius: 0; border-left: 0; border-right: 0; box-shadow: none;
  border-top: 1px solid var(--rule);
}
/* `overflow-x: auto` forces `overflow-y` to `auto` as well, and that second scrollport broke the sticky header
   the same way `overflow: hidden` above did. But a table that genuinely does not fit has to stay reachable --
   clipping it would hide columns with no way to scroll to them -- so the two cases are separated: the wrap
   clips (and the header sticks) while the table fits, and chassis.js hangs `.scrolls-x` on it when it does not,
   which restores sideways scrolling and gives up the sticky header for that one list only. */
.list-table > .table-wrap { border-radius: 0; overflow-x: clip; }
.list-table > .table-wrap.scrolls-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.list-table > .table-wrap.scrolls-x thead th { position: static; }
@media (max-width: 900px) { .list-table > .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; } }
.list-table th:first-child, .list-table td:first-child { padding-left: var(--gut); }
.list-table th:last-child, .list-table td:last-child { padding-right: var(--gut); }
.list-table td .sub { font-size: 11px; }
.list-foot { min-height: 32px; margin-top: 0; padding: 0 0 0 2px; }

/* cells: the padding a spreadsheet uses, not the padding a report uses */
th, td { padding: 0 10px; }
thead th { height: 30px; }
.list-empty { padding: var(--s6) var(--s4); }

/* ---------- record: the identity strip stops being a title page ---------- */
.record { margin: calc(-1 * var(--s3)) calc(-1 * var(--gut)) 0; }
.identity { padding: var(--s3) var(--gut) 0; }
.identity-head h1 { font-size: 18px; }
.record-mark { width: 26px; height: 26px; border-radius: 6px; font-size: 12px; }
.identity-fields { margin-top: 10px; gap: 6px var(--s5); }
.identity-fields li { min-width: 0; }
.tabrow { margin-top: 8px; gap: var(--s4); }
.tabrow a { min-height: 30px; }
.record-body { padding: var(--s4) var(--gut) 0; gap: var(--s5); }
.record-main > section + section { margin-top: var(--s4); }
.record-main h2, .rail-block h2 { margin-bottom: 6px; }
.table-plain { border-radius: var(--radius); }
.rail-block { padding: 10px 0 var(--s3); }

/* ---------- the rest of the surfaces, matched ---------- */
.stats > .stat { padding: 10px var(--s3); }
.stat .n, .figures .n { font-size: 20px; }
.figures > div { padding: 10px var(--s3); }
.lines li { padding: 0; }
.section { margin-top: var(--s4); }
.page-head { margin-bottom: 10px; }
h1 { font-size: 20px; }

/* a two-line cell has to fit the row, not stretch it: 13/1.25 over 11/1.2 clears 36px with room either side */
.list-table td.text { line-height: 1.25; }
.list-table td .cell-text { display: block; line-height: 1.25; }
.list-table td .sub { font-size: 11px; line-height: 1.2; margin-top: 1px; }

/* the rail is a fixed column, so a wide window makes the main column wider instead of spreading everything out */
.record-body { grid-template-columns: minmax(0, 1fr) 300px; }

/* the selection column carries a 15px checkbox and nothing else; it does not need a 42px gutter */
.list-table td.check, .list-table th.check { width: 30px; padding-left: 12px; padding-right: 0; }

/* Today: left-aligned and full width. The brief (§4) asked for a centred 900px column; at 1400px that leaves a
   third of the window empty on the right and a 120px gutter on the left, which is the whitespace this pass is
   removing. It stays a single column of work -- it just starts where every other screen starts. */
/* Full width. The 1100px cap dated from when Today was a narrow column of prose-width lists; it is now
   five tables whose columns (Regarding, Assignee, Due, Priority) each want room, and the cap left ~124px
   of the frame empty at 1440 while truncating cells inside it. `main` already supplies the page gutter. */
.today-page { max-width: none; margin: 0; }
.today-page .figures { margin: var(--s3) 0 var(--s4); }
.today-group + .today-group { margin-top: var(--s4); }
.today-group-label { padding-bottom: 6px; }

/* Overdue / This week / Needs a next action are chassis tables now (`_list.html`), not `<ul class="today-list">`
   groups -- same stacking rule `.today-group + .today-group` already had, just for `.list` sections instead.
   `.today-main` (Tasks, then Leads, full width) gets the same gap above it and between its own two tables. */
.today-page .list + .list { margin-top: var(--s4); }
.today-main { margin-top: var(--s4); }
.today-main .list + .list { margin-top: var(--s4); }

/* ============================================================================================================
   LIQUID GLASS - 2026-09-06, second attempt. The first one was blur and a highlight, which is frosted glass,
   not glass: real Liquid Glass REFRACTS what is behind it. This is the same material capitolaxis.com ships on
   its header and buttons, ported from website/modern/static/css/site.css + src/glass.jsx.

   Wprod's three-layer construction (MIT - /static/wprod-LICENSE.txt, https://codepen.io/wprod/pen/raVpwJL),
   which the owner selected out of an 11-way comparison in website/modern/src/glass-lab:

     1. lens      backdrop-filter: blur(4px) then filter: url(#wprod-lens) - an feDisplacementMap driven by a
                  heavily blurred copy of the element's own alpha, so the edges bend what is behind them and the
                  middle stays clear. This displacement is the part that makes it read as glass.
     2. tint      a thin white wash over the refraction
     3. specular  an inset rim along the top-left, plus a soft inner glow

   The site injects those as three spans (src/glass.jsx). Here they are ::before and ::after, so no template
   changes and nothing to wrap - which matters, because these surfaces hold live controls with their own
   listeners and a wrapper span would break the command menu's layout.

   Scope is unchanged and still Apple's rule: the navigation layer only - things that float over content. The
   command menu, dropdown sheets, the peek panel's bar, toasts. Never a table, row, card or record body.
   ============================================================================================================ */

.glass-host { position: relative; isolation: isolate; }
.glass-host > * { position: relative; z-index: 2; }
.glass-host::before,
.glass-host::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; }
/* layers 1 + 2: the lens, then the tint over it */
.glass-host::before {
  z-index: 0;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  filter: url(#wprod-lens) saturate(120%) brightness(1.15);
  background: rgba(255, 255, 255, .25);
}
/* layer 3: the specular rim */
.glass-host::after {
  z-index: 1;
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .75), inset 0 0 5px rgba(255, 255, 255, .75);
}

/* the four floating surfaces take the material; `.liquid-definitions` holds the filter and paints nothing */
.liquid-definitions { position: absolute; width: 0; height: 0; overflow: hidden; }

dialog.cmdk, dialog.shortcuts-help {
  position: fixed; isolation: isolate; overflow: visible;
  background: rgba(255, 255, 255, .58);
  box-shadow: 0 6px 6px rgba(0, 0, 0, .12), 0 0 20px rgba(0, 0, 0, .08);
  border: 0;
}
dialog.cmdk::backdrop, dialog.shortcuts-help::backdrop { background: rgba(15, 15, 15, .20); }
dialog.cmdk > *, dialog.shortcuts-help > * { position: relative; z-index: 2; }
dialog.cmdk::before, dialog.shortcuts-help::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; z-index: 0; pointer-events: none;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  filter: url(#wprod-lens) saturate(120%) brightness(1.15);
  background: rgba(255, 255, 255, .30);
}
/* rule: never glass on glass - items sitting on the material take a flat fill */
.cmdk-item.active { background: rgba(15, 15, 15, .07); }
.cmdk-hints { background: rgba(15, 15, 15, .04); }
.cmdk-search-row { border-bottom-color: rgba(15, 15, 15, .10); }

.menu .sheet {
  background: rgba(255, 255, 255, .58);
  box-shadow: 0 6px 6px rgba(0, 0, 0, .10), 0 0 20px rgba(0, 0, 0, .07);
  border: 0; isolation: isolate;
}
.menu .sheet > * { position: relative; z-index: 2; }
.menu .sheet::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; z-index: 0; pointer-events: none;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  filter: url(#wprod-lens) saturate(120%) brightness(1.15);
  background: rgba(255, 255, 255, .30);
}
.menu .sheet a:hover, .menu .sheet button:hover { background: rgba(15, 15, 15, .07); }
.menu .sheet a[aria-current="true"] { background: rgba(15, 15, 15, .08); }
.menu .sheet .sep { border-top-color: rgba(15, 15, 15, .09); }

.peek-bar {
  background: rgba(255, 255, 255, .55); isolation: isolate;
  box-shadow: inset 0 -1px 0 rgba(15, 15, 15, .08);
}
.peek-bar > * { position: relative; z-index: 2; }
.peek-bar::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  filter: url(#wprod-lens) saturate(120%) brightness(1.15);
  background: rgba(255, 255, 255, .28);
}
.peek-body { background: var(--surface); }

/* Solid, not glass: a toast lands over dense tables and the translucent version left the text of
   the row behind it showing through the confirmation. Owner, 2026-09-07: "no need". */
.toast { background: var(--surface); border: 1px solid var(--rule); isolation: isolate; }
.toast > * { position: relative; z-index: 2; }
.toast::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; z-index: 0; pointer-events: none;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  filter: url(#wprod-lens) saturate(120%) brightness(1.15);
  background: rgba(255, 255, 255, .32);
}
.toast::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; z-index: 1; pointer-events: none;
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .75), inset 0 0 5px rgba(255, 255, 255, .75);
}

/* ============================================================================================================
   Leads - the differentiator.

   Two earlier versions were wrong in ways worth recording. A solid blue pill read as *selected*. Then a paler
   one had a real bug: `isolation: isolate` made the pill its own backdrop root, so the lens sampled only the
   gradient painted inside it while the pill's own semi-transparent white background let the raw sidebar through
   unfiltered. Two different grounds in one element - which is exactly what "the background does not match the
   liquid glass" looks like.

   The fix is to stop giving the element a background at all. The gradient covers it edge to edge, the lens sits
   over the gradient AND the sidebar behind it as one continuous backdrop, and there is nothing left to mismatch.
   The colour is now barely there: this should read as a faint sheen that happens to move, not as a blue button.
   ============================================================================================================ */

.side-link[data-key="leads"] {
  /* no isolation, no background: one backdrop, and the layers below supply the surface */
  position: relative; overflow: hidden;
  /* The gap above Leads is deliberate. Leads is the one tab that is not day-to-day firm operations -- it is
     the inbound supply -- and it is set apart from the seven operational tabs above it on purpose. Removed on
     2026-09-07 when it was read as an empty row, restored the same day once its purpose was clear: the
     separation IS the point, and the sheen and inset edge did not carry it alone. */
  /* Concentric with the nav that holds it rather than a literal of its own: the pill is inset --nav-inset
     from the sidebar's edge, so its radius is the sidebar's minus that gap, floored at 6px. A nested
     shape that keeps its own radius bows against its parent's corner and reads as wrong before anyone
     can name why. ::before/::after already `border-radius: inherit`, so they follow this automatically. */
  margin: 3px 0; border-radius: max(calc(var(--radius-lg) - var(--nav-inset)), 6px); padding: 0 8px;
  background: none;
  color: var(--ink);
  box-shadow: inset 0 0 0 1px rgba(36, 80, 143, .10);
  transition: box-shadow 160ms ease;
}
.side-link[data-key="leads"] > * { position: relative; z-index: 3; }
.side-link[data-key="leads"] .label { color: var(--ink); font-weight: 500; }
.side-link[data-key="leads"] .icon { color: var(--accent); }

/* the dance: pale blues, blurred, turning under the glass */
.side-link[data-key="leads"]::before {
  /* Owner, 2026-09-07: "make leads background fit the liquid glass block."
     A rotating conic has one hard geometric constraint: to cover a w x h box at every angle it must be a SQUARE
     at least as wide as that box's diagonal, centred on it. The pill is 203x28, so the diagonal is ~205px and
     the wheel is ~215px square -- as tight as it can be drawn and still leave no bare corner mid-turn. Tightening
     it further (a first attempt used -260% -3%) makes the wheel 174 tall by 215 wide, and a quarter turn then
     swings the short side across the long one and opens a gap the sidebar shows through: a visible seam down
     the pill, which is what a background that does not fit its block actually looks like. Percentages are of the
     pill's own box, so both numbers move together if its size ever changes.
     `border-radius: inherit` matches the lens above rather than relying on the parent's clip alone. */
  content: ""; position: absolute; z-index: 0; inset: -334% -3%; pointer-events: none;
  border-radius: inherit;
  /* Light, but not flat: the stops run from near-white to a mid blue. An all-pale wheel measured 1 RGB point of
     movement -- technically dancing, visibly still. But the stops must never reach white either: with a white
     stop in the wheel the pill cycled through looking like an ordinary white nav item, and it has to read as
     itself at every moment of the turn. So: always blue, never saturated, always moving. A smaller wheel (the
     tighter inset) also sweeps more of itself across a 200px pill per turn than the header-sized one did. */
  /* Subtle. The stops are a hair off white -- enough that the sheen moves, not enough to read as a colour.
     The band still never reaches pure white, so the item never blinks back to looking like an ordinary row. */
  background: conic-gradient(from 35deg, #EDF3FB, #DCE8F7 20%, #F7FAFE 40%, #E2ECF9 62%, #EBF2FC 82%, #EDF3FB);
  filter: blur(9px);
  /* "Make the gradient movement lighter." 18s read as a pill that is always visibly moving; at 46s it is a sheen
     you notice having shifted rather than one you watch shift. The colour amplitude is untouched on purpose --
     the note above records that a paler wheel measured one RGB point of travel, which is not movement at all,
     so this buys calm from the clock rather than from the palette. A hair more blur softens the leading edge. */
  animation: glass-color-turn 46s linear infinite;
}
/* Wprod's material over the dance: lens, tint, specular */
.side-link[data-key="leads"]::after {
  content: ""; position: absolute; z-index: 1; inset: 0; border-radius: inherit; pointer-events: none;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  filter: url(#wprod-lens-sm) saturate(120%) brightness(1.04);
  background: rgba(255, 255, 255, .14);
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .55), inset 0 0 4px rgba(255, 255, 255, .5);
}
.side-link[data-key="leads"]:hover { box-shadow: inset 0 0 0 1px rgba(36, 80, 143, .20); }
/* active: the item is already set apart, so this only firms the rim -- no second visual language */
.side-link[data-key="leads"][aria-current="page"] { box-shadow: inset 0 0 0 1px rgba(36, 80, 143, .34); }
.side-link[data-key="leads"][aria-current="page"] .label { color: var(--accent); }

@keyframes glass-color-turn { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ---------- accessibility: what an Apple platform does for free, done by hand (as the site does it too) ------ */

/* Reduce Transparency - the material becomes an opaque surface; depth survives in the shadow. */
@media (prefers-reduced-transparency: reduce) {
  .glass-host::before, dialog.cmdk::before, dialog.shortcuts-help::before,
  .menu .sheet::before, .peek-bar::before, .toast::before { display: none; }
  dialog.cmdk, dialog.shortcuts-help, .menu .sheet, .peek-bar, .toast { background: var(--surface); }
}

/* Increase Contrast - solid surfaces with a hard border; the Leads pill loses the gradient for flat blue. */
@media (prefers-contrast: more) {
  .glass-host::before, dialog.cmdk::before, dialog.shortcuts-help::before,
  .menu .sheet::before, .peek-bar::before, .toast::before { display: none; }
  dialog.cmdk, dialog.shortcuts-help, .menu .sheet, .peek-bar, .toast {
    background: var(--surface); box-shadow: 0 0 0 1px var(--ink), var(--shadow-float);
  }
  .side-link[data-key="leads"]::before, .side-link[data-key="leads"]::after { display: none; }
  .side-link[data-key="leads"] { background: var(--surface); box-shadow: inset 0 0 0 2px var(--accent); }
}

/* Reduce Motion - the gradient stops dancing and rests on one frame. It still reads; it just holds still. */
@media (prefers-reduced-motion: reduce) {
  .side-link[data-key="leads"]::before { animation: none; }
  .side-link[data-key="leads"] { transition: none; }
}

/* ============================================================================================================
   CREATE - the firm's own records: new/edit forms, per-field validation messages, invoice line items.
   docs/NOTES-CREATE.md. Tokens only; no new colour, and the only colour here is --overdue, used for a problem
   the user has to fix, always next to a word that says what it is.
   ============================================================================================================ */
.form-page { max-width: 720px; }
.form-page.wide { max-width: 980px; }
.form-card { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius-lg); padding: var(--s5); box-shadow: var(--shadow-xs); }
.form-card + .form-card { margin-top: var(--s4); }
.form-card > h2 { margin: 0 0 var(--s3); }
.form-card .section-note { margin: calc(var(--s4) * -1 + var(--s1)) 0 var(--s4); color: var(--ink-muted); font-size: var(--fs-label); }
.form-actions { display: flex; gap: var(--s2); align-items: center; margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--rule); }
.form-actions .spacer { flex: 1; }

/* a problem with one field: the message sits under the control it belongs to */
.field-error { display: block; margin-top: 3px; font-size: var(--fs-label); font-weight: 500; color: var(--overdue); }
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--overdue); }
input[aria-invalid="true"]:focus, select[aria-invalid="true"]:focus, textarea[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px rgba(140, 47, 42, .12); }
.form-summary { margin-bottom: var(--s4); padding: var(--s2) var(--s3); border: 1px solid var(--rule); border-left: 2px solid var(--overdue); border-radius: var(--radius); background: var(--surface); color: var(--ink); font-size: var(--fs-dense); }
.req { margin-left: 4px; font-size: 11px; font-weight: 400; color: var(--ink-muted); }
.hint { display: block; margin-top: 3px; font-size: var(--fs-label); color: var(--ink-muted); }

/* invoice line items: rows added and removed before saving, total kept live by /static/lineitems.js */
.lines-table { width: 100%; border-collapse: collapse; }
.lines-table th { height: 30px; font-size: 11px; font-weight: 500; color: var(--ink-muted); text-align: left; border-bottom: 1px solid var(--rule); }
.lines-table th.num, .lines-table td.num { text-align: right; }
.lines-table td { height: auto; padding: 6px 8px 6px 0; border-bottom: 1px solid var(--rule); vertical-align: middle; }
.lines-table td:last-child, .lines-table th:last-child { padding-right: 0; width: 34px; }
/* the description takes whatever the fixed-width columns do not */
.lines-table th:first-child, .lines-table td:first-child { width: 100%; }
.lines-table th.num, .lines-table td.num { width: 1%; white-space: nowrap; }
.lines-table input { min-height: 30px; width: 100%; }
.lines-table .qty { width: 72px; text-align: right; } .lines-table .price { width: 110px; text-align: right; }
.lines-table .amount { font-variant-numeric: tabular-nums; color: var(--ink-muted); white-space: nowrap; }
.line-remove { border: 1px solid transparent; background: none; color: var(--ink-muted); min-height: 26px; min-width: 26px; padding: 2px 6px; border-radius: var(--radius); cursor: pointer; }
.line-remove:hover { background: var(--hover); color: var(--overdue); }
.lines-foot { display: flex; align-items: center; gap: var(--s3); margin-top: var(--s3); }
.lines-foot .spacer { flex: 1; }
.lines-total { font-size: var(--fs-body); font-variant-numeric: tabular-nums; }
.lines-total .n { font-weight: 600; margin-left: var(--s2); }

/* ============================================================================================================
   GLASS, EVERYWHERE - subtly. "Liquid glass should be used subtly everywhere, that's the point."

   The point of the material is that content moves *behind* it. So it goes on every surface that something
   actually passes under, and nowhere it would be a decoration over nothing:

     · the sticky table header   - thirty rows scroll under it; this is the best case in the whole product
     · the sidebar               - the work area scrolls beside it
     · the bulk-action bar       - it sits over the rows it is acting on
     · the list toolbar          - the table scrolls up under it
     plus the four that already had it: command menu, dropdown sheets, the peek bar, toasts.

   Everything here is the same Regular variant at a lower strength than the floating surfaces, because these are
   attached to the page rather than floating over it. Still never on the content layer itself: not a row, not a
   cell, not a card, not a record body.
   ============================================================================================================ */

/* the sticky header: rows pass under it, so it refracts them */
.list-table thead th {
  background: rgba(250, 250, 249, .72);
  -webkit-backdrop-filter: blur(6px) saturate(140%);
  backdrop-filter: blur(6px) saturate(140%);
  box-shadow: inset 0 -1px 0 var(--rule), inset 0 1px 0 rgba(255, 255, 255, .6);
}

/* the sidebar: the work area scrolls beside it */
.sidebar {
  background: rgba(251, 251, 250, .82);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
}

/* the bulk bar sits directly over the rows it acts on */
.list-bulk {
  background: rgba(234, 240, 248, .70);
  -webkit-backdrop-filter: blur(10px) saturate(150%);
  backdrop-filter: blur(10px) saturate(150%);
  border-color: rgba(36, 80, 143, .18);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7);
}

/* the toolbar the table scrolls up under */
.list-top {
  position: sticky; top: 0; z-index: 20;
  background: rgba(255, 255, 255, .78);
  -webkit-backdrop-filter: blur(10px) saturate(150%);
  backdrop-filter: blur(10px) saturate(150%);
  margin: 0 calc(-1 * var(--gut)); padding: 4px var(--gut) 6px;
}

/* controls join the family with a specular edge only -- they float over nothing, so they get no lens */
button, .btn { box-shadow: var(--shadow-xs), inset 0 1px 0 rgba(255, 255, 255, .7); }
button.primary, .btn.primary { box-shadow: var(--shadow-xs), inset 0 1px 0 rgba(255, 255, 255, .16); }
button.quiet, .btn.quiet { box-shadow: none; }

@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .list-table thead th, .sidebar, .list-bulk, .list-top {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .list-table thead th { background: var(--shell); }
  .sidebar { background: var(--shell); }
  .list-bulk { background: var(--accent-weak); }
  .list-top { background: var(--surface); }
}

/* a label's text and its "required" marker share one line inside the flex-column label */
.lbl { display: block; }

/* ---------- Today's answer panels (docs/NOTES-TODAY.md) -------------------------------------------------------
   The queue above says what is late. These four say what is worth doing next, what has gone quiet, what churn
   looks like, and which bills are moving under the firm's own clients. Two columns on a desktop, stacked below
   it; each panel is a plain ruled list, not a card, so the page stays one surface. */
.panels { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5) var(--s6); margin-top: var(--s6); }
@media (max-width: 900px) { .panels { grid-template-columns: minmax(0, 1fr); } }
.panel:not(.widget) > h2 {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  font-size: var(--fs-dense); font-weight: 600; margin: 0 0 6px; padding-bottom: 6px; border-bottom: 1px solid var(--rule);
}
.panel > h2 a { font-size: var(--fs-label); font-weight: 500; }
.panel .none { color: var(--ink-muted); font-size: var(--fs-dense); margin: 6px 0 0; }
.panel-list { list-style: none; margin: 0; padding: 0; }
.panel-list li + li { border-top: 1px solid var(--rule); }
.panel-list a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  min-height: 38px; padding: 4px 6px; margin: 0 -6px; border-radius: 5px; color: var(--ink);
}
.panel-list a:hover { background: var(--hover); text-decoration: none; }
.panel-list .what { min-width: 0; }
.panel-list .name { display: block; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.panel-list .sub { display: block; font-size: var(--fs-label); color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.panel-list .r { flex: 0 0 auto; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; font-size: var(--fs-dense); }
.panel-list .pos { font-size: var(--fs-label); font-weight: 500; }
.panel-list .pos.support { color: var(--clear); } .panel-list .pos.oppose { color: var(--overdue); }

/* churn reads as two separate numbers because they are two separate questions */
.churn { display: flex; flex-direction: column; gap: var(--s3); padding-top: 4px; }
.churn-ours .n { display: block; font-size: 30px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.churn-ours .l, .churn-market .l { display: block; font-size: var(--fs-label); color: var(--ink-muted); }
.churn-ours .none { font-size: var(--fs-dense); }
.bars { list-style: none; margin: 6px 0 0; padding: 0; }
.bars li { display: grid; grid-template-columns: 132px 1fr 44px; align-items: center; gap: var(--s2); min-height: 22px; font-size: var(--fs-label); }
.bars .k { color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bars .bar { height: 6px; border-radius: 3px; background: var(--shell); box-shadow: inset 0 0 0 1px var(--rule); overflow: hidden; }
.bars .bar i { display: block; height: 100%; background: var(--accent); opacity: .55; }
.bars .v { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink); }

/* ============================================================================================================
   UI-FIX - 2026-09-06. Owner: "Tables should be within their own widgets so it's not confusing when you have
   two next to each other."

   On /leads the filter rail and the results table sat side by side with nothing between them: the table ran
   full-bleed out of its own column (a hangover from the DENSITY pass, where the table genuinely *was* the whole
   page), and the rail drew a hairline under every item, so its links read as a second column of table rows.

   The reconciliation, in the shared chassis so every list gets it at once (docs/NOTES-UI-FIX.md):
     · A list table is a bounded surface again - 1px --rule on all four sides, 10px clipped corners, no shadow
       (brief §2 "borders, not shadows"; NOTES-AESTHETIC's "contained grid with a tinted sticky header and
       clipped corners"). Every density value the DENSITY pass set is kept: 36px rows, 10px cells, the thin
       toolbar, no centred max-width. Only the container came back.
     · A filter rail is its own widget beside it, and its items are pill links in the sidebar's vernacular
       rather than ruled rows, so nothing on the screen reads as tabular except the table.
   ============================================================================================================ */

/* the list is one widget: toolbar, chips, grid and footer share the column, and the grid is a bounded surface */
.list-top { margin: 0; padding: 4px 0 6px; }
.list-table {
  margin: 0; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius-lg);
  box-shadow: none; overflow: clip;   /* clip, not hidden -- see the sticky-header note above */
}
.list-table > .table-wrap { border-radius: inherit; }
.list-foot { margin-top: 4px; }
/* the bulk bar replaces the toolbar in place, so it keeps the same edges as the grid below it */
.list-bulk { margin-left: 0; margin-right: 0; }

/* ---------- a rail beside a list: the chassis primitive (.list-layout > .list-rail + .list-main) ------------ */
.list-layout, .leads-layout {
  display: grid; grid-template-columns: 184px minmax(0, 1fr); gap: var(--s4); align-items: start;
}
.list-rail, .filters-rail {
  min-width: 0; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius-lg); padding: 6px;
}
/* groups are separated inside the widget, not by a rule under every item */
.list-rail .group, .filters-rail .group { border-top: 0; padding: 4px 0; }
.list-rail .group + .group, .filters-rail .group + .group {
  margin-top: 6px; padding-top: 8px; border-top: 1px solid var(--rule);
}
.list-rail h2, .filters-rail h2 { margin: 0 0 2px; padding: 0 8px; font-size: 11px; letter-spacing: .01em; }
.list-rail li, .filters-rail li { border-bottom: 0; }
/* a filter is a navigation item, the way the sidebar's are - never a row of data */
.list-rail a, .filters-rail a {
  display: flex; align-items: center; min-height: 26px; padding: 0 8px; border-radius: var(--radius);
  color: var(--ink); font-size: var(--fs-dense); line-height: 1.25;
}
.list-rail a:hover, .filters-rail a:hover { background: var(--hover); color: var(--ink); text-decoration: none; }
.list-rail a[aria-current="true"], .filters-rail a[aria-current="true"] {
  background: var(--shell); color: var(--ink); font-weight: 500; box-shadow: inset 0 0 0 1px var(--rule);
}

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

/* ---------- the peek summary - UI-FIX 2026-09-06 ------------------------------------------------------------
   Owner: "when I click on a lead I don't know what I'm looking at." The panel now renders a summary
   (templates/organizations/_peek.html), not a squeezed copy of the record page: who they are, why the record
   is in front of you, the money, the two actions worth taking, then "Open full record". Tokens only. */
/* the record's name is printed once, by the summary: the bar's copy is announced but not shown (peek.js adds
   `.has-summary` when the body carries its own heading) */
.peek-panel.has-summary .peek-bar .peek-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* the title carried the bar's flexible space; with it out of flow the controls take that job */
.peek-panel.has-summary .peek-bar .peek-nav { margin-left: auto; }
.peek-summary { padding: var(--s4) var(--s5) var(--s5); }
.ps-head { display: flex; align-items: flex-start; gap: 10px; }
.ps-head .record-mark { width: 30px; height: 30px; font-size: 14px; }
.ps-id { min-width: 0; }
.ps-name { font-size: 18px; font-weight: 600; letter-spacing: -0.015em; line-height: 1.25; }
.ps-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 3px 0 0; font-size: var(--fs-label); color: var(--ink-muted); }
.ps-meta .ps-dot { color: var(--rule); }

/* why this record is in front of you: the one fact that produced the signal */
/* bordered, not tinted: --ink-muted clears 4.5:1 on --surface (4.55) and does not on --shell (4.44), and the
   brief's answer for setting a region apart is a rule anyway */
.ps-why { margin-top: var(--s4); padding: 10px var(--s3); background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius-lg); }
.ps-why .l { display: block; font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-muted); }
.ps-why .v { margin: 2px 0 0; font-size: var(--fs-body); line-height: 1.45; color: var(--ink); }
.ps-why .ps-src { margin: 4px 0 0; font-size: var(--fs-label); color: var(--ink-muted); }

/* the facts: label over value, two to a row, no wrapping identity strip */
.ps-facts { list-style: none; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3) var(--s5); margin: var(--s4) 0 0; padding: 0; }
.ps-facts .l { display: block; font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-muted); }
.ps-facts .v { display: block; margin-top: 1px; font-size: var(--fs-dense); font-variant-numeric: tabular-nums; }
.ps-facts .v.overdue { color: var(--overdue); }

.ps-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s4); padding-top: var(--s4); border-top: 1px solid var(--rule); }
.ps-actions form { margin: 0; }
.ps-actions .spacer { flex: 1 1 auto; }

.ps-recent { margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--rule); }
.ps-recent h2 { font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-muted); margin: 0 0 4px; }
.ps-recent .lines li { display: flex; gap: var(--s3); min-height: 26px; align-items: baseline; font-size: var(--fs-dense); }
.ps-recent .lines .when { flex: 0 0 92px; color: var(--ink-muted); font-size: var(--fs-label); }
.ps-recent .none { margin: 0; color: var(--ink-muted); font-size: var(--fs-dense); }

/* The peek panel's Liquid Glass bar had no containing block. `.peek-bar` sets `isolation: isolate` but never
   `position`, so its `::before` - the lens, `backdrop-filter: blur(4px)` plus a 50px `feDisplacementMap` -
   resolved `inset: 0` against `.peek-panel` and covered the WHOLE panel. That is why the peek read as a
   "cramped, half-legible" record: every word below the bar was being drawn through the lens. One line restores
   the material to the bar it was scoped to; nothing about the material itself changes. `.toast` is the same
   omission (its lens resolves against `.toasts`, so with two toasts stacked the first one's lens covers both). */
.peek-bar { position: relative; }
.toast { position: relative; }

/* ---------- Today: the record of work, under the queue of what is due - UI-FIX 2026-09-06 ------------------
   Owner: "Activity isn't showing on the main page." The queue is built from what is *due*, so on a database
   with nothing due it renders one sentence and the firm's own work was nowhere on the landing screen. These
   rows keep the queue's rhythm (same 36px line, same quiet heading, same hairline) and carry no action button,
   because they are a record of what happened rather than something to do. */
.today-activity { margin-top: var(--s5); }
.today-activity.widget > h2.today-group-label { color: var(--ink); font-size: var(--fs-body); font-weight: 600; padding: 10px var(--s4); }
.today-activity .today-row .subject a { color: var(--ink); font-weight: 500; }
.today-activity .today-row .subject a:hover { color: var(--accent); }
.today-activity .today-date { color: var(--ink-muted); }
/* the four answer panels sit below the queue and the activity, and read as a second register */
.panels { margin-top: var(--s5); padding-top: 0; border-top: 0; }


/* ---------- widgets: every list on Today sits on its own bounded surface ------------------------------------
   Owner, 2026-09-07: "all tables need to be in their own widget things, new signals, quiet, recent." Two panels
   side by side with nothing but a hairline under each heading ran together -- you could not tell where one list
   ended and the next began. Same chrome as the grid on Clients (hairline, radius-lg, clipped corners), so this
   is the product's one surface, not a second visual language. The work queue above is deliberately NOT one:
   the brief keeps it a single continuous list, and it is the only thing on the screen that is not a widget. */
.widget {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs); overflow: clip;   /* clip, never hidden -- see the sticky-header note above */
}
.widget > h2 {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  margin: 0; padding: 10px var(--s4); border-bottom: 1px solid var(--rule);
  background: var(--shell); font-size: var(--fs-body); font-weight: 600; color: var(--ink);
}
.widget > h2 a { font-size: var(--fs-dense); font-weight: 500; }
.widget > ul, .widget > p { margin: 0; }
.widget .none { padding: var(--s4); margin: 0; }
/* rows reach the widget's edges, so their padding is the widget's gutter rather than their own margin */
.widget .panel-list a { margin: 0; border-radius: 0; padding: 6px var(--s4); }
.widget .today-row { padding: 0 var(--s4); }
.widget .today-list .today-row:last-child, .widget .panel-list li:last-child a { border-bottom: 0; }
.panels .widget { align-self: start; }

/* ---------- Today: headline cards, the churn band, and the queue -----------------------------------------------
   The cards use the same chrome as the list table on Clients -- 10px radius, hairline, one soft lift -- so the
   dashboard reads as the same product rather than a second visual language (owner: "put shadows, put same
   rounded corner segments you had on clients etc on main dashboard as well"). */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: var(--s3); margin-bottom: var(--s4); }
.card-stat {
  display: flex; flex-direction: column; min-height: 116px; padding: 14px 16px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs); color: var(--ink); text-decoration: none;
  transition: box-shadow var(--t-row) ease;
}
.card-stat:hover { box-shadow: 0 2px 12px rgba(15, 15, 15, .09); text-decoration: none; }
.card-stat:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
/* Owner, 2026-09-07: "figure better, normal text." The label and the line under the number were set at 13px
   and 12px -- caption sizes, for the four numbers the firm looks at first. Both read at normal body size now,
   and the number itself is bigger with its own optical spacing, so the card has one clear step of hierarchy
   (label, figure, what is in it) instead of three sizes of small. */
.card-stat .l { font-size: var(--fs-body); font-weight: 500; color: var(--ink); }
/* `margin-top: 6px`, never `auto`. With `auto` the figure was pushed to whatever room the foot below it left,
   so a card with a two-line sub-line sat 16px high and one with no foot at all sat 23px low -- five numbers the
   firm reads as a row, on five different baselines. The label and the figure are now top-anchored together and
   the foot is pinned to the bottom instead, which keeps the cards equal height AND the figures on one line. */
.card-stat .n {
  display: block; margin-top: 6px; font-size: 32px; font-weight: 600; line-height: 1.05;
  letter-spacing: -0.025em; font-variant-numeric: tabular-nums;
}
.card-stat .foot { display: flex; align-items: center; gap: 6px; margin-top: auto; padding-top: 7px; min-height: 31px; flex-wrap: wrap; }
.card-stat .sub { font-size: var(--fs-dense); color: var(--ink-muted); }
/* the arrow always follows the number; the colour follows whether that direction is good news */
/* A badge, not coloured text: at a glance the reader is looking for direction, and a tinted pill carries that
   further across a five-card strip than a coloured numeral does. Both colours come from the role tokens. */
.card-stat .delta {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 8px; border-radius: var(--radius-pill);
  font-size: var(--fs-label); font-weight: 600; font-variant-numeric: tabular-nums;
  background: var(--delta-up-bg); color: var(--delta-up);
}
.card-stat .delta.down { background: var(--delta-down-bg); color: var(--delta-down); }

.churn-band { margin-bottom: var(--s5); }
.churn-band .panel > h2 { margin-bottom: 8px; }
.churn-band .churn { flex-direction: row; align-items: flex-start; gap: var(--s6); }
.churn-band .churn-ours { flex: 0 0 200px; }
.churn-band .churn-market { flex: 1 1 auto; min-width: 0; }
@media (max-width: 800px) { .churn-band .churn { flex-direction: column; gap: var(--s3); } }

/* Overdue dates were landing at different x on every row: the action button sits in the same flex row and its
   label varies ("Send reminder" / "Mark done" / "Log activity"), which pushed the date around. The date is now
   the last thing in the row and reserves its own column, so the whole column lines up whatever the action is. */
.today-right { display: flex; align-items: center; gap: var(--s3); }
.today-action { order: 1; }
.today-date { order: 2; min-width: 58px; text-align: right; }
.card-stat .sub.caveat { flex-basis: 100%; color: var(--due-soon); }
/* the arithmetic behind the number, on its own line under the change */
.card-stat .sub.basis { flex-basis: 100%; }

/* the import page's one-line search */
.search-row { display: flex; gap: var(--s2); align-items: center; margin-bottom: var(--s4); max-width: 560px; }
.search-row input { flex: 1 1 auto; }

/* first ever open: two things worth doing, shown only while the firm has entered nothing */
.first-run { margin: 0 0 var(--s5); padding: var(--s4); background: var(--shell); border: 1px solid var(--rule); border-radius: var(--radius-lg); }
.first-run > h2 { font-size: var(--fs-dense); font-weight: 600; margin: 0 0 8px; }
.first-run-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.first-run-steps > li { display: flex; flex-direction: column; gap: 2px; }
.first-run-steps a { font-weight: 500; }
.first-run-steps .sub { font-size: var(--fs-label); color: var(--ink-muted); }
/* "Nothing due." was floating in a band the height of a full queue */
.today-empty { padding: var(--s4) 0; text-align: left; }


/* ============================================================================================================
   REFINEMENT PASS — 2026-09-07.  "2018 CRM" -> a 2026 productivity instrument.
   Owner's direction: an overall quality-of-life and UI upgrade, visual changes only.

   The diagnosis this pass answers: the information design was already right and the MATERIAL was missing.
   --shell sat 1.5% off white and --rule read 1.22:1 against it, so the sidebar, the toolbar, the column header
   and the page were all the same colour separated by a line you had to hunt for -- nothing on screen had a
   surface level.  Meanwhile the floating layer had the opposite problem: the command menu and every dropdown
   were 58% white behind blur(4px) with no border, so the table read straight through them.

   So: three legible surface levels, one focus language, one type scale, one motion vocabulary, and real
   hover/active/selected states.  It stays quiet -- no decoration, no hover lifts, no entrance animation, and
   colour still only ever means status.  docs/NOTES-REFINEMENT.md records what moved and why.

   Structured as seven sections in cascade order.  Nothing above this banner was edited; everything here is an
   override, so the pass is reviewable and reversible in one place -- the same convention as the passes above.
   ============================================================================================================ */

/* 01 - foundation: the surface ramp, global primitives, and the four-step type scale */

/* The diagnosis this pass answers: every surface in the product resolved to white. --shell was #FBFBFA, a 1.5%
   step off --surface, so the sidebar, the toolbar, the table header and the page were the same colour with a
   1.22:1 hairline between them. Nothing read as a thing you could act on.

   The ramp is now three deliberate levels, and only three:
     --shell    #F4F4F1   chrome: sidebar, table header, inset fields, widget headers, hints bar
     --surface  #FFFFFF   content: the grid, cards, widgets, the record body
     white              floating: menus, dialogs, toasts -- separated by shadow, not by tint
   A second ':root' rather than an edit to the first: the first block's values are pinned literally by
   tests/chassis/test_tokens.py, and there is precedent for a later block (the DENSITY pass declares one). */
:root {
  --shell: #F4F4F1;      /* ~4.5% off white: the smallest step that still reads as a different material */
  --shell-2: #ECECE8;    /* one level down again -- pressed fills and the active state of a control on the tint */

  /* --paper was set to the same literal as --shell so that "every legacy rule that reaches for it lands on the
     shell tint it was always standing in for" (see the first :root). It has to be an ALIAS to keep that promise
     now that --shell moves; left as a literal it would strand `pre`, `.entry.pinned` and every legacy hover on
     the old near-white and re-open exactly the flatness this pass exists to fix. */
  --paper: var(--shell);

  /* --rule stays the internal hairline. What was missing is an OUTER edge: a bounded surface needs a border you
     can find without hunting, and --rule at 1.22:1 on white is not one. --rule-strong is that edge. */
  --rule-strong: #DBDAD4;

  /* 11px labels and column headers at --ink-muted (4.55:1) sit right on the floor. --ink-soft clears ~7:1 on
     --surface and ~6.4:1 on --shell, which is what small type at 500 actually needs. */
  --ink-soft: #5A5852;

  /* Hover was 4.5% ink on a 1.5% tint -- invisible on the sidebar. Both steps go up now that the ground is darker. */
  --hover: rgba(31, 31, 30, .055);
  --hover-strong: rgba(31, 31, 30, .09);

  /* Two shadows for things that are RAISED but still on the page (a control, a card, a bounded grid), separate
     from --shadow-float, which stays reserved for things that have left it. Each carries its own hairline ring
     so a raised surface never needs a border as well. */
  --shadow-sm: 0 1px 2px rgba(15, 15, 15, .06), 0 0 0 1px rgba(15, 15, 15, .05);
  --shadow-md: 0 6px 16px rgba(15, 15, 15, .09), 0 0 0 1px rgba(15, 15, 15, .06);

  /* One motion vocabulary. --t-fast is for colour only: a hover that takes longer than ~90ms feels like lag in a
     tool someone drives all day. --t-panel / --t-row keep their jobs for things that move. */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --ease-out: cubic-bezier(.2, .8, .3, 1);
  --t-fast: 90ms;
}

html {
  color-scheme: light; -webkit-text-size-adjust: 100%;
  /* the overscroll gutter and any area the frame does not paint belong to the chrome, not to the page */
  background: var(--shell);
  /* 13px UI type on macOS renders a weight heavier than drawn without this; it is the single reason a
     self-hosted Public Sans looks chunkier here than the same face does in Linear or Attio */
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}

/* ---------- scrollbars: chrome, not an operating-system artefact ----------
   A dense grid is scrolled constantly, so the scrollbar is part of the instrument. It is drawn as a thumb only
   -- track transparent, thumb inset by a transparent border so it reads as a 5px pill rather than a 11px bar --
   which is quiet enough to leave on permanently and honest about where you are in a 400-row table. */
* { scrollbar-width: thin; scrollbar-color: rgba(31, 31, 30, .26) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: rgba(31, 31, 30, .22); background-clip: padding-box;
  border: 3px solid transparent; border-radius: var(--radius-pill); min-height: 32px;
}
::-webkit-scrollbar-thumb:hover { background-color: rgba(31, 31, 30, .34); }
::-webkit-scrollbar-corner { background: transparent; }

/* ---------- one focus language ----------
   The accent went back to meaning "this one, right now" in the aesthetic pass, and a focus ring is exactly that:
   a pointer to the one element the keyboard is driving. The later `outline: 2px solid var(--ink)` made the ring
   indistinguishable from the keyboard row cursor and from the ink primary button. Restated on the accent. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--ink); }
input, textarea { caret-color: var(--accent); }

/* ---------- type: four steps, and only four ----------
   title 17/600/-0.012em -- section 14/600/-0.005em -- body 13-14/1.45 -- label 11/500/.01em on --ink-soft.
   The negative tracking is what stops a 600-weight heading at these sizes reading as a browser default. */
h1, h2, h3 { letter-spacing: -0.005em; }
h1 { font-size: 17px; font-weight: 600; letter-spacing: -0.012em; }
h2 { font-size: 14px; font-weight: 600; }
h3 { font-size: var(--fs-dense); font-weight: 600; }
.page-head h1 { font-size: 17px; letter-spacing: -0.012em; }
/* the line under a page title is supporting text, not a second heading */
.page-head .sub { font-size: var(--fs-dense); line-height: 1.45; color: var(--ink-muted); }
.section-head h2 { font-size: 14px; font-weight: 600; letter-spacing: -0.005em; }

/* the label step. 11px is already this file's dominant idiom for a label (`th`, `.identity-fields .l`,
   `.ps-facts .l`, `.cmdk-group-label`, `.rail-block h2` all hand-write it); .label and .caption were the two
   that stayed at 12 and so read as undersized body rather than as labels. */
.label { font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-soft); }
.caption { font-size: 11px; letter-spacing: .01em; color: var(--ink-soft); }
/* one step below body, not a fourth size wedged between body and the label */
.small { font-size: var(--fs-dense); }
/* an intro paragraph is body at the top of its range -- 16px was a heading pretending to be prose */
.lede { font-size: 15px; line-height: 1.5; letter-spacing: -0.005em; }
/* a column of figures aligns on the decimal AND on the stroke: tabular widths plus the tightening every
   number in this product carries, so a right-aligned money column reads as one block instead of a ragged one */
.num { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }

/* a key cap is a small raised object on the chrome tint, so it takes the outer edge rather than the hairline */
kbd.key {
  font: 500 11px/1 var(--mono); color: var(--ink-soft);
  background: var(--shell); border: 1px solid var(--rule-strong); border-radius: 4px;
  box-shadow: 0 1px 0 rgba(15, 15, 15, .05);
}

/* ---------- one colour transition for every interactive primitive ----------
   These all change background, border or shadow on hover, focus and press, and each was doing it instantly
   while the peek panel and the toasts eased. Colour only, never `all`: a transition on layout properties is
   what makes an interface feel slow, and `all` catches them by accident. The global
   prefers-reduced-motion block earlier in the file already turns this off. */
a,
button, .btn,
.side-link,
.chip,
.seg a, .seg button,
.menu .sheet a, .menu .sheet button,
.tabrow a {
  transition:
    background-color var(--t-fast) linear,
    border-color var(--t-fast) linear,
    box-shadow var(--t-fast) linear,
    color var(--t-fast) linear;
}

/* 02 — chrome: the shell and the floating layer */

/* ---------- the rail is chrome, not another sheet of paper ----------------------------------------------------
   The sidebar was `rgba(251, 251, 250, .82)` over a blur. Nothing scrolls under it -- the work area sits BESIDE
   it, in a separate flex column -- so the material was refracting the page's own white ground and compositing to
   #FCFCFB. Two surfaces the eye reads as one, separated by a hairline at 1.22:1. It is solid --shell now, and
   --shell is the tint that actually steps off white, so the rail reads as chrome and the active white pill
   finally has something to be raised against. The blur is cancelled on both prefixes because it is declared in
   two earlier blocks. */
.sidebar {
  background: var(--shell);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-right: 1px solid var(--rule-strong);   /* the outer edge of a bounded surface, not an internal hairline */
}

/* Inactive items drop to a softer ink so the active pill wins the row on weight and value at the same time.
   #55534E clears 7:1 on the tint; --ink-muted (#78766F) is under 4:1 there and was the reason the whole nav
   read as one flat block of grey. */
.side-link { color: #55534E; }
.side-link .icon { color: #7A7872; }
.side-link:hover { background: var(--hover); color: var(--ink); }
.side-link:hover .icon { color: var(--ink); }

/* The active item is a raised white pill on the tint -- a surface level, not a colour wash. Hover is a neutral
   wash with no lift, so the two states can never be confused for each other. */
.side-link[aria-current="page"] {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.side-link[aria-current="page"] .icon { color: var(--ink); }
.side-link .count { color: var(--overdue); }

/* the rail's own divider carries the outer-edge weight; a --rule hairline is invisible on the tint */
.side-user { border-top: 1px solid var(--rule-strong); }
.side-user > summary:hover { background: var(--hover); }
.side-user .name { color: var(--ink); font-weight: 500; }
.side-user .caption { color: var(--ink-soft); }

/* ---------- the floating layer: things that leave the page have to look like they left it --------------------
   Every floating surface was `rgba(255, 255, 255, .58)` with `blur(4px)` and `border: 0`, so a table row read
   straight through the command palette. Opacity to .92, the lens raised to a real blur, the brightness lift
   dropped from 1.15 (which blew the tint out to white) to 1.02, and a genuine edge plus weight underneath. */

dialog.cmdk, dialog.shortcuts-help {
  background: rgba(255, 255, 255, .92);
  border: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-float);
}
/* the palette has to sit on something, or it reads as a card that happens to be centred */
dialog.cmdk::backdrop, dialog.shortcuts-help::backdrop { background: rgba(15, 15, 15, .34); }
dialog.cmdk::before, dialog.shortcuts-help::before {
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  backdrop-filter: blur(24px) saturate(180%);
  filter: url(#wprod-lens) saturate(120%) brightness(1.02);
  background: rgba(255, 255, 255, .12);
}
/* items sitting on the material take a flat neutral fill -- never glass on glass, and never the accent: the
   keyboard cursor is "you are here", which is not a status */
.cmdk-item.active { background: var(--hover-strong); }
.cmdk-group-label { color: var(--ink-soft); }
.cmdk-hints { background: var(--shell); border-top: 1px solid var(--rule); color: var(--ink-soft); }
.cmdk-search-row { border-bottom: 1px solid var(--rule); }

/* the smaller sheets float a shorter distance, so they take the medium weight rather than the float */
.menu .sheet {
  background: rgba(255, 255, 255, .92);
  border: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-md);
}
.menu .sheet::before {
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  backdrop-filter: blur(24px) saturate(180%);
  filter: url(#wprod-lens) saturate(120%) brightness(1.02);
  background: rgba(255, 255, 255, .12);
}
/* the user menu carries both classes and wins the cascade on its own; without this restatement it keeps the
   old opaque-white-with-a-hairline treatment while every other sheet moves */
.side-user.menu .sheet {
  background: rgba(255, 255, 255, .92);
  border: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-md);
}

/* ---------- peek bar: a lens over nothing --------------------------------------------------------------------
   The panel is a flex column -- the body scrolls in its own box BELOW the bar, never under it -- so the bar's
   refraction had no moving backdrop to refract and only washed the title out. It becomes the tinted bar the
   table header is, with an outer-edge rule under it. `content: none` removes the pseudo-element outright, which
   takes its backdrop-filter with it. The bare `.peek-bar` selector is left alone (its one-declaration
   `position: relative` rule is load-bearing); this states the same thing one level more specific. */
.peek-bar::before { content: none; }
.peek-panel .peek-bar {
  background: var(--shell);
  border-bottom: 1px solid var(--rule-strong);
  box-shadow: none;
}

/* ---------- toast: opaque, so drop the glass that was running over it ----------------------------------------
   ::before was a leftover lens and ::after a specular rim, both from the glass pass, both still painting over a
   surface the owner made solid on 2026-09-07. The rim washed the toast's own white out; the lens had been
   over-constrained down to a 6px dot in the corner. Neither says anything, so neither draws. */
.toast::before, .toast::after { content: none; }
.toasts .toast {
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-float);
}
/* the one place a toast carries colour: a problem keeps its leading edge */
.toasts .toast.err, .toasts .toast.warn { border-left: 2px solid var(--due-soon); }

/* ---------- narrow screens: the rail becomes a drawer, so it takes a drawer's weight ------------------------- */
@media (max-width: 900px) {
  .topbar { background: var(--shell); border-bottom: 1px solid var(--rule-strong); }
  html[data-drawer="open"] .sidebar { box-shadow: var(--shadow-float); }
  /* the scrim was .35 of a near-black over a white page and still read as a haze; the drawer needs a ground */
  html[data-drawer="open"] .backdrop { background: rgba(15, 15, 15, .34); }
}

/* ---------- accessibility, restated ---------------------------------------------------------------------------
   The two settings blocks that make these surfaces opaque are declared EARLIER in this file at the same
   specificity, so the rules above would silently win inside them. Same intent, restated last. */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  dialog.cmdk, dialog.shortcuts-help, .menu .sheet, .side-user.menu .sheet, .toasts .toast {
    background: var(--surface);
  }
}
@media (prefers-contrast: more) {
  dialog.cmdk, dialog.shortcuts-help, .menu .sheet, .side-user.menu .sheet {
    box-shadow: 0 0 0 1px var(--ink), var(--shadow-float);
  }
}

/* 03 — the table / list chassis */

/* ---------- the grid as ONE bounded object ------------------------------------------------------------------
   --rule-strong on the outside, --rule for every hairline inside. Today the container edge, the header edge and
   the row edges are all the same 1.22:1 line, so a table reads as a field of lines rather than a thing sitting
   on the page; one darker edge is what separates "an object" from "some HTML".
   `div.list-table`, not `.list-table`: the chassis rule that pins the border, the radius, `overflow: clip` and
   `margin: 0` is load-bearing (tests/chassis/test_tokens.py::test_a_list_table_is_a_bounded_surface), so the
   outer edge is restated on the element+class rather than rewritten in place. The table still takes no shadow. */
div.list-table { border-color: var(--rule-strong); }
.table-plain { border-color: var(--rule-strong); }
/* the rail beside a list is the same kind of surface, so it gets the same edge (its `border: 1px solid var(--rule)`
   declaration stays where the UI-FIX block put it; only the colour is restated) */
aside.list-rail, aside.filters-rail { border-color: var(--rule-strong); }

/* ---------- the column header ------------------------------------------------------------------------------
   11px / 500 / .01em on --ink-soft (~7:1 on the tint): a label, clearly not data. --shell is the tint that
   actually reads now, so the header band separates from the rows without a second line. */
th {
  background: var(--shell); color: var(--ink-soft);
  font-size: 11px; font-weight: 500; letter-spacing: .01em;
}
/* the band's own bottom edge is the strong rule -- it is a container edge, not a row hairline */
thead th { box-shadow: inset 0 -1px 0 var(--rule-strong); }
/* The sticky header was 72% opaque over a 6px blur, so scrolled row text smeared straight through it -- the one
   place in the product where the material actively costs legibility. Near-opaque tint, modest blur: rows still
   move behind it, but nothing of them survives as text. (`div.list-table` outranks the glass block's
   `.list-table thead th`; the selector keeps `thead`, which is where the material is allowed to live.) */
div.list-table thead th {
  background: rgba(244, 244, 241, .94);
  -webkit-backdrop-filter: blur(4px) saturate(130%);
  backdrop-filter: blur(4px) saturate(130%);
  box-shadow: inset 0 -1px 0 var(--rule-strong);
}

/* ---------- the sort affordance ----------------------------------------------------------------------------
   `content: " ↓"` appended a glyph the moment a column was sorted, so the label jumped sideways under the
   pointer that had just clicked it. The chevron is drawn from two borders and its box is always in the flex
   row -- sorting changes opacity and direction, never geometry, so nothing reflows. No position: anywhere in a
   `th` selector; that would join the header's position declarations and kill the sticky header. */
th a { display: inline-flex; align-items: center; gap: 5px; color: var(--ink-soft); }
th a:hover { color: var(--ink); }
th a::after {
  content: ""; flex: 0 0 auto; width: 5px; height: 5px;
  border-left: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: translateY(-1px) rotate(-45deg);
  opacity: 0; transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
th a.on { color: var(--ink); }
th a.on::after { content: ""; opacity: 1; }
th a.on.asc::after { content: ""; transform: translateY(1px) rotate(135deg); }

/* ---------- the toolbar ------------------------------------------------------------------------------------
   `.list-top` sits on a white page: 78% white plus a 10px blur buys nothing and costs the row text that scrolls
   under it. Solid --surface. position/top/z-index are restated because this element is measured into --top-h by
   chassis.js and the column header parks at that offset; both backdrop-filter prefixes are cancelled. */
.list-top {
  position: sticky; top: 0; z-index: 20;
  background: var(--surface);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* the bulk bar replaces the toolbar in place and sits directly over the rows it acts on, so it stays opaque for
   the same reason -- and keeps the toolbar's anchor and stacking exactly */
.list-bulk {
  position: sticky; top: 0; z-index: 20;
  background: var(--accent-weak);
  -webkit-backdrop-filter: none; backdrop-filter: none;
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  box-shadow: none;
}

/* ---------- row states, in increasing strength --------------------------------------------------------------
   Hover was a 4.5% wash on a 36px row: at that height it is below the threshold of "the pointer is here".
   --hover (5.5%) with a 120ms fade on the cell reads without becoming a colour. The keyboard cursor is a step
   stronger and carries an ink edge; selection is the accent, because the bulk bar acts on it. The 2px marker
   rides `td:first-child` only -- on the bare td it draws once per column boundary and stripes the row. */
tbody td { transition: background-color var(--t-row) linear; }
tbody tr:hover td { background: var(--hover); }
tbody tr.key-focused td { background: var(--hover-strong); box-shadow: none; }
tbody tr.key-focused td:first-child { box-shadow: inset 2px 0 0 var(--ink); }
tbody tr.picked td, tbody tr.picked:hover td { background: var(--accent-weak); }
tbody tr.picked td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
/* the peeked row had the striping defect in both places it is declared (the base rule and the chassis one),
   so both are split here: fill on the row, marker on the leading edge */
tbody tr[data-peeking] td { background: var(--hover-strong); box-shadow: none; }
tbody tr[data-peeking] td:first-child { box-shadow: inset 2px 0 0 var(--ink); }
div.list-table tbody tr[data-peeking] td { background: var(--hover-strong); box-shadow: none; }
div.list-table tbody tr[data-peeking] td:first-child { box-shadow: inset 2px 0 0 var(--ink); }

/* ---------- the selection gutter ----------------------------------------------------------------------------
   The vertical hairlines are what make this read as a grid, but one of them fell between the 30px checkbox
   gutter and the first data column -- and the checkbox is opacity:0 at rest, so every row opened with an empty
   boxed cell. The gutter is chrome, not a column: no divider on either side of it. */
div.list-table td.check + td, div.list-table th.check + th { border-left: 0; }

/* ---------- the empty row -----------------------------------------------------------------------------------
   Nothing to click, so nothing lights up under the pointer. (The empty state's own type is the shared primitive
   in fragment 04; this only keeps the container honest.) */
tbody tr.none td, tbody tr.none:hover td { background: var(--surface); }
.list-empty strong { font-size: var(--fs-body); font-weight: 600; letter-spacing: -0.005em; color: var(--ink); }
.list-empty span { color: var(--ink-muted); }

/* ---------- .table-plain, folded into the chassis's language ------------------------------------------------
   Reports, Market, Entities and the record sub-tables were a second table vernacular: bigger text, an untinted
   header, and entity names as accent-blue links beside the chassis's ink-at-500. Same header tint, same 11px
   labels, same hairlines -- one table language in the product, not two. */
.table-plain table { font-size: var(--fs-dense); }
.table-plain th { background: var(--shell); color: var(--ink-soft); font-size: 11px; font-weight: 500; letter-spacing: .01em; }
/* a name in a cell is the row's subject, not a link to decorate: ink at 500, accent only under the pointer */
.table-plain td a { color: var(--ink); font-weight: 500; }
.table-plain td a:hover { color: var(--accent); text-decoration: none; }
.table-plain tbody tr[data-href]:hover td { background: var(--hover); }

/* 04 — controls, chips, cards, empty states */

/* ---------- buttons: a raised control on a tinted shell, not a white gloss on white ------------------------
   `button, .btn` carried `inset 0 1px 0 rgba(255,255,255,.7)` -- a white highlight painted on a white button,
   which is invisible everywhere except the ink primary, where it read as a 2013 sheen. Both gloss lines go, and
   the secondary button gets what it never had: an edge that is visible against the page (--rule-strong is ~2:1
   on white, --rule is 1.22:1) and hover/active fills a user can actually see change. */
button, .btn {
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  color: var(--ink);
  font-weight: 500;
}
/* One step down from --shell (#F4F4F1) for hover and one further to --shell-2 for the press, so the two states
   are distinguishable from each other and not only from rest. */
button:hover, .btn:hover { background: #F0F0EC; border-color: #CBCAC4; box-shadow: var(--shadow-sm); }
button:active, .btn:active { background: var(--shell-2); border-color: #C2C1BA; box-shadow: none; }

/* the ink primary: its own hover and press, and a shadow tuned for a dark fill (the neutral ring in --shadow-sm
   is invisible under ink, and the white gloss on top of it was the 2013 tell) */
button.primary, .btn.primary { background: var(--ink); color: var(--surface); border-color: var(--ink); box-shadow: 0 1px 2px rgba(15, 15, 15, .16); }
button.primary:hover, .btn.primary:hover { background: #34332F; border-color: #34332F; box-shadow: 0 1px 2px rgba(15, 15, 15, .16); }
button.primary:active, .btn.primary:active { background: #131312; border-color: #131312; box-shadow: none; }

button.quiet, .btn.quiet { background: transparent; border-color: transparent; box-shadow: none; color: var(--ink-muted); }
button.quiet:hover, .btn.quiet:hover { background: var(--hover); border-color: transparent; box-shadow: none; color: var(--ink); }
button.quiet:active, .btn.quiet:active { background: var(--hover-strong); box-shadow: none; }

/* destructive: the word still carries the meaning, the tint only appears once the pointer commits to it */
button.danger, .btn.danger { color: var(--overdue); }
button.danger:hover, .btn.danger:hover { background: var(--bad-soft); border-color: rgba(140, 47, 42, .30); color: var(--overdue); }

button:disabled, .btn[aria-disabled="true"] { opacity: .55; box-shadow: none; cursor: default; }
button:disabled:hover, .btn[aria-disabled="true"]:hover { background: var(--surface); border-color: var(--rule-strong); }

.icon-btn { color: var(--ink-soft); }
.icon-btn:hover { color: var(--ink); }
.icon-btn.quiet { border-color: transparent; background: transparent; box-shadow: none; }

.text-link { background: none; border: 0; box-shadow: none; padding: 0; color: var(--accent); font-weight: 500; }
.text-link:hover { color: var(--accent-hi); background: none; }

/* ---------- fields: one focus language, and a control that looks like a control ---------------------------
   Focus was a 3px halo of 4.5% black on a --rule border: on a 13px input in a filter bar there was no visible
   difference between focused and not. The accent is the right colour for it -- it is the one thing on screen
   that means "this one, right now". */
input, select, textarea { border: 1px solid var(--rule-strong); background: var(--surface); box-shadow: none; }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
input::placeholder, textarea::placeholder { color: var(--ink-soft); }
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--overdue); }

/* the search field is inset in the tint rather than raised out of it, so a toolbar reads as one bar */
input[type=search] { background: var(--shell); border-color: var(--rule-strong); box-shadow: none; }
input[type=search]:focus { background: var(--surface); border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }

/* De-chromed select. The caret is drawn as two solid-colour gradients (a data: URI fails the asset rule and
   `select` cannot carry a pseudo-element), positioned as a pair of triangles that read as one chevron. */
select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 26px;
  background-color: var(--surface);
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%),
                    linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
  background-position: calc(100% - 15px) center, calc(100% - 11px) center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}

/* The OS checkbox is the last unstyled control in the product and it shows: a blue-grey Aqua box beside
   13px Public Sans. `accent-color` could only ever tint the tick, never the resting box, which is the state
   a table full of unchecked rows actually displays.
   The tick is a pseudo-element, so a browser that declines to render one on an <input> still gets a filled
   accent box -- which is what a native checked box looks like anyway. Nothing depends on the glyph. */
input[type=checkbox], input[type=radio] {
  -webkit-appearance: none;
  appearance: none;
  display: inline-grid;
  place-items: center;
  width: 15px; height: 15px; min-height: 0; padding: 0; margin: 0;
  border: 1px solid var(--rule-strong); border-radius: 4px;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  vertical-align: middle;
}
input[type=radio] { border-radius: 50%; }
input[type=checkbox]:hover, input[type=radio]:hover { border-color: #B7B6AE; }
input[type=checkbox]:checked, input[type=radio]:checked { background: var(--accent); border-color: var(--accent); box-shadow: none; }
input[type=checkbox]:checked::after {
  content: ""; width: 4px; height: 8px;
  border: solid var(--surface); border-width: 0 1.6px 1.6px 0;
  transform: translateY(-1px) rotate(45deg);
}
/* the radio's dot is an inset ring, so it needs no pseudo-element at all */
input[type=radio]:checked { box-shadow: inset 0 0 0 3px var(--surface); }
/* the header's mixed state: the same bar the empty-state mark uses, drawn as a solid-colour gradient */
input[type=checkbox]:indeterminate { background: linear-gradient(var(--surface), var(--surface)) center / 7px 1.6px no-repeat, var(--accent); border-color: var(--accent); box-shadow: none; }
input[type=checkbox]:disabled, input[type=radio]:disabled { opacity: .5; cursor: default; }

/* toolbars: the field labels are labels, at the label size */
.filters label, .toolbar label { font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-soft); }

/* segmented control: an inset track, the active option a raised white pill (the same figure/ground the
   sidebar's active nav item uses, so a "which view" control never reads as a filled button) */
.seg { border-color: var(--rule-strong); background: var(--shell); }
.seg a, .seg button { color: var(--ink-soft); box-shadow: none; background: transparent; }
.seg a:hover, .seg button:hover { color: var(--ink); background: var(--hover); }
.seg a[aria-current="true"], .seg button[aria-pressed="true"] { background: var(--surface); color: var(--ink); font-weight: 500; box-shadow: var(--shadow-sm); }

/* ---------- one token language ----------------------------------------------------------------------------
   Four unrelated inline-token treatments coexisted: `.chip` (a pill with an inset ring), `.list-chips .chip`
   (which overrode the fill back to white, so the active filter chip on every list was invisible), `.status`
   (a dot plus a word) and `.card-stat .delta` (a tinted pill). Two survive, and they mean different things:

     · a ROUNDED RECT on --shell with a --rule-strong ring is a token -- a filter, a source, an attribute
     · a PILL is reserved for a number that moved (`.delta`, the sidebar's overdue count)
     · the status dot + word is untouched as a concept; only its baseline is fixed

   so the shape itself now carries information instead of being three ways of drawing the same thing. */
.chip {
  display: inline-flex; align-items: center; gap: 4px;
  min-height: 20px; padding: 1px 8px;
  border: 0; border-radius: var(--radius);
  background: var(--shell); box-shadow: inset 0 0 0 1px var(--rule-strong);
  color: var(--ink); font-size: 11px; font-weight: 500; letter-spacing: .01em; line-height: 1.5;
  white-space: nowrap; vertical-align: middle;
}
.chip.range { color: var(--ink-soft); }
/* the full radius is the numeric badge's, so "a pill means a number" holds across the product */

/* the active filter chip: it was white-on-white, which is the one chip in the product that has to be findable
   because it is the reason the list is short. `.k` is the field name, the value is the ink. */
.list-chips .chip, .filter-chips .chip { background: var(--shell); color: var(--ink); box-shadow: inset 0 0 0 1px var(--rule-strong); padding-right: 4px; }
.list-chips .chip .k, .filter-chips .chip .k { color: var(--ink-soft); font-weight: 500; }
.list-chips .chip a, .filter-chips .chip a { color: var(--ink-soft); border-radius: 3px; }
.list-chips .chip a:hover, .filter-chips .chip a:hover { color: var(--overdue); background: var(--hover); }
.list-chips .clear { color: var(--ink-soft); font-size: 11px; font-weight: 500; }

/* Status keeps the brief's dot + word exactly. What changes is that the box sits on the text baseline instead
   of being vertically centred against it: an inline-flex aligned `middle` puts its own centre at the parent's
   x-height, so the word inside it rode 1-2px high against everything else on the line. With `align-items:
   baseline` the empty dot's baseline (its bottom edge) is the row's baseline, the label sits on that same
   line, and a 7px dot standing on the baseline lands on the optical centre of the lowercase. */
.status:not(th):not(td), .dot-label {
  display: inline-flex; align-items: baseline; gap: 6px;
  vertical-align: baseline; white-space: nowrap; color: var(--ink);
}
.status:not(th):not(td)::before, .dot-label::before, .sdot {
  content: ""; display: inline-block; flex: 0 0 auto;
  width: 7px; height: 7px; border-radius: 50%; background: var(--neutral);
}
.sdot { vertical-align: baseline; }

/* ---------- tabs: one underlined-tab language, in ink -----------------------------------------------------
   `.tabrow` (record pages) moved to ink in the aesthetic pass and `.tabs` (everywhere else) stayed on the
   accent, so the same control was two colours depending on the page. Ink wins: the accent means "act on this". */
.tabs a, .tabrow a { font-size: var(--fs-dense); font-weight: 500; color: var(--ink-soft); }
.tabs a:hover, .tabrow a:hover { color: var(--ink); text-decoration: none; }
.tabs a.on, .tabs a[aria-current="page"], .tabrow a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--ink); }

/* ---------- notices ---------------------------------------------------------------------------------------
   Both are a bordered strip with a 2px leading edge in the colour of what happened. They only needed the outer
   edge to become visible; the structure was already right. */
.flash, .notice { border: 1px solid var(--rule-strong); background: var(--surface); border-radius: var(--radius); }
.flash { border-left: 2px solid var(--accent); }
.flash.err, .flash.warn { border-left-color: var(--due-soon); }
.notice { border-left: 2px solid var(--due-soon); }

/* ---------- surfaces: one card chrome ---------------------------------------------------------------------
   --rule-strong on the OUTER edge of a bounded surface, --rule for the hairlines inside it. That single
   distinction is what makes a card read as a thing sitting on the page rather than a rectangle drawn on it. */
.card { border-color: var(--rule-strong); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); }
.card.highlight { border-color: var(--accent); }
/* the widget is the same object; it already has the border and the radius, so it only wants the outer ring */
.widget { box-shadow: var(--shadow-sm); }
.widget > h2 a { color: var(--accent); }

/* ---------- "a number with a label", once ------------------------------------------------------------------
   Three components did this job: `.stats > .stat` (a bordered strip), `.figures` (a rule-topped band with no
   container, which is what Billing shows) and `.cards > .card-stat` (a card, which is what Today shows). The
   firm's headline numbers changed component between two screens. They are one family now -- same outer edge,
   same radius, same padding, same chrome -- and the layout differs where the job differs: a strip where the
   numbers are read across, a card where each one is a target you click.
   The two type scales are DELIBERATE and are not being unified. Today's card leads a screen, so its figure is
   32px over a 14px ink label -- the owner's 2026-09-07 "figure better, normal text" call, and both values are
   pinned by tests/chassis/test_widgets_and_lens.py. Billing's strip is a band of six numbers read across, so it
   takes 20px over the 11px label step. A hero and a band are not the same rank, and making them match would
   flatten the one screen that is supposed to lead. */
.stats { border: 1px solid var(--rule-strong); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-sm); overflow: clip; }
/* `.stat` also carries `.card`, so without this every cell of the strip inherits the card's shadow */
.stats > .stat { padding: 12px 14px; border-left: 1px solid var(--rule); box-shadow: none; }
.stats > .stat:first-child { border-left: 0; }

.figures {
  background: var(--surface);
  border: 1px solid var(--rule-strong); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); overflow: clip;
}
.figures > div { padding: 12px 14px; border-left: 1px solid var(--rule); }
.figures > div:first-child { border-left: 0; padding-left: 14px; }

/* the band figure: 20px, the card figure stays 32px (it is the headline of its own surface) */
.stat .n, .figures .n { font-size: 20px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.stat .l, .stat-top .l, .figures .l { font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-soft); margin-top: 2px; }
.stat .sub { font-size: var(--fs-dense); color: var(--ink-muted); }

/* Every caller of stat_card() passes a meaningful sprite name and `.stat-top .tile { display: none }` threw it
   away. It comes back as a 22px monochrome mark -- the tile is the label's icon, never a coloured badge. */
.stat-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.stat-top .l { margin-top: 0; }
.stat-top .tile { display: inline-grid; width: 22px; height: 22px; border-radius: 6px; }
.stat-top .tile .icon { width: 13px; height: 13px; }
.tile, .doc-icon {
  border: 0; border-radius: 7px;
  background: var(--shell); box-shadow: inset 0 0 0 1px var(--rule);
  color: var(--ink-soft);
}
.avatar { border-radius: 7px; box-shadow: inset 0 0 0 1px rgba(36, 80, 143, .16); }
.avatar.ink { box-shadow: none; }
.record-mark { box-shadow: inset 0 0 0 1px var(--rule-strong); color: var(--ink-soft); }

/* a movement is a pill; a sentence is not */
.stat .delta { display: block; margin-top: 6px; font-size: var(--fs-dense); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.stat .delta.up, .stat .delta.down {
  display: inline-flex; align-items: center; gap: 3px; margin-top: 6px;
  padding: 2px 8px; border-radius: var(--radius-pill);
  font-size: 11px; font-weight: 600;
}
.stat .delta.up { background: var(--delta-up-bg); color: var(--delta-up); }
.stat .delta.down { background: var(--delta-down-bg); color: var(--delta-down); }

.card-stat { border: 1px solid var(--rule-strong); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); padding: 14px 16px; }
/* no hover-lift: the card grows a darker edge instead. A shadow that changes on hover makes a five-card strip
   twitch as the pointer crosses it, and the brief forbids it. */
.card-stat:hover { box-shadow: var(--shadow-sm); border-color: #CDCCC6; text-decoration: none; }
.card-stat .l { font-size: var(--fs-body); font-weight: 500; color: var(--ink); }
.card-stat .n { display: block; margin-top: 6px; font-size: 32px; font-weight: 600; line-height: 1.05; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.card-stat .sub { font-size: var(--fs-dense); color: var(--ink-muted); }
.card-stat .delta { border-radius: var(--radius-pill); font-size: 11px; font-weight: 600; }

/* ---------- the record's own labels, on the same scale ----------------------------------------------------- */
.identity-fields .l { font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-soft); }
.identity-fields .v { font-size: var(--fs-dense); color: var(--ink); }
.rail-block li .sub, .rail-block li .r { color: var(--ink-soft); }
.panel-list .sub { color: var(--ink-soft); }

/* ---------- one empty state ---------------------------------------------------------------------------------
   Six of these existed, every one of them a line of grey text -- including the first thing a new firm sees on
   /pipeline, which was a column heading over a 90px void. One primitive: a mark, the situation in ink, the
   hint under it, and room for the action that fills it.

   No `display` is set, deliberately: half of these are a <td> and half are a <div> or <p>, and a `display:
   block` here would collapse the table cells. The mark is a background-drawn bar rather than a glyph so it
   announces nothing to a screen reader, which only ever needs the sentence. */
.empty,
.list-empty,
.today-empty,
.panel > p.none,
.widget > p.none,
.filing-sources-none {
  padding: 48px 24px;
  text-align: center;
  color: var(--ink);
  font-size: var(--fs-body); font-weight: 600; letter-spacing: -0.005em; line-height: 1.45;
  margin: 0;
}
.empty::before,
.list-empty::before,
.today-empty::before,
.panel > p.none::before,
.widget > p.none::before,
.filing-sources-none::before {
  content: "";
  display: block; width: 28px; height: 28px; margin: 0 auto 12px;
  border-radius: 8px;
  background: linear-gradient(var(--ink-soft), var(--ink-soft)) center / 10px 1.5px no-repeat, var(--shell);
  box-shadow: inset 0 0 0 1px var(--rule-strong);
}
/* The ::before above is the FALLBACK mark -- a dash in a tile, for a call site that names no icon. Where the
   template does pass one, the real sprite icon replaces it rather than sitting beside a second empty tile. */
.empty:has(> .tile)::before,
.list-empty:has(> .tile)::before,
.today-empty:has(> .tile)::before,
.panel > p.none:has(> .tile)::before,
.widget > p.none:has(> .tile)::before { content: none; }
.empty > .tile, .list-empty > .tile, .today-empty > .tile,
.panel > p.none > .tile, .widget > p.none > .tile {
  display: grid; place-items: center; width: 28px; height: 28px; margin: 0 auto 12px;
  border-radius: 8px; background: var(--shell); color: var(--ink-soft);
  border: 0; box-shadow: inset 0 0 0 1px var(--rule-strong);
}
.empty > .tile .icon, .list-empty > .tile .icon, .today-empty > .tile .icon,
.panel > p.none > .tile .icon, .widget > p.none > .tile .icon { width: 15px; height: 15px; }
/* the situation reads at the container's own size; the hint is a step down and capped so it stays one thought */
.empty strong, .list-empty strong { display: block; color: var(--ink); font-size: var(--fs-body); font-weight: 600; margin: 0; }
.empty span, .empty p, .list-empty span, .list-empty p, .today-empty span {
  display: block; max-width: 44ch; margin: 5px auto 0;
  font-size: var(--fs-dense); font-weight: 400; letter-spacing: 0; color: var(--ink-soft);
}
/* room for a real button, whichever form the call site uses */
.empty a, .list-empty a { display: inline-block; margin-top: var(--s4); font-size: var(--fs-dense); font-weight: 500; }
.empty .btn, .list-empty .btn { margin-top: var(--s4); }
td.empty { padding: 48px 24px; }
/* the one compact case: this sits under a single field on a filing, not in a panel of its own */
.filing-sources-none { padding: 20px 12px; font-size: var(--fs-dense); font-weight: 500; }
.filing-sources-none::before { width: 22px; height: 22px; margin-bottom: 8px; }

/* ---------- stepper (legacy) ------------------------------------------------------------------------------- */
.stepper .dot { border-color: var(--rule-strong); box-shadow: var(--shadow-sm); color: var(--ink-soft); }
.stepper li.done .dot, .stepper li.current .dot { box-shadow: none; }

/* 05 — motion: state transitions only, colours/sizes/layout stay wherever they were declared */

/* ---------- command palette: showModal() pops a native <dialog> with no transition today; this is the single
   most-used interaction in the product and the one every modern CRM gets judged on. The base (closed) rule
   and the [open] rule both restate opacity/transform so the browser has a real "from" and "to" either way the
   attribute flips; @starting-style supplies the frame the very first paint of the open state needs, since a
   plain transition can't interpolate across a display:none -> displayed jump on its own. */
dialog.cmdk, dialog.shortcuts-help {
  opacity: 0;
  transform: translateY(-4px) scale(.98);
  transition:
    opacity var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out),
    overlay var(--t-fast) allow-discrete,
    display var(--t-fast) allow-discrete;
}
dialog.cmdk[open], dialog.shortcuts-help[open] {
  opacity: 1;
  transform: translateY(0) scale(1);
}
@starting-style {
  dialog.cmdk[open], dialog.shortcuts-help[open] {
    opacity: 0;
    transform: translateY(-4px) scale(.98);
  }
}
/* the scrim fades with it instead of snapping to full-dark the instant showModal() runs */
dialog.cmdk::backdrop, dialog.shortcuts-help::backdrop {
  opacity: 1;
  transition: opacity var(--t-fast) var(--ease-out), overlay var(--t-fast) allow-discrete, display var(--t-fast) allow-discrete;
}
@starting-style {
  dialog.cmdk::backdrop, dialog.shortcuts-help::backdrop { opacity: 0; }
}

/* the active row should glide the way Raycast/Spotlight do, not teleport between rows on arrow-key move */
.cmdk-item { transition: background-color var(--t-fast) var(--ease); }

/* keyboard focus lands on .peek-panel programmatically (peek.js); "outline: none" elsewhere leaves a keyboard
   user with zero confirmation the panel took focus. Inset + negative offset so the ring reads on a panel
   whose own edge sits flush against the viewport, instead of getting clipped there. */
.peek-panel:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

/* ---------- bulk-selection bar: today it's a hard display swap between .list-top and .list-bulk. Crossfade
   both directions instead of a pop -- the "display" transition is what lets an already-hidden/shown element
   hold its old rendered state for one frame so the discrete swap can be timed against the fade. */
.list-top {
  opacity: 1;
  transition: opacity var(--t-fast) var(--ease), display var(--t-fast) allow-discrete;
}
.list.selecting .list-top { opacity: 0; }
@starting-style {
  .list-top { opacity: 0; }
}
.list-bulk {
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease-out), display var(--t-fast) allow-discrete;
}
.list.selecting .list-bulk { opacity: 1; }
@starting-style {
  .list.selecting .list-bulk { opacity: 0; }
}

/* ---------- board: collapse and drop-zone ---------- */
/* 200px -> 48px was an instant snap; give the column itself something to animate between. Children still hard-
   toggle with display:none, which is fine -- only the rail width needs to read as a resize, not a cut. */
.board-col { transition: flex-basis 140ms var(--ease), width 140ms var(--ease); }
/* board.js never applies .board-col.over yet (visual-only pass, can't touch JS here -- see handoff), but the
   rule needs to be ready the moment it is: the transition lives on the drop target itself so the tint eases
   in and back out instead of hard-cutting when the class starts getting toggled. */
.board-col-body { transition: background-color var(--t-fast) var(--ease); }

/* the composer's tab underline is a state change (which tab is "on"), not a click flash */
.composer-tabs .tab { transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); }

/* ---------- inline field edit: htmx swaps the whole .field-cell outerHTML, so a plain CSS transition on the
   element has nothing to animate from -- there's no "old" node. htmx's own addedClass/settlingClass (default
   "htmx-added" / "htmx-settling", removed after the 20ms settle delay) give the freshly-swapped node a real
   starting state to transition away from, so the swap reads as a change instead of a flicker. This never fires
   on page load: those classes are only ever added by an htmx swap, never present on initial render. */
.field-cell.htmx-added, .field-cell.htmx-settling { opacity: 0; }
.field-cell { transition: opacity var(--t-fast) var(--ease-out); }

/* 06 — charts, sparklines, and the peek panel's content blocks */

/* Bars: base+fill were the same rect drawn twice (base = solid slot, fill = hatched overlay at
   .55 opacity), which is why the bars read as a 2013 texture. Base now carries no fill or stroke
   of its own — it exists only so .cur can turn solid — and .fill carries a flat, translucent
   accent tint. Giving .fill an explicit `fill:` overrides the inline `fill="url(#hatch-N)"`
   attribute in the markup (a presentation attribute loses to any author rule), so the hatch
   pattern stops rendering without touching the SVG. */
.chart .bar .base { fill: none; stroke: none; rx: 2px; }
.chart .bar .fill { fill: var(--accent); opacity: .14; rx: 2px; }
.chart .bar.cur .base { fill: var(--accent); stroke: none; }
.chart .bar.cur .fill { opacity: 0; }
/* hover raises the tint instead of drawing an outline around the bar */
.chart .bar:hover .fill, .chart .bar:focus .fill { opacity: .22; }
.chart .bar.cur:hover .fill, .chart .bar.cur:focus .fill { opacity: 0; }
.chart .bar:hover .base, .chart .bar:focus .base { fill: none; stroke: none; }
.chart .bar.cur:hover .base, .chart .bar.cur:focus .base { fill: var(--accent-hi); }

/* the hatch pattern's own stroke is now moot (nothing references it once .fill is opaque-flat),
   but kill it outright so a future markup change can't bring the texture back silently */
.chart .hatch { stroke: none; }

/* gridlines were full-strength --rule, i.e. as loud as a real edge; a plot grid should almost
   disappear */
.chart .grid { stroke: rgba(15, 15, 15, .055); }

/* axis + category labels in the quieter ink step, matching the app's other small-print */
.chart text { fill: var(--ink-soft); font-size: 11px; }
.chart .lab { fill: var(--ink-soft); }

/* tooltip: keep the ink fill and white 11px/500 label, add the rounded, hairline-edged card
   read the rest of the floating layer now has (rx already 6 in the markup) */
.chart .tip rect { fill: var(--ink); stroke: rgba(255, 255, 255, .14); stroke-width: 1; }
.chart .tip text { fill: #fff; font-size: 11px; font-weight: 500; }

/* horizontal mini-bars (e.g. distribution lists): same translucent-accent language as the chart
   bars above, no borrowed shadow */
.bars .bar { background: var(--shell); box-shadow: inset 0 0 0 1px var(--rule); }
.bars .bar i { opacity: .55; }

/* sparklines read fine already; just align the muted/current colors with the new ink steps */
.spark-line { color: var(--ink-soft); }
.spark rect { fill: var(--rule); }
.spark rect.zero { fill: var(--shell); }

/* activity timeline: quiet the connecting rule, keep the accent dot doing the pointing */
.timeline li { border-left: 1px solid var(--rule); }
.timeline li::before { background: var(--rule-strong); }
.timeline li.accent::before, .timeline li.note-status_change::before { background: var(--accent); }
.entry { border-top: 1px solid var(--rule); }
.entry.pinned { background: var(--shell); }

/* .ps-why is the product's actual differentiator — "why this record is in front of you" — and it
   was sitting on --surface inside a --surface panel: a 1.22:1 border around a same-colour fill,
   an invisible box. Give it the shell tint, a real edge, and a 2px accent leading edge so it
   reads as the callout it is. */
.ps-why {
  background: var(--shell);
  border: 1px solid var(--rule-strong);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: 10px var(--s3) 10px calc(var(--s3) - 1px);
}

/* pre blocks were --paper on --rule, i.e. no distinction from the page underneath them */
pre {
  background: var(--shell);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  overflow: auto;
  max-height: 420px;
  white-space: pre-wrap;
  margin: 0;
  line-height: 1.55;
}

/* 07 — settings + auth/error shells */

/* The Settings stylesheet that used to be migrated here now lives in static/settings.css, which
   base.html loads after this file. That workstream restructured the Settings markup at the same time
   (settings/_macros.html, `.set-group` rather than `.set-groups`), so a second set of rules written
   against the older markup would only have been dead weight and, where the two overlapped, an argument
   about which one wins. One home for it, and it is not this file. */

/* Auth and error shells — these pages used inline centering styles and are otherwise unstyled.
   One family covers both: a narrow centred column, a tile for an icon, a title at page scale, a
   muted line beneath it. The template wave swaps their inline styles for these classes. */
.auth-page, .error-page { max-width: 400px; margin: 15vh auto 0; padding: 0 var(--s4) var(--s6); text-align: center; }
.auth-page .tile, .error-page .tile { width: 40px; height: 40px; margin: 0 auto var(--s4); display: flex; align-items: center; justify-content: center;
                                       border: 1px solid var(--rule-strong); border-radius: var(--radius-lg); background: var(--shell); color: var(--ink-muted); }
.auth-page .tile svg, .error-page .tile svg { width: 18px; height: 18px; }
.auth-page h1, .error-page h1 { font-size: 17px; font-weight: 600; letter-spacing: -0.012em; margin: 0 0 6px; }
.auth-page p, .error-page p { font-size: 13px; line-height: 1.45; color: var(--ink-muted); margin: 0 auto; max-width: 42ch; }
.auth-page p + p, .error-page p + p { margin-top: var(--s2); }
.auth-page form { text-align: left; display: grid; gap: var(--s3); margin-top: var(--s4); }
.auth-page form label { display: flex; flex-direction: column; gap: 4px; font-size: 11px; font-weight: 500; letter-spacing: .01em; color: var(--ink-soft); }
.auth-page input, .auth-page button { width: 100%; }
.auth-page .actions, .error-page .actions { margin-top: var(--s4); }

/* ---------- SHELL: the markup hooks this pass's templates added ---------------------------------------------
   Four small rules, all of them the CSS half of a class that now exists in base.html / _list.html /
   _command.html. Nothing above is edited. */

/* `.list-top` held six unrelated children as flat siblings and leaned on a margin-auto on `.search` to get the
   New button to the right edge -- so search, Filter, Columns and the Pipeline view switch could never be spaced
   as the one group they are. They now sit in `.list-tools`, which carries the auto margin instead: the title
   menu is the left child, this group is the middle, the primary verb is the right. `.list-top` keeps its own
   sticky/z-index declarations untouched -- chassis.js measures that element into --top-h. */
.list-tools { display: flex; align-items: center; gap: var(--s2); margin-left: auto; flex-wrap: wrap; }
/* narrow: the group drops to its own full-width line under the title, which is what the pre-existing
   `.list-top .search { margin-left: 0; flex-basis: 100% }` at the 900px breakpoint was buying before the search
   box had a parent of its own -- that 100% now resolves against `.list-tools`, so the group has to be the full
   line for it to mean the same thing. */
@media (max-width: 900px) { .list-tools { margin-left: 0; flex-basis: 100%; } }

/* the column menu's one line of instruction, previously a style attribute on the element */
.col-menu .sheet .col-menu-hint { padding: var(--s1) var(--s2); }

/* The user row is the only <summary> in the rail, and with `list-style: none` it looked exactly like the eight
   buttons above it that just navigate. Two borders, the same construction as the sort chevron on `th a::after`
   -- not a sprite, because the sidebar's icon references are counted (see the note in base.html). */
.side-user > summary { position: relative; }
.side-user > summary .side-caret {
  flex: 0 0 auto; width: 5px; height: 5px; margin-left: auto; margin-right: 2px;
  border-right: 1.5px solid var(--ink-soft); border-bottom: 1.5px solid var(--ink-soft);
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.side-user > summary:hover .side-caret { border-color: var(--ink); }
.side-user[open] > summary .side-caret { transform: translateY(1px) rotate(-135deg); }

/* The command palette's empty state takes the shared primitive's mark and type, but not its page-sized padding:
   this one sits inside a 640px dialog between a search row and a hint bar, not in the middle of a view. */
.cmdk-empty.empty { padding: var(--s5) var(--s4); }
.cmdk-hint { display: inline-flex; align-items: center; gap: 6px; }

/* ---------- page-stack: one gap token between a run of top-level page sections -------------------------------
   Hand-rolled pages spaced their sections with `style="margin-top:16px"` and `style="margin-top:var(--s4)"` --
   two ways of writing the same 16px that happened to agree on the number but not the mechanism, so a page
   template edit (say, an $s4 redefinition later) would silently diverge between them. One rule, on a wrapper,
   replaces both call sites: `.page-stack > * + *` puts the gap on the space BETWEEN children, which is what a
   page actually wants -- no gap above the first section, no gap needed once a section is removed or reordered.
   (LISTS-UI markup pass, 2026-09-07.) */
.page-stack > * + * { margin-top: var(--s4); }

/* ---------- row-actions: trailing row controls, revealed on hover/focus -----------------------------------
   Several hand-rolled rows jammed a `<form style="display:inline">` or `style="display:inline-flex">` straight
   against the row's text so a Snooze/Assign control would sit on the same line. That leaves every row's controls
   permanently on screen, which is the same defect Today's rows had before `.today-action`. `.row-actions` wraps
   the trailing controls the same way: an inline-flex group, invisible until the row (a `<tr>` or a `<li>`) is
   hovered or the group itself holds focus, so a table or list reads as data first and controls second.
   (LISTS-UI markup pass, 2026-09-07.) */
.row-actions { display: inline-flex; align-items: center; gap: var(--s2); opacity: 0; transition: opacity var(--t-row); }
tr:hover .row-actions, tr:focus-within .row-actions,
li:hover .row-actions, li:focus-within .row-actions,
.row-actions:focus-within { opacity: 1; }

/* ---------- identity fields say what they hold (RECORDS markup pass, 2026-09-07) -----------------------------
   `.identity-fields` was a flat strip in which a status, a person, an amount and a due date all rendered as the
   same grey label over the same black line. The markup now tags each `<li>` with `data-kind`, so the four things
   a record's header actually carries can be told apart without adding a single word or colour: money and dates
   get figures that line up under each other, a person gets the small tile the avatar idiom uses everywhere else,
   and status keeps the inline-flex the chip needs. Values and labels are untouched. */
.identity-fields li[data-kind="money"] .v,
.identity-fields li[data-kind="date"] .v { font-variant-numeric: tabular-nums; letter-spacing: -0.005em; }
.identity-fields li[data-kind="status"] .v { display: inline-flex; }
/* the tile only where the value is a name -- an empty field draws nothing, and the Owner cell (which holds a
   <select>, not a name) keeps its control flush with the label */
.identity-fields li[data-kind="person"] .v:not(:empty):not(:has(form)) { display: inline-flex; align-items: center; gap: 6px; }
.identity-fields li[data-kind="person"] .v:not(:empty):not(:has(form))::before {
  content: ""; flex: none; width: 16px; height: 16px; border-radius: 5px;
  background: var(--shell); box-shadow: inset 0 0 0 1px var(--rule-strong);
}

/* A verb that carries a sprite: 15px reads as a mark beside a 13px label, where the sprite's own 16px reads as
   a second glyph. The gap is already on `button, .btn`. */
button > .icon, .btn > .icon, .menu > summary > .icon { width: 15px; height: 15px; }
button.small > .icon, .btn.small > .icon { width: 14px; height: 14px; }

/* The record page's two composer forms spaced their action row with `style="margin-top:var(--s3)"`. The
   relationship is structural -- an action row always follows the grid it submits -- so it belongs here. */
.form-grid + .actions { margin-top: var(--s3); }

/* `.page-head` sets its title at 17px against the record header's 18px, so the mark steps down with it. */
.page-head .record-mark { width: 24px; height: 24px; font-size: 11px; }

/* ---------- RECONCILIATION ---------------------------------------------------------------------------------
   The pass above was written as seven fragments in disjoint territories, which is what kept them from
   overwriting each other -- but it left five seams where two territories met. Each is fixed here, once, at the
   end, rather than by reopening a fragment. */

/* 1. The page title read at three sizes. The generic `h1` moved to the new 17px step, but the list view's own
      name (a <summary>, sized by the DENSITY pass) stayed at 15px and the record title at 18px -- so on Clients
      the page's own name was the smallest heading on screen and on a record it was the largest. One step. */
.list-top .view-menu > summary { font-size: 17px; font-weight: 600; letter-spacing: -0.012em; }
.identity h1, .identity-head h1 { font-size: 17px; letter-spacing: -0.012em; }

/* 2. `.label` is a shared utility AND the class on every sidebar item's text span, so moving the utility to the
      11px/--ink-soft label step silently shrank the primary navigation and flattened its active state. The nav
      is not a label: it is the app's top-level UI and takes the body step. Restated here because the two rules
      that need to disagree live in different fragments' territories. */
.side-link .label { font-size: var(--fs-dense); font-weight: 500; color: inherit; letter-spacing: 0; }
.side-link { color: #55534E; }
.side-link:hover { color: var(--ink); }
.side-link[aria-current="page"] { color: var(--ink); font-weight: 600; }
.side-link[aria-current="page"] .label { font-weight: 600; }

/* 3. `.list-tools` groups search + Filter + Columns, but `.list-top .search` still carried the `margin-left:
      auto` it needed back when it was a direct child of the toolbar. Inside the new group that auto margin ate
      the free space and pushed Columns onto a second line. The group owns the auto margin now; the search does
      not, and the group does not wrap -- it is three controls, not a paragraph. */
.list-tools { flex-wrap: nowrap; min-width: 0; }
/* nowrap on its own only moves the problem: on Pipeline the group carries a fifth child (the Table/Kanban
   switch) and the row then overflowed its own right edge, clipping "Kanban" off the window. The search is the
   one elastic control in the group, so it is the one that gives -- everything else holds its size. */
.list-tools > * { flex: 0 0 auto; }
.list-tools .search { margin-left: 0; flex: 0 1 240px; min-width: 110px; }
.list-tools .search input { min-width: 0; }
@media (max-width: 900px) { .list-tools { flex-wrap: wrap; } .list-tools .search { flex-basis: 100%; } }

/* 4. Rows are tabindex=0 inside a grid with clipped corners, so the shared `outline-offset: 2px` focus ring drew
      a box that overlapped the rows above and below and was sliced off at the table's edge. A focused row takes
      the same leading-edge marker the selected and keyboard-cursor rows use, drawn inside the cell. */
.list-table tbody tr:focus-visible { outline: none; }
.list-table tbody tr:focus-visible td { background: var(--hover); }
.list-table tbody tr:focus-visible td:first-child { box-shadow: inset 2px 0 0 var(--accent); }

/* 5. Two modal grounds disagreed: the command menu's ::backdrop went to .34 while the peek panel's scrim stayed
      at .22, so the same gesture dimmed the page by different amounts depending on which surface opened. */
.peek-scrim { background: rgba(15, 15, 15, .34); }

/* The crumb above a page title existed only under `.identity-head`; `page_head()` can now render one on any
   page, so it needs the same treatment there. */
.page-head .crumb { display: block; font-size: var(--fs-label); color: var(--ink-soft); margin-bottom: 2px; }
.page-head .crumb a { color: inherit; }
.page-head .crumb a:hover { color: var(--ink); }

/* The pre-chassis tables on Reports, Market, Competitors and Bills are a bare <table> inside a `.table-wrap`,
   not a `.table-plain`, so the rule that made a row's name read as ink never reached them: the chassis lists
   showed an organisation as ink at 500 and these showed the same organisation as an accent-blue link at 400.
   Scoping to the first cell keeps it to the row's identity, which is exactly what the chassis marks -- and it
   cannot touch the chassis itself, whose first cell is the selection checkbox and holds no link. */
.table-wrap td:first-child a { color: var(--ink); font-weight: 500; }
.table-wrap td:first-child a:hover { color: var(--accent); }

/* ---------- REVIEW FIXES, same day ---------------------------------------------------------------------------
   Three defects an adversarial pass over the running app turned up in the work above. */

/* 1. The chart bars were left at 14% accent over white -- about 1.25:1, well under the 3:1 that WCAG 1.4.11
      asks of a graphical object you are meant to read a value off. The whole point of the flat fill was to
      replace a hatch that read as texture; it has to be legible as data. 62% clears 3:1 against the plot's
      white ground while staying clearly lighter than the solid current-period bar. */
.chart .bar .fill { opacity: .62; }
.chart .bar:hover .fill, .chart .bar:focus .fill { opacity: .78; }
.chart .bar.cur:hover .fill, .chart .bar.cur:focus .fill { opacity: 0; }

/* 2. The keyboard cursor and the peeked row had converged on exactly the same treatment -- --hover-strong plus
      an ink leading edge -- so with the panel open you could not tell which row you were about to act on from
      the one already showing in it. They mean different things and now look different: the cursor is neutral
      and quiet, the open record carries the accent, and neither is --accent-weak, which belongs to selection. */
tbody tr.key-focused td { background: var(--hover); }
tbody tr.key-focused td:first-child { box-shadow: inset 2px 0 0 var(--ink); }
tbody tr[data-peeking] td,
div.list-table tbody tr[data-peeking] td { background: var(--hover-strong); }
tbody tr[data-peeking] td:first-child,
div.list-table tbody tr[data-peeking] td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
/* the focus ring on a row follows the keyboard cursor, not the open record */
.list-table tbody tr:focus-visible td:first-child { box-shadow: inset 2px 0 0 var(--ink); }

/* ---------- REVIEW FIXES, part two ---------------------------------------------------------------------------
   What an adversarial pass over the running app found once the material landed. Grouped by the claim each one
   falsifies, because every item here is a place the pass said it had unified something and had not. */

/* "ONE SECTION HEADING." An <h2> was rendering at 11, 12, 13 and 14px depending on which screen you were on --
   on the client record the section headings were 13px, the same size as the table text beside them, so a
   section did not read as a section at all. */
.record-main h2, .rail-block h2, .ps-recent h2 { font-size: 14px; font-weight: 600; letter-spacing: -0.005em; color: var(--ink); }
/* Settings had the opposite fault: h2 and h3 were pixel-identical, so nine sub-headings on /settings/roles read
   as nine peers of the section above them. h3 goes back to the body step the scale gives it. */
.set-mode h3, .set-cap h3 { font-size: var(--fs-dense); }

/* "ONE LABEL RANK." The 11px step landed on column headers and stat labels but not on the other things doing
   the same job, so on /leads the rail's group headings were 4.55:1 six pixels from column headers at 7.11:1. */
.today-group-label { font-size: 11px; font-weight: 500; color: var(--ink-soft); letter-spacing: .01em; }
.filters-rail h2, .list-rail h2, .identity-head .crumb, .invoice-doc-kv dt, .col-menu .sheet .col-item { color: var(--ink-soft); }
.list-table tbody .sub, .ps-why .l, .ps-facts .l, .ps-why .ps-src { color: var(--ink-soft); }

/* "ONE FOCUS LANGUAGE." Two ink rings survived the restatement, and both fire on Today within four tab presses
   of the accent one -- the headline cards and the click-to-edit field. */
.card-stat:focus-visible, .field-display:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* and the row-focus fix reached the chassis but not the tables that predate it */
.table-plain tbody tr:focus-visible { outline: none; }
.table-plain tbody tr:focus-visible td { background: var(--hover); }
.table-plain tbody tr:focus-visible td:first-child { box-shadow: inset 2px 0 0 var(--ink); }

/* "ONE TABLE." Three container treatments survived: the chassis at 10px radius, `.table-plain` at 6px with a
   lift, and a bare <table> in a `.table-wrap` with neither -- two of them visible on adjacent Reports screens.
   One bounded surface, one radius, one edge. The `.list-table` and `.card` cases are excluded: the first is
   pinned by tests as its own surface, the second already provides the edge for the table inside it. */
.table-wrap:not(.list-table > .table-wrap):not(.card > .table-wrap) {
  border: 1px solid var(--rule-strong); border-radius: var(--radius-lg); overflow: clip; background: var(--surface);
}
.table-plain { border-radius: var(--radius-lg); box-shadow: none; }
/* /reports stacks three of these, each auto-sizing its own first column, so the description text started at
   three different x down one screen. A shared measure makes them read as one index. */
.report-group table { table-layout: fixed; }
.report-group td:first-child { width: 280px; }

/* "ONE EMPTY STATE." The primitive reached the page and the widget but not the record rail or the peek panel,
   so a client record showed both treatments at once -- a designed empty state in the centre column and a grey
   sentence in the rail beside it. The rail is ~300px wide, so it takes a compact variant. */
.rail-block > p.none, .ps-recent > p.none {
  padding: 20px 12px; text-align: center; color: var(--ink);
  font-size: var(--fs-dense); font-weight: 500; letter-spacing: 0; margin: 0;
}
.rail-block > p.none::before, .ps-recent > p.none::before {
  content: ""; display: block; width: 22px; height: 22px; margin: 0 auto 8px; border-radius: 7px;
  background: linear-gradient(var(--ink-soft), var(--ink-soft)) center / 8px 1.5px no-repeat, var(--shell);
  box-shadow: inset 0 0 0 1px var(--rule-strong);
}

/* A field's boundary is a graphical object, so WCAG asks 3:1 of it; --rule-strong is 1.40:1 and was never meant
   to carry a control's edge -- it is the edge of a surface sitting ON a page, not of a well cut INTO one. */
input, select, textarea { border-color: #BCBAB1; }
input[type=search] { border-color: #C7C5BC; }

/* The column-reorder grips were drawn in --rule: 1.22:1, the exact ratio this whole pass exists to remove, and
   under a hint that reads "Drag to reorder". */
.col-menu .sheet .col-item .grip { color: var(--rule-strong); }
.col-menu .sheet .col-item:hover .grip { color: var(--ink-soft); }

/* The blanket `button, .btn { box-shadow: var(--shadow-sm) }` reached the composer's Note/Task tabs, which are
   <button>s pretending to be a tab row: they came out as underlined tabs wearing a card's ring and lift. */
.composer-tabs .tab { box-shadow: none; border-radius: 0; }

/* One control height per toolbar row: the segmented switch stood 3px proud of the 26px search and menus beside
   it, because the pass gave `.seg` a 2px inset track that the density block's 26px controls do not have. */
.list-top .seg { min-height: 26px; }
.list-top .seg a, .list-top .seg button { min-height: 24px; font-size: var(--fs-dense); }

/* `.set-tag` was a second token primitive here, and a pill -- which this pass reserves for a number. The
   Settings workstream now owns that token in static/settings.css, which loads after this file, so an override
   written against the older markup would have been argued with rather than obeyed. Left to its owner. */

/* The invoice document is the one artefact the firm sends a client, and the pass never reached it -- so it sat
   on the same screen as a Payments table with a stronger edge and a lift than the document above it. */
.invoice-doc { border-color: var(--rule-strong); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); }
.invoice-doc-meta h1 { font-size: 17px; font-weight: 600; letter-spacing: -0.012em; color: var(--ink-soft); }
.invoice-doc-block .line, .invoice-doc .terms { color: var(--ink-soft); }

/* The kanban board kept the 1.16:1 edge the pass replaced everywhere else, and its column titles were off the
   label step. */
.board-col { border-color: var(--rule-strong); }
.board-col-title { font-size: var(--fs-label); font-weight: 500; color: var(--ink-soft); letter-spacing: .01em; }

/* A "person" field drew a 16px empty grey square before the name -- a mark with nothing in it. The label above
   already says the value is a person, so the square was carrying no information at all. */
.identity-fields li[data-kind="person"] .v:not(:empty):not(:has(form))::before { content: none; }

/* The open-mode banner and the list toolbar were both `position: sticky; top: 0`, so on any list long enough to
   scroll the toolbar slid underneath the banner and was sliced in half, then hidden. The banner is a one-time
   standing notice about configuration, not something you need pinned while you work a 200-row list. */
.open-auth-banner { position: static; }

/* ---------- REVIEW FIXES, part three -------------------------------------------------------------------------
   The last three, all from the craft lens. */

/* A checkbox's box IS the control -- there is nothing else to identify it by -- so its resting boundary has the
   strongest claim on WCAG 1.4.11's 3:1 of anything in the product. It was drawn at the same weight as a field. */
input[type=checkbox], input[type=radio] { border-color: #949289; }
input[type=checkbox]:hover, input[type=radio]:hover { border-color: #7E7C73; }

/* `.toolbar` is the pre-chassis filter bar (Market, Competitors, Bills). Unlike `.list-top` it had no ground at
   all, so on /reports/market a row of inputs and buttons floated on the page above a bounded table. It is the
   same furniture as a list toolbar and now sits on the same tint. */
.toolbar {
  padding: var(--s2) var(--s3); background: var(--shell);
  border: 1px solid var(--rule-strong); border-radius: var(--radius-lg);
}
.toolbar input, .toolbar select { background: var(--surface); }

/* `.chip.num` was written as the pill half of the "a pill means a number" rule and no markup ever used it, so
   the rule had no second half to point at. The counts the product actually shows -- the overdue numeral in the
   sidebar and the delta on a headline card -- are the pill cases, and they are styled where they live. Keeping
   a selector nothing renders is how a stylesheet starts lying about itself, so it goes. */

/* ---------- Billing's aging band, owner's direction 2026-09-07: "larger, more padding, and lower" -------------
   `.figures` renders in exactly one place -- billing/_aging.html -- so it IS this band, and it can be sized for
   the job rather than as a generic primitive. It is the first thing on the screen and it answers "what am I
   owed and how late is it", so it gets to be read across the room. The figure steps up from the 20px band size
   toward the 32px headline card without reaching it: seven cells still have to sit on one line at 1280px.
   The margin lives here now rather than in a style attribute on the element. */
.figures { margin: var(--s5) 0 var(--s5); }
/* The vertical padding is what reads as air here; the horizontal is what "Share of billings collected" -- the
   one long label in the band -- has to fit inside. At 18px it wrapped to two lines below about 1500px and took
   the whole band 14px taller with it, so the horizontal stays at 14 and the height comes from the vertical. */
.figures > div { padding: 20px 14px; }
.figures > div:first-child { padding-left: 14px; }
.figures .n { font-size: 26px; }
.figures .l { margin-top: 4px; }

/* ============================================================================================================
   SHELL PASS — 2026-09-07 (second batch). Owner's direction, in their words:
     "make the sidebar floating, and round its corners a bit"
     "its not full width on the main dash"
     "these notifications arent right, they have no padding with whats around them"
     "reports needs more padding between icon boxes and rows"
     "on tables for the multicheck that should be always visible"
     "when multiselect - one selected, ui glitching not smooth very"
   ============================================================================================================ */

/* ---------- the rail floats ----------------------------------------------------------------------------------
   Three grounds instead of two. The window is --shell-2, the rail is a --shell card lifted off it, and the work
   area stays white -- so the rail reads as a thing sitting on the app rather than a column cut out of it. The
   gutter around it is what does the floating; the radius only softens it. `.frame` is untouched on purpose:
   it holds the document scroll and the sticky toolbar's offset, and giving it a box of its own would put a
   scrollport between `--top-h` and the header it parks. */
html, .app { background: var(--shell-2); }
.sidebar {
  margin: var(--s2) 0 var(--s2) var(--s2);
  height: calc(100vh - var(--s4));
  border: 1px solid var(--rule-strong); border-right: 1px solid var(--rule-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  background: var(--shell);
  overflow: clip;
}
/* the firm row and the user row sat against a hard edge; inside a rounded card they need the corner back */
.side-firm { border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
.side-user { border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
/* the drawer on narrow screens is a full-height overlay, not a floating card */
@media (max-width: 900px) {
  .sidebar { margin: 0; height: 100vh; border-radius: 0; box-shadow: none; }
  .side-firm, .side-user { border-radius: 0; }
}

/* ---------- Today runs the full width ------------------------------------------------------------------------
   Every other screen is edge to edge from the rail to the window; Today alone was capped at 1100px, so on a
   1512px window the landing screen ended 180px short of where every list starts and the space read as missing
   rather than as margin. */
.today-page { max-width: none; }

/* ---------- notices have room around them --------------------------------------------------------------------
   The open-mode banner sat flush against the first thing under it, and `.notice` (the filings page's "Billfish
   prepares the draft. The firm files it.") had a tighter box than anything it sits between. A standing notice
   is a paragraph of consequence, not a chip: it gets the page's own rhythm above and below it, and enough
   padding that the sentence is not touching its own border. */
.open-auth-banner { padding: 10px var(--s5); }
.notice { padding: var(--s3) var(--s4); margin: 0 0 var(--s4); line-height: 1.5; align-items: flex-start; }
.notice .icon { margin-top: 2px; }
.notice + * { margin-top: 0; }
.flash { padding: var(--s3) var(--s4); margin-bottom: var(--s4); }

/* ---------- Reports: the icon has room to be an icon ----------------------------------------------------------
   The tile sat 8px from its label and the rows were the grid's 36px, so a 28px mark had 4px of air above and
   below it and the index read as a cramped table rather than as a list of reports. */
.report-group td { padding-top: 10px; padding-bottom: 10px; }
.report-group td .row { gap: var(--s3); }
.report-group td:first-child { padding-left: var(--s4); }
.report-group td:last-child { padding-right: var(--s4); }

/* ---------- selection: the checkbox is always there ------------------------------------------------------------
   The box faded in on hover, so pointing at a row made a control appear under the cursor and moving away took
   it again -- which is the "glitching" in the owner's note as much as the bar swap is. A selection column that
   is always drawn costs one hairline of ink and stops the row from changing shape as you cross it.
   The bar swap keeps its contract (tests/chassis/test_sticky_and_selection.py: the bulk bar replaces the
   toolbar in place, sharing its sticky top and z-index, so the column header's offset never gaps). What made
   it jump was that the two bars were different heights; they are the same height now, and they cross-fade. */
.list-table td.check input, .list-table th.check input { opacity: 1; }
/* The toolbar stands 36px on its 26px controls and the bulk bar was 30px, so selecting a row moved the whole
   grid up six pixels and clearing it moved it back down -- a jump on every click, which is most of what reads
   as "not smooth". The bar that replaces it is the same height as the bar it replaces. */
.list-bulk { min-height: 36px; transition: opacity var(--t-row) var(--ease-out); }

/* A notice is a <p> on two pages and a <div> on eight, and the base `p { max-width: 72ch }` was quietly
   capping the two: on /filings the standing "Billfish prepares the draft. The firm files it." ran 617px wide
   inside a 1288px column and read as a stray card rather than as a statement about the whole page. A notice
   speaks for what is under it, so it is as wide as what is under it. Its own line length is governed by the
   line-height and padding above, not by a cap that only half the call sites inherited. */
.notice { max-width: none; }
/* one leading a page gets the page's top rhythm rather than sitting against the chrome */
main > .notice:first-child, .set > .notice:first-child { margin-top: var(--s3); }

/* ---------- the Leads pill, second look ------------------------------------------------------------------------
   Owner: "i dont see any changes in leads button." Correct -- and the reason is this pass, not the pill. The
   capsule was tuned against a rail that was effectively white (--shell at #FBFBFA), where a wheel of #EDF3FB
   and an accent ring at 10% was already at the edge of visible. The rail is now #F4F4F1, a warm grey, and a
   pale cool blue at 4% saturation disappears into it completely.
   So the tint gets its saturation back and the ring gets its weight. Everything the tests pin is untouched:
   the wheel's inset geometry, its `border-radius: inherit`, the 46s turn, and the base.html lens filter. The
   stops stay off #FFFFFF -- a white stop is what makes the capsule blink back into an ordinary row. */
.side-link[data-key="leads"]::before {
  background: conic-gradient(from 35deg, #D5E2F4, #B7CCEA 20%, #C8D9F0 45%, #A9C1E4 70%, #CBDBF1 88%, #D5E2F4);
}
.side-link[data-key="leads"] {
  box-shadow: inset 0 0 0 1px rgba(36, 80, 143, .30), 0 1px 2px rgba(36, 80, 143, .10);
  color: var(--ink); font-weight: 600;
}
.side-link[data-key="leads"] .icon { color: var(--accent); }
.side-link[data-key="leads"]:hover { box-shadow: inset 0 0 0 1px rgba(36, 80, 143, .40), 0 1px 3px rgba(36, 80, 143, .14); }
.side-link[data-key="leads"][aria-current="page"] { box-shadow: inset 0 0 0 1.5px rgba(36, 80, 143, .52), 0 1px 3px rgba(36, 80, 143, .16); }

/* The user menu inherits `.menu .sheet { min-width: 220px }`, which is wider than the 216px rail it opens
   inside, so its right edge was being cut off by the rail's own overflow -- unnoticeable while the rail had no
   corners of its own, obvious now that it is a card. It is pinned to both of the rail's edges already, so it
   does not need a minimum at all. */
.side-user .sheet { min-width: 0; }

}
