/* ============================================================================================
   STYLES PROPERTY MANAGEMENT — UNIFIED DESIGN SYSTEM  (single source of truth)
   --------------------------------------------------------------------------------------------
   ESTATE language, formalized. Cream paper, gold accent, dark-ink shell, Cormorant Garamond
   display + Jost utility labels + Lora body. This file is the ONE stylesheet every lane aligns
   to: it (1) re-declares the canonical design tokens (identical values to the inline server.py
   page() block — zero visual regression), (2) adds the systematic scale tokens + a dark-safe
   theme + a motion system, and (3) ships the shared components that were missing: page-header,
   tabs, empty / loading / error states, skeleton, toast — plus refinements to card, table,
   button, field, badge.

   LOAD ORDER: injected once at the END of <head>, AFTER the inline base block and {head_extra_css},
   so its intentional refinements win. Served same-origin at /assets/design-system.css (CSP
   style-src 'self' already permits it).

   SPEC + USAGE: docs/pms/DESIGN_SYSTEM.md  ·  owner: Style Agent (frontend-design lane)
   RULE FOR LANES: build with these CLASSES and TOKENS. Do not hardcode hex colors, px shadows,
   or ad-hoc greens — reach for a --token. New shared UI patterns get added HERE, not per-module.
   ============================================================================================ */

/* ── 1 · TOKENS ─────────────────────────────────────────────────────────────────────────────
   Canonical estate tokens (re-declared identically so this file is self-contained and portable)
   PLUS the systematic scales (type / space / radius / elevation / motion / z). */
:root {
  /* ink + paper (estate, canonical) */
  --ink:#1A1814; --ink-soft:#3D3833; --ink-mute:#5A534A;
  --paper:#F6F1E7; --paper-elev:#FBF7EE; --paper-deep:#ECE5D5;
  --rule:#D9CFB8; --rule-soft:#E8DFC8;
  --gold:#B58A3E; --gold-deep:#73531A; --gold-soft:#E3BF7C;
  --shell:#1A1814; --shell-text:#F6F1E7;
  /* a white that flips in dark mode — use instead of literal #fff on token-driven surfaces */
  --on-accent:#FFFFFF;

  /* semantic state (estate, canonical) */
  --state-good:#1F5F38; --state-good-bg:#DDEDD9;
  --state-warn:#7A4A00; --state-warn-bg:#F6E6C2;
  --state-bad:#B0312A;  --state-bad-bg:#F4D7D2;
  --state-info:#2C5478; --state-info-bg:#DBE6F0;

  /* type families (estate, canonical) */
  --font-display:'Cormorant Garamond','EB Garamond',Georgia,serif;
  --font-body:'Lora',Georgia,'Times New Roman',serif;
  --font-util:'Jost','Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-mono:Consolas,'SFMono-Regular',ui-monospace,monospace;

  /* type scale */
  --text-2xs:10px; --text-xs:11px; --text-sm:12.5px; --text-md:15px;
  --text-lg:18px; --text-xl:22px; --text-2xl:28px; --text-3xl:40px; --text-4xl:58px;
  --tracking-label:.12em; --tracking-eyebrow:.14em;

  /* spacing (4px base) */
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px; --space-5:20px;
  --space-6:24px; --space-8:32px; --space-10:40px; --space-12:48px; --space-16:64px;

  /* radius */
  --radius-xs:3px; --radius-sm:4px; --radius-md:6px; --radius-lg:8px; --radius-xl:10px; --radius-pill:999px;

  /* elevation — warm-tinted to match the paper, NOT neutral gray */
  --shadow-soft:0 1px 0 rgba(26,24,20,0.04);
  --shadow-0:0 1px 0 rgba(26,24,20,.04);
  --shadow-1:0 1px 2px rgba(26,24,20,.06),0 1px 0 rgba(26,24,20,.03);
  --shadow-2:0 4px 14px rgba(26,24,20,.08);
  --shadow-3:0 12px 32px rgba(26,24,20,.12);
  --shadow-4:0 22px 60px rgba(26,24,20,.13);
  --shadow-focus:0 0 0 3px rgba(181,138,62,.28);

  /* motion */
  --dur-fast:.12s; --dur-base:.18s; --dur-slow:.32s;
  --ease-standard:cubic-bezier(.2,.6,.2,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-in-out:cubic-bezier(.65,0,.35,1);

  /* z-index ladder */
  --z-sticky:40; --z-nav:50; --z-dropdown:1200; --z-toast:1600; --z-modal:2000;

  /* layout */
  --col-max:1280px; --col-narrow:760px; --col-resident:1080px;

  /* brand mirror (estate) */
  --brand-primary:#1A1814; --brand-accent:#B58A3E; --brand-paper:#F6F1E7; --brand-ink:#1A1814;
}

/* ── 2 · DARK-SAFE THEME ────────────────────────────────────────────────────────────────────
   Opt-in via <html data-theme="dark"> (a single-attribute flip — Shell/orchestrator wire the
   toggle). Components are token-driven, so remapping the surface/ink tokens flips the whole app
   without touching component CSS. Warm near-black surfaces keep the estate character; gold stays.
   NOTE: spots that still hardcode #fff / rgba(255,255,255,…) won't flip — those are a tracked
   token-migration follow-on (see DESIGN_SYSTEM.md §dark). */
:root[data-theme="dark"] {
  color-scheme:dark;
  --ink:#F3ECDC; --ink-soft:#D9CFBA; --ink-mute:#A99C83;
  --paper:#15130E; --paper-elev:#1E1B14; --paper-deep:#100E0A;
  --rule:#39321F; --rule-soft:#2B2618;
  --gold:#D2A451; --gold-deep:#E3BF7C; --gold-soft:#F0D8A6;
  --shell:#100E0A; --shell-text:#F3ECDC;
  --on-accent:#15130E;

  --state-good:#7FC79A; --state-good-bg:#16291D;
  --state-warn:#E0B24A; --state-warn-bg:#2E2410;
  --state-bad:#E8867E;  --state-bad-bg:#2E1714;
  --state-info:#7FB1DE; --state-info-bg:#142331;

  --shadow-soft:0 1px 0 rgba(0,0,0,.3);
  --shadow-0:0 1px 0 rgba(0,0,0,.3);
  --shadow-1:0 1px 2px rgba(0,0,0,.4),0 1px 0 rgba(0,0,0,.3);
  --shadow-2:0 4px 14px rgba(0,0,0,.45);
  --shadow-3:0 12px 32px rgba(0,0,0,.55);
  --shadow-4:0 22px 60px rgba(0,0,0,.6);
  --shadow-focus:0 0 0 3px rgba(210,164,81,.34);

  --brand-paper:#15130E;
}
:root[data-theme="dark"] body { background:var(--paper); color:var(--ink-soft); }

/* ── 3 · BASE REFINEMENTS ───────────────────────────────────────────────────────────────────
   Light-touch upgrades to the global baseline. Same token names → applies app-wide. */
::selection { background:rgba(181,138,62,.24); color:var(--ink); }
:focus-visible { outline:2px solid var(--gold-deep); outline-offset:2px; border-radius:var(--radius-xs); }
html { scroll-behavior:smooth; }
a { transition:color var(--dur-fast) var(--ease-standard); }
.muted { color:var(--ink-mute); }
.tnum { font-variant-numeric:tabular-nums lining-nums; }

/* Money semantics — canonical, retires ad-hoc inline color on figures. */
.money { font-variant-numeric:tabular-nums lining-nums; white-space:nowrap; }
.money--due,.money--neg { color:var(--state-bad); font-weight:600; }
.money--credit,.money--pos { color:var(--state-good); font-weight:600; }
.money--zero { color:var(--ink-mute); }

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

/* ── 4 · PAGE HEADER ────────────────────────────────────────────────────────────────────────
   One aligned, full-width, LEFT-aligned header stack: eyebrow → title → sub on the left, actions
   on the right. Sits on the SAME grid/left-edge as the cards below it — directly fixes the
   "centered-narrow-block floating above full-width cards" defect class. Compose with .band. */
.page-header {
  display:flex; align-items:flex-end; justify-content:space-between; gap:var(--space-6);
  flex-wrap:wrap; width:100%; margin:0 0 var(--space-6);
  padding-bottom:var(--space-4); border-bottom:1px solid var(--rule);
}
.page-header__lead { min-width:0; }
.page-header .eyebrow { margin:0 0 var(--space-2); }
.page-header h1, .page-header .page-title { margin:0; }
.page-header__sub { margin:var(--space-2) 0 0; color:var(--ink-soft); font-size:var(--text-md); max-width:72ch; }
.page-header__actions { display:flex; flex-wrap:wrap; gap:var(--space-2); align-items:center; }
.breadcrumb {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2); margin:0 0 var(--space-3);
  color:var(--ink-mute); font-family:var(--font-util); font-size:var(--text-sm); letter-spacing:.04em;
}
.breadcrumb a { color:var(--ink-mute); text-decoration:none; }
.breadcrumb a:hover { color:var(--ink); }
.breadcrumb__sep { opacity:.5; }
@media (max-width:640px){
  .page-header { align-items:flex-start; flex-direction:column; gap:var(--space-3); }
  .page-header__actions { width:100%; }
}

/* ── 5 · CARD ───────────────────────────────────────────────────────────────────────────────
   Aliases + refinements for the canonical surface. .card already exists; these add a header slot
   and density/quiet variants without changing default look. */
.card { background:var(--paper-elev); border:1px solid var(--rule); border-radius:var(--radius-lg);
  padding:var(--space-5); box-shadow:var(--shadow-1); }
.card--pad-lg { padding:var(--space-8); }
.card--pad-sm { padding:var(--space-4); }
.card--quiet { background:var(--paper-deep); box-shadow:none; }
.card--flush { padding:0; overflow:hidden; }
.card--raised { box-shadow:var(--shadow-3); }
.card-head {
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-4);
  margin:0 0 var(--space-4); flex-wrap:wrap;
}
.card-head h2,.card-head h3 { margin:0; font-family:var(--font-display); font-size:var(--text-xl);
  line-height:1.1; color:var(--ink); font-weight:600; }
.card-foot { margin-top:var(--space-4); padding-top:var(--space-4); border-top:1px solid var(--rule-soft);
  display:flex; gap:var(--space-2); flex-wrap:wrap; align-items:center; }
.card-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:var(--space-4); }

/* ── 6 · STAT / KPI ─────────────────────────────────────────────────────────────────────────
   .kpi remains canonical (defined inline). .stat-card is the design-system alias with explicit
   delta semantics so figures don't need inline color. */
.stat-card { display:block; min-height:104px; padding:var(--space-4); text-decoration:none; color:inherit;
  background:var(--paper-elev); border:1px solid var(--rule); border-radius:var(--radius-md); box-shadow:var(--shadow-soft);
  transition:border-color var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard); }
a.stat-card:hover { border-color:var(--gold-deep); transform:translateY(-1px); }
.stat-card__label { margin:0 0 var(--space-2); color:var(--ink-mute); font-family:var(--font-util);
  font-size:var(--text-xs); font-weight:600; letter-spacing:var(--tracking-label); text-transform:uppercase; }
.stat-card__value { color:var(--ink); font-family:var(--font-body); font-size:var(--text-xl); line-height:1.1;
  font-weight:500; font-variant-numeric:tabular-nums lining-nums; }
.stat-card__delta { margin-top:var(--space-2); font-family:var(--font-util); font-size:var(--text-sm); color:var(--ink-mute); }
.stat-card__delta--up { color:var(--state-good); }
.stat-card__delta--down { color:var(--state-bad); }

/* ── 7 · DATA TABLE ─────────────────────────────────────────────────────────────────────────
   .tbl / .tbl-wrap are canonical (inline). These add opt-in zebra + compact density + a crisper
   sticky-header shadow, none of which change a plain .tbl. */
.tbl--zebra tbody tr:nth-child(even) td { background:color-mix(in srgb, var(--paper-deep) 45%, var(--paper-elev)); }
.tbl--compact td { padding:6px 12px; font-size:var(--text-sm); }
.tbl--compact thead th { padding:8px 12px; }
.tbl thead th { box-shadow:inset 0 -1px 0 var(--rule), 0 6px 10px -10px rgba(26,24,20,.4); }
.tbl tbody tr { transition:background var(--dur-fast) var(--ease-standard); }
.tbl-empty-row td { padding:var(--space-10) var(--space-6); text-align:center; color:var(--ink-mute); }

/* ── 8 · BUTTONS ────────────────────────────────────────────────────────────────────────────
   .btn / .btn--gold / .btn--outline / .btn--good are canonical. Add ghost, danger, sizes, icon,
   and a loading state. */
.btn--ghost { background:transparent; color:var(--ink); border-color:transparent; }
.btn--ghost:hover { background:var(--paper-deep); color:var(--ink); filter:none; }
.btn--danger { background:var(--state-bad); border-color:var(--state-bad); color:var(--on-accent); }
.btn--danger:hover { filter:brightness(1.08); color:var(--on-accent); }
.btn--sm { min-height:32px; padding:0 12px; font-size:var(--text-xs); }
.btn--lg { min-height:48px; padding:0 24px; font-size:var(--text-md); }
.btn--block { width:100%; }
.btn--icon { padding:0; width:40px; min-height:40px; }
.btn--sm.btn--icon { width:32px; min-height:32px; }
.btn.is-loading { color:transparent !important; pointer-events:none; position:relative; }
.btn.is-loading::after {
  content:""; position:absolute; width:16px; height:16px; top:calc(50% - 8px); left:calc(50% - 8px);
  border:2px solid currentColor; border-top-color:transparent; border-radius:50%;
  color:var(--on-accent); animation:ds-spin .7s linear infinite;
}
.btn-row { display:flex; flex-wrap:wrap; gap:var(--space-2); align-items:center; }

/* ── 9 · FORM FIELD ─────────────────────────────────────────────────────────────────────────
   .field / .field-inline + inputs are canonical. Add hint, error, and required affordances. */
.field-hint { margin:var(--space-1) 0 0; color:var(--ink-mute); font-family:var(--font-util); font-size:var(--text-xs); }
.field-error { margin:var(--space-1) 0 0; color:var(--state-bad); font-family:var(--font-util);
  font-size:var(--text-xs); font-weight:600; }
.field.is-error input,.field.is-error select,.field.is-error textarea,
.field-inline.is-error input,.field-inline.is-error select,.field-inline.is-error textarea {
  border-color:var(--state-bad); background:color-mix(in srgb, var(--state-bad-bg) 40%, var(--paper-elev));
}
.req::after { content:"*"; color:var(--state-bad); margin-left:3px; font-weight:700; }
.input-group { display:flex; align-items:stretch; }
.input-group > input { border-radius:var(--radius-sm) 0 0 var(--radius-sm); }
.input-group > .btn,.input-group > button { border-radius:0 var(--radius-sm) var(--radius-sm) 0; margin-left:-1px; }

/* ── 10 · BADGE / CHIP ──────────────────────────────────────────────────────────────────────
   .chip / .pill + state variants are canonical. .badge is a semantic alias; add a leading dot. */
.badge { display:inline-flex; align-items:center; gap:6px; min-height:24px; padding:3px 10px;
  border-radius:var(--radius-pill); border:1px solid var(--rule); background:var(--paper-deep); color:var(--ink);
  font-family:var(--font-util); font-size:var(--text-xs); font-weight:600; letter-spacing:.06em;
  text-transform:uppercase; white-space:nowrap; line-height:1.1; }
.badge--good { background:var(--state-good-bg); color:var(--state-good); border-color:transparent; }
.badge--warn { background:var(--state-warn-bg); color:var(--state-warn); border-color:transparent; }
.badge--bad  { background:var(--state-bad-bg);  color:var(--state-bad);  border-color:transparent; }
.badge--info { background:var(--state-info-bg); color:var(--state-info); border-color:transparent; }
.badge__dot { width:7px; height:7px; border-radius:50%; background:currentColor; flex:0 0 auto; }

/* ── 11 · TABS ──────────────────────────────────────────────────────────────────────────────
   Anchor-based (no JS): server marks the current tab with .is-active. Underline-on-rule idiom,
   matches the resident sub-nav so it reads native. Scrolls horizontally on small screens. */
.tabs { display:flex; align-items:stretch; gap:var(--space-1); margin:0 0 var(--space-5);
  border-bottom:1px solid var(--rule); overflow-x:auto; scrollbar-width:none; }
.tabs::-webkit-scrollbar { display:none; }
.tab { display:inline-flex; align-items:center; gap:var(--space-2); padding:11px 14px; white-space:nowrap;
  color:var(--ink-soft); text-decoration:none; font-family:var(--font-util); font-size:var(--text-sm);
  font-weight:600; letter-spacing:.1em; text-transform:uppercase;
  border-bottom:2px solid transparent; margin-bottom:-1px; transition:color var(--dur-fast) var(--ease-standard); }
.tab:hover { color:var(--ink); border-bottom-color:var(--rule); }
.tab.is-active { color:var(--gold-deep); border-bottom-color:var(--gold-deep); }
.tab__count { font-variant-numeric:tabular-nums; font-size:var(--text-xs); color:var(--ink-mute); }
.tab.is-active .tab__count { color:var(--gold-deep); }

/* ── 12 · EMPTY STATE ───────────────────────────────────────────────────────────────────────
   Richer than the bare .empty: a card with optional glyph, headline, guidance, CTA. Use for
   zero-row tables, unbuilt queues, "nothing due" panels. */
.empty-state { display:flex; flex-direction:column; align-items:center; text-align:center; gap:var(--space-3);
  padding:var(--space-12) var(--space-6); background:var(--paper-elev); border:1px dashed var(--rule);
  border-radius:var(--radius-lg); color:var(--ink-soft); }
.empty-state__glyph { width:48px; height:48px; display:flex; align-items:center; justify-content:center;
  border-radius:var(--radius-pill); background:var(--paper-deep); color:var(--gold-deep);
  font-size:22px; line-height:1; border:1px solid var(--rule); }
.empty-state__title { margin:0; font-family:var(--font-display); font-size:var(--text-xl); color:var(--ink); font-weight:600; }
.empty-state__sub { margin:0; max-width:48ch; font-size:var(--text-md); line-height:1.5; color:var(--ink-mute); }
.empty-state .btn { margin-top:var(--space-2); }
.empty-state--bare { background:transparent; border:none; padding:var(--space-12) var(--space-6); }

/* ── 13 · LOADING — spinner + skeleton ──────────────────────────────────────────────────────*/
@keyframes ds-spin { to { transform:rotate(360deg); } }
.spinner { display:inline-block; width:20px; height:20px; border:2px solid var(--rule);
  border-top-color:var(--gold-deep); border-radius:50%; animation:ds-spin .7s linear infinite; vertical-align:middle; }
.spinner--lg { width:34px; height:34px; border-width:3px; }
.loading-state { display:flex; flex-direction:column; align-items:center; gap:var(--space-3);
  padding:var(--space-12) var(--space-6); color:var(--ink-mute); font-family:var(--font-util);
  font-size:var(--text-sm); letter-spacing:.06em; text-transform:uppercase; }
@keyframes ds-shimmer { 0% { background-position:-480px 0; } 100% { background-position:480px 0; } }
.skeleton { display:block; border-radius:var(--radius-sm);
  background:linear-gradient(90deg, var(--paper-deep) 0%, var(--rule-soft) 40%, var(--paper-deep) 80%);
  background-size:480px 100%; animation:ds-shimmer 1.3s ease-in-out infinite; }
.skeleton--text { height:.8em; margin:.3em 0; }
.skeleton--line { height:14px; margin:8px 0; }
.skeleton--title { height:26px; width:46%; margin:0 0 14px; }
.skeleton--btn { height:40px; width:120px; border-radius:var(--radius-sm); }
.skeleton--row { height:44px; margin:0 0 6px; }
.skeleton--avatar { width:44px; height:44px; border-radius:50%; }

/* ── 14 · ERROR STATE ───────────────────────────────────────────────────────────────────────
   Branded in-page failure panel (replaces leaking a raw stack / Python default page into the UI). */
.error-state { display:flex; flex-direction:column; align-items:center; text-align:center; gap:var(--space-3);
  padding:var(--space-12) var(--space-6); background:var(--state-bad-bg);
  border:1px solid rgba(176,49,42,.24); border-radius:var(--radius-lg); color:var(--ink); }
.error-state__code { font-family:var(--font-util); font-size:var(--text-xs); font-weight:700;
  letter-spacing:.16em; text-transform:uppercase; color:var(--state-bad); }
.error-state__title { margin:0; font-family:var(--font-display); font-size:var(--text-2xl); color:var(--ink); font-weight:600; }
.error-state__sub { margin:0; max-width:52ch; font-size:var(--text-md); line-height:1.5; color:var(--ink-soft); }

/* ── 15 · TOAST ─────────────────────────────────────────────────────────────────────────────
   Server-rendered flash messages. CSS-only: animates in, then auto-dismisses (slides/fades out)
   with no JS. Drop a .toast-stack near the top of <body> and emit .toast children. For a sticky
   (no auto-dismiss) message add .toast--sticky. */
.toast-stack { position:fixed; top:var(--space-5); right:var(--space-5); z-index:var(--z-toast);
  display:flex; flex-direction:column; gap:var(--space-2); max-width:min(380px,calc(100vw - 32px)); pointer-events:none; }
.toast { pointer-events:auto; display:flex; align-items:flex-start; gap:var(--space-3);
  padding:var(--space-3) var(--space-4); background:var(--paper-elev); color:var(--ink);
  border:1px solid var(--rule); border-left:3px solid var(--gold-deep); border-radius:var(--radius-md);
  box-shadow:var(--shadow-3); font-size:var(--text-sm); line-height:1.4;
  animation:ds-toast-in var(--dur-slow) var(--ease-out), ds-toast-out var(--dur-slow) var(--ease-in-out) 5s forwards; }
.toast--sticky { animation:ds-toast-in var(--dur-slow) var(--ease-out); }
.toast--good { border-left-color:var(--state-good); }
.toast--warn { border-left-color:var(--state-warn); }
.toast--bad  { border-left-color:var(--state-bad); }
.toast__icon { flex:0 0 auto; font-weight:700; color:var(--gold-deep); }
.toast--good .toast__icon { color:var(--state-good); }
.toast--bad  .toast__icon { color:var(--state-bad); }
.toast--warn .toast__icon { color:var(--state-warn); }
.toast__body { min-width:0; }
.toast__title { display:block; font-family:var(--font-util); font-weight:700; font-size:var(--text-xs);
  letter-spacing:.08em; text-transform:uppercase; margin-bottom:2px; }
@keyframes ds-toast-in { from { opacity:0; transform:translateX(16px); } to { opacity:1; transform:none; } }
@keyframes ds-toast-out { to { opacity:0; transform:translateX(16px); height:0; padding-block:0; margin:0; border-width:0; } }

/* ── 16 · MOTION UTILITIES ──────────────────────────────────────────────────────────────────
   One well-orchestrated page-load reveal beats scattered micro-animations. Put .reveal on a few
   top-level blocks; stagger with --i. Honors prefers-reduced-motion (guarded in §3). */
@keyframes ds-reveal { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }
.reveal { opacity:0; animation:ds-reveal var(--dur-slow) var(--ease-out) forwards;
  animation-delay:calc(var(--i, 0) * 55ms); }
.hover-lift { transition:transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard); }
.hover-lift:hover { transform:translateY(-2px); box-shadow:var(--shadow-3); }

/* ── 17 · SHELL / CHROME POLISH ─────────────────────────────────────────────────────────────
   Subtle elevation + smoother interactions on the global chrome the Style lane owns. Visual only
   — no structural/route changes (Shell owns nav structure). */
.shell { box-shadow:0 1px 0 rgba(0,0,0,.18), 0 8px 24px -18px rgba(0,0,0,.5); }
.shell-nav .nav-group-menu { border-radius:var(--radius-md); box-shadow:var(--shadow-3); }
.shell-nav a, .shell-nav .nav-group-label { transition:color var(--dur-fast) var(--ease-standard),
  border-color var(--dur-fast) var(--ease-standard); }
.kpi { transition:border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard),
  transform var(--dur-fast) var(--ease-standard); }

/* ── 18 · SMALL UTILITIES ───────────────────────────────────────────────────────────────────*/
.stack { display:grid; gap:var(--space-4); }
.stack--sm { gap:var(--space-2); }
.stack--lg { gap:var(--space-6); }
.cluster { display:flex; flex-wrap:wrap; gap:var(--space-2); align-items:center; }
.cluster--between { justify-content:space-between; }
.divider { height:1px; background:var(--rule); border:0; margin:var(--space-5) 0; }
.text-center { text-align:center; }
.text-right { text-align:right; }
.hidden { display:none !important; }
