/* =====================================================================
 * Tech Efficient portal — design system
 *
 * One stylesheet, no build step. Sections, in cascade order:
 *   tokens · base · shell (rail, top bar, timer bar) · forms · buttons ·
 *   page head · stat tiles · filter bar · tabs + segmented control ·
 *   tables · pills · menus · alerts · empty states · overlays (modal,
 *   drawer, confirm, toasts, palette) · charts · page sections · utilities.
 *
 * Every component reads the tokens below; dark mode is a pure token flip.
 * Old class names (.cards/.card, .status-pill, .filter-chip, .list-filters,
 * .toolbar, plain tables) render in the new look so unmigrated pages stay
 * consistent while they are moved onto the new components.
 * =================================================================== */

/* `hidden` must actually hide.
   `button, .btn { display: inline-flex }` (and other explicit display rules)
   outrank the user agent's `[hidden] { display: none }`, so an element the JS
   hides by setting .hidden = true stays on screen — including the per-row Save
   buttons on the banking worklist, which banking.js has been "hiding" since it
   shipped and which were visible the whole time. Fixes the class of bug rather
   than each element. */
[hidden] { display: none !important; }

/* =====================================================================
 * Tokens
 * =================================================================== */
:root {
  color-scheme: light;

  /* Surface hierarchy: page → panel → inset */
  --bg:           #f3f4f7;
  --panel:        #ffffff;
  --panel-soft:   #f8f9fb;
  --panel-inset:  #eef0f4;
  --bg-soft:      #f5f7fa;    /* row hover, expanded body fills */
  --line:         #e4e7ec;
  --line-soft:    #edf0f4;
  --line-strong:  #cfd5de;    /* hover / emphasis borders */
  --line-hair:    rgba(16, 24, 40, .08);

  /* Ink */
  --ink:          #0f1419;
  --ink-2:        #2b3340;
  --ink-muted:    #5f6977;
  --ink-faint:    #8b94a3;

  /* Accent (indigo) */
  --accent:        #4f46e5;
  --accent-2:      #6366f1;
  --accent-hover:  #4338ca;
  --accent-soft:   #eef2ff;
  --accent-soft-2: #e0e7ff;
  --accent-ring:   rgba(79, 70, 229, .28);   /* soft halo only (text inputs) — never the focus indicator alone */
  --on-accent:     #ffffff;
  --link:          #4f46e5;
  --link-hover:    #4338ca;
  /* Fills that carry white text (buttons, brand mark, avatar). Separate from
     --accent, which is also a text/link color: in dark the text accent is too
     light to put white on (#7079ef is 3.7:1). White on these is >= 4.5:1. */
  --accent-fill:       var(--accent);       /* 6.3:1 */
  --accent-fill-hover: var(--accent-hover); /* 7.9:1 */
  /* Accent-colored text sitting on --accent-soft (chips, flags, counts). */
  --accent-ink:        var(--accent);       /* 5.6:1 on --accent-soft */

  /* Keyboard focus: a 2px surface gap + 2px solid accent ring (6.3:1 on white,
     4.8:1 on the dark panel). The inset form is for tabs and segments, whose
     neighbours sit flush against them. */
  --focus-ring:        0 0 0 2px var(--panel), 0 0 0 4px var(--accent);
  --focus-ring-danger: 0 0 0 2px var(--panel), 0 0 0 4px var(--danger-solid);
  --focus-inset:       inset 0 0 0 2px var(--accent);

  /* Solid ink surfaces (near-black chips / buttons / tooltips) — tokenized so
     dark mode never paints white text onto a light fill. */
  --solid:        #14181f;
  --solid-hover:  #000000;
  --on-solid:     #ffffff;
  /* High-emphasis neutral button (timer Start, .btn-solid): near-black in
     light; in dark the accent fill — a white slab would be the brightest
     thing on every dark screen. */
  --emphasis:       #14181f;
  --emphasis-hover: #000000;
  --on-emphasis:    #ffffff;

  /* Semantic triplets (text / bg / hairline) so dark can flip cleanly */
  --danger:       #b42318;
  --danger-bg:    #fee4e2;
  --danger-line:  #fbcaca;
  --danger-solid: #d92d20;
  --danger-solid-hover: #b42318;
  --warn:         #92400e;
  --warn-bg:      #fef3c7;
  --warn-line:    #fde68a;
  --warn-solid:   #d97706;
  --ok:           #047857;
  --ok-bg:        #d1fae5;
  --ok-line:      #a7f3d0;
  --ok-solid:     #10b981;
  --info:         #1e40af;
  --info-bg:      #dbeafe;
  --info-line:    #bfdbfe;
  --neutral:      #4b5563;
  --neutral-bg:   #e5e7eb;
  --neutral-line: #d1d5db;
  --good:         var(--ok);      /* .hc-edit-ok / -err referenced these before they existed */
  --bad:          var(--danger);
  /* Floating "inverse" surfaces: toasts, chart tooltips, rail labels. Near-black
     in light; in dark a raised panel (not a white slab — quiet chrome). */
  --inverse-bg:     #14181f;
  --inverse-ink:    #ffffff;
  --inverse-muted:  rgba(255, 255, 255, .74);
  --inverse-line:   #14181f;
  --inverse-ok:     #34d399;
  --inverse-danger: #f87171;
  --inverse-warn:   #fbbf24;
  --inverse-info:   #93c5fd;

  /* Rail — its own palette, dark in both themes */
  --rail-bg:        #0a0d12;
  --rail-bg-2:      #11161e;
  --rail-line:      #1f2630;
  --rail-edge:      #000000;
  --rail-ink:       #d6dae0;
  --rail-ink-2:     #9aa3b1;
  --rail-active:    #ffffff;
  --rail-hover:     rgba(255, 255, 255, .05);
  --rail-active-bg: rgba(255, 255, 255, .08);
  --rail-badge-bg:  #dc2626;   /* white 11px count = 4.8:1 (#e5484d was 3.9) */
  --rail-badge-info:#2563eb;   /* white = 5.2:1 (#3b82f6 was 3.7) */
  --rail-badge-ink: #ffffff;
  --rail-w:         240px;
  --rail-w-compact: 64px;
  --rail-cur:       var(--rail-w);

  /* Effect: one surface shadow, one overlay shadow (old names kept as aliases) */
  --shadow-1:   0 1px 2px rgba(16, 24, 40, .04), 0 1px 3px rgba(16, 24, 40, .04);
  --shadow-pop: 0 12px 32px rgba(16, 24, 40, .14), 0 2px 6px rgba(16, 24, 40, .06);
  --shadow-xs:  var(--shadow-1);
  --shadow-sm:  var(--shadow-1);
  --shadow-md:  var(--shadow-pop);
  --shadow-lg:  var(--shadow-pop);
  --scrim:      rgba(10, 13, 18, .48);

  --radius-sm:  6px;     /* controls */
  --radius:     8px;
  --radius-lg:  12px;    /* cards */
  --radius-xl:  16px;    /* overlays */

  /* Type */
  --font-sans:  "Inter var", "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono:  ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, monospace;
  --fs-display: 28px;
  --fs-h2:      17px;
  --fs-h3:      15px;
  --fs-body:    14px;
  --fs-sm:      13px;
  --fs-xs:      12px;
  --fs-2xs:     11px;
  --fs-stat:    26px;

  --t-fast:     120ms cubic-bezier(.4, 0, .2, 1);
  --t-base:     180ms cubic-bezier(.4, 0, .2, 1);

  /* Layout */
  --gutter:       32px;   /* page gutter: 32px desktop, 16px phone */
  --top-h:        0px;    /* height of the sticky mobile top bar (52px <= 900px) */
  --content-max:  1240px;
  --control-h:    36px;
  --control-h-sm: 28px;

  /* Chart chrome */
  --chart-grid:        #edf0f4;
  --chart-axis:        #cfd5de;
  --chart-bar:         #4f46e5;
  --chart-bar-hover:   #4338ca;
  --chart-bar-partial: #c7cbf7;
  --chart-ref:         #8b94a3;
  --spark:             #c9cfd9;
  --spark-accent:      #4f46e5;
  --delta-up:          #047857;
  --delta-down:        #b42318;
  --delta-flat:        #5f6977;   /* = --ink-muted: it is text */
  /* Edge shadow on a sideways-scrolling .table-wrap (shows only when it scrolls). */
  --scroll-shadow:     rgba(16, 24, 40, .24);

  /* Categorical series (validated, adjacent pairs — use in order, never
     cycled; past 8 fold into "Other"). Text never wears these. */
  --series-1: #4f46e5;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
  --series-6: #008300;
  --series-7: #2a78d6;
  --series-8: #e34948;

  /* Inline icons for form controls (stroke #8b94a3 reads on both themes). */
  --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b94a3' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --icon-search:  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b94a3' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
}

/* =====================================================================
 * Dark theme — a pure token flip. Applies when the OS prefers dark (and
 * the user hasn't forced light via the toggle) or when data-theme="dark"
 * is set. The declaration list is duplicated across the two selectors on
 * purpose: one can't group a media query with an attribute selector.
 * =================================================================== */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg:           #0b0e13;
    --panel:        #14181f;
    --panel-soft:   #1a1f28;
    --panel-inset:  #1f252f;
    --bg-soft:      #1a1f28;
    --line:         #262e3a;
    --line-soft:    #1f2630;
    --line-strong:  #3a4453;
    --line-hair:    rgba(255, 255, 255, .08);

    --ink:          #e9edf3;
    --ink-2:        #c4ccd6;
    --ink-muted:    #939cac;
    --ink-faint:    #6c7684;

    --accent:        #7079ef;
    --accent-2:      #838bf5;
    --accent-hover:  #939aff;
    --accent-soft:   #1d2140;
    --accent-soft-2: #262c5c;
    --accent-ring:   rgba(112, 121, 239, .40);
    --on-accent:     #ffffff;
    --link:          #949bff;
    --link-hover:    #b3b8ff;
    --accent-fill:       #5b5fe3;   /* white = 5.0:1 */
    --accent-fill-hover: #4f53d6;   /* white = 5.9:1 */
    --accent-ink:        #949bff;   /* 6.2:1 on --accent-soft */

    --solid:        #e9edf3;
    --solid-hover:  #ffffff;
    --on-solid:     #0b0e13;
    --emphasis:       #5b5fe3;
    --emphasis-hover: #4f53d6;
    --on-emphasis:    #ffffff;

    --danger:       #f9a8a2;
    --danger-bg:    rgba(239, 68, 68, .16);
    --danger-line:  rgba(239, 68, 68, .34);
    --danger-solid: #d0353b;        /* white = 4.9:1 */
    --danger-solid-hover: #b42a30;  /* white = 6.3:1 */
    --warn:         #fbcf7a;
    --warn-bg:      rgba(245, 158, 11, .15);
    --warn-line:    rgba(245, 158, 11, .32);
    --warn-solid:   #f59e0b;
    --ok:           #6ee7b7;
    --ok-bg:        rgba(16, 185, 129, .15);
    --ok-line:      rgba(16, 185, 129, .32);
    --ok-solid:     #34d399;
    --info:         #9cc2ff;
    --info-bg:      rgba(59, 130, 246, .16);
    --info-line:    rgba(59, 130, 246, .34);
    --neutral:      #aab3c0;
    --neutral-bg:   rgba(148, 163, 184, .16);
    --neutral-line: rgba(148, 163, 184, .30);
    --inverse-bg:     #232a35;
    --inverse-ink:    #e9edf3;
    --inverse-muted:  #b4bcc8;
    --inverse-line:   #3a4453;
    --inverse-ok:     #6ee7b7;
    --inverse-danger: #f9a8a2;
    --inverse-warn:   #fbcf7a;
    --inverse-info:   #9cc2ff;

    --rail-bg:      #0d1218;
    --rail-bg-2:    #10151d;
    --rail-line:    #232b36;
    --rail-edge:    #000000;
    --rail-ink:     #d6dae0;
    --rail-ink-2:   #929ba9;

    --shadow-1:   0 1px 2px rgba(0, 0, 0, .35), 0 1px 3px rgba(0, 0, 0, .3);
    --shadow-pop: 0 16px 40px rgba(0, 0, 0, .55), 0 2px 6px rgba(0, 0, 0, .4);
    --scrim:      rgba(0, 0, 0, .6);

    --chart-grid:        #1f2630;
    --chart-axis:        #3a4453;
    --chart-bar:         #7079ef;
    --chart-bar-hover:   #939aff;
    --chart-bar-partial: #2e3470;
    --chart-ref:         #6c7684;
    --spark:             #3a4453;
    --spark-accent:      #838bf5;
    --delta-up:          #6ee7b7;
    --delta-down:        #f9a8a2;
    --delta-flat:        #939cac;
    --scroll-shadow:     rgba(0, 0, 0, .55);

    --series-1: #7079ef;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-6: #008300;
    --series-7: #3987e5;
    --series-8: #e66767;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:           #0b0e13;
  --panel:        #14181f;
  --panel-soft:   #1a1f28;
  --panel-inset:  #1f252f;
  --bg-soft:      #1a1f28;
  --line:         #262e3a;
  --line-soft:    #1f2630;
  --line-strong:  #3a4453;
  --line-hair:    rgba(255, 255, 255, .08);

  --ink:          #e9edf3;
  --ink-2:        #c4ccd6;
  --ink-muted:    #939cac;
  --ink-faint:    #6c7684;

  --accent:        #7079ef;
  --accent-2:      #838bf5;
  --accent-hover:  #939aff;
  --accent-soft:   #1d2140;
  --accent-soft-2: #262c5c;
  --accent-ring:   rgba(112, 121, 239, .40);
  --on-accent:     #ffffff;
  --link:          #949bff;
  --link-hover:    #b3b8ff;
  --accent-fill:       #5b5fe3;   /* white = 5.0:1 */
  --accent-fill-hover: #4f53d6;   /* white = 5.9:1 */
  --accent-ink:        #949bff;   /* 6.2:1 on --accent-soft */

  --solid:        #e9edf3;
  --solid-hover:  #ffffff;
  --on-solid:     #0b0e13;
  --emphasis:       #5b5fe3;
  --emphasis-hover: #4f53d6;
  --on-emphasis:    #ffffff;

  --danger:       #f9a8a2;
  --danger-bg:    rgba(239, 68, 68, .16);
  --danger-line:  rgba(239, 68, 68, .34);
  --danger-solid: #d0353b;        /* white = 4.9:1 */
  --danger-solid-hover: #b42a30;  /* white = 6.3:1 */
  --warn:         #fbcf7a;
  --warn-bg:      rgba(245, 158, 11, .15);
  --warn-line:    rgba(245, 158, 11, .32);
  --warn-solid:   #f59e0b;
  --ok:           #6ee7b7;
  --ok-bg:        rgba(16, 185, 129, .15);
  --ok-line:      rgba(16, 185, 129, .32);
  --ok-solid:     #34d399;
  --info:         #9cc2ff;
  --info-bg:      rgba(59, 130, 246, .16);
  --info-line:    rgba(59, 130, 246, .34);
  --neutral:      #aab3c0;
  --neutral-bg:   rgba(148, 163, 184, .16);
  --neutral-line: rgba(148, 163, 184, .30);
  --inverse-bg:     #232a35;
  --inverse-ink:    #e9edf3;
  --inverse-muted:  #b4bcc8;
  --inverse-line:   #3a4453;
  --inverse-ok:     #6ee7b7;
  --inverse-danger: #f9a8a2;
  --inverse-warn:   #fbcf7a;
  --inverse-info:   #9cc2ff;

  --rail-bg:      #0d1218;
  --rail-bg-2:    #10151d;
  --rail-line:    #232b36;
  --rail-edge:    #000000;
  --rail-ink:     #d6dae0;
  --rail-ink-2:   #929ba9;

  --shadow-1:   0 1px 2px rgba(0, 0, 0, .35), 0 1px 3px rgba(0, 0, 0, .3);
  --shadow-pop: 0 16px 40px rgba(0, 0, 0, .55), 0 2px 6px rgba(0, 0, 0, .4);
  --scrim:      rgba(0, 0, 0, .6);

  --chart-grid:        #1f2630;
  --chart-axis:        #3a4453;
  --chart-bar:         #7079ef;
  --chart-bar-hover:   #939aff;
  --chart-bar-partial: #2e3470;
  --chart-ref:         #6c7684;
  --spark:             #3a4453;
  --spark-accent:      #838bf5;
  --delta-up:          #6ee7b7;
  --delta-down:        #f9a8a2;
  --delta-flat:        #939cac;
  --scroll-shadow:     rgba(0, 0, 0, .55);

  --series-1: #7079ef;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-6: #008300;
  --series-7: #3987e5;
  --series-8: #e66767;
}

/* Layout tokens that change with the viewport. */
@media (max-width: 900px) { :root { --top-h: 52px; } }
@media (max-width: 720px) { :root { --gutter: 16px; --fs-stat: 22px; } }

/* =====================================================================
 * Base
 * =================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  font-size: 14px;                 /* rem = 14px: the old 15px scale, one step denser */
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scrollbar-gutter: stable;        /* no layout jump when an overlay locks scroll */
  /* A focused control or an #anchor scrolls into view clear of the sticky
     chrome: the phone top bar (--top-h), the sticky filter bar (--sticky-top:
     its height + its 8px gap) and a sticky Save / Cancel bar (--sticky-bottom:
     how much of the viewport's bottom it covers — its height + its offset
     while it is stuck). ui.js measures the two bars and keeps those two
     properties on <html> only while each bar is there and sticky, so a page
     without them is padded for the top bar alone. A page's own scroll-margin
     adds to this: it needs no var(--top-h) of its own. */
  scroll-padding-top: calc(var(--top-h) + var(--sticky-top, 0px) + 8px);
  scroll-padding-bottom: calc(var(--sticky-bottom, 0px) + 8px);
}
/* Without ui.js nothing measures the bars: assume a one-row filter bar (34px
   controls, 9px padding, 1px border, 8px gap) and a one-row Save bar (36px
   buttons, 12px padding, 1px rule). */
@media not all and (max-width: 720px) {
  html:not(.ui-js):has(.filterbar[data-sticky]) { --sticky-top: 62px; }
}
html:not(.ui-js):has(.form-actions.is-sticky, .form-actions[data-sticky]) { --sticky-bottom: 61px; }
html.ui-scroll-lock { overflow: hidden; }

html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  font-feature-settings: "cv11", "ss01";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 { color: var(--ink); text-wrap: balance; }
h1 {
  font-size: var(--fs-display);
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -.02em;
  margin: 0 0 8px;
}
h2 {
  font-size: var(--fs-h2);
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: -.01em;
  margin: 28px 0 10px;
}
h3 {
  font-size: var(--fs-h3);
  line-height: 1.35;
  font-weight: 600;
  letter-spacing: -.005em;
  margin: 20px 0 8px;
}
h4 { font-size: 13.5px; line-height: 1.4; font-weight: 600; margin: 16px 0 6px; }
small { font-size: var(--fs-xs); }
strong, b { font-weight: 600; }
hr { border: 0; border-top: 1px solid var(--line); margin: 20px 0; }
img { max-width: 100%; height: auto; }

a { color: var(--link); text-decoration: none; transition: color var(--t-fast); }
a:hover { color: var(--link-hover); }
/* A link inside running text is underlined: its color alone (1.1:1 against
   muted copy) can't mark it. Element specificity only, so a page's own link
   class and every button class keep their look. */
:where(p, .muted, .field-help, .table-summary, .page-meta, .cell-sub, [class*="-lede"], .alert) a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

code, kbd, samp, pre { font-family: var(--font-mono); }
:not(pre) > code {
  font-size: .88em;
  background: var(--panel-inset);
  color: var(--ink-2);
  padding: .1em .35em;
  border-radius: 4px;
}

::selection { background: var(--accent-soft-2); color: var(--ink); }

/* Focus: a visible ring on keyboard focus only, everywhere. Controls swap the
   outline for the 3px accent ring (see buttons / inputs below). */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
a:focus-visible { border-radius: 3px; }
[tabindex="-1"]:focus, dialog:focus, dialog:focus-visible { outline: none; }

.muted { color: var(--ink-muted); }
.faint { color: var(--ink-faint); }
.link-muted { color: var(--ink-muted); text-decoration: underline; text-underline-offset: 2px; }
.link-muted:hover { color: var(--ink); }
.nowrap { white-space: nowrap; }
.tabular { font-variant-numeric: tabular-nums; }
.sr-only, .visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* JS-dependent visibility (ui.js adds .ui-js to <html>). */
html.ui-js .js-hide { display: none !important; }
html:not(.ui-js) .no-js-hide { display: none !important; }
/* Held previous render while a JS-driven re-render is in flight. */
.is-loading { opacity: .5; transition: opacity var(--t-base); pointer-events: none; }

/* =====================================================================
 * Layout
 * =================================================================== */
body.has-nav {
  display: grid;
  grid-template-columns: var(--rail-cur) minmax(0, 1fr);
  min-height: 100vh;
  /* Paint the rail column on the body too, so a full-page capture (or any
     view taller than the sticky rail) shows one continuous rail. */
  background: linear-gradient(to right, var(--rail-bg) var(--rail-cur), var(--bg) var(--rail-cur)) repeat-y;
}
body.no-nav { display: block; }

.main {
  padding: 24px var(--gutter) 64px;
  width: 100%;
  max-width: calc(var(--content-max) + 2 * var(--gutter));
  min-width: 0;
}
.main--wide { max-width: none; }

/* "Skip to content" — the first focusable element of every page. Parked
   above the viewport (no shadow, so nothing bleeds in) until focused. */
.skip-link {
  position: fixed;
  left: 12px;
  top: 12px;
  z-index: 1100;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--ink);
  font-weight: 550;
  box-shadow: none;
  transform: translateY(calc(-100% - 24px));
  transition: transform var(--t-fast);
}
.skip-link:focus, .skip-link:focus-visible {
  transform: none;
  outline: none;
  box-shadow: var(--shadow-pop), var(--focus-ring);
}

/* =====================================================================
 * Rail (sidebar)
 *
 * Markup contract for the shell (layout_head.php):
 *   aside.sidebar
 *     .sidebar-brand > .brand-mark + .brand-text(.brand-name + .brand-sub)
 *     a.sidebar-search[href="/search.php"][data-palette-open] > svg + .sidebar-search-label
 *                                              + kbd.kbd.hide-touch[data-mod="K"]  ("Ctrl K" off Apple)
 *     nav > a[.active|aria-current] > .nav-ico + .nav-label + .nav-badge(.nav-badge-info)
 *       .nav-group > button.nav-group-toggle[data-nav-group][aria-expanded][aria-controls]
 *                    > span + svg.nav-chev
 *                  + .nav-group-items#id > a…
 *       .nav-section-label (static, non-collapsible heading)
 *     .sidebar-foot > .sidebar-user(.avatar + .sidebar-user-text > .sidebar-user-name + .sidebar-user-email)
 *                   + .sidebar-foot-row > .theme-toggle[aria-label] + button.rail-toggle[data-rail-toggle]
 *                   + .sidebar-foot-row > a.link-muted (Sign out) + .sidebar-version
 * =================================================================== */
.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 12px 12px;
  background: var(--rail-bg);
  color: var(--rail-ink);
  border-right: 1px solid var(--rail-edge);
  scrollbar-width: thin;
  scrollbar-color: var(--rail-line) transparent;
}
.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 4px 6px 12px;
  margin: 0 0 6px;
  border-bottom: 1px solid var(--rail-line);
  color: #fff;
  font-size: 14px;
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -.01em;
}
.brand-mark {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--accent-fill);
  color: #fff;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1;
}
.brand-text { display: flex; flex-direction: column; min-width: 0; }
.brand-name { color: #fff; font-weight: 650; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.brand-sub { color: var(--rail-ink-2); font-size: 11px; font-weight: 500; margin-top: 2px; }

/* Search styled like an input: "Search or jump to…  ⌘K". A link to /search.php,
   so it works without JS; ui.js opens the palette instead. */
.sidebar-search {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  width: 100%;
  height: 34px;
  margin: 2px 0 8px;
  padding: 0 6px 0 10px;
  border: 1px solid var(--rail-line);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .04);
  color: var(--rail-ink-2);
  font: inherit;
  font-size: 13px;
  font-weight: 400;
  text-align: left;
  box-shadow: none;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.sidebar-search:hover { background: rgba(255, 255, 255, .07); color: var(--rail-ink); border-color: #2c3542; }
.sidebar-search:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 1px; border-radius: var(--radius-sm); box-shadow: none; }
.sidebar-search svg { width: 15px; height: 15px; flex: 0 0 auto; }
.sidebar-search-label { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-search .kbd { background: rgba(255, 255, 255, .06); border-color: var(--rail-line); color: var(--rail-ink-2); box-shadow: none; }

.sidebar nav { display: flex; flex-direction: column; gap: 1px; }
.sidebar nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 32px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--rail-ink-2);
  font-size: 13.5px;
  font-weight: 500;
  line-height: 20px;
  white-space: nowrap;
  transition: color var(--t-fast), background var(--t-fast);
}
.sidebar nav a:hover { color: var(--rail-active); background: var(--rail-hover); }
.sidebar nav a.active,
.sidebar nav a[aria-current="page"] { color: var(--rail-active); background: var(--rail-active-bg); }
.sidebar nav a.active::before,
.sidebar nav a[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 7px;
  bottom: 7px;
  width: 2px;
  border-radius: 0 2px 2px 0;
  background: var(--accent-2);
}
.sidebar nav a:focus-visible { outline: 2px solid var(--accent-2); outline-offset: -2px; }
/* Nav icons (inline SVG, stroke = currentColor so they follow the link ink). */
.nav-ico {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  display: inline-flex;
  opacity: .78;
}
.nav-ico svg { width: 100%; height: 100%; display: block; }
.sidebar nav a:hover .nav-ico,
.sidebar nav a.active .nav-ico,
.sidebar nav a[aria-current="page"] .nav-ico { opacity: 1; }
.nav-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Badges: pills from rail tokens. */
.nav-badge {
  margin-left: auto;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--rail-badge-bg);
  color: var(--rail-badge-ink);
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.nav-badge-info, .nav-badge.is-info { background: var(--rail-badge-info); }

.nav-section-label {
  margin: 14px 0 2px;
  padding: 4px 10px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--rail-ink-2);
  opacity: .85;                     /* 5.1:1 on the dark rail; .75 was 4.3 */
}
/* Collapsible nav groups (ui.js persists open/closed in localStorage
   'te-nav-groups'; the group holding the active page always opens). */
.nav-group { display: flex; flex-direction: column; gap: 1px; margin-top: 10px; }
.nav-group-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  height: 26px;
  margin: 0 0 2px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  box-shadow: none;
  color: var(--rail-ink-2);
  opacity: .85;
  font: inherit;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  cursor: pointer;
  user-select: none;
}
.nav-group-toggle:hover { opacity: 1; background: var(--rail-hover); color: var(--rail-ink); }
.nav-group-toggle:focus-visible { outline: 2px solid var(--accent-2); outline-offset: -2px; box-shadow: none; }
.nav-chev { width: 12px; height: 12px; flex: 0 0 auto; transition: transform var(--t-fast); }
.nav-group-toggle[aria-expanded="false"] .nav-chev { transform: rotate(-90deg); }
.nav-group-items { display: flex; flex-direction: column; gap: 1px; }

.sidebar-foot {
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid var(--rail-line);
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 12.5px;
}
.sidebar-user {
  min-width: 0;
  color: var(--rail-ink);
  font-size: 12.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar-user:has(.avatar) { display: flex; align-items: center; gap: 10px; padding: 2px 4px; }
.avatar {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent-fill);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
}
.sidebar-user-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.sidebar-user-name { color: #fff; font-weight: 550; overflow: hidden; text-overflow: ellipsis; }
.sidebar-user-email { color: var(--rail-ink-2); font-size: 11px; overflow: hidden; text-overflow: ellipsis; }
.sidebar .link-muted { color: var(--rail-ink-2); font-size: 12.5px; text-decoration: none; }
.sidebar .link-muted:hover { color: #fff; }
.sidebar-version {
  align-self: flex-start;
  color: var(--rail-ink-2);
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--rail-line);
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 1px 7px;
  border-radius: 999px;
}
.sidebar-foot-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; }

/* Theme + rail toggles — pill buttons in the rail foot. */
.theme-toggle,
.rail-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--rail-line);
  border-radius: 999px;
  color: var(--rail-ink-2);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  box-shadow: none;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
}
.theme-toggle:hover,
.rail-toggle:hover { color: #fff; background: rgba(255, 255, 255, .09); border-color: var(--rail-ink-2); }
.theme-toggle:active, .rail-toggle:active { transform: none; }
.theme-toggle:focus-visible, .rail-toggle:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 1px; box-shadow: none; }
.theme-toggle svg, .rail-toggle svg { width: 14px; height: 14px; display: block; }
.theme-toggle-ico { display: inline-flex; }
/* The visible glyph reflects the *effective* theme (mirrors the token logic). */
.theme-ico-sun { display: none; }
.theme-ico-moon { display: inline-flex; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-ico-sun { display: inline-flex; }
  :root:not([data-theme="light"]) .theme-ico-moon { display: none; }
}
[data-theme="dark"] .theme-ico-sun { display: inline-flex; }
[data-theme="dark"] .theme-ico-moon { display: none; }
[data-theme="light"] .theme-ico-sun { display: none; }
[data-theme="light"] .theme-ico-moon { display: inline-flex; }

/* Hover label for the compact rail (ui.js positions it; fixed so the rail's
   overflow can't clip it). */
.rail-tip {
  position: fixed;
  z-index: 1200;
  transform: translateY(-50%);
  padding: 4px 8px;
  border: 1px solid var(--inverse-line);
  border-radius: var(--radius-sm);
  background: var(--inverse-bg);
  color: var(--inverse-ink);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  box-shadow: var(--shadow-pop);
  pointer-events: none;
}

/* Compact 64px icon rail: html[data-rail="compact"] (ui.js, 'te-rail'). */
@media (min-width: 901px) {
  html[data-rail="compact"] { --rail-cur: var(--rail-w-compact); }
  html[data-rail="compact"] .sidebar { padding: 14px 8px 12px; }
  html[data-rail="compact"] .sidebar-brand { justify-content: center; padding: 4px 0 12px; font-size: 0; }
  html[data-rail="compact"] .brand-text,
  html[data-rail="compact"] .sidebar-search .kbd,
  html[data-rail="compact"] .sidebar-user-text,
  html[data-rail="compact"] .sidebar-version { display: none; }
  /* Visually hidden but still each control's accessible name (display:none
     would leave an unnamed search button and theme toggle). */
  html[data-rail="compact"] .nav-label,
  html[data-rail="compact"] .sidebar-search-label,
  html[data-rail="compact"] .theme-toggle > span:not(.theme-toggle-ico),
  html[data-rail="compact"] .rail-toggle > span {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
  }
  html[data-rail="compact"] .sidebar-search { justify-content: center; padding: 0; }
  html[data-rail="compact"] .sidebar nav a { justify-content: center; gap: 0; padding: 8px 0; font-size: 0; }
  html[data-rail="compact"] .sidebar nav a.active::before,
  html[data-rail="compact"] .sidebar nav a[aria-current="page"]::before { left: -8px; }
  html[data-rail="compact"] .nav-badge {
    position: absolute; top: 5px; right: 12px;
    width: 8px; height: 8px; min-width: 0;
    padding: 0 !important; font-size: 0 !important; line-height: 0;
    border-radius: 50%;
  }
  html[data-rail="compact"] .nav-section-label {
    height: 1px; margin: 10px 8px; padding: 0; overflow: hidden;
    font-size: 0; background: var(--rail-line); opacity: 1; pointer-events: none;
  }
  /* The icon rail shows every group's links, so a group's toggle has nothing
     to do there: it leaves the rail, the tab order and the accessibility
     tree, and the group draws the same 1px divider in its place. */
  html[data-rail="compact"] .nav-group-toggle { display: none; }
  html[data-rail="compact"] .nav-group::before {
    content: ""; flex: 0 0 auto; height: 1px; margin: 10px 8px; background: var(--rail-line);
  }
  html[data-rail="compact"] .nav-group-items[hidden] { display: flex !important; }
  html[data-rail="compact"] .sidebar-user { justify-content: center; font-size: 0; padding: 2px 0; }
  html[data-rail="compact"] .sidebar-foot-row { flex-direction: column; align-items: center; }
  html[data-rail="compact"] .theme-toggle,
  html[data-rail="compact"] .rail-toggle { padding: 0 7px; }
  html[data-rail="compact"] .sidebar .link-muted { font-size: 11px; text-align: center; }
  html[data-rail="compact"] .rail-toggle svg { transform: scaleX(-1); }
}

/* =====================================================================
 * Mobile chrome: below 900px the rail becomes an off-canvas drawer opened
 * by the hamburger in a sticky top bar. The toggle is a visually hidden
 * checkbox so it works with no JS.
 *
 *   input#navToggle.nav-toggle-cb + header.mobile-topbar
 *     > label.nav-toggle-btn[for=navToggle] > .nav-toggle-bar ×3
 *     + .mobile-topbar-brand
 *     + a.mobile-topbar-search[href="/search.php"][data-palette-open][aria-label] > svg
 *     + a.mobile-timer-chip (only while a timer runs)
 *   + label.nav-scrim[for=navToggle] + aside.sidebar
 * =================================================================== */
.mobile-topbar,
.nav-scrim,
.timer-toggle-bar { display: none; }   /* desktop: hidden */
/* Visually hidden (not display:none) so the <label for> reliably toggles
   it across browsers; position:absolute keeps it out of layout flow. */
.nav-toggle-cb,
.timer-toggle-cb {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
/* Each toggle is a tab stop only where its label shows. */
@media not all and (max-width: 900px) { .nav-toggle-cb { visibility: hidden; } }
@media not all and (max-width: 720px) { .timer-toggle-cb { visibility: hidden; } }

@media (max-width: 900px) {
  /* Drop the 2-column grid so the rail can float above content. */
  body.has-nav { display: block; background: var(--bg); }

  .mobile-topbar {
    display: flex;
    align-items: center;
    gap: 6px;
    position: sticky;
    top: 0;
    z-index: 40;
    height: 52px;
    padding: 0 8px 0 4px;
    background: var(--rail-bg);
    color: #fff;
    border-bottom: 1px solid var(--rail-edge);
  }
  /* The rail is a drawer here, so there is nothing to collapse. */
  .rail-toggle { display: none; }
  /* The drawer's close (×), which ui.js shows here: the brand row's right
     end, in the rail's colors like the top bar's icons. */
  .sidebar .sidebar-close { flex: 0 0 auto; margin-left: auto; color: var(--rail-ink-2); }
  .sidebar .sidebar-close:hover { background: rgba(255, 255, 255, .08); color: #fff; }
  .sidebar .sidebar-close:focus-visible { outline: 2px solid var(--accent-2); outline-offset: -2px; box-shadow: none; }
  .mobile-topbar-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
    color: #fff;
    font-weight: 650;
    font-size: 14.5px;
    letter-spacing: -.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .mobile-topbar-brand .brand-mark { width: 24px; height: 24px; border-radius: 6px; font-size: 10px; }
  .nav-toggle-btn {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    width: 40px;
    height: 40px;
    padding: 10px;
    flex: 0 0 auto;
    cursor: pointer;
    border-radius: var(--radius-sm);
  }
  .nav-toggle-btn:hover { background: rgba(255, 255, 255, .06); }
  .nav-toggle-cb:focus-visible + .mobile-topbar .nav-toggle-btn { outline: 2px solid var(--accent-2); outline-offset: -2px; }
  .nav-toggle-bar { display: block; height: 2px; background: #fff; border-radius: 2px; }
  /* Search icon + running-timer chip on the right of the bar. */
  .mobile-topbar-search {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    flex: 0 0 auto;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--rail-ink);
    box-shadow: none;
    transition: background var(--t-fast), color var(--t-fast);
  }
  .mobile-topbar-search:hover { background: rgba(255, 255, 255, .08); color: #fff; }
  .mobile-topbar-search:focus-visible { outline: 2px solid var(--accent-2); outline-offset: -2px; border-radius: var(--radius-sm); box-shadow: none; }
  .mobile-topbar-search svg { width: 18px; height: 18px; }
  .mobile-timer-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 46vw;
    height: 28px;
    padding: 0 10px;
    border-radius: 999px;
    background: rgba(112, 121, 239, .18);
    color: #c7cbff;
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .mobile-timer-chip::before {
    content: "";
    flex: 0 0 auto;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ok-solid);
    animation: timer-pulse 1.6s ease-in-out infinite;
  }

  /* Off-canvas drawer */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 272px;
    max-width: 86vw;
    height: 100vh;
    height: 100dvh;
    z-index: 50;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--t-base), visibility 0s linear 180ms;
  }
  .nav-toggle-cb:checked ~ .sidebar {
    transform: translateX(0);
    visibility: visible;
    box-shadow: var(--shadow-pop);
    transition: transform var(--t-base), visibility 0s;
  }

  /* Dim + tap-to-close backdrop (the label closes the checkbox). */
  .nav-scrim {
    position: fixed;
    inset: 0;
    z-index: 45;
    background: var(--scrim);
  }
  .nav-toggle-cb:checked ~ .nav-scrim { display: block; }
}

@media (max-width: 720px) {
  .main { padding: 12px var(--gutter) 56px; }

  /* Clock-in / timer banner: collapsed by default on phones, tap to expand.
     Desktop renders it unconditionally. */
  .timer-toggle-bar {
    display: flex;
    flex-direction: row;   /* override the global label { flex-direction: column } */
    align-items: center;
    gap: 8px;
    min-height: 42px;
    padding: 0 12px;
    margin: 0 0 12px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-1);
    cursor: pointer;
    font-weight: 600;
    font-size: 13px;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
  }
  .timer-toggle-chev {
    display: inline-block;
    flex: 0 0 auto;
    color: var(--ink-faint);
    transition: transform var(--t-fast);
  }
  .timer-toggle-cb:checked ~ .timer-toggle-bar .timer-toggle-chev { transform: rotate(90deg); }
  .timer-toggle-cb:focus-visible ~ .timer-toggle-bar { box-shadow: var(--focus-ring); }
  .timer-toggle-running { border-color: var(--accent); color: var(--accent); }
  .timer-toggle-running .timer-toggle-chev { color: var(--accent); }
  .timer-toggle-running::after {
    content: "";
    order: -1;
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ok-solid);
    animation: timer-pulse 1.6s ease-in-out infinite;
  }
  .timer-shell-body { display: none; }
  .timer-toggle-cb:checked ~ .timer-shell-body { display: block; }
}

/* =====================================================================
 * Signed-out pages (sign-in, invite, sign-out, the public upload page).
 * Their cards live in assets/pages/auth.css and assets/pages/upload.css.
 * =================================================================== */
/* Full viewport width (override the base .main max-width) so place-items
   centers the auth card on the whole screen. */
body.no-nav .main { display: grid; place-items: center; min-height: 100vh; padding: 2rem; max-width: none; width: 100%; }

/* =====================================================================
 * Forms
 *
 * Label above control (13px 550, 6px gap). Controls 36px, panel fill,
 * hairline border, 6px radius; focus = accent border + 3px ring.
 * .field-help (muted 12px) and .field-error (danger) sit under a control;
 * [aria-invalid="true"] marks the control itself.
 * =================================================================== */
form { display: flex; flex-direction: column; gap: 16px; }
label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--ink-2);
}
fieldset {
  margin: 0;
  padding: 12px 16px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  min-width: 0;
}
legend { padding: 0 6px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-muted); }

input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="url"],
input[type="number"],
input[type="date"],
input[type="month"],
input[type="week"],
input[type="time"],
input[type="search"],
input[type="datetime-local"],
textarea,
select {
  font-family: inherit;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 20px;
  color: var(--ink);
  background-color: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 7px 10px;               /* 20 + 14 + 2 = 36px */
  width: 100%;
  min-width: 0;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background-color var(--t-fast);
}
/* Placeholders are text — on a filter bar's search the only visible label —
   so they take muted ink (5.6:1 light, 6.4:1 dark), not --ink-faint. */
input::placeholder, textarea::placeholder { color: var(--ink-muted); opacity: 1; }
/* Chrome pads its date/time editor and picker icon, making those fields 38px
   next to 36px neighbours; zero the padding so paired fields line up. */
input::-webkit-datetime-edit { padding: 0; }
input::-webkit-datetime-edit-fields-wrapper { padding: 0; }
input::-webkit-calendar-picker-indicator { margin: 0 0 0 4px; padding: 0; }
input:hover:not(:focus):not(:disabled),
textarea:hover:not(:focus):not(:disabled),
select:hover:not(:focus):not(:disabled) { border-color: var(--line-strong); }
textarea { min-height: 96px; resize: vertical; line-height: 1.5; }
input[type="search"] {
  appearance: none;
  -webkit-appearance: none;
  padding-left: 32px;
  background-image: var(--icon-search);
  background-repeat: no-repeat;
  background-position: 10px 50%;
  background-size: 15px 15px;
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
/* Native arrow off, our chevron on. The (0,2,1) specificity is deliberate:
   page rules that reset `background` keep the chevron and the room for it. */
select:not([multiple]):not([size]) {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  background-image: var(--icon-chevron);
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 14px 14px;
  text-overflow: ellipsis;
  cursor: pointer;
}
select[multiple], select[size] { padding: 4px; }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
input:disabled, textarea:disabled, select:disabled,
input[readonly], textarea[readonly] {
  background-color: var(--panel-inset);
  color: var(--ink-muted);
  cursor: not-allowed;
}
input[readonly], textarea[readonly] { cursor: text; }
/* (0,3,1) and after the hover rule, so it beats input[type=…] and :hover. */
:is(input, select, textarea)[aria-invalid="true"]:not(:focus):not(:disabled) { border-color: var(--danger); }
:is(input, select, textarea)[aria-invalid="true"]:focus { border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-line); }
/* Separate rule: an engine without :user-invalid drops only this one. */
:is(input, select, textarea):user-invalid { border-color: var(--danger); }
input[type="checkbox"], input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
  flex: 0 0 auto;
}
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
input[type="file"] { font: inherit; font-size: var(--fs-sm); color: var(--ink-muted); max-width: 100%; }
input[type="file"]::file-selector-button {
  font: inherit;
  font-weight: 550;
  margin-right: 10px;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--ink-2);
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { border-color: var(--line-strong); background: var(--panel-soft); }

.field-help { display: block; font-size: var(--fs-xs); font-weight: 400; color: var(--ink-muted); line-height: 1.45; }
.field-error { display: block; font-size: var(--fs-xs); font-weight: 550; color: var(--danger); line-height: 1.45; }
/* A field whose help or error text follows its <label> instead of sitting
   inside it, where a screen reader would read it into the control's name
   and again through aria-describedby:
   div.field > label[for] + control#id[aria-describedby] + .field-help#…
   Zero specificity, so a page's show/hide rule for a field always wins. */
:where(.field) { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.req { color: var(--danger); margin-left: 2px; }

.checkbox-inline {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-weight: 500;
}
.checkbox-inline input[type="checkbox"], .checkbox-inline input[type="radio"] { width: 16px; margin: 0; }
.checkbox-inline small { flex-basis: 100%; margin-top: 2px; font-weight: 400; color: var(--ink-muted); }

/* Grids: 2 columns from 720px, 1 below; 3-up from 900px. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px 20px;
}
.form-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px 20px;
}
.form-row-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.form-grid .span-2 { grid-column: 1 / -1; }
@media (max-width: 900px) { .form-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px) {
  .form-grid, .form-grid-3, .form-row-2 { grid-template-columns: minmax(0, 1fr); }
}
.form-section { display: flex; flex-direction: column; gap: 16px; padding-top: 4px; }
.form-section + .form-section { border-top: 1px solid var(--line-soft); padding-top: 20px; }
/* Save / Cancel row. On a long form add .is-sticky (or data-sticky) and it
   stays pinned to the bottom of the viewport while the form is on screen. */
.form-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}
.form-actions.is-sticky,
.form-actions[data-sticky] {
  position: sticky;
  bottom: 0;
  z-index: 5;
  background: var(--bg);
}
.card-form .form-actions.is-sticky, .card-form .form-actions[data-sticky],
.panel .form-actions.is-sticky, .panel .form-actions[data-sticky] { background: var(--panel); }
.form-actions .spacer { flex: 1 1 auto; }

/* =====================================================================
 * Buttons
 *
 * button / .btn = primary (accent fill). Variants: .btn-secondary (panel +
 * line), .btn-ghost (transparent), .btn-danger (solid red), .btn-link,
 * sizes .btn-sm (28px; .btn-small is the old name), .btn-icon (square —
 * give it an aria-label). 36px, 6px radius, 13.5px 550.
 * The variant classes carry the full button box on their own, so an
 * <a class="btn-secondary"> renders as a button without also needing .btn.
 * =================================================================== */
button,
.btn,
.btn-secondary,
.btn-ghost,
.btn-danger,
.btn-link,
.btn-icon {
  font: inherit;
  font-size: 13.5px;
  font-weight: 550;
  line-height: 20px;
  background: var(--accent-fill);
  color: var(--on-accent);
  border: 1px solid transparent;
  padding: 7px 14px;                 /* 20 + 14 + 2 = 36px */
  border-radius: var(--radius-sm);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  white-space: nowrap;
  text-decoration: none;
  vertical-align: middle;
  box-shadow: var(--shadow-1);
  transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast),
              transform var(--t-fast), border-color var(--t-fast), opacity var(--t-fast);
}
/* Hover changes the fill only, and at class-level specificity: any page rule
   that restyles its own <button> (close ×, chips, icon buttons) keeps its
   look on hover instead of flashing the accent fill. */
:where(button):hover { background: var(--accent-fill-hover); }
.btn:hover { background: var(--accent-fill-hover); color: var(--on-accent); }
button:active, .btn:active { transform: translateY(1px); }
/* Keyboard focus: 2px surface gap + 2px solid accent (>= 3:1 on every surface). */
button:focus-visible, .btn:focus-visible,
.btn-secondary:focus-visible, .btn-ghost:focus-visible, .btn-danger:focus-visible,
.btn-link:focus-visible, .btn-icon:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn svg, button svg { flex: 0 0 auto; }
.btn > svg, .btn-secondary > svg, .btn-ghost > svg, .btn-danger > svg { width: 15px; height: 15px; }

.btn-primary { background: var(--accent-fill); color: var(--on-accent); border-color: transparent; }
.btn-primary:hover { background: var(--accent-fill-hover); color: var(--on-accent); }
.btn-secondary {
  background: var(--panel);
  color: var(--ink-2);
  border-color: var(--line);
  box-shadow: var(--shadow-1);
}
.btn-secondary:hover { background: var(--panel-soft); color: var(--ink); border-color: var(--line-strong); }
.btn-ghost {
  background: transparent;
  color: var(--ink-muted);
  border-color: transparent;
  box-shadow: none;
}
.btn-ghost:hover { background: var(--panel-inset); color: var(--ink); }
.btn-danger { background: var(--danger-solid); color: #fff; border-color: transparent; }
.btn-danger:hover { background: var(--danger-solid-hover); color: #fff; }
.btn-danger:focus-visible { box-shadow: var(--focus-ring-danger); }
.btn-link {
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0 2px;
  color: var(--link);
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 3px;
}
.btn-link:hover { background: none; color: var(--link-hover); text-decoration-color: currentColor; }
.btn-link.is-danger:hover, .btn-link-danger:hover { color: var(--danger); }
.btn-solid { background: var(--emphasis); color: var(--on-emphasis); }
.btn-solid:hover { background: var(--emphasis-hover); color: var(--on-emphasis); }

.btn-sm, .btn-small {
  padding: 3px 10px;                 /* 20 + 6 + 2 = 28px */
  font-size: 12.5px;
  gap: 5px;
}
.btn-sm > svg, .btn-small > svg { width: 14px; height: 14px; }
.btn-lg { padding: 9px 18px; font-size: 14px; }
.btn-icon {
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  color: var(--ink-muted);
  border-color: transparent;
  box-shadow: none;
}
.btn-icon:hover, .btn-icon[aria-expanded="true"] { background: var(--panel-inset); color: var(--ink); }
.btn-icon svg { width: 16px; height: 16px; }
.btn-icon.btn-sm, .btn-icon.btn-small { width: 28px; height: 28px; padding: 0; }
.btn-block { width: 100%; }

button:disabled, .btn[disabled], .btn.is-disabled, .btn[aria-disabled="true"],
.btn-secondary[disabled], .btn-ghost[disabled], .btn-danger[disabled], .btn-icon[disabled] {
  opacity: .5;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}
a.btn.is-disabled, a.btn[aria-disabled="true"] { pointer-events: none; }
/* Submit in flight (ui.js marks POST submits; also usable by hand). */
.is-busy, [aria-busy="true"]:is(button, .btn) { cursor: progress; opacity: .85; }
:is(button, .btn).is-busy::before,
[aria-busy="true"]:is(button, .btn)::before {
  content: "";
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: ui-spin .7s linear infinite;
}
:is(button, .btn).is-busy:disabled,
[aria-busy="true"]:is(button, .btn):disabled { opacity: .8; cursor: progress; }
@keyframes ui-spin { to { transform: rotate(360deg); } }
.btn-group { display: inline-flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* Row action buttons (today's tables). The cell stays a table cell — a flex
   <td> falls out of the row's borders — and its children sit on one line. */
.actions-cell { white-space: nowrap; }
div.actions-cell, span.actions-cell { display: flex; gap: 6px; align-items: center; }
td.actions-cell > * { vertical-align: middle; }
td.actions-cell > * + * { margin-left: 4px; }
.actions-cell a, .actions-cell button { padding: 3px 8px; font-size: 12.5px; font-weight: 550; }
.inline-form { display: inline; }

/* =====================================================================
 * Page header — .page-head (new) and .toolbar (today's header, same look)
 *
 *   header.page-head > .page-head-text(.eyebrow + h1.page-title + .page-meta)
 *                    + .page-actions(buttons)
 * =================================================================== */
.page-head,
.toolbar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 16px;
  margin: 4px 0 20px;
}
.page-head-text { min-width: 0; flex: 1 1 auto; }
.eyebrow {
  margin: 0 0 4px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
/* A breadcrumb link in the eyebrow (the parent page's name, on a form page)
   is a 13px line of text. Its hit area reaches 36px into room the eyebrow
   makes above and below itself, clear of the timer banner over it and of
   the title under it. Every page head's eyebrow makes that room, link or
   not, so the title sits at the same height on every page; the link reads
   as the eyebrow on every form and underlines on hover. */
.page-head .eyebrow { margin-top: 8px; margin-bottom: 9px; }
.page-head .eyebrow > a { position: relative; color: inherit; text-decoration: none; }
.page-head .eyebrow > a:hover { color: var(--link-hover); text-decoration: underline; text-underline-offset: 2px; }
.page-head .eyebrow > a::after { content: ""; position: absolute; inset: -12px -10px -11px; }
.page-title,
.toolbar > h1 {
  margin: 0;
  font-size: var(--fs-display);
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -.02em;
}
.toolbar > h2, .toolbar > h3 { margin: 0; }
.page-meta {
  margin: 6px 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.page-actions,
.toolbar-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
/* Section label (uppercase) used above lists on several pages. */
.section-heading {
  margin: 24px 0 10px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: 28px 0 10px;
}
.section-head > h2, .section-head > h3 { margin: 0; }
@media (max-width: 720px) {
  .page-head { flex-direction: column; align-items: stretch; gap: 12px; }
  .page-actions { width: 100%; }
  .page-actions > .btn, .page-actions > a, .page-actions > button { flex: 1 1 auto; }
  .page-actions > form:has(> :is(button, .btn)) { flex: 1 1 auto; display: flex; flex-direction: row; }
  .page-actions > form > :is(button, .btn) { flex: 1 1 auto; }
  .page-title, .toolbar > h1 { font-size: 24px; }
}

/* =====================================================================
 * Stat tile (.stat) — the KPI contract
 *
 *   a.stat[href] when it filters/navigates, div.stat when static.
 *   .stat-label (+ .stat-hint) · .stat-value · .stat-delta(.is-good|.is-bad|
 *   .is-flat) (+ .stat-vs) · svg.spark · .stat-flag ("✓ Filtering", shown
 *   when .is-active). Grid: .stats.
 * Today's .cards > .card(.card-static) > .card-num + .card-label renders as
 * the same tile (label above value).
 * =================================================================== */
.stats,
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
  margin: 0 0 20px;
}
/* A strip of exactly 4 (or 8, 12 …) tiles: 4 columns, then 2 — auto-fit
   would leave 3 + 1 orphan between ~720 and ~860px of content width. */
:is(.stats, .cards):has(> :nth-child(4n):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1180px) {
  :is(.stats, .cards):has(> :nth-child(4n):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.stat {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "label label"
    "value spark"
    "delta delta";
  align-items: end;
  align-content: start;
  column-gap: 12px;
  min-width: 0;
  padding: 14px 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  color: var(--ink);
  text-decoration: none;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}
/* Values sit level across a strip. Each tile spans three rows of the strip
   and borrows them (subgrid), so a hint that wraps makes the label row taller
   for every tile on that line, not just its own: label, value and delta rows
   line up tile to tile. The space between lines rides each tile's top margin
   instead of the strip's row gap, which a subgrid would otherwise split into
   the tile's own rows; the strip's negative top margin takes the first line's
   back, so the gap above the strip is unchanged. (A subgrid can't be a size
   container, so the tile doesn't change layout by its own width: the
   sparkline takes the room the value leaves, up to 96px, and the delta gets
   the full width.) */
@supports (grid-template-rows: subgrid) {
  .stats { row-gap: 0; margin-top: -12px; }
  .stats > .stat { grid-row: span 3; grid-template-rows: subgrid; row-gap: 0; margin-top: 12px; }
}
.stat:has(> .spark) { grid-template-columns: fit-content(100%) minmax(0, 1fr); }
a.stat { cursor: pointer; }
a.stat:hover { border-color: var(--line-strong); transform: translateY(-1px); color: var(--ink); }
a.stat:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.stat-label {
  grid-area: label;
  align-self: start;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 6px;
  min-width: 0;
  margin-bottom: 8px;
  font-size: 12.5px;
  font-weight: 550;
  line-height: 1.35;
  color: var(--ink-muted);
}
.stat-hint { font-size: 11.5px; font-weight: 400; color: var(--ink-muted); }
.stat-value {
  grid-area: value;
  min-width: 0;
  font-size: var(--fs-stat);
  font-weight: 650;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--ink);
  font-variant-numeric: normal;     /* proportional figures at display size */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stat-value.is-pos { color: var(--ok); }
.stat-value.is-neg { color: var(--danger); }
.stat-delta {
  grid-area: delta;
  align-self: start;
  display: flex;
  flex-wrap: wrap;                  /* "vs <period>" drops to its own line; never clipped */
  align-items: baseline;
  gap: 0 4px;
  min-width: 0;
  margin-top: 7px;
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.35;
}
.stat-delta.is-good { color: var(--delta-up); }
.stat-delta.is-bad  { color: var(--delta-down); }
.stat-delta.is-flat { color: var(--delta-flat); }
.stat-delta .stat-arrow { font-size: 10px; }
.stat-vs { font-weight: 400; color: var(--ink-muted); }
.stat > .spark { grid-area: spark; justify-self: end; align-self: center; width: min(96px, 100%); }
/* "✓ Filtering" sits on the active tile's top border, like a tab on the
   accent ring: it takes no room from the label, so the tile's value stays
   level with its neighbours'. (Ui::statTile renders it inside .stat-label,
   so it is read with the label.) */
.stat-flag {
  position: absolute;
  top: -10px;
  right: 12px;
  display: none;
  align-items: center;
  gap: 4px;
  padding: 1px 8px 1px 6px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 16px;
  white-space: nowrap;
}
.stat-flag svg { width: 11px; height: 11px; }
.stat.is-active,
.stat.is-active:hover {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft-2);
}
.stat.is-active .stat-flag { display: inline-flex; }

/* Today's tiles, aliased onto the stat look. */
.card {
  display: block;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: 14px 16px;
  color: var(--ink);
  transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}
.cards > .card {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: stretch;
  gap: 8px;
  min-width: 0;
  font: inherit;
  text-align: left;
  white-space: normal;
}
a.card, button.card, .card-clickable { cursor: pointer; }
.card:hover { background: var(--panel); }
a.card:hover, button.card:hover, .card-clickable:hover {
  border-color: var(--line-strong);
  transform: translateY(-1px);
  color: var(--ink);
}
a.card:focus-visible, button.card:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.card-static:hover { transform: none; box-shadow: var(--shadow-1); border-color: var(--line); }
.card-static.card-clickable:hover { transform: translateY(-1px); border-color: var(--line-strong); }
.card-num {
  order: 2;
  font-size: var(--fs-stat);
  font-weight: 650;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--ink);
  font-variant-numeric: normal;
  overflow-wrap: anywhere;
}
.card-label {
  order: 1;
  font-size: 12.5px;
  font-weight: 550;
  line-height: 1.35;
  color: var(--ink-muted);
}
/* Money direction (banking, the import preview and the rule preview). */
.card-num.is-pos, .amt-pos { color: var(--ok); }
.card-num.is-neg, .amt-neg { color: var(--danger); }

@media (max-width: 720px) {
  .stats, .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-bottom: 12px; }
  .stat, .card { padding: 12px; }
  .cards > .card { gap: 6px; }
  .stat > .spark { width: min(64px, 100%); height: 24px; }
  @supports (grid-template-rows: subgrid) {
    .stats { row-gap: 0; margin-top: -8px; }
    .stats > .stat { margin-top: 8px; }
  }
  .stat-vs { flex-basis: 100%; }      /* the comparison keeps its own line, never dropped */
}

/* Sparkline (Ui::sparkline): 12 points in --spark, last segment + point in
   --spark-accent with a 2px surface ring. */
.spark { display: block; width: 96px; height: 28px; overflow: visible; flex: 0 0 auto; }
.spark-line { fill: none; stroke: var(--spark); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.spark-last { fill: none; stroke: var(--spark-accent); stroke-width: 1.5; stroke-linecap: round; }
.spark-dot { fill: var(--spark-accent); stroke: var(--panel); stroke-width: 2; }

/* =====================================================================
 * Filter bar (.filterbar) — one GET form above what it scopes.
 *
 *   form.filterbar[data-sticky]
 *     label.sel > span.sel-lbl + select          (period first)
 *     .filterbar-range[data-custom-range] > label.sel(From) + label.sel(To)
 *     label.search > svg + input[type=search]
 *     label.sel …                                 (dimension selects)
 *     noscript > button.btn.btn-secondary         (Apply)
 *     .chips > .chips-lbl + a.chip(.chip-k + .chip-v + svg) + a.chip-clear
 * =================================================================== */
.filterbar {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
}
.filterbar[data-sticky] {
  position: sticky;
  top: calc(var(--top-h) + 8px);
  z-index: 20;
  margin: 0 -11px 16px;             /* the 1px border + 10px padding: contents stay on the page edge */
  padding: 9px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background: transparent;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background-color var(--t-fast);
}
.filterbar[data-sticky].is-stuck {
  background: var(--panel);
  border-color: var(--line);
  box-shadow: var(--shadow-pop);
}
.filterbar-range { display: contents; }
.filterbar-spacer { flex: 1 1 auto; }

.sel {
  position: relative;
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  min-width: 0;
  height: 34px;
  padding: 0 0 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  box-shadow: var(--shadow-1);
  color: var(--ink);
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
.sel:hover { border-color: var(--line-strong); }
.sel-lbl { font-size: var(--fs-xs); font-weight: 500; color: var(--ink-muted); white-space: nowrap; }
.sel select,
.sel input {
  height: 32px;
  border: 0;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background-color: transparent;
  box-shadow: none;
  padding: 0 8px 0 0;
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--ink);
  max-width: 220px;
  width: auto;
}
.sel select:not([multiple]):not([size]) { padding-right: 28px; background-position: right 8px center; }
.sel input[type="date"], .sel input[type="month"] { padding-right: 8px; }
.sel select:focus, .sel input:focus { box-shadow: none; border-color: transparent; }
.sel:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.sel.is-set { border-color: var(--accent-soft-2); background: var(--accent-soft); }
.sel.is-set .sel-lbl { color: var(--accent-ink); }

.search {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  flex: 1 1 240px;
  max-width: 340px;
  min-width: 0;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  box-shadow: var(--shadow-1);
  color: var(--ink-faint);
  font-weight: 400;
  cursor: text;
}
.search > svg { width: 15px; height: 15px; flex: 0 0 auto; }
.search input,
.search input[type="search"] {
  height: 32px;
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent none;
  box-shadow: none;
  font-size: 13.5px;
  color: var(--ink);
}
.search input:focus { box-shadow: none; }
.search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.filterbar > input[type="search"] { flex: 1 1 240px; max-width: 340px; width: auto; height: 34px; padding-top: 0; padding-bottom: 0; }
.filterbar > .btn, .filterbar > button, .filterbar noscript .btn { padding-top: 6px; padding-bottom: 6px; }

/* Active-filter chips: one removable chip per param + "Clear all". */
.chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  flex-basis: 100%;
  margin: 0 0 16px;
}
.filterbar .chips { margin: 2px 0 0; }
.chips-lbl { font-size: var(--fs-xs); color: var(--ink-muted); margin-right: 2px; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  padding: 0 5px 0 9px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 12.5px;
  font-weight: 550;
  white-space: nowrap;
  max-width: 100%;
}
.chip:hover { color: var(--accent-ink); border-color: var(--accent-soft-2); }
.chip-k { font-weight: 400; color: var(--ink-muted); }
.chip-k::after { content: "·"; margin: 0 3px; }
.chip-v { overflow: hidden; text-overflow: ellipsis; }
/* Both may shrink: a flex item's automatic minimum is its widest chip, which
   beats .chip's max-width and pushes a phone page sideways. */
.chips,
.chip { min-width: 0; }
.chip svg { width: 16px; height: 16px; padding: 2px; border-radius: 50%; flex: 0 0 auto; transition: background var(--t-fast); }
.chip:hover svg { background: var(--accent-soft-2); }
.chip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.chip-clear {
  margin-left: 4px;
  padding: 2px 4px;
  border-radius: 4px;
  color: var(--ink-muted);
  font-size: 12.5px;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.chip-clear:hover { color: var(--ink); }

/* The older filter row (.list-filters: the Reports options form still uses it) in the same look. */
.list-filters {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 16px;
}
.list-filters > label {
  flex-direction: row;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--ink-muted);
}
.list-filters input,
.list-filters select,
.list-filters .list-filters-search,
.list-filters .list-filters-select {
  width: auto;
  padding-top: 6px;
  padding-bottom: 6px;              /* 34px */
  font-size: var(--fs-sm);
  box-shadow: var(--shadow-1);
}
.list-filters input[type="search"], .list-filters .list-filters-search { flex: 1 1 240px; max-width: 340px; }
.list-filters .list-filters-select { flex: 0 1 240px; }
.list-filters .btn, .list-filters button { padding-top: 6px; padding-bottom: 6px; }
.list-filters .btn-ghost { color: var(--ink-muted); }
@media (max-width: 720px) {
  /* A wrapped three-row bar would eat a fifth of a phone screen: phones scroll it away. */
  .filterbar[data-sticky],
  .filterbar[data-sticky].is-stuck { position: static; margin: 0 0 12px; padding: 0; background: none; border: 0; box-shadow: none; }
  .filterbar .search, .filterbar > input[type="search"] { flex: 1 1 100%; max-width: none; }
  .filterbar .sel { flex: 1 1 calc(50% - 4px); }
  .filterbar .sel select, .filterbar .sel input { max-width: none; width: 100%; flex: 1 1 auto; }
  .list-filters .list-filters-search,
  .list-filters .list-filters-select,
  .list-filters input[type="search"] { flex: 1 1 100%; max-width: none; }
  .list-filters select { flex: 1 1 auto; min-width: 0; }
  /* A <label for> standing alone before its control: keep the pair together. */
  .list-filters > label:not(:has(input, select)) { flex: 0 0 100%; margin-bottom: -4px; }
}

/* =====================================================================
 * Tabs (.tabs) and today's status chip rows (.filter-row > .filter-chip)
 *
 *   nav.tabs > a(.is-active|aria-current) "Open" + span.cnt "12"
 * =================================================================== */
.tabs {
  display: flex;
  align-items: stretch;
  gap: 2px;
  min-width: 0;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--line);
}
.tabs::-webkit-scrollbar { display: none; }
.tabs > a,
.tabs > button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 11px 10px 10px;
  border: 0;
  border-radius: 4px 4px 0 0;
  background: none;
  box-shadow: none;
  color: var(--ink-muted);
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: 20px;
  white-space: nowrap;
  transition: color var(--t-fast), box-shadow var(--t-fast);
}
.tabs > a:hover, .tabs > button:hover { color: var(--ink); background: none; }
.tabs > .is-active,
.tabs > [aria-current="page"],
.tabs > [aria-current="true"],
.tabs > [aria-selected="true"] { color: var(--ink); box-shadow: inset 0 -2px 0 var(--accent); }
.tabs > a:focus-visible, .tabs > button:focus-visible { outline: none; box-shadow: var(--focus-inset); }
/* ui.js marks a tab row that scrolls sideways: fade the edge that hides more tabs. */
.tabs.has-more-right, .filter-row.has-more-right { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent); }
.tabs.has-more-left, .filter-row.has-more-left { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 32px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 32px), transparent); }
.tabs.has-more-left.has-more-right, .filter-row.has-more-left.has-more-right {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 32px, #000 calc(100% - 32px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 32px, #000 calc(100% - 32px), transparent);
}
.cnt {
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--panel-inset);
  color: var(--ink-muted);
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}
.tabs > .is-active .cnt,
.tabs > [aria-current="page"] .cnt,
.tabs > [aria-selected="true"] .cnt { background: var(--accent-soft); color: var(--accent-ink); }

.filter-row {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: 2px;
  margin: 0 0 16px;
  border-bottom: 1px solid var(--line);
}
.filter-row > .muted,
.filter-row > span:not([class]) {
  align-self: center;
  padding: 0 8px 0 0;
  font-size: var(--fs-xs);
  color: var(--ink-muted);
}
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 10px 8px;
  border-radius: 4px 4px 0 0;
  color: var(--ink-muted);
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: 20px;
  white-space: nowrap;
  transition: color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
.filter-chip:hover { color: var(--ink); }
.filter-chip.active { color: var(--ink); box-shadow: inset 0 -2px 0 var(--accent); }
.filter-chip.active:hover { color: var(--ink); }
.filter-chip:focus-visible { outline: none; box-shadow: var(--focus-inset); }
/* A row with no active chip is navigation (e.g. the banking pager), not a
   status selector: render its chips as small secondary buttons. */
.filter-row:not(:has(.filter-chip.active)) { border-bottom: 0; align-items: center; gap: 8px; }
.filter-row:not(:has(.filter-chip.active)) .filter-chip {
  padding: 3px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  box-shadow: var(--shadow-1);
  color: var(--ink-2);
}
.filter-row:not(:has(.filter-chip.active)) .filter-chip:hover { border-color: var(--line-strong); background: var(--panel-soft); color: var(--ink); }
@media (max-width: 720px) {
  .filter-row:has(.filter-chip.active) { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .filter-row::-webkit-scrollbar { display: none; }
}

/* Segmented control (.seg). */
.seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel-inset);
  overflow: visible;
}
.seg > button, .seg > a {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  box-shadow: none;
  color: var(--ink-muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 550;
  line-height: 20px;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast);
}
.seg > button + button { border-left: 0; }
.seg > button:hover, .seg > a:hover { color: var(--ink); background: transparent; }
.seg > .is-on, .seg > [aria-pressed="true"], .seg > .active,
.seg > .is-on:hover {
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow-1), 0 0 0 1px var(--line-hair);
}
.seg > :focus-visible { outline: none; box-shadow: var(--focus-inset); }
.seg-sm > button, .seg-sm > a { padding: 1px 8px; font-size: var(--fs-xs); }

/* Pager under a table (banking ledger by URL; the invoice editor's pick
   tables in the page). Its numbers are links or buttons: as buttons they
   drop the global button fill. */
.pager {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 14px;
  border-top: 1px solid var(--line);
}
.pager-pages { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 2px; }
.pager-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 6px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  box-shadow: none;
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 550;
  font-variant-numeric: tabular-nums;
}
.pager-num:hover { background: var(--panel-inset); color: var(--ink); }
.pager-num.is-current { background: var(--accent-soft); color: var(--accent-ink); box-shadow: inset 0 0 0 1px var(--accent-soft-2); }
.pager-num:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.pager-gap { min-width: 20px; text-align: center; color: var(--ink-muted); }
.pager-range { margin-left: auto; font-size: var(--fs-xs); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
@media (pointer: coarse), (max-width: 720px) {
  .pager-num { min-width: 36px; height: 36px; }
}
@media (max-width: 720px) {
  .pager { padding: 10px 12px; }
  .pager-range { flex-basis: 100%; margin-left: 0; }
}

/* =====================================================================
 * Data tables
 *
 *   section.table-panel
 *     .table-head > nav.tabs + .table-tools
 *     .table-summary "12 invoices · <strong>$40,200.00</strong> total"
 *     .table-wrap > table.tbl.sortable[data-cards]
 *   td.num (right, tabular) · td > .cell-two(primary + .cell-sub) ·
 *   td.cell-act (primary action + details.menu) · tfoot tr.row-total
 * Plain <table> inside .table-wrap gets the same look.
 * Below 720px a table[data-cards] turns each row into a card (labels come
 * from td[data-label], which ui.js fills from the header); any other table
 * scrolls sideways inside .table-wrap — the page never does.
 * =================================================================== */
.table-panel {
  margin: 0 0 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  min-width: 0;
}
.table-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 12px;
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}
.table-head .tabs { border-bottom: 0; margin-bottom: -1px; flex: 0 1 auto; }
.table-head > h2, .table-head > h3, .table-title { margin: 12px 0; font-size: var(--fs-h3); font-weight: 600; }
.table-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 8px 0; margin-left: auto; }
.table-summary {
  padding: 9px 16px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink-muted);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.table-summary strong, .table-summary b { color: var(--ink); font-weight: 600; }
.table-panel > .table-wrap,
.table-panel > form > .table-wrap {
  border: 0;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  box-shadow: none;
}
.table-panel > .table-wrap:first-child { border-radius: var(--radius-lg); }
.table-panel > .empty { border-top: 0; }

.table-wrap {
  /* Edge shadows that appear only while there is more table to scroll to
     (the panel-colored covers scroll with the content and hide the shadow
     at whichever edge is reached). macOS hides overlay scrollbars, so this
     is the only cue that a column of actions sits off to the right. */
  background:
    linear-gradient(to right, var(--panel) 30%, transparent) left center / 28px 100% no-repeat local,
    linear-gradient(to left, var(--panel) 30%, transparent) right center / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--scroll-shadow), transparent) left center / 14px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--scroll-shadow), transparent) right center / 14px 100% no-repeat scroll,
    var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  /* auto (not hidden) so a table wider than the viewport scrolls
     horizontally instead of being clipped — important on mobile. */
  overflow: auto;
  /* Positioned too, so it is the containing block of anything absolute
     inside (the .sr-only text in a row's buttons): its overflow clips them,
     and they can't widen the page past the viewport. */
  position: relative;
  -webkit-overflow-scrolling: touch;
  box-shadow: var(--shadow-1);
  max-width: 100%;
}
/* A vertically scrolling wrap: the only place a .tbl header can stick
   (.table-wrap is a sideways scroll container, so a sticky <th> sticks to it,
   not to the page). Use it for long lists inside a card or modal. */
.table-wrap.scroll-y { max-height: min(70vh, 720px); }

table { width: 100%; border-collapse: separate; border-spacing: 0; }
th, td { padding: 10px 14px; text-align: left; vertical-align: middle; }
td {
  color: var(--ink-2);
  font-size: 13.5px;
  line-height: 1.45;
  border-top: 1px solid var(--line-soft);
}
thead th {
  padding: 10px 14px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  color: var(--ink-muted);
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 16px;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}
tbody th { font-weight: 600; color: var(--ink); font-size: 13.5px; border-top: 1px solid var(--line-soft); }
thead + tbody > tr:first-child > *,
table > tbody:first-child > tr:first-child > * { border-top: 0; }
tbody tr { transition: background var(--t-fast); }
tbody tr:hover { background: var(--bg-soft); }
tfoot td, tfoot th {
  border-top: 1px solid var(--line);
  background: var(--panel-soft);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}
/* Totals row. */
tfoot .row-total th,
tfoot .row-total td,
tr.row-total > * {
  border-top: 1px solid var(--line-strong);
  background: var(--panel-soft);
  color: var(--ink);
  font-weight: 650;
}
/* Right-align numeric columns app-wide; tabular figures so digits align. */
.num { text-align: right; font-variant-numeric: tabular-nums; }
th.num, td.num { text-align: right; }

/* Unmigrated plain tables (no .tbl): headers may wrap and cells are 10px
   narrower a side, so today's 10–11 column lists fit a 1440px laptop instead
   of hiding their action column past the card edge. */
.table-wrap > table:not(.tbl):not(.txn-table) thead th { white-space: normal; }
.table-wrap > table:not(.tbl):not(.txn-table) :is(th, td) { padding-left: 10px; padding-right: 10px; }

/* .tbl rows: ~40px single-line, ~52px with a .cell-two second line. */
.tbl { font-size: 13.5px; }
.tbl td { padding-top: 9px; padding-bottom: 9px; }
.tbl thead th { position: sticky; top: 0; z-index: 2; }
/* Void rows: muted ink, not opacity — the pill and the actions stay legible. */
.tbl tbody tr.is-void td { color: var(--ink-muted); }
.tbl tbody tr.is-void :is(.cell-id, .cell-id a, .cell-two > :first-child) { color: var(--ink-muted); font-weight: 500; }
.tbl-compact td, .tbl-compact tbody th, .tbl-compact tfoot th, .tbl.tbl-compact td { padding: 8px 12px; font-size: var(--fs-sm); }
.tbl-compact thead th { padding: 8px 12px; }   /* keeps the 11px header type */
.cell-two { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.cell-two > :first-child { color: var(--ink); font-weight: 500; }
/* The row's record link reads as ink; on hover it shows it is the way in. */
.tbl .cell-two > a:first-child:hover { text-decoration: underline; text-underline-offset: 3px; }
.cell-sub { display: block; margin-top: 1px; font-size: var(--fs-xs); font-weight: 400; color: var(--ink-muted); }
.cell-sub.is-warn { color: var(--warn); font-weight: 550; }
.cell-sub.is-danger { color: var(--danger); font-weight: 550; }
td > strong > a, .cell-id a { white-space: nowrap; }
.cell-id, .cell-id a { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cell-id a:hover { color: var(--accent); }
.cell-date { white-space: nowrap; font-variant-numeric: tabular-nums; }
.cell-act {
  width: 1%;
  white-space: nowrap;
  text-align: right;
  padding-top: 6px;
  padding-bottom: 6px;
}
.cell-act > * + * { margin-left: 4px; }
.cell-act .menu { vertical-align: middle; }

/* Sortable column headers (table.sortable — see assets/sort.js). */
.th-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.th-sortable:hover { color: var(--ink); }
.th-sort-btn { position: relative; display: inline-flex; align-items: center; border-radius: 4px; cursor: pointer; }
.th-sort-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.th-sort-btn::after,
table.sortable th.th-sortable:not(:has(.th-sort-btn))::after {
  content: "";
  position: absolute;
  top: 50%;
  left: calc(100% + 3px);
  width: 10px;
  height: 10px;
  margin-top: -5px;
  background: currentColor;
  -webkit-mask: var(--icon-chevron) center / 10px 10px no-repeat;
  mask: var(--icon-chevron) center / 10px 10px no-repeat;
  opacity: 0;
  transition: opacity var(--t-fast), transform var(--t-fast);
}
.th-sortable:hover .th-sort-btn::after { opacity: .45; }
.th-sortable.sort-asc .th-sort-btn::after,
.th-sortable.sort-desc .th-sort-btn::after { opacity: 1; background: var(--accent); }
.th-sortable.sort-asc .th-sort-btn::after { transform: rotate(180deg); }
th.num .th-sort-btn::after { left: auto; right: calc(100% + 3px); }
table.sortable th.th-sortable:not(:has(.th-sort-btn)) { position: relative; }
table.sortable th.th-sortable:not(:has(.th-sort-btn))::after { left: auto; right: 2px; }
table.sortable th.th-sortable:not(:has(.th-sort-btn)):hover::after { opacity: .45; }
table.sortable th.th-sortable.sort-asc:not(:has(.th-sort-btn))::after,
table.sortable th.th-sortable.sort-desc:not(:has(.th-sort-btn))::after { opacity: 1; background: var(--accent); }
table.sortable th.th-sortable.sort-asc:not(:has(.th-sort-btn))::after { transform: rotate(180deg); }

/* Truncated cell with hover-to-see-full (uses native title="") */
.cell-notes {
  max-width: 22rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-muted);
  cursor: help;
}
/* Inline value with a hover-for-detail tooltip (native title="") — e.g. the
   Files page download count expands to the full access history. */
.has-tip { cursor: help; border-bottom: 1px dotted var(--ink-faint); }
/* Disclosure for a short history list inside a cell (Files: access log). */
.access-log { display: inline-block; }
/* The count is the control: a 24px target (36px on touch, below) whose
   padding the negative margins take back, so the number stays in line with
   the plain counts in the rows around it. */
.access-log > summary {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  min-width: 24px;
  min-height: 24px;
  margin: -2px -6px;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  list-style: none;
  text-decoration: underline dotted var(--ink-faint);
  text-underline-offset: 3px;
}
.access-log > summary::-webkit-details-marker { display: none; }
.access-log > summary:hover { color: var(--ink); }
.access-log[open] > summary { color: var(--ink); }
.access-log-list {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--ink-muted);
  white-space: nowrap;
}

@media (max-width: 720px) {
  th, td { padding: 10px 12px; }
  thead th { padding: 9px 12px; }
  .table-head { padding: 0 8px; }
  .table-summary { padding: 8px 12px; }

  /* Cards: every row becomes a stacked card with label/value pairs. */
  table[data-cards] { min-width: 0 !important; table-layout: auto; }
  /* No header row: each card labels its own values (td::before). It leaves
     the tab order too, or its sort buttons would be invisible stops. */
  table[data-cards] thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
    visibility: hidden;
  }
  table[data-cards] tbody,
  table[data-cards] tfoot,
  table[data-cards] tr,
  table[data-cards] td,
  table[data-cards] tbody th { display: block; width: 100%; }
  table[data-cards] tbody tr,
  table[data-cards] tfoot tr { padding: 12px 14px; border-top: 1px solid var(--line); }
  table[data-cards] tbody tr:first-child { border-top: 0; }
  table[data-cards] tbody tr:hover { background: transparent; }
  table[data-cards] td,
  table[data-cards] tbody th {
    display: flow-root;            /* contains the floated label */
    padding: 4px 0;
    border: 0;
    text-align: right;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  table[data-cards] td::before,
  table[data-cards] tbody th::before {
    content: attr(data-label);
    float: left;
    max-width: 45%;
    margin-right: 12px;
    color: var(--ink-muted);
    font-size: var(--fs-xs);
    font-weight: 500;
    line-height: 20px;
    text-align: left;
  }
  table[data-cards] td:not([data-label])::before,
  table[data-cards] td[data-label=""]::before { display: none; }
  table[data-cards] td > .cell-two { align-items: flex-end; text-align: right; }
  table[data-cards] td .cell-sub { margin-top: 0; }
  /* First cell is the card's title line. */
  table[data-cards] tbody tr > :first-child,
  table[data-cards] tfoot tr > :first-child {
    padding-bottom: 6px;
    text-align: left;
    font-size: 14px;
  }
  table[data-cards] tbody tr > :first-child::before,
  table[data-cards] tfoot tr > :first-child::before { display: none; }
  table[data-cards] tbody tr > :first-child > .cell-two { align-items: flex-start; text-align: left; }
  /* The title's record link is the card's way in: a 36px tap target. Inline
     block, not flex, so mixed content ("<mark>Build</mark> filters") keeps
     its spaces. (Zero specificity: a page that sizes its own link keeps it.) */
  :where(table[data-cards] tbody tr > :first-child) > :where(a:first-child),
  :where(table[data-cards] tbody tr > :first-child > .cell-two) > :where(a:first-child),
  :where(table[data-cards] tbody tr > :first-child > .cell-two > :first-child) > :where(a:only-child) {
    display: inline-block;
    box-sizing: border-box;
    min-height: 36px;
    padding-block: 8px;
  }
  table[data-cards] td.cell-act {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    width: 100%;
    margin-top: 8px;
    padding-top: 10px;
    border-top: 1px solid var(--line-soft);
  }
  table[data-cards] td.cell-act > * + * { margin-left: 0; }
  table[data-cards] td.cell-act::before { display: none; }
  /* Cards: the drill-down editor's status sits before its Update button. */
  table[data-cards] td.cell-act > .hc-edit-status { order: -1; margin: 0; }
  table[data-cards] tfoot td:empty, table[data-cards] tfoot th:empty { display: none; }
  table[data-cards] tfoot tr { background: var(--panel-soft); }
}

/* =====================================================================
 * Status pill (.pill) — dot + label, never color alone
 *
 *   <span class="pill pill-paid"><i></i>Paid</span>   (Ui::pill)
 * Today's .status-pill.status-* map onto the same variants.
 * =================================================================== */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 8px 0 7px;
  border: 1px solid var(--neutral-line);
  border-radius: 999px;
  background: var(--neutral-bg);
  color: var(--neutral);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  vertical-align: middle;
}
.pill > i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.pill-draft, .pill-neutral { background: var(--neutral-bg); color: var(--neutral); border-color: var(--neutral-line); }
.pill-sent,  .pill-info    { background: var(--info-bg);    color: var(--info);    border-color: var(--info-line); }
.pill-paid,  .pill-ok      { background: var(--ok-bg);      color: var(--ok);      border-color: var(--ok-line); }
.pill-overdue, .pill-warn  { background: var(--warn-bg);    color: var(--warn);    border-color: var(--warn-line); }
.pill-danger               { background: var(--danger-bg);  color: var(--danger);  border-color: var(--danger-line); }
.pill-accent               { background: var(--accent-soft); color: var(--accent-ink); border-color: var(--accent-soft-2); }
.pill-void                 { background: transparent;       color: var(--ink-muted); border-color: var(--line-strong); }
/* A user-chosen color (todo kinds and the like): one hue in --tint, tinted
   here so it reads in both themes. Ui::tint('#1d4ed8', 'Bug') emits
   <span class="pill pill-tint" style="--tint:#1d4ed8">. */
.pill-tint {
  --tint: var(--neutral);
  background: color-mix(in srgb, var(--tint) 13%, transparent);
  border-color: color-mix(in srgb, var(--tint) 32%, transparent);
  color: color-mix(in oklab, var(--tint) 58%, var(--ink));
}

/* Old markup: <span class="status-pill status-paid">paid</span>. Same look; the
   dot is painted as a background so the label can stay an inline block and
   pick up sentence case from ::first-letter. */
.status-pill {
  display: inline-block;
  height: 22px;
  padding: 0 8px 0 19px;
  border: 1px solid var(--neutral-line);
  border-radius: 999px;
  background-color: var(--neutral-bg);
  background-image: radial-gradient(circle at 10px 50%, currentColor 0 3px, transparent 3.5px);
  background-repeat: no-repeat;
  color: var(--neutral);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 20px;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
  vertical-align: middle;
}
.status-pill::first-letter { text-transform: uppercase; }
a.status-pill:hover { filter: brightness(.97); }
/* Variants set colors only (never the `background` shorthand), so the dot survives. */
.status-active, .status-paid, .status-done, .status-ok {
  background-color: var(--ok-bg); border-color: var(--ok-line); color: var(--ok);
}
.status-inactive, .status-on_hold, .status-overdue, .status-uncategorized, .status-warn {
  background-color: var(--warn-bg); border-color: var(--warn-line); color: var(--warn);
}
.status-archived, .status-draft, .status-duplicate {
  background-color: var(--neutral-bg); border-color: var(--neutral-line); color: var(--neutral);
}
.status-completed, .status-sent, .status-open, .status-info {
  background-color: var(--info-bg); border-color: var(--info-line); color: var(--info);
}
.status-internal { background-color: var(--accent-soft); border-color: var(--accent-soft-2); color: var(--accent-ink); }
.status-void { background-color: transparent; border-color: var(--line-strong); color: var(--ink-muted); }
.status-error, .status-danger { background-color: var(--danger-bg); border-color: var(--danger-line); color: var(--danger); }

/* Small neutral count / label chip (categories, rules). */
.tag {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 7px;
  border-radius: 999px;
  background: var(--panel-inset);
  color: var(--ink-muted);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: tabular-nums;
  vertical-align: middle;
}

/* =====================================================================
 * Overflow menu — <details class="menu"> works without JS; ui.js closes it
 * on outside click / Esc and positions the list so table overflow can't
 * clip it.
 *
 *   details.menu > summary.btn-icon[aria-label] + .menu-list > a | button |
 *                  form > button | hr ;  .is-danger for destructive items
 * =================================================================== */
.menu { position: relative; display: inline-block; text-align: left; }
.menu > summary { list-style: none; cursor: pointer; }
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::marker { content: ""; }
.menu-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 60;
  min-width: 200px;
  max-width: min(320px, calc(100vw - 24px));
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow-pop);
  display: flex;
  flex-direction: column;
  gap: 1px;
  white-space: normal;
}
.menu-left .menu-list { left: 0; right: auto; }
.menu-list.is-fixed { position: fixed; top: auto; right: auto; max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px); overflow-y: auto; overscroll-behavior: contain; }
.menu-list form { display: block; margin: 0; }
.menu-list a,
.menu-list button {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 9px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  box-shadow: none;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 450;
  line-height: 18px;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
.menu-list a:hover, .menu-list button:hover { background: var(--panel-inset); color: var(--ink); }
.menu-list a:focus-visible, .menu-list button:focus-visible { background: var(--panel-inset); color: var(--ink); outline: none; box-shadow: var(--focus-inset); }
.menu-list svg { width: 14px; height: 14px; color: var(--ink-faint); flex: 0 0 auto; }
.menu-list .is-danger { color: var(--danger); }
.menu-list .is-danger svg { color: inherit; }
.menu-list .is-danger:hover, .menu-list .is-danger:focus-visible { background: var(--danger-bg); color: var(--danger); }
.menu-list .is-danger:focus-visible { box-shadow: inset 0 0 0 2px var(--danger); }
.menu-list hr { border: 0; border-top: 1px solid var(--line); margin: 5px 4px; }
.menu-label { padding: 6px 10px 2px; font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); }
.menu-list .kbd { margin-left: auto; }

/* =====================================================================
 * Alerts (inline, page-level / form errors; transient notices are toasts)
 * =================================================================== */
.alert {
  margin: 0 0 16px;
  padding: 10px 14px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.45;
}
.alert-error { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-line); }
.alert-ok    { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-line); }
.alert-warn  { background: var(--warn-bg);   color: var(--warn);   border-color: var(--warn-line); }
.alert-info  { background: var(--info-bg);   color: var(--info);   border-color: var(--info-line); }
.alert a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.alert strong { font-weight: 650; }

/* =====================================================================
 * Empty state (.empty) — Ui::emptyState(); today's .empty-state too
 * =================================================================== */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 40px 20px;
  text-align: center;
  color: var(--ink-muted);
}
.empty-ico {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-bottom: 8px;
  border-radius: 10px;
  background: var(--panel-inset);
  color: var(--ink-faint);
}
.empty-ico svg { width: 20px; height: 20px; }
.empty-title { margin: 0; font-size: var(--fs-h3); font-weight: 600; color: var(--ink); }
.empty-text { margin: 0; max-width: 46ch; font-size: var(--fs-sm); }
.empty .btn, .empty .btn-secondary { margin-top: 12px; }
.empty-state {
  margin: 0 0 16px;
  padding: 24px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--panel);
  color: var(--ink-muted);
  text-align: center;
  font-size: var(--fs-sm);
}

/* Keyboard key (.kbd) */
.kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--panel-soft);
  box-shadow: inset 0 -1px 0 var(--line);
  color: var(--ink-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}
.kbd + .kbd { margin-left: 3px; }

/* =====================================================================
 * Overlays — native <dialog> (top layer, Esc, inert background). ui.js
 * adds backdrop-click close, [data-close], scroll lock, focus in/out and
 * stacking.
 *
 *   dialog.modal(.modal-sm|.modal-lg)
 *     header.modal-head > .modal-head-text(h2.modal-title + p.modal-sub)
 *                       + button.btn-icon[data-close][aria-label=Close]
 *     .modal-body(.modal-body-flush for edge-to-edge tables)
 *     footer.modal-foot > buttons (right-aligned; .spacer pushes left)
 *   dialog.drawer — same parts, a 440px right-side panel.
 * A <form> may wrap head/body/foot; it becomes the flex column.
 * =================================================================== */
dialog {
  color: var(--ink);
  max-width: none;
  max-height: none;
}
dialog::backdrop {
  background: rgba(10, 13, 18, .48);
  background: var(--scrim, rgba(10, 13, 18, .48));
}
dialog.modal,
dialog.drawer,
dialog.palette {
  padding: 0;
  border: 1px solid var(--line);
  background: var(--panel);
  box-shadow: var(--shadow-pop);
  overflow: hidden;
}
dialog.modal {
  width: min(calc(100vw - 32px), 640px);
  max-height: calc(100dvh - 48px);
  margin: auto;
  border-radius: var(--radius-xl);
}
dialog.modal.modal-sm { width: min(calc(100vw - 32px), 420px); }
dialog.modal.modal-lg { width: min(calc(100vw - 32px), 920px); }
dialog.modal.modal-xl { width: min(calc(100vw - 32px), 1120px); }
dialog.modal[open],
dialog.drawer[open] { display: flex; flex-direction: column; }
dialog.modal > form,
dialog.drawer > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 0;
  margin: 0;
}
.modal-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 16px 12px 20px;
  border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}
.modal-head-text { flex: 1 1 auto; min-width: 0; }
.modal-title {
  margin: 0;
  font-size: var(--fs-h2);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -.01em;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.modal-sub {
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.modal-head > .btn-icon { margin: -4px -4px 0 0; flex: 0 0 auto; }
.modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 18px 20px;
  overscroll-behavior: contain;
}
/* Form fields stacked in a modal body get the form rhythm. */
dialog.modal .modal-body > label,
dialog.modal .modal-body > .form-grid,
dialog.modal .modal-body > .form-row-2,
dialog.modal .modal-body > fieldset,
dialog.modal .modal-body > .checkbox-inline { margin-bottom: 14px; }
.modal-body > :first-child { margin-top: 0; }
.modal-body > :last-child { margin-bottom: 0; }
.modal-body-flush, .modal-body.flush { padding: 0; }
.modal-body .table-wrap { border-radius: var(--radius); box-shadow: none; }
.modal-body-flush .table-wrap, .modal-body.flush .table-wrap { border: 0; border-radius: 0; }
.modal-body .tbl thead th, .modal-body thead th { position: sticky; top: 0; z-index: 2; }
.modal-body p { margin: 0 0 10px; color: var(--ink-2); line-height: 1.55; }
.modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 16px 12px 20px;
  border-top: 1px solid var(--line);
  background: var(--panel-soft);
  flex: 0 0 auto;
}
.modal-foot .spacer { flex: 1 1 auto; }
.modal-foot-note { margin-right: auto; font-size: var(--fs-xs); color: var(--ink-muted); }

dialog.drawer {
  width: min(440px, 100vw);
  height: 100%;
  max-height: none;
  margin: 0 0 0 auto;
  border-width: 0 0 0 1px;
  border-radius: 0;
}
dialog.drawer.drawer-lg { width: min(640px, 100vw); }
.drawer .modal-body { display: flex; flex-direction: column; gap: 14px; }
.drawer .modal-body > form { gap: 14px; }

/* Confirm (UI.confirm) */
.confirm-body { color: var(--ink-2); line-height: 1.55; }
.confirm-body p { margin: 0 0 8px; }
.confirm-body p:last-child { margin-bottom: 0; }

@media (prefers-reduced-motion: no-preference) {
  dialog.modal[open], dialog.palette[open] { animation: ui-pop 160ms cubic-bezier(.2, .8, .2, 1); }
  dialog.drawer[open] { animation: ui-slide 200ms cubic-bezier(.2, .8, .2, 1); }
  dialog[open]::backdrop { animation: ui-fade 160ms ease-out; }
}
@keyframes ui-pop   { from { opacity: 0; transform: translateY(8px) scale(.985); } }
@keyframes ui-slide { from { opacity: 0; transform: translateX(24px); } }
@keyframes ui-fade  { from { opacity: 0; } }

/* Phones: modals become full-screen sheets; head and foot stay put while
   the body scrolls. */
@media (max-width: 640px) {
  dialog.modal,
  dialog.modal.modal-sm,
  dialog.modal.modal-lg,
  dialog.modal.modal-xl,
  dialog.drawer,
  dialog.drawer.drawer-lg {
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    margin: 0;
    border: 0;
    border-radius: 0;
  }
  .modal-head { padding: 14px 12px 10px 16px; }
  .modal-body { padding: 16px; }
  .modal-foot { padding: 10px 12px 10px 16px; padding-bottom: max(10px, env(safe-area-inset-bottom)); }
  .modal-foot > .btn, .modal-foot > button, .modal-foot > a { flex: 1 1 auto; }
  .modal-foot > .modal-foot-note { flex-basis: 100%; }
  /* A two-line confirm or the shortcuts list: a bottom sheet sized to its
     content, buttons right under the question. */
  dialog.modal.modal-sm {
    inset: auto 0 0 0;               /* bottom-anchored only, so height: auto is the content's */
    height: auto;
    max-height: 85dvh;
    margin: 0;
    border-top: 1px solid var(--line);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  }
}
@media (max-width: 640px) and (prefers-reduced-motion: no-preference) {
  dialog.modal.modal-sm[open] { animation: ui-sheet 200ms cubic-bezier(.2, .8, .2, 1); }
}
@keyframes ui-sheet { from { opacity: 0; transform: translateY(24px); } }

/* =====================================================================
 * Toasts — UI.toast(msg, {kind, timeout, undo}); server flashes render here.
 * Bottom-right; bottom-center on phones.
 * =================================================================== */
.toasts {
  position: fixed;
  right: 16px;
  bottom: calc(16px + var(--sticky-bottom, 0px));   /* above a sticky Save bar (ui.js measures it) */
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  width: min(400px, calc(100vw - 32px));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 10px 10px 10px 12px;
  border: 1px solid var(--inverse-line);
  border-radius: 10px;
  background: var(--inverse-bg);
  color: var(--inverse-ink);
  box-shadow: var(--shadow-pop);
  font-size: 13.5px;
  line-height: 1.4;
  animation: ui-toast-in 180ms cubic-bezier(.2, .8, .2, 1);
  transition: opacity var(--t-base), transform var(--t-base);
}
.toast.is-leaving { opacity: 0; transform: translateY(6px); }
.toast-ico {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  margin-top: 6px;
  border-radius: 50%;
  background: var(--inverse-ok);
}
.toast-error .toast-ico { background: var(--inverse-danger); }
.toast-warn  .toast-ico { background: var(--inverse-warn); }
.toast-info  .toast-ico { background: var(--inverse-info); }
.toast-msg { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; padding: 1px 0; }
.toast-action {
  flex: 0 0 auto;
  margin: -2px 0;
  padding: 2px 8px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  box-shadow: none;
  color: inherit;
  font-size: var(--fs-sm);
  font-weight: 650;
  line-height: 20px;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.toast-action:hover { background: rgba(127, 127, 127, .18); }
.toast-x {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  margin: -4px -4px -4px 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  box-shadow: none;
  color: inherit;
  opacity: .65;
  font-size: 18px;
  line-height: 1;
}
.toast-x:hover { opacity: 1; background: rgba(127, 127, 127, .18); }
.toast-action:focus-visible, .toast-x:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; box-shadow: none; }
@keyframes ui-toast-in { from { opacity: 0; transform: translateY(8px); } }
@media (max-width: 640px) {
  .toasts {
    left: 12px; right: 12px; width: auto; align-items: center;
    bottom: calc(12px + var(--sticky-bottom, 0px));
    bottom: calc(max(12px, env(safe-area-inset-bottom)) + var(--sticky-bottom, 0px));
  }
}

/* =====================================================================
 * Command palette (⌘K / Ctrl+K) — built by ui.js
 * =================================================================== */
dialog.palette {
  width: min(640px, calc(100vw - 32px));
  max-height: min(560px, calc(100dvh - 96px));
  margin: 12vh auto auto;
  border-radius: var(--radius-xl);
}
dialog.palette[open] { display: flex; flex-direction: column; }
.palette-search {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 54px;
  padding: 0 14px 0 16px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-faint);
  flex: 0 0 auto;
}
.palette-search > svg { width: 18px; height: 18px; flex: 0 0 auto; }
.palette-input,
.palette-search input.palette-input {
  flex: 1 1 auto;
  height: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent none;
  box-shadow: none;
  color: var(--ink);
  font-size: 15px;
  outline: none;
}
.palette-input:focus { box-shadow: none; }
/* The "esc" chip is a real button: on a phone it is the only way out. */
.palette-close {
  flex: 0 0 auto;
  height: auto;
  padding: 2px;
  border: 0;
  border-radius: 6px;
  background: none;
  box-shadow: none;
  color: var(--ink-muted);
  font-size: var(--fs-sm);
  font-weight: 550;
}
.palette-close:hover { background: none; color: var(--ink); }
.palette-close-txt { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.palette-close:hover .kbd { border-color: var(--line-strong); color: var(--ink); }
.palette-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 6px;
  overscroll-behavior: contain;
}
.palette-group-label {
  padding: 10px 10px 4px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.palette-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 38px;
  padding: 7px 10px;
  border-radius: var(--radius);
  color: var(--ink-2);
  font-size: 13.5px;
  cursor: pointer;
  scroll-margin: 6px;
}
.palette-item:hover { color: var(--ink); }
.palette-item[aria-selected="true"] { background: var(--accent-soft); color: var(--ink); }
/* The active option needs more than a pale fill: a solid accent bar. */
.palette-item[aria-selected="true"]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}
.palette-item-ico {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border-radius: var(--radius-sm);
  background: var(--panel-inset);
  color: var(--ink-muted);
}
.palette-item[aria-selected="true"] .palette-item-ico { background: var(--panel); color: var(--accent-ink); }
.palette-item-ico svg { width: 15px; height: 15px; }
.palette-item-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.palette-item-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-item-label mark { background: none; color: var(--accent-ink); font-weight: 650; }
.palette-item-sub { font-size: var(--fs-xs); color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-item-meta { flex: 0 0 auto; font-size: var(--fs-xs); color: var(--ink-muted); }
.palette-empty { padding: 28px 16px; text-align: center; color: var(--ink-muted); font-size: var(--fs-sm); }
.palette-status { padding: 6px 10px 2px; font-size: var(--fs-xs); color: var(--ink-muted); }
.palette-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  padding: 8px 14px;
  border-top: 1px solid var(--line);
  background: var(--panel-soft);
  color: var(--ink-muted);
  font-size: var(--fs-xs);
  flex: 0 0 auto;
}
.palette-foot span { display: inline-flex; align-items: center; gap: 4px; }
@media (max-width: 640px) {
  dialog.palette { width: 100%; max-height: none; height: 100%; margin: 0; border: 0; border-radius: 0; }
  .palette-foot { display: none; }
  /* Full screen, so no backdrop to tap: the close control becomes "Cancel". */
  .palette-search { gap: 8px; padding-right: 4px; }
  .palette-close { min-width: 64px; height: 40px; padding: 0 10px; color: var(--link); font-size: 15px; }
  .palette-close .kbd { display: none; }
  .palette-close .palette-close-txt { position: static; width: auto; height: auto; overflow: visible; clip: auto; white-space: nowrap; }
}

/* Shortcuts help (?) */
.shortcut-list { display: grid; grid-template-columns: auto 1fr; gap: 10px 16px; margin: 0; align-items: center; }
.shortcut-list dt { display: flex; gap: 3px; justify-content: flex-end; }
.shortcut-list dd { margin: 0; color: var(--ink-2); }

/* =====================================================================
 * Chart card (.chart-card) + chart marks
 *
 *   section.chart-card
 *     .chart-head > div(h2.chart-title + p.chart-sub) + .chart-tools(.seg …)
 *     .chart-plot > svg (marks: g.c-band[data-tip][data-drill] > rect.c-hit + rect.c-bar)
 *     .chart-table[hidden] (the table-view twin)
 *     .chart-legend > .key(.sw | .sw-partial | .sw-line | .sw-ref + label)
 *     .chart-foot (note; "Click a bar to see the rows behind it.")
 * Marks: bars <= 24px, 4px rounded data end, 2px gap; lines 2px round;
 * markers r >= 4 with a 2px surface ring; grid/axes 1px solid; the
 * reference line is solid 1px --chart-ref with a direct label.
 * Text never wears a series color.
 * =================================================================== */
.chart-card {
  margin: 0 0 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  min-width: 0;
}
.chart-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 12px;
  padding: 14px 18px 4px;
}
.chart-title { margin: 0; font-size: var(--fs-h3); font-weight: 600; line-height: 1.35; letter-spacing: -.005em; }
.chart-sub { margin: 2px 0 0; font-size: 12.5px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.chart-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.chart-plot { position: relative; padding: 8px 14px 0 8px; min-width: 0; transition: opacity var(--t-base); }
.chart-plot > svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart-plot.is-loading { opacity: .5; }
.chart-table { padding: 8px 0 0; }
.chart-table .table-wrap { border-width: 1px 0; border-radius: 0; box-shadow: none; }
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  padding: 10px 18px 0;
  font-size: var(--fs-xs);
  color: var(--ink-muted);
}
.key { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.sw { width: 10px; height: 10px; border-radius: 2px; background: var(--chart-bar); flex: 0 0 auto; }
.sw-partial { background: var(--chart-bar-partial); box-shadow: inset 0 0 0 1px var(--chart-bar); }
.sw-line { width: 14px; height: 0; border-top: 2px solid var(--chart-bar); border-radius: 0; background: none; }
.sw-ref { width: 14px; height: 0; border-top: 1px solid var(--chart-ref); border-radius: 0; background: none; }
.sw-dot { width: 8px; height: 8px; border-radius: 50%; }
.chart-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  padding: 8px 18px 14px;
  font-size: var(--fs-xs);
  color: var(--ink-muted);
}
.chart-foot .chart-hint { margin-left: auto; color: var(--ink-muted); }
.chart-empty { padding: 40px 0; text-align: center; color: var(--ink-muted); font-size: var(--fs-sm); }
/* UI.chart.bars marks a card with nothing to chart: no legend, no "click a bar" hint. */
.chart-card.is-empty .chart-legend,
.chart-card.is-empty .chart-foot [data-chart-only] { display: none; }
@media (max-width: 720px) {
  .chart-head { padding: 12px 14px 2px; }
  .chart-plot { padding: 6px 2px 0 4px; }   /* 13 weekly bands stay >= 24px wide at 390px */
  .chart-legend { padding: 8px 14px 0; }
  .chart-foot { padding: 6px 14px 12px; }
  .chart-foot .chart-hint { margin-left: 0; }
}

/* SVG primitives */
.chart-plot text, .c-tick, .c-xlab, .c-ref-lbl, .c-val { font-family: var(--font-sans); }
.c-grid { stroke: var(--chart-grid); stroke-width: 1; shape-rendering: crispEdges; }
.c-axis { stroke: var(--chart-axis); stroke-width: 1; shape-rendering: crispEdges; }
.c-tick { fill: var(--ink-muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.c-tick-y { text-anchor: end; }
.c-xlab { fill: var(--ink-muted); font-size: 11px; text-anchor: middle; font-variant-numeric: tabular-nums; }
.c-val { fill: var(--ink-muted); font-size: 11px; font-weight: 600; text-anchor: middle; font-variant-numeric: tabular-nums; }
.c-bar { fill: var(--chart-bar); transition: fill var(--t-fast), opacity var(--t-fast); }
.c-bar.is-partial { fill: var(--chart-bar-partial); stroke: var(--chart-bar); stroke-width: 1; }
.c-line { fill: none; stroke: var(--chart-bar); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.c-area { fill: var(--chart-bar); opacity: .1; }
.c-dot { fill: var(--chart-bar); stroke: var(--panel); stroke-width: 2; }
.c-ref { stroke: var(--chart-ref); stroke-width: 1; shape-rendering: crispEdges; }
/* Direct labels keep a panel-colored halo, so they stay legible over a bar. */
.c-ref-lbl { fill: var(--ink-muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.c-ref-lbl, .c-val { paint-order: stroke; stroke: var(--panel); stroke-width: 3px; stroke-linejoin: round; }
.c-ref-lbl.is-end { text-anchor: end; }
.c-hit { fill: transparent; }
.c-band { outline: none; }
.c-band[data-drill], .c-band[role="button"] { cursor: pointer; }
.c-band:hover .c-bar, .c-band:focus-visible .c-bar, .c-band.is-hover .c-bar { fill: var(--chart-bar-hover); }
.c-band:hover .c-bar.is-partial,
.c-band:focus-visible .c-bar.is-partial,
.c-band.is-hover .c-bar.is-partial { fill: var(--accent-soft-2); }
/* Keyboard focus on a mark: its whole column, outlined in the accent. */
.c-band:focus-visible .c-hit { fill: var(--accent-soft); fill-opacity: .7; stroke: var(--accent); stroke-width: 1.5; }
.c-band:hover .c-dot, .c-band:focus-visible .c-dot, .c-band.is-hover .c-dot { fill: var(--chart-bar-hover); }
/* Line marks (assets/chart-line.js — UI.chart.line). The period still in
   progress is drawn like the partial bar and the legend's .sw-partial swatch:
   a light fill inside an accent outline. The run into it is quieter. */
.c-cross { stroke: var(--chart-axis); stroke-width: 1; shape-rendering: crispEdges; pointer-events: none; }
.c-line.is-partial { stroke: color-mix(in srgb, var(--chart-bar) 50%, var(--panel)); }   /* solid, not opacity: the run's two halves overlap at their round caps */
.c-dot.is-partial { fill: var(--chart-bar-partial); stroke: var(--chart-bar); stroke-width: 1.5; }
.c-band:hover .c-dot.is-partial,
.c-band:focus-visible .c-dot.is-partial,
.c-band.is-hover .c-dot.is-partial { fill: var(--accent-soft-2); }
.chart-plot.has-hover .c-band:not(:hover):not(.is-hover) .c-bar { opacity: .55; }
.c-s1 { fill: var(--series-1); } .c-s2 { fill: var(--series-2); } .c-s3 { fill: var(--series-3); } .c-s4 { fill: var(--series-4); }
.c-s5 { fill: var(--series-5); } .c-s6 { fill: var(--series-6); } .c-s7 { fill: var(--series-7); } .c-s8 { fill: var(--series-8); }

/* Tooltip (.tip) — value first and strong, label second. UI.chart.tooltip()
   positions it inside the nearest .chart-plot. */
.tip {
  position: absolute;
  z-index: 30;
  left: 0;
  top: 0;
  width: max-content;              /* never narrowed by where it sits in the plot */
  min-width: 120px;
  max-width: 260px;
  padding: 8px 11px;
  border: 1px solid var(--inverse-line);
  border-radius: var(--radius);
  background: var(--inverse-bg);
  color: var(--inverse-ink);
  box-shadow: var(--shadow-pop);
  font-size: var(--fs-xs);
  line-height: 1.35;
  pointer-events: none;
  transform: translate(-50%, calc(-100% - 10px));
  white-space: normal;
}
.tip::after {
  content: "";
  position: absolute;
  left: var(--tip-arrow, 50%);
  bottom: -4px;
  width: 9px;
  height: 9px;
  background: inherit;
  border: solid var(--inverse-line);
  border-width: 0 1px 1px 0;
  border-radius: 0 0 2px 0;
  transform: translateX(-50%) rotate(45deg);
}
.tip.is-below { transform: translate(-50%, 10px); }
.tip.is-below::after { top: -5px; bottom: auto; border-width: 1px 0 0 1px; border-radius: 2px 0 0 0; }
/* Beside the mark (no room above it inside the plot). */
.tip.is-right { transform: translate(12px, 0); }
.tip.is-left  { transform: translate(calc(-100% - 12px), 0); }
.tip.is-right::after, .tip.is-left::after { top: var(--tip-arrow-y, 14px); bottom: auto; }
.tip.is-right::after { left: -5px; border-width: 0 0 1px 1px; border-radius: 0 0 0 2px; transform: translateY(-50%) rotate(45deg); }
.tip.is-left::after  { left: auto; right: -5px; border-width: 1px 1px 0 0; border-radius: 0 2px 0 0; transform: translateY(-50%) rotate(45deg); }
.tip-v { display: block; margin-bottom: 2px; font-size: 15px; font-weight: 650; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.tip-l { display: block; color: var(--inverse-muted); }
.tip-cta { display: block; margin-top: 4px; color: var(--inverse-muted); font-size: var(--fs-2xs); }

/* ── Hours chart (assets/hours-chart.js): its card is a .chart-card (the
   .hours-chart-wrap class is only a script hook). On the Time page the
   drill-down edits in place: each row's project, date, hours and
   description, with a per-row Update button. ──────────────────────────── */
.hc-edit-table td { vertical-align: middle; }
.hc-edit-table .hc-edit-date,
.hc-edit-table .hc-edit-hours,
.hc-edit-table .hc-edit-project,
.hc-edit-table .hc-edit-desc {
  font-size: var(--fs-sm);
  color: var(--ink);
  background-color: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 5px 8px;
}
.hc-edit-table .hc-edit-project { padding-right: 28px; }
.hc-edit-table .hc-edit-date { width: 9rem; }
.hc-edit-table .hc-edit-project { width: 100%; min-width: 12rem; }
.hc-edit-table .hc-edit-hours { width: 5.5rem; text-align: right; }
.hc-edit-table .hc-edit-desc { width: 100%; min-width: 16rem; min-height: 0; resize: vertical; }
.hc-edit-table .hc-edit-act { white-space: nowrap; }
/* A row's status ("Saved ✓", or why it didn't save) sits under its Update
   button and wraps: the cell is nowrap, and a long error on one line would
   widen the table past the modal. */
.hc-edit-status { display: block; width: max-content; max-width: 12rem; margin: 4px 0 0 auto; font-size: var(--fs-xs); line-height: 1.35; white-space: normal; text-align: right; }
.hc-edit-status:empty { margin-top: 0; }
.hc-edit-ok { color: var(--good); }
.hc-edit-err { color: var(--bad); }
.hc-edit-note { font-size: var(--fs-xs); color: var(--ink-muted); font-style: italic; }
.hc-edit-gone { opacity: 0; transition: opacity .8s ease; }
/* Plain-text client label above each row's project (keeps the cell narrow). */
.hc-edit-client {
  max-width: 16rem;
  margin-bottom: 2px;
  font-size: var(--fs-2xs);
  line-height: 1.2;
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Highlight a row with unsaved edits (amber = pending). */
.hc-edit-table tr.hc-edit-dirty > td { background: var(--warn-bg); }
.hc-edit-table tr.hc-edit-dirty > td:first-child { box-shadow: inset 3px 0 0 var(--warn-solid); }
/* "Update all" is the last button in the modal foot (no auto margin of its
   own: the foot note's margin-right: auto already pushes the buttons right). */
.hc-edit-updateall:disabled { opacity: .45; cursor: default; }

/* =====================================================================
 * Page sections — page-specific vocabulary, on the shared tokens
 * =================================================================== */

/* ── Invoice running totals (sticky panel) ───────────────────────────── */
.invoice-totals {
  position: sticky;
  top: calc(var(--top-h) + 8px);
  z-index: 5;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 5px 28px;
  margin: 0 0 20px;
  padding: 14px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  font-variant-numeric: tabular-nums;
}
.invoice-totals-row { display: contents; }
.invoice-totals-label  { color: var(--ink-muted); font-size: var(--fs-sm); }
.invoice-totals-amount { text-align: right; font-weight: 600; color: var(--ink); }
.invoice-totals-grand .invoice-totals-label  { color: var(--ink); font-size: 14.5px; font-weight: 600; }
.invoice-totals-grand .invoice-totals-amount { font-size: 20px; letter-spacing: -.01em; font-variant-numeric: normal; }
.invoice-totals-meta {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  margin-top: 5px;
  padding-top: 8px;
  border-top: 1px solid var(--line-soft);
  color: var(--ink-muted);
  font-size: var(--fs-xs);
}

/* ── Active-timer banner (views/timer_banner.php) ────────────────────── */
.timer-banner {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px 16px;
  margin: 0 0 20px;
  padding: 11px 16px 11px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
/* Idle is simpler: icon | text | project select | start button */
.timer-banner-idle { grid-template-columns: auto minmax(0, 1fr) minmax(14rem, 22rem) auto; }
/* Active: icon | text | controls | actions; description on its own row */
.timer-banner-active { grid-template-columns: auto minmax(12rem, 1fr) auto auto; border-color: var(--accent-soft-2); }
.timer-banner-over   { border-color: var(--warn-line); }
@media (max-width: 900px) {
  .timer-banner,
  .timer-banner-active { grid-template-columns: auto minmax(0, 1fr); }
  .timer-banner-controls,
  .timer-banner-actions,
  .timer-banner-desc { grid-column: 1 / -1; }
  /* Idle: icon + text on row 1; the project select and Start share row 2
     (Start is a direct child, so it must not land in the icon column). */
  .timer-banner-idle { grid-template-columns: auto minmax(0, 1fr) auto; }
  .timer-banner-idle .timer-banner-project { grid-column: 1 / 3; }
  .timer-banner-idle > .timer-banner-primary { grid-column: 3; }
}
/* Left state bar */
.timer-banner::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--line-strong);
  transition: background var(--t-base);
}
.timer-banner-active::before { background: var(--accent); }
.timer-banner-over::before   { background: var(--warn-solid); }
/* Icon — circular, SVG inside, centered */
.timer-banner-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  align-self: center;
  border-radius: 50%;
  background: var(--panel-inset);
  color: var(--ink-muted);
}
.timer-banner-icon svg { display: block; width: 17px; height: 17px; }
.timer-banner-active .timer-banner-icon { background: var(--accent-soft); color: var(--accent); }
.timer-banner-over   .timer-banner-icon { background: var(--warn-bg); color: var(--warn); }
.timer-banner-active .timer-banner-icon::after,
.timer-banner-over   .timer-banner-icon::after {
  content: "";
  position: absolute;
  top: -1px;
  right: -1px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ok-solid);
  border: 2px solid var(--panel);
  animation: timer-pulse 1.6s ease-in-out infinite;
}
.timer-banner-over .timer-banner-icon::after { background: var(--warn-solid); }
@keyframes timer-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.3); opacity: .6; }
}
.timer-banner-text { min-width: 0; }
.timer-banner-title {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -.005em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.timer-banner-title strong { font-weight: 650; }
.timer-banner-sep { color: var(--ink-faint); margin: 0 4px; font-weight: 400; }
.timer-banner-meta {
  margin-top: 2px;
  color: var(--ink-muted);
  font-size: 12.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.timer-banner-dot { color: var(--ink-faint); margin: 0 4px; }
.timer-banner select,
.timer-banner input[type=text],
.timer-banner input[type=time] {
  padding-top: 6px;
  padding-bottom: 6px;
  font-size: 13.5px;
}
.timer-banner-project { width: 100%; min-width: 0; }
/* Start / End time field with inline label */
.timer-banner-end {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 4px 0 9px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.timer-banner-end > span {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.timer-banner-end input[type=time] {
  width: 6.2rem;
  height: 30px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  box-shadow: none;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.timer-banner-end input[type=time]:focus { box-shadow: none; }
.timer-banner-end:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.timer-banner-controls { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 8px; }
/* Quick-adjust: the segmented-control look */
.timer-banner-quick {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  background: var(--panel-inset);
  border: 1px solid var(--line);
  border-radius: 7px;
}
.timer-banner-quick button {
  padding: 3px 8px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  box-shadow: none;
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.timer-banner-quick button:hover { background: var(--panel); color: var(--ink); box-shadow: var(--shadow-1); }
.timer-banner-quick button:focus-visible { box-shadow: var(--focus-inset); }
.timer-banner-active .timer-banner-desc { grid-column: 2 / -1; width: 100%; }
.timer-banner-actions { display: inline-flex; align-items: center; gap: 6px; }
.timer-banner-primary {
  padding: 7px 14px;
  background: var(--emphasis);
  color: var(--on-emphasis);
  border: 1px solid transparent;
  font-size: 13.5px;
  font-weight: 600;
}
.timer-banner-primary:hover { background: var(--emphasis-hover); color: var(--on-emphasis); }
.timer-banner-active .timer-banner-primary { background: var(--accent-fill); color: var(--on-accent); }
.timer-banner-active .timer-banner-primary:hover { background: var(--accent-fill-hover); color: var(--on-accent); }
.timer-banner-save {
  padding: 7px 12px;
  background: var(--panel);
  color: var(--ink-2);
  border: 1px solid var(--line);
  font-size: 13.5px;
  font-weight: 550;
}
.timer-banner-save:hover { background: var(--panel-soft); color: var(--ink); border-color: var(--line-strong); }
.timer-banner-cancel {
  padding: 4px 6px;
  border: 0;
  background: none;
  box-shadow: none;
  color: var(--ink-muted);
  font-size: 12.5px;
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.timer-banner-cancel:hover { background: none; color: var(--danger); }

/* ── Expandable-row table (Time — views/time_list.php). A real <table> so
   columns line up; a row toggles a hidden body row under it. .exp-body-text
   is shared with the Todos and Notes lists. ─────────────────────────── */
.exp-table { width: 100%; }
.exp-table td, .exp-table th { padding: 10px 12px; vertical-align: middle; }
.exp-table thead th { padding-top: 10px; padding-bottom: 10px; }
.exp-table .exp-row { cursor: pointer; }
.exp-table .exp-row:hover { background: var(--bg-soft); }
.exp-table .exp-row.open { background: var(--bg-soft); }
/* Expanded body row */
.exp-table .exp-body > td {
  padding: 10px 16px 14px;
  background: var(--bg-soft);
  border-top: 0;
}
.exp-table .exp-body:hover { background: transparent; }
.exp-body-text {
  padding: 4px 0 6px;
  color: var(--ink-2);
  font-size: 13.5px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Attachment lists on the todo form and the todos list. */
.todo-attachment-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 5px 0 8px;
  padding: 0;
  font-size: var(--fs-sm);
}
.todo-attachment { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }

/* Attachment lists — the notes list's expanded row and the edit form. */
.note-attachment-list { list-style: none; display: flex; flex-direction: column; gap: 4px; margin: 8px 0 0; padding: 0; }
.note-attachment { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-size: var(--fs-sm); }
.note-attachment-kind {
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--panel-inset);
  color: var(--ink-muted);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .02em;
  white-space: nowrap;
}
.note-attachment-kind.kind-recording_video { background: var(--danger-bg); color: var(--danger); }
.note-attachment-kind.kind-recording_audio { background: var(--warn-bg);   color: var(--warn); }
.note-attachment-kind.kind-chat            { background: var(--accent-soft); color: var(--accent-ink); }
.note-attachment-kind.kind-config          { background: var(--info-bg);   color: var(--info); }
/* Meeting fields on the note form — hidden unless kind=meeting. */
.note-meeting-fields { display: none; margin: 0 0 16px; padding: 12px 16px; border: 1px solid var(--line); border-radius: var(--radius); }
.note-meeting-fields legend { padding: 0 6px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-muted); }
.note-form-meeting .note-meeting-fields { display: block; }
/* Duration only renders when the meeting note is attached to a project. */
.note-duration-field { display: none; }
.note-form-meeting.note-form-project-target .note-duration-field { display: flex; }
.note-existing-attachments { margin-top: 12px; font-size: var(--fs-sm); }
.note-existing-attachments .note-attachment-list { padding: 4px 0; }

/* Drag-and-drop file picker (note form, todo form, files). */
.note-dropzone, .dropzone {
  position: relative;
  padding: 16px 18px;
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--panel);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.note-dropzone:hover, .dropzone:hover { border-color: var(--accent-2); background: var(--panel-soft); }
.note-dropzone.is-over, .dropzone.is-over { border-color: var(--accent); background: var(--accent-soft); }
/* dropzone.js moves the real file input off-screen; the zone shows its focus. */
.note-dropzone:focus-within, .dropzone:focus-within { border-color: var(--accent); box-shadow: var(--focus-ring); }
.note-dropzone-label, .dropzone-label { display: block; font-weight: 550; color: var(--ink-2); }
.note-dropzone-hint, .dropzone-hint { display: block; margin-top: 4px; font-size: var(--fs-sm); font-weight: 400; color: var(--ink-muted); }
.note-file-preview, .dropzone-preview {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 8px 0 0;
  padding: 0;
  font-size: var(--fs-sm);
  color: var(--ink-2);
}
.note-file-preview[hidden], .dropzone-preview[hidden] { display: none; }
.note-file-preview li::before, .dropzone-preview li::before { content: "📎 "; }
/* Submit: spinner + upload progress while the XHR is in flight. */
.note-form-submit { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
/* Unsaved-draft restore banner (note-draft.js) + the autosave status line. */
.note-draft-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
  padding: 9px 12px;
  border: 1px solid var(--warn-line);
  border-radius: var(--radius);
  background: var(--warn-bg);
  color: var(--ink);
  font-size: 13.5px;
}
.note-draft-banner-msg { flex: 1 1 16rem; }
.note-draft-status { min-height: 1em; font-size: var(--fs-xs); color: var(--ink-muted); }
/* [hidden] must beat the display rules below, else the spinner runs and
   the progress text shows from page load. */
.note-spinner[hidden],
.note-upload-progress[hidden] { display: none !important; }
.note-spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-left: 8px;
  border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: #fff;
  border-radius: 50%;
  vertical-align: -2px;
  animation: note-spin .7s linear infinite;
}
@keyframes note-spin { to { transform: rotate(360deg); } }
.note-upload-progress { display: flex; align-items: center; gap: 8px; min-width: 260px; }
.note-upload-progress progress {
  -webkit-appearance: none;
  appearance: none;
  width: 240px;
  height: 6px;
  border: none;
  border-radius: 999px;
  background: var(--panel-inset);
  overflow: hidden;
}
.note-upload-progress progress::-webkit-progress-bar { background: var(--panel-inset); border-radius: 999px; }
.note-upload-progress progress::-webkit-progress-value { background: var(--accent); border-radius: 999px; transition: width .15s ease; }
.note-upload-progress progress::-moz-progress-bar { background: var(--accent); border-radius: 999px; }

/* Per-client upload URL widget on the client edit page */
.upload-share {
  margin-top: 28px;
  padding: 18px 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.upload-share h2 { margin: 0 0 5px; }
.upload-share-row { display: flex; gap: 8px; margin-top: 12px; }
.upload-share-row input { flex: 1; font-family: var(--font-mono); font-size: var(--fs-sm); background-color: var(--panel-soft); }

/* ── Business units (managed inline on the client form) ──────────────── */
.bu-list { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.bu-card {
  padding: 12px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.bu-card.muted { opacity: .72; }
.bu-card-head { display: grid; grid-template-columns: minmax(0, 1fr) 8rem auto; align-items: end; gap: 10px; }
.bu-card-head label { gap: 4px; }
.bu-card-meta { display: flex; align-items: center; gap: 12px; padding-bottom: 8px; font-size: var(--fs-xs); white-space: nowrap; }
.bu-card-meta .checkbox-inline { font-size: var(--fs-xs); flex-wrap: nowrap; }
.bu-bill { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line-soft); }
.bu-bill > summary { cursor: pointer; list-style: none; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2); }
.bu-bill > summary::-webkit-details-marker { display: none; }
.bu-bill > summary::before { content: "\25B8"; margin-right: 5px; color: var(--ink-faint); }
.bu-bill[open] > summary::before { content: "\25BE"; }
.bu-bill > summary .muted { font-weight: 400; }
.bu-bill > .form-grid, .bu-bill > label { margin-top: 8px; }
.bu-add { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.bu-add input { width: auto; background-color: var(--panel-soft); }
.bu-add #newBuName { flex: 1 1 16rem; }
.bu-add #newBuCode { flex: 0 1 9rem; }
@media (max-width: 720px) {
  .bu-card-head { grid-template-columns: minmax(0, 1fr); }
  .bu-card-meta { padding-bottom: 0; }
}

/* ── Finance views — shared building blocks ──────────────────────────── */
.panel {
  padding: 16px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.card-form {
  padding: 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.mono { font-family: var(--font-mono); font-size: .88em; }
.detail-grid {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px 16px;
  margin: 0;
  font-size: 13.5px;
}
.detail-grid dt { color: var(--ink-muted); font-weight: 500; }
.detail-grid dd { margin: 0; color: var(--ink-2); min-width: 0; overflow-wrap: anywhere; }

/* ── Banking ledger — the densest table in the app. The description takes
   whatever width is left; the page runs past the reading cap (.main--wide)
   because a coding worklist is the one screen worth filling. ─────────── */
.txn-table { table-layout: fixed; min-width: 900px; }
.txn-table th, .txn-table td { padding: 6px 9px; vertical-align: middle; line-height: 1.35; }
.txn-table thead th { padding-top: 9px; padding-bottom: 9px; white-space: nowrap; }
.txn-c-date   { width: 5rem; white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--ink-muted); }
.txn-c-desc   { width: auto; }
.txn-c-acct   { width: 5.75rem; white-space: nowrap; }
.txn-c-amt    { width: 7.75rem; }
.txn-c-cat    { width: 15.5rem; }
.txn-c-client { width: 10.5rem; }
.txn-c-act    { width: 4.75rem; }
.txn-acct { font-size: var(--fs-xs); color: var(--ink-muted); cursor: help; font-variant-numeric: tabular-nums; }
/* Chase's ACH descriptions: clamp to two lines, whole string in the title. */
.txn-c-desc > .txn-memo,
.txn-c-desc > [data-row-state] { display: block; margin-top: 2px; }
.txn-c-desc > [data-row-state]:empty { display: none; }
.txn-desc {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.txn-memo { font-size: var(--fs-xs); }
/* The selects carry the row's real work, so they get the cell's full width. */
.txn-table select { width: 100%; max-width: none; padding-top: 4px; padding-bottom: 4px; font-size: var(--fs-sm); }
.txn-table .autosave-note { display: block; margin-top: 2px; font-size: var(--fs-2xs); color: var(--ink-muted); }
.txn-table .autosave-note[data-kind="ok"] { color: var(--ok); }
.txn-table .autosave-note[data-kind="error"] { color: var(--danger); font-weight: 550; }
/* Sortable headers are links (the sort happens on the server). */
.txn-table th.th-sortable { padding: 0; }
.txn-table th.th-sortable a {
  display: block;
  padding: 9px 9px;
  color: inherit;
  font: inherit;
  white-space: nowrap;
}
.txn-table th.th-sortable a:hover { color: var(--accent-ink); background: var(--panel-soft); }
.txn-table th.num.th-sortable a { text-align: right; }

/* =====================================================================
 * Phones — page-level tightening (components carry their own rules)
 * =================================================================== */
@media (max-width: 720px) {
  h2 { margin-top: 24px; }
  .toolbar { gap: 10px 12px; }
  .toolbar-actions { flex-wrap: wrap; }
  .exp-table td, .exp-table th { padding: 8px 9px; }
  /* iOS Safari zooms the whole page when a control under 16px takes focus.
     Component and page rules size their controls with more specific
     selectors (and some scripts inject their own styles), so this one
     invariant is !important rather than a specificity race. */
  input:not([type]),
  input:is([type="text"], [type="email"], [type="password"], [type="tel"], [type="url"],
           [type="number"], [type="date"], [type="month"], [type="week"], [type="time"],
           [type="search"], [type="datetime-local"]),
  textarea,
  select { font-size: 16px !important; }
  .timer-banner-end input[type=time] { width: 7.4rem; }
}

/* =====================================================================
 * Touch — 40px icon targets; 36px text buttons, segments and chips,
 * wherever the pointer is coarse or the screen is phone-sized.
 * =================================================================== */
@media (pointer: coarse), (max-width: 720px) {
  .btn-sm, .btn-small,
  .actions-cell a, .actions-cell button,
  .filter-row:not(:has(.filter-chip.active)) .filter-chip,
  .toast-action { min-height: 36px; }
  .btn-icon, .btn-icon.btn-sm, .btn-icon.btn-small,
  .toast-x { width: 40px; height: 40px; min-width: 40px; }
  .modal-head > .btn-icon { margin: -8px -8px -6px 0; }
  .seg > button, .seg > a,
  .seg-sm > button, .seg-sm > a { min-height: 36px; padding-left: 12px; padding-right: 12px; }
  .timer-banner-quick button { min-height: 36px; padding-left: 10px; padding-right: 10px; }
  .menu-list a, .menu-list button, .sidebar nav a { min-height: 40px; }
  .palette-item { min-height: 44px; }
  .chip { height: 36px; }
  .chip-clear { display: inline-flex; align-items: center; min-height: 36px; padding: 0 6px; }
  .access-log > summary { min-width: 36px; min-height: 36px; margin-top: -8px; margin-bottom: -8px; }
  .nav-group-toggle, .theme-toggle, .rail-toggle { height: 36px; }
  .sidebar-search { height: 40px; }
  .sel, .search { height: 40px; }
  .sel select, .sel input, .search input, .search input[type="search"] { height: 38px; }
}
/* Keyboard-only affordances (the "?" help, shortcut hints) on touch screens. */
@media (hover: none) and (pointer: coarse) {
  .hide-touch { display: none !important; }
}

/* =====================================================================
 * Print — the page, not the chrome
 * =================================================================== */
@media print {
  body.has-nav { display: block; background: #fff; }
  .sidebar, .mobile-topbar, .nav-scrim, .timer-shell, .toasts, .skip-link,
  .filterbar, .page-actions, .toolbar-actions, .cell-act, .menu { display: none !important; }
  .main { max-width: none; padding: 0; }
  .table-wrap, .table-panel, .chart-card, .stat, .card { box-shadow: none; }
}

/* =====================================================================
 * Reduced motion
 * =================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
