/* ============================================================================
   VANTAGE (Lagos) -- ibjx design system + app shell
   Hand-authored, design-token based (ported from the ibjx design system).
   Self-contained (no framework, no build step). Light + dark mode via
   <html data-theme="dark"> (light is the default; the OS preference is never
   consulted). WCAG-AA colour choices. Navy chrome is the premium instrument
   base; the party blue (--party) is a DATA colour (charts/deck), never chrome.
   ASCII only in this file (see CLAUDE.md / Epic A contract).
   ============================================================================ */

/* ---- Design tokens ------------------------------------------------------- */
:root {
  /* Brand */
  --primary:        #1e3a5f;
  --primary-dk:     #152d4a;
  --primary-lt:     #2563eb;
  /* Text-only link colour: --primary-lt stays for FILLS (white-on-blue buttons);
     dark mode lifts --link so link text clears 4.5:1 on the navy surfaces. */
  --link:           var(--primary-lt);
  --link-hover:     var(--primary);
  --accent:         #0e9f6e;
  /* Campaign (party) data colours -- charts/deck, never chrome */
  --party:          #1e88e5;
  --party-deep:     #1565c0;
  --party-lt:       #90caf9;
  --party-soft:     #e3f2fd;
  /* Semantic */
  --success:        #059669;
  --success-bg:     #f0fdf4;
  --success-text:   #047857;
  --warning:        #d97706;
  --warning-bg:     #fffbeb;
  --warning-text:   #b45309;
  --danger:         #dc2626;
  --danger-bg:      #fef2f2;
  --danger-text:    #b91c1c;
  --info:           #2563eb;
  --info-bg:        #eff6ff;
  --info-text:      #1e40af;
  /* Neutrals (slate ramp) */
  --slate-50:  #f8fafc;  --slate-100: #f1f5f9;  --slate-200: #e2e8f0;
  --slate-300: #cbd5e1;  --slate-400: #94a3b8;  --slate-500: #64748b;
  --slate-600: #475569;  --slate-700: #334155;  --slate-800: #1e293b;
  --slate-900: #0f172a;
  /* Surfaces */
  --bg:        #f0f4f8;
  --surface:   #ffffff;
  --surface-2: #f8fafc;
  --border:    #e2e8f0;
  --border-strong: #cbd5e1;
  --text:      #1e293b;
  --text-muted:#596980;
  --text-faint:#94a3b8;
  /* Effects */
  /* Modal scrim. Themed, because the light value is an ink-navy alpha: over the
     dark ground (#0b1220) it is LIGHTER than the page, so an overlay stopped
     reading as modal. Dark uses black at a higher alpha instead. */
  --scrim:     rgba(15,23,42,.5);
  /* Horizontal-scroll affordance on .table-wrap / .table-scroll and the sidebar
     nav. An ink alpha vanishes on the dark surface (#111a2e), so dark uses a
     black alpha strong enough to read against it. */
  --edge-shadow: rgba(15,23,42,.14);
  --radius:    10px;
  --radius-sm: 6px;
  --radius-lg: 14px;
  --shadow:    0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);
  --shadow-md: 0 4px 6px -1px rgba(15,23,42,.08), 0 2px 4px -2px rgba(15,23,42,.06);
  --shadow-lg: 0 10px 25px -5px rgba(15,23,42,.12), 0 8px 10px -6px rgba(15,23,42,.08);
  --shadow-xl: 0 20px 45px -12px rgba(15,23,42,.28);
  /* Layout */
  --sidebar-w:     256px;
  --sidebar-w-collapsed: 68px;
  --topbar-h:      60px;
  --fs-md:         15px;
  /* fonts.css declares the "InterVariable" face, so it must lead the stack. */
  --font: "InterVariable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
  --transition: .18s cubic-bezier(.4,0,.2,1);
}

/* Dark mode. Selector matches tokens.css's specificity (:root[data-theme])
   so the navy palette wins the tie by source order. */
:root[data-theme="dark"] {
  --bg:        #0b1220;
  --surface:   #111a2e;
  --surface-2: #0e1626;
  --border:    #1e293b;
  --border-strong: #334155;
  --text:      #e2e8f0;
  --text-muted:#94a3b8;
  --text-faint:#7c8ba1;
  --primary:   #1e3a5f;
  --link:      #7db2ff;   /* ~8:1 on #111a2e */
  --link-hover:#a9ccff;
  --party:     #42a5f5;
  --party-deep:#90caf9;
  --party-lt:  #1565c0;
  --party-soft:#0d2a4a;
  --success-bg:#052e1a;
  --warning-bg:#2a1c05;
  --danger-bg: #2a0d0d;
  --info-bg:   #0d1a33;
  --success-text: #4ade80;
  --warning-text: #fbbf24;
  --danger-text:  #f87171;
  --info-text:    #93c5fd;
  --slate-100: #1e293b;
  --slate-600: #cbd5e1;
  --scrim:     rgba(0,0,0,.66);
  --edge-shadow: rgba(0,0,0,.62);
  --shadow:    0 1px 3px rgba(0,0,0,.4);
  --shadow-md: 0 4px 6px -1px rgba(0,0,0,.5);
  --shadow-lg: 0 10px 25px -5px rgba(0,0,0,.6);
  --shadow-xl: 0 20px 45px -12px rgba(0,0,0,.7);
}

/* ---- Legacy token bridge (web shell only) --------------------------------
   The per-page sheets (cockpit, intelligence, geo-map, register, upload,
   broadcast) have now been re-tokenised to the ibjx vocabulary, so the bridge
   is down to the ONE name a template still reads: --green, the default
   sparkline stroke baked into templates/dashboard/partials/svg.php (kept
   byte-identical to the Abia partial by contract). Everything else that the
   bridge used to alias (--ink/--text-2/--muted/--line*/--tint/--surface-3/
   --surface-inset/--ok/--warn/--crit/--soft/--warm/--amber*/--red*/--gold*/
   --info-soft/--green-mid/--green-bright/--green-deep/--side-bg*/--focus) was
   grepped across templates/, public/assets/{css,js}/ and src/ and has no
   remaining consumer on the web shell; tokens.css still declares those names
   natively for the field PWA and the deck, which never load this file.
   Declared for both themes so tokens.css's own dark value cannot win the tie. */
:root, :root[data-theme="dark"] {
  --green: var(--primary-lt);
  --scroll-thumb: var(--slate-300); --scroll-thumb-hover: var(--slate-400);
}
:root[data-theme="dark"] {
  --scroll-thumb: #334155; --scroll-thumb-hover: #475569;
}

/* ---- Reset & base -------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
h1,h2,h3,h4,h5,h6 { margin: 0; font-weight: 600; color: var(--text); line-height: 1.25; }
h1 { font-size: 22px; } h2 { font-size: 18px; } h3 { font-size: 15px; }
p { margin: 0 0 12px; }
a { color: var(--link); text-decoration: none; transition: color var(--transition); }
a:hover { color: var(--link-hover); }
.num, .text-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
code, kbd { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .92em; }
:focus-visible { outline: 2px solid var(--primary-lt); outline-offset: 2px; }
::selection { background: rgba(37,99,235,.2); }

/* Premium scrollbars -- slim, rounded, themed (Firefox: scrollbar-*; WebKit: pseudos). */
* { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* ---- App shell layout ---------------------------------------------------- */
/* 100dvh tracks the dynamic viewport on iOS Safari (100vh overshoots when the
   browser chrome is retracted); 100vh stays as the fallback. */
.app { min-height: 100vh; min-height: 100dvh; display: flex; }
.app__main {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  margin-left: var(--sidebar-w); transition: margin-left var(--transition);
}
.app.is-collapsed .app__main { margin-left: var(--sidebar-w-collapsed); }
.app__content { flex: 1; padding: 24px; max-width: 1600px; width: 100%; margin: 0 auto; }
.app__content:focus { outline: none; }
/* Real <button> scrim behind the mobile drawer (shown only <=1024px, see Responsive). */
.app__scrim {
  display: none; position: fixed; inset: 0; z-index: 39; margin: 0; padding: 0; border: 0;
  appearance: none; background: var(--scrim); cursor: pointer;
}

/* ---- Sidebar ------------------------------------------------------------- */
.sidebar {
  position: fixed; top: 0; left: 0; bottom: 0; width: var(--sidebar-w);
  background: var(--primary-dk); color: rgba(255,255,255,.85);
  display: flex; flex-direction: column; z-index: 40;
  transition: width var(--transition); overflow: hidden;
}
.app.is-collapsed .sidebar { width: var(--sidebar-w-collapsed); }
.sidebar__brand {
  display: flex; align-items: center; gap: 10px; height: var(--topbar-h);
  padding: 0 20px; border-bottom: 1px solid rgba(255,255,255,.08); flex-shrink: 0;
}
/* Brand tile as a text mark: navy "V" on a white tile (works as <a> or <span>). */
.sidebar__brand-mark {
  width: 34px; height: 34px; border-radius: 8px; flex-shrink: 0;
  background: #fff; color: var(--primary-dk); display: grid; place-items: center; overflow: hidden;
  font-weight: 800; font-size: 15px; letter-spacing: .02em; text-decoration: none;
}
.sidebar__brand-mark:hover { color: var(--primary); }
.sidebar__brand-logo { width: 30px; height: 30px; object-fit: contain; display: block; }
.sidebar__brand-text { font-weight: 700; font-size: 14px; line-height: 1.15; color: #fff; white-space: nowrap; letter-spacing: .01em; }
.sidebar__brand-sub {
  display: block; font-size: 10px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: rgba(255,255,255,.6);
}
.app.is-collapsed .sidebar__brand-text { display: none; }
/* The nav scrolls under a sticky .sidebar__foot. At a short viewport the last
   item used to be sliced mid-glyph flush against the footer with nothing to
   say the list continued, so the nav carries the same scroll affordance the
   table wrapper uses: two "cover" gradients painted in the panel colour with
   background-attachment:local (they scroll away with the content) and two
   shadow gradients pinned to the box, which are therefore visible ONLY at an
   edge with more content behind it. Plus 20px of bottom breathing room. */
.sidebar__nav {
  flex: 1; overflow-y: auto; padding: 12px 10px 20px; scrollbar-color: rgba(255,255,255,.18) transparent;
  background-image:
    linear-gradient(to bottom, var(--primary-dk) 60%, transparent),
    linear-gradient(to top, var(--primary-dk) 60%, transparent),
    linear-gradient(to bottom, rgba(0,0,0,.45), transparent),
    linear-gradient(to top, rgba(0,0,0,.45), transparent);
  background-position: top center, bottom center, top center, bottom center;
  background-repeat: no-repeat;
  background-size: 100% 26px, 100% 26px, 100% 14px, 100% 14px;
  background-attachment: local, local, scroll, scroll;
}
.sidebar__nav::-webkit-scrollbar { width: 6px; }
.sidebar__nav::-webkit-scrollbar-thumb { background: rgba(255,255,255,.18); border-radius: 3px; border: 0; }
.sidebar__nav::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.30); }
.nav-section { margin-bottom: 4px; }
.nav-section__label {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  color: rgba(255,255,255,.55); padding: 12px 12px 6px;
}
/* Collapsed rail: the group label keeps its box as the section spacer but its
   text is clipped (clip-path leaves it in the accessibility tree, unlike
   font-size:0 / display:none). */
.app.is-collapsed .nav-section__label { text-align: center; padding: 12px 0 6px; clip-path: inset(50%); }
.nav-item {
  position: relative;
  display: flex; align-items: center; gap: 11px; padding: 9px 12px; margin: 1px 0;
  border-radius: 8px; color: rgba(255,255,255,.78); font-size: 13.5px; font-weight: 500;
  white-space: nowrap; transition: background var(--transition), color var(--transition);
  cursor: pointer; border-left: 3px solid transparent; text-decoration: none;
}
.nav-item:hover { background: rgba(255,255,255,.08); color: #fff; }
.nav-item.is-active { background: rgba(14,159,110,.22); color: #fff; border-left-color: var(--accent); }
.nav-item svg { flex-shrink: 0; opacity: .85; }
.app.is-collapsed .nav-item { justify-content: center; padding: 10px; }
/* Visually-hidden clip, NOT display:none: the label stays the link's accessible
   name on the icon rail (WCAG 2.4.4 / 4.1.2); the <a title> adds the tooltip. */
.app.is-collapsed .nav-item__text { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.nav-item__badge { margin-left: auto; background: var(--danger); color: #fff; font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 10px; }
.app.is-collapsed .nav-item__badge { display: none; }
/* Focus on the navy panel must be white to be visible (contrast). */
.sidebar :focus-visible { outline-color: #fff; }
.sidebar a:focus-visible, .sidebar button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; box-shadow: none; }

/* Sidebar footer environment pill. */
.sidebar__foot { padding: 12px 16px; border-top: 1px solid rgba(255,255,255,.08); flex-shrink: 0; }
.env-pill {
  display: inline-flex; align-items: center; gap: 7px; font-size: 11px;
  font-weight: 600; letter-spacing: .04em; color: rgba(255,255,255,.7);
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
  padding: 5px 11px; border-radius: 999px; white-space: nowrap;
}
.env-pill__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 0 rgba(14,159,110,.5); animation: env-pulse 2.4s infinite; }
@keyframes env-pulse {
  0% { box-shadow: 0 0 0 0 rgba(14,159,110,.45); }
  70% { box-shadow: 0 0 0 6px rgba(14,159,110,0); }
  100% { box-shadow: 0 0 0 0 rgba(14,159,110,0); }
}
.app.is-collapsed .sidebar__foot { display: none; }
@media (prefers-reduced-motion: reduce) { .env-pill__dot { animation: none; } }

/* Accessible skip link -- visually hidden until keyboard focus. */
.skip-link {
  position: absolute; left: -9999px; top: 8px; z-index: 200;
  background: var(--surface); color: var(--text); padding: 8px 14px;
  border-radius: 8px; box-shadow: var(--shadow-lg); font-weight: 600;
}
.skip-link:focus { left: 8px; }

/* ---- Topbar -------------------------------------------------------------- */
.topbar {
  position: sticky; top: 0; z-index: 30; height: var(--topbar-h);
  display: flex; align-items: center; gap: 16px; padding: 0 24px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.topbar__toggle, .topbar__icon-btn {
  display: grid; place-items: center; width: 38px; height: 38px; border-radius: 8px;
  border: none; background: transparent; color: var(--text-muted); cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.topbar__toggle:hover, .topbar__icon-btn:hover { background: var(--surface-2); color: var(--text); }
.topbar__search { flex: 1; max-width: 420px; position: relative; margin: 0; }
.topbar__search input {
  width: 100%; height: 38px; padding: 0 14px 0 38px; border-radius: 8px; font-family: inherit;
  border: 1px solid var(--border); background: var(--surface-2); font-size: 13.5px; color: var(--text);
}
.topbar__search input:focus { outline: none; border-color: var(--primary-lt); background: var(--surface); }
.topbar__search svg { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-faint); pointer-events: none; }
.topbar__spacer { flex: 1; }
.topbar__right { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.topbar__user {
  display: flex; align-items: center; gap: 9px; padding: 5px 10px 5px 6px; border-radius: 10px;
  cursor: pointer; transition: background var(--transition); border: 1px solid transparent;
  background: transparent; font-family: inherit; text-align: left; color: inherit;
}
.topbar__user:hover { background: var(--surface-2); border-color: var(--border); }
/* The chip is a <summary> (native disclosure, works without app.js): drop the marker. */
summary.topbar__user { list-style: none; }
summary.topbar__user::-webkit-details-marker { display: none; }
.topbar__user-caret { display: inline-flex; color: var(--text-muted); transition: transform var(--transition); }
[aria-expanded="true"] .topbar__user-caret, details[open] > .topbar__user .topbar__user-caret { transform: rotate(180deg); }
.avatar {
  width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0; display: grid; place-items: center;
  background: linear-gradient(135deg, var(--primary), var(--primary-lt)); color: #fff; font-weight: 600; font-size: 13px;
}
.topbar__user-meta { line-height: 1.2; }
.topbar__user-name { font-size: 13px; font-weight: 600; color: var(--text); max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar__user-role { font-size: 11px; color: var(--text-muted); text-transform: capitalize; }
/* Theme toggle icon swap (two spans, CSS-driven by [data-theme]). */
.theme-ic-moon, .theme-ic-sun { align-items: center; }
.theme-ic-sun { display: none; }
[data-theme="dark"] .theme-ic-sun { display: inline-flex; }
[data-theme="dark"] .theme-ic-moon { display: none; }

/* Dropdown */
.dropdown { position: relative; }
.dropdown__menu {
  position: absolute; right: 0; top: calc(100% + 6px); min-width: 200px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: 6px; z-index: 50;
  opacity: 0; visibility: hidden; transform: translateY(-6px); pointer-events: none;
  transition: opacity var(--transition), transform var(--transition), visibility var(--transition);
}
.dropdown__menu.is-open, .dropdown[open] > .dropdown__menu { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .dropdown__menu { transition: none; transform: none; } }
.dropdown__item {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 7px;
  color: var(--text); font-size: 13.5px; cursor: pointer; transition: background var(--transition); text-decoration: none;
}
.dropdown__item:hover, .dropdown__item:focus-visible { background: var(--surface-2); color: var(--link); }
.dropdown__item svg { color: var(--text-muted); flex-shrink: 0; }
.dropdown__divider { height: 1px; background: var(--border); margin: 5px 0; }
.dropdown__header { padding: 10px 12px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.dropdown__header b { display: block; font-size: 13px; color: var(--text); }
.dropdown__header small { color: var(--text-muted); font-size: 11.5px; }
/* Sign-out form reset (button styled as a menu item). */
.dropdown__form { margin: 0; }
.dropdown__item--button { width: 100%; background: none; border: none; text-align: left; font: inherit; cursor: pointer; }

/* ---- Page header / breadcrumb -------------------------------------------- */
.page-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 22px; flex-wrap: wrap; }
.page-title { font-size: 22px; font-weight: 700; margin-top: 4px; }
.page-header__sub { margin: 4px 0 0; color: var(--text-muted); font-size: 13px; max-width: 78ch; }
.page-header__actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; min-width: 0; max-width: 100%; }
.breadcrumb { display: flex; align-items: center; gap: 4px; font-size: 12.5px; color: var(--text-muted); flex-wrap: wrap; }
.breadcrumb a { color: var(--text-muted); }
.breadcrumb a:hover { color: var(--link); }
.breadcrumb span[aria-current] { color: var(--text); font-weight: 500; }
.crumb-sep { display: inline-flex; color: var(--border-strong); }

/* ---- Cards --------------------------------------------------------------- */
/* No overflow:hidden on the base card: dropdowns/tips inside cards must not
   clip. Clipping lives on .card--flush and the table wrapper instead. */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
.card__header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--border); }
.card__title { font-size: 14px; font-weight: 600; }
.card__body { padding: 18px; }
.card__footer { padding: 12px 18px; border-top: 1px solid var(--border); background: var(--surface-2); border-radius: 0 0 var(--radius) var(--radius); }
/* Plain content cards (no header/body slots) keep their own padding; :where()
   keeps this at class specificity so .card--flush and the guest cards win. */
.card:where(:not(:has(> .card__body)):not(:has(> .card__header))) { padding: 20px; }
.card--flush { padding: 0; overflow: hidden; }

/* ---- Stat tiles ---------------------------------------------------------- */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 16px; margin-bottom: 22px; }
.stats-grid--6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.stats-grid--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (min-width: 1800px) { .stats-grid--6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
@media (max-width: 1439px) { .stats-grid--5 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1279px) { .stats-grid--6 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 900px) { .stats-grid--5 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } }
.stat-tile {
  display: flex; align-items: center; gap: 14px; padding: 18px; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow);
  position: relative; overflow: visible; color: inherit;
}
.stat-tile::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--primary-lt); border-radius: var(--radius) 0 0 var(--radius); }
.stat-tile--primary::before { background: var(--primary-lt); }
.stat-tile--success::before { background: var(--success); }
.stat-tile--warning::before { background: var(--warning); }
.stat-tile--danger::before  { background: var(--danger); }
.stat-tile--info::before    { background: var(--accent); }
.stat-tile__icon { width: 46px; height: 46px; border-radius: 12px; display: grid; place-items: center; flex-shrink: 0; background: rgba(37,99,235,.1); color: var(--primary-lt); }
.stat-tile--success .stat-tile__icon { background: rgba(5,150,105,.1); color: var(--success); }
.stat-tile--warning .stat-tile__icon { background: rgba(217,119,6,.1); color: var(--warning); }
.stat-tile--danger .stat-tile__icon  { background: rgba(220,38,38,.1); color: var(--danger); }
.stat-tile--info .stat-tile__icon    { background: rgba(14,159,110,.1); color: var(--accent); }
.stat-tile__visual { flex: 0 0 56px; width: 56px; height: 56px; display: grid; place-items: center; }
.stat-tile__body { min-width: 0; flex: 1; }
.stat-tile__label { font-size: 12px; color: var(--text-muted); font-weight: 500; }
.stat-tile__value { font-size: 22px; font-weight: 700; color: var(--text); display: flex; align-items: center; gap: 8px; margin-top: 2px; font-variant-numeric: tabular-nums; }
.stat-tile__sub { font-size: 11.5px; color: var(--text-muted); margin-top: 2px; }
.stat-tile__foot { margin-top: 6px; font-size: 11.5px; color: var(--text-muted); }
.stat-tile__trend { display: inline-flex; align-items: center; gap: 2px; font-size: 12px; font-weight: 600; }
.stat-tile__trend--up { color: var(--success-text); }
.stat-tile__trend--down { color: var(--danger-text); }
a.stat-tile, .stat-tile--link { text-decoration: none; transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition); }
a.stat-tile:hover, .stat-tile--link:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--border-strong); color: inherit; }
a.stat-tile:focus-visible, .stat-tile--link:focus-visible { outline: 2px solid var(--primary-lt); outline-offset: 2px; }

/* ---- Buttons ------------------------------------------------------------- */
/* Bare .btn is the primary action (Lagos templates use class="btn" alone);
   .btn-ghost / .btn-outline / .btn-small are Lagos aliases of the ibjx modifiers. */
.btn, .btn-ghost, .btn-outline {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 16px; border-radius: var(--radius-sm); font-size: 13.5px; font-weight: 600;
  border: 1px solid transparent; cursor: pointer; white-space: nowrap; line-height: 1.4; font-family: inherit;
  transition: background var(--transition), border-color var(--transition), box-shadow var(--transition), transform .05s;
  text-decoration: none; background: var(--primary-lt); color: #fff;
}
.btn:hover { background: #1d4ed8; color: #fff; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible, .btn-ghost:focus-visible, .btn-outline:focus-visible { outline: 2px solid var(--primary-lt); outline-offset: 2px; box-shadow: none; }
.btn:disabled, .btn.is-disabled { opacity: .55; cursor: not-allowed; }
.btn svg { flex-shrink: 0; }
.btn .btn-ico { width: 16px; height: 16px; }
.btn--primary { background: var(--primary-lt); color: #fff; }
.btn--primary:hover { background: #1d4ed8; color: #fff; }
.btn--navy { background: var(--primary); color: #fff; }
.btn--navy:hover { background: var(--primary-dk); color: #fff; }
.btn--success { background: var(--success); color: #fff; }
.btn--success:hover { background: #047857; color: #fff; }
.btn--danger { background: var(--danger); color: #fff; }
.btn--danger:hover { background: #b91c1c; color: #fff; }
.btn--warning, .btn--gold { background: var(--warning); color: #fff; }
.btn--warning:hover, .btn--gold:hover { background: #b45309; color: #fff; }
.btn--ghost, .btn-ghost { background: transparent; border-color: var(--border); color: var(--text); }
.btn--ghost:hover, .btn-ghost:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text); }
.btn--outline, .btn-outline { background: transparent; border-color: var(--primary-lt); color: var(--primary-lt); }
.btn--outline:hover, .btn-outline:hover { background: var(--primary-lt); color: #fff; }
.btn--sm, .btn-small { padding: 5px 11px; font-size: 12px; }
/* WCAG 2.2 SC 2.5.8 floor: 24x24 CSS px, and a 12px label floor on mobile.
   The old 3px/11px pair measured 23px tall with an 11px label. */
.btn--xs { padding: 3px 9px; font-size: 12px; min-height: 24px; border-radius: 5px; }
.btn--lg { padding: 11px 24px; font-size: 15px; }
.btn--block { width: 100%; }
.btn--icon { padding: 8px; width: 36px; height: 36px; }

/* ---- Pills / status ------------------------------------------------------ */
/* Tone text colours come from the --*-text tokens, so dark mode needs no
   per-alias remap. The alias names map onto the four tones. */
.pill { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; letter-spacing: .01em; line-height: 1.5; white-space: nowrap; }
.pill--success, .pill--active, .pill--verified, .pill--resolved, .pill--served, .pill--paid { background: var(--success-bg); color: var(--success-text); }
.pill--danger, .pill--failed, .pill--overdue { background: var(--danger-bg); color: var(--danger-text); }
.pill--warning, .pill--pending, .pill--processing { background: var(--warning-bg); color: var(--warning-text); }
.pill--info, .pill--new { background: var(--info-bg); color: var(--info-text); }
.pill--neutral, .pill--cancelled, .pill--inactive { background: var(--slate-100); color: var(--slate-600); }

/* ---- Tables -------------------------------------------------------------- */
/* Grid children default to min-width:auto, which lets a wide table push a card
   past its column; min-width:0 makes the wrapper scroll its own content instead. */
.grid-2 > *, .grid-3 > *, .stats-grid > * { min-width: 0; }
.grid-2 > .card, .grid-3 > .card { display: flex; flex-direction: column; }
.grid-2 > .card > .card__body, .grid-3 > .card > .card__body { flex: 1 1 auto; }
/* Scroll container. The layered gradients are a scroll affordance: the edge
   shadows (attachment: scroll) show only where the local "cover" gradients
   (attachment: local) have scrolled away. */
.table-wrap, .table-scroll {
  position: relative; overflow-x: auto; max-width: 100%; min-width: 0;
  border: 1px solid var(--border); border-radius: var(--radius);
  background-color: var(--surface); scrollbar-width: thin; -webkit-overflow-scrolling: touch;
  /* Gradient stops interpolate in premultiplied alpha, so `transparent` fades
     the START colour out instead of dragging it through grey -- the "cover"
     gradients therefore track --surface in BOTH themes (the old literal
     rgba(255,255,255,0) end-stop was a white haze on the dark surface) and the
     shadow colour comes from --edge-shadow (an ink alpha was invisible on
     #111a2e: the affordance disappeared entirely in dark mode). */
  background-image:
    linear-gradient(to right, var(--surface) 40%, transparent),
    linear-gradient(to left, var(--surface) 40%, transparent),
    linear-gradient(to right, var(--edge-shadow), transparent),
    linear-gradient(to left, var(--edge-shadow), transparent);
  background-position: left center, right center, left center, right center;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}
.table-wrap--tall { max-height: min(70vh, 720px); overflow-y: auto; }
.card__body > .table-wrap:only-child { margin: -18px; width: calc(100% + 36px); max-width: none; border: 0; border-radius: 0; }
table.data-table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: auto; min-width: var(--table-min, 0); background: transparent; }
.data-table--fixed { table-layout: fixed; --table-min: 760px; }
.data-table--wide { --table-min: 1040px; }
.data-table caption { caption-side: top; text-align: left; padding: 10px 14px; font-size: 12.5px; color: var(--text-muted); }
.data-table caption.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); }
.data-table thead th {
  position: sticky; top: 0; z-index: 1; background: var(--surface-2); color: var(--text-muted);
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 11px 14px; text-align: left; border-bottom: 1px solid var(--border); white-space: nowrap; vertical-align: middle;
}
.data-table thead th a { color: inherit; display: inline-flex; align-items: center; gap: 4px; }
.data-table thead th.is-sortable { cursor: pointer; user-select: none; }
/* The whole cell shows a pointer, so the whole cell must BE the target: the
   sort link fills it (negative margins cancel the th padding above, which the
   link then re-applies). Without this the real hit area was the ~16px-high
   text run inside a 39px cell -- under the WCAG 2.5.8 24x24 minimum and a lie
   to the cursor. :focus-visible stays on the anchor, so the ring now outlines
   the whole target. */
.data-table thead th.is-sortable > a { display: flex; margin: -11px -14px; padding: 11px 14px; }
.data-table thead th.is-sortable.col-num > a { justify-content: flex-end; }
.data-table thead th.is-sortable:hover, .data-table thead th a:hover { color: var(--text); }
.data-table thead th[aria-sort] a { color: var(--text); }
.data-table tbody td { padding: 11px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; color: var(--text); white-space: nowrap; }
.data-table--fixed tbody td { overflow: hidden; text-overflow: ellipsis; }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover td { background: var(--surface-2); }
:root[data-theme="dark"] .data-table tbody tr:hover td { background: rgba(255,255,255,.04); }
.data-table--nav tbody tr { cursor: pointer; }
/* Column role classes (emitted on th and mirrored onto td). */
.data-table .col-num, .data-table th[style*="text-align:right"], .data-table td[style*="text-align:right"] { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.data-table .col-trunc { max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.data-table .col-wrap { white-space: normal; overflow: visible; text-overflow: clip; }
.data-table .col-status { white-space: normal; }
.data-table .col-status .pill { white-space: nowrap; margin: 1px 0; }
.data-table .col-actions { width: 1%; white-space: nowrap; text-align: right; }
.data-table--fixed .col-actions { width: 92px; }
.data-table .col-check { width: 40px; text-align: center; padding-left: 10px; padding-right: 10px; }
.data-table .num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.data-table .cell-actions { display: inline-flex; gap: 5px; align-items: center; flex-wrap: nowrap; justify-content: flex-end; }
/* Identity cell (avatar + name + sub). Keyed on the avatar so the Lagos
   status .chip (components.css) is untouched. */
.idchip, .chip:has(> .chip__avatar) { display: inline-flex; align-items: center; gap: 8px; max-width: 100%; min-width: 0; padding: 0; border: 0; background: none; white-space: normal; font-size: inherit; font-weight: inherit; line-height: inherit; }
.idchip > div, .chip:has(> .chip__avatar) > div { min-width: 0; overflow: hidden; }
.chip__name, .chip__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.chip__avatar { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; background: var(--slate-100); color: var(--slate-600); font-weight: 700; font-size: 12px; flex-shrink: 0; }
.chip__name { font-weight: 600; color: var(--text); font-size: 13px; }
.chip__sub { font-size: 11px; color: var(--text-muted); }

/* Grid toolbar + pagination */
.grid-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.grid-toolbar__filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.grid-toolbar__spacer { flex: 1; }
/* Per-grid search (its own class: .topbar__search is hidden on phones). */
.grid-search { position: relative; max-width: 280px; flex: 1 1 200px; min-width: 160px; }
.grid-search input { width: 100%; height: 38px; padding: 0 14px 0 38px; border-radius: 8px; font-family: inherit; border: 1px solid var(--border); background: var(--surface-2); font-size: 13.5px; color: var(--text); }
.grid-search input:focus { outline: none; border-color: var(--primary-lt); background: var(--surface); }
.grid-search svg { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-faint); pointer-events: none; }
.data-table tr.grid-pagination-row td { padding: 4px 14px; background: var(--surface); white-space: normal; text-align: left; max-width: none; }
.data-table tr.grid-pagination-row:hover td { background: var(--surface); }
.pagination { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 4px; flex-wrap: wrap; }
.pagination__info { font-size: 12.5px; color: var(--text-muted); }
.pagination__pages { display: flex; gap: 4px; }
/* inline-flex, NOT grid: display:grid gives every child its own implicit row,
   so a "Next <chevron>" button stacked the label over the icon in a two-line
   pill (/admin/audit, /admin/users). A single child still centres. */
.page-btn { min-width: 34px; height: 34px; padding: 0 10px; border-radius: 7px; border: 1px solid var(--border); background: var(--surface); color: var(--text); font-size: 13px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; text-decoration: none; }
.page-btn:hover { background: var(--surface-2); }
.page-btn.is-active { background: var(--primary-lt); color: #fff; border-color: var(--primary-lt); }
.page-btn:disabled { opacity: .4; cursor: not-allowed; }

/* ---- Forms --------------------------------------------------------------- */
/* .field-control is the ibjx control; the bare Lagos primitives (.field > label
   + input/select/textarea, .input) are aliased with :where() so page sheets and
   auth.css keep their specificity edge. */
.field { margin-bottom: 16px; }
.field-label, .field > label { display: block; font-size: 12.5px; font-weight: 600; color: var(--text); margin-bottom: 6px; }
.field-label .req { color: var(--danger); }
.field-input { position: relative; display: flex; align-items: center; }
.field-icon { position: absolute; left: 12px; color: var(--text-faint); display: inline-flex; pointer-events: none; }
.field-control,
.field > :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"])),
.field > :where(select, textarea),
.field .input-icon > :where(input),
.input {
  width: 100%; padding: 9px 12px; border-radius: var(--radius-sm); border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text); font-size: 13.5px; font-family: var(--font); line-height: 1.4;
  transition: border-color var(--transition), box-shadow var(--transition);
}
.field-control--with-icon .field-control, .field-input--with-icon input, .field-input.field-control--with-icon input, .field .input-icon > :where(input) { padding-left: 38px; }
.field-control:focus, .field > :where(input, select, textarea):focus, .input:focus { outline: none; border-color: var(--primary-lt); box-shadow: 0 0 0 3px rgba(37,99,235,.14); }
.field-control::placeholder, .input::placeholder { color: var(--text-faint); }
textarea.field-control, .field > :where(textarea) { min-height: 90px; resize: vertical; }
select.field-control, .field > :where(select), select.input { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%2364748b' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; padding-right: 34px; }
.input--sm { width: auto; padding: 5px 10px; font-size: 13px; }
.field.has-error .field-control, .field.has-error > :where(input, select, textarea) { border-color: var(--danger); }
.field-error { color: var(--danger); font-size: 11.5px; margin-top: 4px; }
.field-hint, .field .hint, .hint { color: var(--text-muted); font-size: 11.5px; margin-top: 4px; }
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 16px; }
.field-row .field { min-width: 0; }
.fieldset-title { font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--text-muted); border-bottom: 1px solid var(--border); padding-bottom: 6px; margin: 18px 0 12px; }
.fieldset-title:first-child { margin-top: 2px; }
.input-icon { position: relative; }
.input-icon .input-icon-glyph { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-faint); pointer-events: none; display: inline-flex; }
.input-icon:focus-within .input-icon-glyph { color: var(--primary-lt); }
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary-lt); }
.field input[type="checkbox"], .field input[type="radio"] { width: 16px; height: 16px; min-height: 0; }

/* ---- Alerts / flash ------------------------------------------------------ */
.flash-stack { position: fixed; top: 74px; right: 24px; z-index: 60; display: flex; flex-direction: column; gap: 10px; max-width: 380px; }
.alert, .alert-error, .alert-ok, .alert-warn, .alert-info, .alert-success {
  display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border-radius: var(--radius-sm);
  border-left: 4px solid var(--border-strong); font-size: 13px; background: var(--surface); margin: 0 0 14px; line-height: 1.45;
}
.flash-stack .alert { margin: 0; box-shadow: var(--shadow-md); }
.alert--success, .alert-ok, .alert-success { border-left-color: var(--success); background: var(--success-bg); color: var(--success-text); }
.alert--danger, .alert--error, .alert-error { border-left-color: var(--danger); background: var(--danger-bg); color: var(--danger-text); }
.alert--warning, .alert-warn { border-left-color: var(--warning); background: var(--warning-bg); color: var(--warning-text); }
.alert--info, .alert-info { border-left-color: var(--info); background: var(--info-bg); color: var(--info-text); }
.alert--bar { border-left: 3px solid currentColor; }
.alert svg { flex-shrink: 0; }
.alert__icon { flex-shrink: 0; display: inline-flex; margin-top: 1px; }
.alert__msg { flex: 1; }
.alert__close { background: none; border: none; cursor: pointer; color: inherit; opacity: .6; padding: 0; display: inline-flex; }
.alert__close:hover { opacity: 1; }

/* ---- Modals -------------------------------------------------------------- */
.modal-overlay { position: fixed; inset: 0; background: var(--scrim); z-index: 70; display: grid; place-items: center; padding: 24px; }
.modal { background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-xl); width: 100%; max-width: 560px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; }
.modal__header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; background: var(--primary); color: #fff; }
.modal__title { font-size: 15px; font-weight: 600; color: #fff; }
.modal__close { background: none; border: none; color: rgba(255,255,255,.8); cursor: pointer; display: inline-flex; }
.modal__close:hover { color: #fff; }
.modal__body { padding: 20px; overflow-y: auto; }
/* Forms inside modals inherit the flex chain so a tall form scrolls in the body. */
.modal > form { display: flex; flex-direction: column; min-height: 0; flex: 1; overflow: hidden; }
.modal__footer { padding: 14px 20px; border-top: 1px solid var(--border); background: var(--surface-2); display: flex; justify-content: flex-end; gap: 8px; }
[x-cloak] { display: none !important; }

/* ---- Utilities ----------------------------------------------------------- */
.muted { color: var(--text-muted); }
.faint { color: var(--text-faint); }
.text-sm { font-size: 13px; } .text-xs { font-size: 11.5px; }
.text-right { text-align: right; } .text-center { text-align: center; }
.mt-0{margin-top:0}.mt-2{margin-top:8px}.mt-3{margin-top:12px}.mt-4{margin-top:16px}.mt-5{margin-top:24px}
.mb-0{margin-bottom:0}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:12px}.mb-4{margin-bottom:16px}.mb-5{margin-bottom:24px}
.flex { display: flex; } .items-center { align-items: center; } .gap-2 { gap: 8px; } .gap-3 { gap: 12px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.hidden { display: none; }
.visually-hidden, .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.nav-ico { width: 18px; height: 18px; flex-shrink: 0; }
.spinner { width: 18px; height: 18px; border: 2px solid var(--border); border-top-color: var(--primary-lt); border-radius: 50%; animation: spin .6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* htmx loading indicator */
.htmx-indicator { opacity: 0; transition: opacity .2s; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }
/* Top progress bar for htmx requests (app.js drives width/opacity inline). */
#progress-bar { position: fixed; top: 0; left: 0; height: 3px; width: 0; background: linear-gradient(90deg, var(--accent), var(--primary-lt)); z-index: 100; transition: width .2s; opacity: 0; }
body.htmx-request #progress-bar { opacity: 1; }

/* ---- Skeleton shimmer ---------------------------------------------------- */
.skeleton { position: relative; overflow: hidden; background: var(--slate-100); border-radius: var(--radius-sm); color: transparent; min-height: 1em; pointer-events: none; }
.skeleton::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.55), rgba(255,255,255,0)); animation: skeleton-shimmer 1.4s infinite; }
:root[data-theme="dark"] .skeleton::after { background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.08), rgba(255,255,255,0)); }
.skeleton--text { height: 12px; margin: 6px 0; }
.skeleton--title { height: 18px; width: 40%; }
.skeleton--tile { height: 92px; border-radius: var(--radius); }
.skeleton--row { height: 40px; }
@keyframes skeleton-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skeleton::after { animation: none; } }

/* ---- Record detail pages ------------------------------------------------- */
/* Single-record ("show") views: a navy header band with the identifier +
   status badges, then grouped field cards with a definition grid. */
.record { display: flex; flex-direction: column; gap: 16px; }
.record-band {
  display: flex; align-items: center; gap: 16px; padding: 18px 22px;
  border-radius: var(--radius); border: 1px solid var(--border);
  background: linear-gradient(120deg, var(--primary) 0%, var(--primary-dk) 100%);
  color: #fff; box-shadow: var(--shadow-md); position: relative; overflow: hidden;
}
.record-band::after { content: ''; position: absolute; right: -40px; top: -40px; width: 180px; height: 180px; border-radius: 50%; background: rgba(255,255,255,.06); }
.record-band__avatar { width: 54px; height: 54px; border-radius: 14px; flex-shrink: 0; display: grid; place-items: center; background: rgba(255,255,255,.14); color: #fff; font-weight: 700; font-size: 22px; border: 1px solid rgba(255,255,255,.18); overflow: hidden; }
.record-band__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.record-band__main { flex: 1; min-width: 0; z-index: 1; }
.record-band__title { font-size: 19px; font-weight: 700; color: #fff; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.record-band__meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; font-size: 12.5px; color: rgba(255,255,255,.78); }
.record-band__id { font-variant-numeric: tabular-nums; font-weight: 600; color: rgba(255,255,255,.92); }
.record-band__sep { color: rgba(255,255,255,.4); }
.record-band__badges { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; z-index: 1; }
.record-band__badges .pill { background: rgba(255,255,255,.16); color: #fff; }
.record-subhead { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); margin: 22px 0 12px; padding-top: 14px; border-top: 1px solid var(--border); }
.card__body > .def-grid:first-child + .record-subhead, .card__body > .record-subhead:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.def-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 20px; }
.def-grid--single { grid-template-columns: 1fr; }
.def-grid__item { min-width: 0; }
.def-grid__label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); margin-bottom: 3px; }
.def-grid__value { font-size: 13.5px; font-weight: 600; color: var(--text); word-break: break-word; line-height: 1.4; }
.def-grid__value.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
.def-grid__item--wide { grid-column: 1 / -1; }
.record-amount { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 16px 18px; border-radius: var(--radius); background: var(--surface-2); border: 1px solid var(--border); }
.record-amount__label { font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
.record-amount__value { font-size: 26px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
@media (max-width: 700px) { .def-grid { grid-template-columns: 1fr; } }

/* ---- Inline SVG charts --------------------------------------------------- */
/* Hand-built SVG generated server-side (templates/dashboard/partials/svg.php):
   no chart library, CSP-safe. These classes only size/colour the SVG. */
.chart { display: block; width: 100%; height: auto; overflow: visible; }
.chart-donut { display: block; width: 96px; height: 96px; flex-shrink: 0; }
.donut-track { fill: none; stroke: var(--slate-100); }
.donut-value { fill: none; stroke-linecap: round; transition: stroke-dashoffset .4s cubic-bezier(.4,0,.2,1); }
.donut-center-num { fill: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
.donut-center-sub { fill: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; }
.meter { height: 8px; border-radius: 999px; background: var(--slate-100); overflow: hidden; }
.meter > span { display: block; height: 100%; border-radius: inherit; background: var(--success); transition: width .4s cubic-bezier(.4,0,.2,1); }
.meter--warn > span { background: var(--warning); }
.meter--crit > span { background: var(--danger); }
.spark { display: block; width: 100%; height: 44px; }
.spark-line { fill: none; stroke: var(--party); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.spark-fill { fill: var(--party); opacity: .12; stroke: none; }
.spark-dot { fill: var(--party-deep); stroke: var(--surface); stroke-width: 1.5; }
.stat-value { font-size: 26px; font-weight: 700; line-height: 1.15; color: var(--text); margin: 0 0 4px; font-variant-numeric: tabular-nums; }
.legend-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .donut-value, .meter > span { transition: none; } }

/* ---- Notification centre (topbar bell) ----------------------------------- */
.notif { position: relative; }
.notif__bell { position: relative; }
.notif__badge {
  position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; padding: 0 4px;
  display: grid; place-items: center; background: var(--danger); color: #fff;
  font-size: 10px; font-weight: 800; line-height: 1; border-radius: 9px;
  border: 2px solid var(--surface); box-sizing: content-box;
}
/* [hidden] must beat display:grid (else a 0-count badge still shows). */
.notif__badge[hidden] { display: none; }
.notif.has-unread .notif__bell { color: var(--primary-lt); }
.notif__menu {
  position: absolute; right: 0; top: calc(100% + 8px); width: 360px; max-width: calc(100vw - 24px);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); z-index: 500; overflow: hidden; pointer-events: none;
  opacity: 0; visibility: hidden; transform: translateY(-6px); transition: opacity .16s, transform .16s, visibility .16s;
}
/* The ROOT (.notif) carries .is-open, not the menu. */
.notif.is-open .notif__menu { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }
.notif__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.notif__title { font-weight: 700; font-size: 14px; color: var(--text); }
.notif__markall { background: none; border: none; color: var(--link); font-size: 12px; font-weight: 600; cursor: pointer; padding: 2px 4px; font-family: inherit; }
.notif__markall:hover { text-decoration: underline; }
.notif__list { max-height: min(60vh, 440px); overflow-y: auto; -webkit-overflow-scrolling: touch; }
.notif__empty { padding: 30px 16px; text-align: center; color: var(--text-muted); font-size: 13px; }
.notif__item { display: flex; align-items: flex-start; gap: 11px; padding: 12px 14px; text-decoration: none; color: var(--text); border-bottom: 1px solid var(--border); position: relative; transition: background .12s; }
.notif__item:last-child { border-bottom: none; }
.notif__item:hover { background: var(--surface-2); }
.notif__item.is-unread { background: color-mix(in srgb, var(--primary-lt) 6%, transparent); }
.notif__ic { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; background: var(--surface-2); color: var(--text-muted); }
.notif__item--primary .notif__ic, .notif__item--info .notif__ic { background: color-mix(in srgb, var(--primary-lt) 15%, transparent); color: var(--primary-lt); }
.notif__item--success .notif__ic { background: rgba(5,150,105,.14); color: var(--success); }
.notif__item--warning .notif__ic { background: rgba(217,119,6,.14); color: var(--warning); }
.notif__item--danger  .notif__ic { background: rgba(220,38,38,.14); color: var(--danger); }
.notif__txt { min-width: 0; display: flex; flex-direction: column; gap: 2px; flex: 1; }
.notif__t { font-size: 13px; font-weight: 700; color: var(--text); line-height: 1.25; }
.notif__b { font-size: 12px; color: var(--text-muted); line-height: 1.3; word-break: break-word; }
.notif__time { font-size: 11px; color: var(--text-muted); margin-top: 1px; }
.notif__dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: var(--primary-lt); margin-top: 6px; }
.notif__foot { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 11px 14px; text-align: center; font-size: 12.5px; font-weight: 600; color: var(--link); text-decoration: none; border-top: 1px solid var(--border); }
.notif__foot:hover { background: var(--surface-2); }
@media (max-width: 640px) {
  .notif__menu { position: fixed; right: 8px; left: 8px; top: calc(var(--topbar-h, 60px) + env(safe-area-inset-top, 0px) + 6px); width: auto; max-width: none; }
}

/* ---- App footer ---------------------------------------------------------- */
.app-footer { padding: 14px 24px; border-top: 1px solid var(--border); background: var(--surface); color: var(--text-muted); font-size: 12.5px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
.app-footer a { color: var(--text-muted); }
.app-footer a:hover { color: var(--link); }
@media (max-width: 640px) { .app-footer { display: none; } }

/* ---- Contextual help drawer (Epic C) ------------------------------------- */
.topbar__help { color: var(--accent); }
.topbar__help:hover { background: rgba(14,159,110,.12); color: var(--accent); }
/* Above the phone bottom tab bar (z 200): the drawer is a modal dialog, so
   the tab bar must not float over its footer -- or stay tappable through it. */
.help-drawer { position: fixed; inset: 0; z-index: 1050; }
.help-drawer__scrim { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; padding: 0; cursor: pointer; background: var(--scrim); opacity: 0; transition: opacity var(--transition); }
.help-drawer.is-open .help-drawer__scrim { opacity: 1; }
.help-drawer__panel { position: absolute; top: 0; right: 0; bottom: 0; width: min(440px, 100%); display: flex; flex-direction: column; background: var(--surface); border-left: 1px solid var(--border); box-shadow: var(--shadow-xl); transform: translateX(100%); transition: transform var(--transition); }
.help-drawer.is-open .help-drawer__panel { transform: none; }
.help-drawer__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 18px 20px 14px; border-bottom: 1px solid var(--border); background: linear-gradient(135deg, var(--primary), var(--primary-dk)); color: #fff; }
.help-drawer__eyebrow { margin: 0 0 2px; font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: rgba(255,255,255,.6); }
.help-drawer__title { margin: 0; font-size: 17px; font-weight: 700; color: #fff; }
.help-drawer__x { display: grid; place-items: center; width: 32px; height: 32px; flex-shrink: 0; border: 0; border-radius: 8px; cursor: pointer; background: rgba(255,255,255,.12); color: #fff; transition: background var(--transition); }
.help-drawer__x:hover { background: rgba(255,255,255,.22); }
.help-drawer__body { flex: 1; overflow-y: auto; padding: 18px 20px 24px; }
.help-summary { margin: 0 0 20px; font-size: 14px; line-height: 1.6; color: var(--text); padding-left: 12px; border-left: 3px solid var(--accent); }
.help-block { margin-bottom: 22px; }
.help-block__title { display: flex; align-items: center; gap: 7px; margin: 0 0 10px; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
.help-block__title svg { color: var(--accent); }
.help-steps { margin: 0; padding-left: 20px; display: grid; gap: 9px; }
.help-steps li { font-size: 13.5px; line-height: 1.55; color: var(--text); }
.help-steps li::marker { color: var(--accent); font-weight: 700; }
.help-tips { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.help-tips li { position: relative; padding: 10px 12px 10px 32px; font-size: 13px; line-height: 1.55; color: var(--text); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.help-tips li::before { content: '!'; position: absolute; left: 11px; top: 10px; display: grid; place-items: center; width: 15px; height: 15px; border-radius: 50%; background: var(--warning); color: #fff; font-size: 10px; font-weight: 800; }
.help-terms { margin: 0; display: grid; gap: 8px; }
.help-terms dt { font-size: 13px; font-weight: 700; color: var(--text); }
.help-terms dd { margin: 2px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--text-muted); }
.help-drawer__foot { padding: 11px 20px; border-top: 1px solid var(--border); background: var(--surface-2); color: var(--text-faint); font-size: 11.5px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.help-drawer__all { color: var(--link); text-decoration: none; font-weight: 600; white-space: nowrap; }
.help-drawer__all:hover { text-decoration: underline; }
.help-drawer__foot kbd, .kbd { display: inline-block; padding: 1px 5px; margin: 0 1px; border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: 4px; background: var(--surface); font-family: inherit; font-size: 10.5px; color: var(--text-muted); line-height: 1.5; }
body.help-open { overflow: hidden; }
@media (max-width: 560px) { .help-drawer__panel { width: 100%; } }
@media (prefers-reduced-motion: reduce) { .help-drawer__panel, .help-drawer__scrim { transition: none; } }

/* ---- First-run tour prompt ([data-tour]) --------------------------------- */
.tour-prompt { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 11px 14px; margin-bottom: 16px; border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius-sm); background: var(--surface-2); }
.tour-prompt__icon { color: var(--accent); display: inline-flex; flex-shrink: 0; }
.tour-prompt__text { margin: 0; flex: 1 1 260px; font-size: 12.5px; line-height: 1.5; color: var(--text-muted); }
.tour-prompt__text strong { color: var(--text); }
.tour-prompt__actions { display: flex; gap: 8px; flex-shrink: 0; }
/* The banner ships [hidden] and app.js reveals it only on a screen this
   browser has not seen; display:flex above would otherwise beat the UA
   [hidden] rule and show it to everyone, forever. */
.tour-prompt[hidden] { display: none; }

/* ---- Inline tips ([data-tip]) -------------------------------------------- */
[data-tip] { position: relative; cursor: help; border-bottom: 1px dotted var(--text-faint); }
/* One ring for the whole shell (F7): 2px --primary-lt, offset 2px. The green
   --accent must never carry meaning as a foreground colour. */
[data-tip]:focus-visible { outline: 2px solid var(--primary-lt); outline-offset: 2px; }
.tip-bubble { position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); z-index: 90; width: max-content; max-width: 260px; padding: 8px 11px; border-radius: var(--radius-sm); background: var(--slate-900); color: #fff; font-size: 12px; font-weight: 400; line-height: 1.45; text-align: left; white-space: normal; box-shadow: var(--shadow-lg); cursor: auto; }
.tip-bubble::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: var(--slate-900); }
[data-theme="dark"] .tip-bubble { background: var(--slate-700); }
[data-theme="dark"] .tip-bubble::after { border-top-color: var(--slate-700); }

/* ---- Dashboard quick links / hub cards ----------------------------------- */
.ql-head { display: flex; align-items: center; justify-content: space-between; margin: 22px 0 12px; }
.ql-head__title { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.ql-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; margin-bottom: 20px; }
.ql-card { display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); text-decoration: none; color: inherit; transition: transform .14s, box-shadow .14s, border-color .14s; }
.ql-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--accent); color: inherit; }
.ql-card__icon { width: 42px; height: 42px; border-radius: 11px; display: grid; place-items: center; flex: 0 0 auto; background: rgba(37,99,235,.1); color: var(--primary-lt); }
.ql-card__body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.ql-card__name { font-size: 14px; font-weight: 700; color: var(--text); }
.ql-card__desc { font-size: 12px; color: var(--text-muted); line-height: 1.4; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.ql-card__go { color: var(--text-faint); flex: 0 0 auto; transition: color .14s, transform .14s; }
.ql-card:hover .ql-card__go { color: var(--primary-lt); transform: translateX(2px); }

/* ---- Autocomplete (data-lookup typeahead) -------------------------------- */
.lta-menu { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 1200; background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg); max-height: 280px; overflow-y: auto; padding: 4px; }
.lta-item { display: flex; flex-direction: column; gap: 1px; width: 100%; text-align: left; padding: 8px 10px; border: none; background: none; border-radius: 6px; cursor: pointer; font: inherit; color: var(--text); }
.lta-item:hover, .lta-item.is-active { background: var(--surface-2); }
.lta-item.is-active { box-shadow: inset 0 0 0 1px var(--primary-lt); }
.lta-item__label { font-size: 13.5px; font-weight: 600; }
.lta-item__detail { font-size: 12px; color: var(--text-muted); }
.field:has(input[data-lookup]) { position: relative; }

/* ---- Command palette ([data-palette]) ------------------------------------ */
.palette { position: fixed; inset: 0; z-index: 1300; display: grid; align-items: start; justify-items: center; padding: 12vh 16px 16px; }
.palette[hidden] { display: none; }
.palette__scrim { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; appearance: none; background: var(--scrim); cursor: pointer; }
.palette__dialog { position: relative; width: min(640px, 100%); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-xl); overflow: hidden; animation: palette-in .16s ease-out; }
.palette__row { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--border); color: var(--text-faint); }
.palette__input { flex: 1; min-width: 0; border: 0; background: transparent; font: inherit; font-size: 15px; color: var(--text); outline: none; padding: 4px 0; }
.palette__input::placeholder { color: var(--text-faint); }
.palette__list { max-height: min(60vh, 420px); overflow-y: auto; padding: 6px; margin: 0; list-style: none; }
.palette__group { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--text-faint); padding: 10px 10px 4px; }
.palette__option { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 8px; color: var(--text); font-size: 13.5px; cursor: pointer; text-decoration: none; }
.palette__option svg { color: var(--text-muted); flex-shrink: 0; }
.palette__option[aria-selected="true"], .palette__option:hover { background: var(--surface-2); color: var(--link); }
.palette__option[aria-selected="true"] svg { color: var(--link); }
.palette__option-sub { margin-left: auto; font-size: 11.5px; color: var(--text-faint); white-space: nowrap; }
.palette__empty { padding: 26px 16px; text-align: center; color: var(--text-muted); font-size: 13px; }
.palette__foot { display: flex; gap: 14px; padding: 8px 14px; border-top: 1px solid var(--border); background: var(--surface-2); font-size: 11.5px; color: var(--text-faint); }
@keyframes palette-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ---- Keyboard shortcuts overlay ([data-shortcuts]) ----------------------- */
.shortcuts { position: fixed; inset: 0; z-index: 1300; display: grid; place-items: center; padding: 16px; background: var(--scrim); }
.shortcuts[hidden] { display: none; }
.shortcuts__panel { position: relative; width: min(560px, 100%); max-height: 90vh; overflow-y: auto; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-xl); padding: 20px 22px; }
.shortcuts__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.shortcuts__title { font-size: 16px; font-weight: 700; }
.shortcuts__x { width: 32px; height: 32px; border: 0; border-radius: 8px; background: var(--surface-2); color: var(--text-muted); cursor: pointer; display: grid; place-items: center; }
.shortcuts__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 24px; margin: 0; padding: 0; list-style: none; }
.shortcuts__row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; color: var(--text); }
.shortcuts__keys { display: inline-flex; gap: 3px; flex-shrink: 0; }

/* ---- Bottom navigation (phones) ------------------------------------------
   Below the 640px phone breakpoint the off-canvas drawer is replaced by a fixed
   bottom tab bar + a "More" sheet. RBAC-filtered server-side; safe-area aware.
   Hidden by default; revealed only inside the phone media query. */
.abn { display: none; }
.abn-sheet { display: none; }
.abn-sheet[hidden] { display: none; }
/* While the sheet is open, pin the page so the background cannot scroll (JS
   stores/restores the scroll offset via top:). Inert outside phones. */
body.abn-sheet-open { position: fixed; left: 0; right: 0; width: 100%; overflow: hidden; }

@media (max-width: 640px) {
  .abn {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 200;
    display: flex; align-items: stretch;
    background: var(--surface); border-top: 1px solid var(--border);
    box-shadow: 0 -6px 20px -10px rgba(15,23,42,.22);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    -webkit-backdrop-filter: saturate(1.6) blur(8px); backdrop-filter: saturate(1.6) blur(8px);
  }
  .abn__tab {
    flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; padding: 8px 2px 7px; background: none; border: none; cursor: pointer;
    color: var(--text-muted); text-decoration: none; font-family: inherit;
    -webkit-tap-highlight-color: transparent; transition: color .15s;
  }
  .abn__ic { display: grid; place-items: center; height: 24px; position: relative; }
  .abn__ic svg { width: 23px; height: 23px; }
  /* 11.5px, not 10.5px: these five labels exist ONLY at phone size, where
     sub-12px type is hardest to read. Single words, so the bar has the room. */
  .abn__lbl { font-size: 11.5px; font-weight: 600; letter-spacing: .01em; line-height: 1; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .abn__tab.is-active { color: var(--link); }
  .abn__tab.is-active .abn__ic::after { content: ''; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); width: 26px; height: 3px; border-radius: 0 0 3px 3px; background: var(--primary-lt); }
  .abn__tab:active { background: var(--surface-2); }

  /* More sheet (bottom drawer) */
  .abn-sheet { display: block; position: fixed; inset: 0; z-index: 1100; }
  .abn-sheet[hidden] { display: none; }
  /* The scrim is a real <button>: reset its chrome. */
  .abn-sheet__scrim { position: absolute; inset: 0; display: block; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; appearance: none; background: var(--scrim); cursor: pointer; animation: abn-fade .2s ease; }
  .abn-sheet__panel {
    position: absolute; left: 0; right: 0; bottom: 0;
    /* Clamp to the SMALL viewport so the panel is never taller than what is
       visible when mobile Safari's URL bar is shown. */
    display: flex; flex-direction: column; max-height: 86vh; max-height: 86svh;
    background: var(--surface); border-radius: 20px 20px 0 0; box-shadow: var(--shadow-xl);
    padding: 8px 16px calc(14px + env(safe-area-inset-bottom, 0px));
    animation: abn-up .26s cubic-bezier(.22,1,.36,1); overscroll-behavior: contain;
  }
  @supports (max-height: 100dvh) { .abn-sheet__panel { max-height: 86dvh; } }
  .abn-sheet__grip { flex: 0 0 auto; width: 40px; height: 4px; border-radius: 3px; background: var(--border); margin: 4px auto 10px; }
  .abn-sheet__head { flex: 0 0 auto; background: var(--surface); display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 0 2px 12px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
  .abn-sheet__who { display: flex; align-items: center; gap: 11px; min-width: 0; }
  .abn-sheet__avatar { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; background: var(--primary-dk); color: #fff; font-weight: 800; font-size: 16px; }
  .abn-sheet__idn { display: flex; flex-direction: column; min-width: 0; }
  .abn-sheet__name { font-weight: 700; font-size: 15px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .abn-sheet__role { font-size: 12px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .abn-sheet__x { flex: 0 0 auto; width: 34px; height: 34px; border: none; background: var(--surface-2); border-radius: 50%; display: grid; place-items: center; color: var(--text-muted); cursor: pointer; }
  /* The body is the scroll container; a bottom fade signals "more below" and
     is removed by the JS-toggled .is-scrollend class. */
  .abn-sheet__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 6px; padding-bottom: 4px;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent 100%); mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent 100%); }
  .abn-sheet__body.is-scrollend { -webkit-mask-image: none; mask-image: none; }
  .abn-sheet__group-label { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); padding: 12px 6px 4px; }
  /* repeat(2, minmax(0, 1fr)), NOT `1fr 1fr`: `1fr` is minmax(AUTO,1fr), so a
     nowrap label sized each track to its own min-content and the two tracks
     together overflowed the sheet (measured 222.86 + 173.86 inside a 358px
     body at 390px wide -- the whole right column, chips included, sat off
     screen with no scroll affordance). min-width:0 on the link AND its label
     lets the ellipsis do its job inside the track. */
  .abn-sheet__links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
  .abn-sheet__link { display: flex; align-items: center; gap: 11px; min-width: 0; padding: 12px; border-radius: var(--radius); color: var(--text); text-decoration: none; font-size: 14px; font-weight: 600; background: var(--surface-2); }
  .abn-sheet__link svg { color: var(--primary-lt); flex: 0 0 auto; }
  .abn-sheet__link.is-active { background: color-mix(in srgb, var(--primary-lt) 12%, transparent); color: var(--link); }
  .abn-sheet__link span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .abn-sheet__link .abn-sheet__chip { flex: 0 0 auto; margin-left: auto; padding: 1px 6px; font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; border-radius: 6px; background: var(--surface); color: var(--text-muted); border: 1px solid var(--border); }
  .abn-sheet__signout { flex: 0 0 auto; margin: 0; padding-top: 10px; border-top: 1px solid var(--border); }
  .abn-sheet__out { width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 13px; border: 1px solid var(--danger); background: var(--danger-bg); color: var(--danger-text); font-weight: 700; font-size: 15px; border-radius: var(--radius); cursor: pointer; font-family: inherit; }
  @keyframes abn-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
  @keyframes abn-fade { from { opacity: 0; } to { opacity: 1; } }

  /* App-mode chrome (phones): the tab bar replaces the drawer + footer. */
  .app-shell .abn { display: flex; }
  .app-shell .app__content { padding-bottom: calc(66px + env(safe-area-inset-bottom, 0px)); }
  .app-shell .app__main > footer { display: none !important; }
  .app-shell .topbar__toggle { display: none !important; }
  .app-shell .app.is-mobile-open .sidebar { transform: translateX(-100%); visibility: hidden; }
  .app-shell .app__scrim { display: none !important; }
}
/* Installed-PWA polish: extra top safe-area for the sticky topbar in standalone. */
@media (max-width: 640px) and (display-mode: standalone) {
  .app-shell .topbar { padding-top: env(safe-area-inset-top, 0px); height: calc(var(--topbar-h) + env(safe-area-inset-top, 0px)); }
}
@media (prefers-reduced-motion: reduce) { .abn-sheet__panel, .abn-sheet__scrim, .palette__dialog { animation: none; } }

/* ---- Responsive ------------------------------------------------------------
   Breakpoint scale (the 1024 drawer BP is mirrored in app.js MOBILE_BP):
     1024px  tablet / mobile -- sidebar becomes an off-canvas drawer
      768px  narrow -- two/three-up content grids collapse to one column
      640px  phone -- tighter chrome, hide non-essential topbar bits
   --------------------------------------------------------------------------- */
@media (max-width: 1024px) {
  .app__main { margin-left: 0; }
  /* The closed drawer is visibility:hidden (not just off-screen), so its links
     leave the tab order and the accessibility tree; the visibility flip is
     delayed on close so the slide-out still animates. */
  .sidebar { transform: translateX(-100%); visibility: hidden; transition: transform var(--transition), visibility 0s linear var(--transition); width: var(--sidebar-w); }
  .app.is-mobile-open .sidebar { transform: translateX(0); visibility: visible; transition: transform var(--transition), visibility 0s; box-shadow: var(--shadow-xl); }
  .app.is-mobile-open .app__scrim:not([hidden]) { display: block; }
  /* On mobile the collapse rail is meaningless -- always the full drawer width. */
  .app.is-collapsed .sidebar { width: var(--sidebar-w); }
  .app.is-collapsed .app__main { margin-left: 0; }
  .app.is-collapsed .sidebar__brand-text, .app.is-collapsed .sidebar__foot, .app.is-collapsed .nav-item__badge { display: revert; }
  .app.is-collapsed .nav-item__text { position: static; width: auto; height: auto; padding: 0; margin: 0; overflow: visible; clip: auto; }
  .app.is-collapsed .nav-section__label { text-align: left; padding: 12px 12px 6px; clip-path: none; }
  .app.is-collapsed .nav-item { justify-content: flex-start; padding: 9px 12px; }
  .topbar__toggle { display: inline-flex; }
}
@media (max-width: 768px) {
  .grid-2, .grid-3 { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .app__content { padding: 16px 14px; }
  .topbar { padding: 0 14px; }
  /* Only the global TOPBAR search hides on phones; .grid-search stays. */
  .topbar__search { display: none; }
  .topbar__user-meta { display: none; }
  .grid-toolbar { align-items: stretch; }
  .grid-toolbar__filters { width: 100%; }
  .grid-toolbar__filters > select, .grid-toolbar__filters > .field-control { flex: 1 1 46%; min-width: 0; width: auto !important; }
  .grid-search { max-width: none; width: 100%; flex-basis: 100%; order: 5; }
  .grid-toolbar__spacer { display: none; }
  .data-table td, .data-table th { padding: 8px 10px; }
  .modal-overlay { padding: 12px; }
  .page-header { flex-wrap: wrap; gap: 10px; }
  .page-header__actions { width: 100%; justify-content: flex-start; }
  /* Two-up stays denser than a single column on a phone, but an odd tile
     count left a half-width orphan in the last row -- the "scattered" look.
     The last child of an odd set spans both tracks instead. */
  .stats-grid, .stats-grid--5, .stats-grid--6 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .stats-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* A stat tile is lead-BESIDE-body at every other width. Two-up on a 390px
     phone that leaves the figure 89px of a 173px tile -- 79px behind a donut --
     and the measurements say that is not enough for the number the tile exists
     to show: "+12.6 pp" broke across two lines on the cockpit, and every one
     of the 13 routes with a stats grid was squeezed the same way. The lead
     moves ABOVE the body on phones, so the figure, its label and its sub-line
     each get the tile's full 147px. */
  .stat-tile { flex-direction: column; align-items: flex-start; padding: 12px 13px; gap: 8px; }
  .stat-tile__icon { width: 38px; height: 38px; border-radius: 10px; }
  .stat-tile__icon svg { width: 19px; height: 19px; }
  /* A visual is DATA (the coverage donut carries its own figure), so it keeps
     its 56px where the decorative icon chip shrinks. The basis is reset
     because in column direction `flex: 0 0 56px` would be sizing its HEIGHT. */
  .stat-tile__visual { flex: 0 0 auto; }
  .stat-tile__body { width: 100%; }
  .stat-tile__value { font-size: 22px; }
  /* The label's tip host is inline: without this the dotted tip underline is
     the only thing that wraps, and a two-line label reads as a broken link. */
  .stat-tile__label [data-tip] { display: inline-block; }
  .flash-stack { right: 10px; left: 10px; max-width: none; }
  /* Comfortable touch target on the breakpoint where every control is tapped. */
  .btn--xs { min-height: 32px; padding: 5px 11px; }
}

/* ---- Phone data tables (stacked) ------------------------------------------
   A .data-table keeps its column layout inside a horizontally scrolling
   .table-wrap. On a 390px phone that means the reader sees the first one or
   two columns and everything that matters -- status, due date, the VALUE
   column on /admin/settings, the row actions -- sits off-screen behind a
   scroll nobody discovers. Measured before this change: /admin/settings showed
   SETTING and nothing else, so no setting could be read or changed on a phone.

   At <=640px each row becomes its own card and each cell a label/value pair.
   The label comes from the matching <th>, copied onto the cell as data-label
   by initStackedTables() in app.js -- doing it in script keeps 44 templates
   free of per-cell duplication, and with scripting off the table simply stays
   in its scrollable wrapper, which is the behaviour we had. A table opts out
   with data-no-stack; tables narrower than three columns are left alone. */
@media (max-width: 640px) {
  .table-wrap:has(.data-table--stack) {
    overflow: visible;
    max-height: none;
    border: 0;
    border-radius: 0;
    background: none;
    background-image: none;
  }

  .card__body > .table-wrap:has(.data-table--stack):only-child {
    margin: 0;
    width: auto;
  }

  /* --table-min is what drives the desktop floor (table.data-table sets
     min-width: var(--table-min, 0), which outranks a bare class selector),
     so the stack neutralises the property rather than fighting it. */
  table.data-table--stack { --table-min: 0; min-width: 0; table-layout: auto; display: block; }
  .data-table--stack > tbody, .data-table--stack > tfoot { display: block; }

  /* The header still names the columns for assistive tech; the cards carry
     the same names visually through data-label. */
  .data-table--stack > thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  .data-table--stack > tbody > tr {
    display: block;
    margin-bottom: 8px;
    padding: 0 0 2px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    box-shadow: var(--shadow);
  }

  .data-table--stack > tbody > tr:last-child { margin-bottom: 0; }
  .data-table--stack > tbody > tr:hover td { background: none; }

  .data-table--stack > tbody > tr > td {
    display: grid;
    grid-template-columns: minmax(5.5em, 34%) 1fr;
    gap: 8px;
    align-items: baseline;
    width: auto;
    max-width: none;
    padding: 5px 12px;
    border: 0;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    text-align: left;
    font-size: 13px;
    line-height: 1.45;
  }

  /* The first cell is the record's name, so it reads as the card's heading
     rather than one more label/value pair -- without it a six-column table
     costs six identical lines per row and the eye has nothing to anchor on. */
  .data-table--stack > tbody > tr > td:first-child {
    grid-template-columns: 1fr;
    padding: 9px 12px 7px;
    margin-bottom: 2px;
    border-bottom: 1px solid var(--border);
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    background: var(--surface-2);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  }

  .data-table--stack > tbody > tr > td:first-child::before { content: none; }
  .data-table--stack > tbody > tr > td:last-child { padding-bottom: 9px; }

  /* Column widths are a TABLE-mode concept and every one of them is wrong once
     the row is a card. They come from two places that both outrank a plain
     descendant selector -- the shared roles (.data-table .col-trunc, 0,2,0)
     and each page's own <style> (.col-agent, .col-pu, .col-role on
     /structure/agents) -- and enumerating them is a losing game: the next
     bespoke column class would silently collapse again. Measured before this
     rule: .col-trunc capped the card title at 260px of 322 (the short grey
     header band), .col-actions { width: 1% } squeezed the Reset button on
     /admin/settings into 24px, and /structure/agents had 150 collapsed cells.
     !important is the right tool here: this is a layout-mode switch, scoped to
     one class inside one media query, not a specificity skirmish. */
  .data-table--stack > tbody > tr > td {
    width: auto !important;
    min-width: 0 !important;
    max-width: none !important;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
    /* Right-alignment is a column concept too: in a card the value sits beside
       its label, so a right-aligned .col-num pushed the audit card's timestamp
       title to the far edge. */
    text-align: left !important;
  }

  .data-table--stack > tbody > tr > td::before {
    content: attr(data-label);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
  }

  /* A cell with no column name of its own (row actions, checkboxes) uses the
     full width rather than leaving an empty label gutter. */
  .data-table--stack > tbody > tr > td[data-label=""],
  .data-table--stack > tbody > tr > td.col-actions,
  .data-table--stack > tbody > tr > td.col-check {
    grid-template-columns: 1fr;
  }

  .data-table--stack > tbody > tr > td[data-label=""]::before,
  .data-table--stack > tbody > tr > td.col-actions::before,
  .data-table--stack > tbody > tr > td.col-check::before { content: none; }

  .data-table--stack > tbody > tr > td.col-actions { justify-items: start; }

  /* A pill is a badge, not a bar: as a grid item it would stretch the whole
     value column. Inputs and the action clusters keep their stretch. */
  .data-table--stack > tbody > tr > td .pill,
  .data-table--stack > tbody > tr > td .chip,
  .data-table--stack > tbody > tr > td > .num { justify-self: start; }
  .data-table--stack .cell-actions { flex-wrap: wrap; }
  .data-table--stack > tbody > tr > td.col-num { text-align: left; }

  /* A full-width "nothing here" row must not sprout a label gutter. */
  .data-table--stack > tbody > tr > td[colspan] { grid-template-columns: 1fr; }
  .data-table--stack > tbody > tr > td[colspan]::before { content: none; }

  /* A hidden row must stay hidden: the stack sets tr { display: block }, which
     would otherwise beat the UA's [hidden] rule. */
  .data-table--stack > tbody > tr[hidden] { display: none; }

  /* Reveal control for the phone row limit (app.js). Full width, real target,
     and it states how much of the page is on screen so the count never
     contradicts the "Showing 1-50 of N" line above it. */
  .table-more { margin-top: 10px; display: grid; gap: 6px; justify-items: stretch; }
  .table-more .btn { min-height: 40px; }
  .table-more__note { margin: 0; text-align: center; font-size: 12px; color: var(--text-muted); }
  .table-more[hidden] { display: none; }

  /* Pagination sits outside the card stack, not inside a table cell. */
  .data-table--stack > tfoot > tr, .data-table--stack > tfoot > tr > td { display: block; padding: 0; border: 0; }

  /* Tab strips scroll instead of overflowing their card (/admin/settings,
     /account both ran their tabs off the right edge). */
  .tab-strip, [data-tabs-root] > .tab-strip {
    overflow-x: auto;
    flex-wrap: nowrap;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .tab-strip::-webkit-scrollbar { display: none; }
  .crm-tab { flex: 0 0 auto; min-height: 40px; }

  /* WCAG 2.5.8: nothing tappable below 24px on the surface where everything
     is tapped. Measured before this change: 62 sub-24px targets on
     /structure/agents alone. */
  .data-table a, .data-table button, .data-table summary, .pagination a, .pagination button,
  .page-btn, .cell-actions .btn, .btn--xs {
    min-height: 32px;
  }

  /* A <summary> is a real control, but it lays out as a text run: the audit
     log's "JSON" disclosures measured 16.7px tall. min-height alone does not
     centre the marker, so it becomes a flex box first. */
  .data-table summary { display: flex; align-items: center; width: fit-content; }

  /* Breadcrumbs are the up-navigation on every page and measured 18.8px tall
     ("Home" at 35x18.8 on all 20 routes). inline-flex is what lets min-height
     apply at all, and the trail still sits on the header's first line. */
  .breadcrumb a, .breadcrumb span[aria-current] {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
  }

  /* The register drill toggle is the only way into an LGA's wards and measured
     22px WIDE (WCAG 2.5.8 wants 24); the checkbox is the whole control on the
     broadcast audience picker. Both get a real target without changing the
     glyph. */
  .drill-toggle { min-width: 32px; min-height: 32px; }
  /* 24, not 20: WCAG 2.5.8 is a 24px floor and these measured 20x20 on the
     broadcast audience picker, /social and /intelligence. */
  input[type="checkbox"], input[type="radio"] { min-width: 24px; min-height: 24px; }

  /* Standalone links inside a card need a tap target of their own; an inline
     link in running prose is exempt and keeps its natural size. */
  .data-table--stack td > a:only-child,
  .audit-actor__name, .notif__markall,
  .mover-list a, .rank-list a { display: inline-flex; align-items: center; min-height: 24px; }

  .cell-actions .btn, .page-btn { display: inline-flex; align-items: center; }
  .pagination { flex-wrap: wrap; gap: 8px; }
}

/* ---- Print (F8) -----------------------------------------------------------
   Four screens are designed to be printed (cockpit, E-Day board, history
   results, audit log) and every other screen must at least not embarrass
   itself on paper. The rules below were written against measurements taken
   under Emulation.setEmulatedMedia({media:'print'}) in BOTH themes -- printing
   from a dark session is the case that breaks, because <html data-theme="dark">
   is still set while the paper is white. */
@page { margin: 14mm; }
@media print {
  /* Every token the dark block sets must be restored to its light value here,
     or it prints its dark value on white paper. The party/link family was the
     gap: --party-deep #90caf9 measured 1.75:1 and --link #7db2ff 2.17:1 on
     white. Keep this list in sync with the :root[data-theme="dark"] block. */
  :root, :root[data-theme="dark"] {
    --bg: #fff; --surface: #fff; --surface-2: #f8fafc; --border: #cbd5e1; --border-strong: #94a3b8;
    --text: #0f172a; --text-muted: #475569; --text-faint: #64748b; --slate-100: #f1f5f9; --slate-600: #475569;
    --success-bg: #f0fdf4; --warning-bg: #fffbeb; --danger-bg: #fef2f2; --info-bg: #eff6ff;
    --success-text: #047857; --warning-text: #b45309; --danger-text: #b91c1c; --info-text: #1e40af;
    --primary: #1e3a5f; --primary-dk: #152d4a; --primary-lt: #2563eb;
    --party: #1e88e5; --party-deep: #1565c0; --party-lt: #90caf9; --party-soft: #e3f2fd;
    --link: #1d4ed8; --link-hover: #1e40af; --info: #2563eb;
    --scrim: rgba(15,23,42,.5); --edge-shadow: transparent;
    --shadow: none; --shadow-md: none; --shadow-lg: none; --shadow-xl: none;
  }
  /* Chrome and controls. .card__footer is NOT hidden wholesale (some carry
     real provenance prose); the button-only case is handled below. */
  .sidebar, .topbar, .app-footer, .abn, .abn-sheet, #progress-bar, .flash-stack, .help-drawer, .tour-prompt,
  .page-header__actions, .map-toggle, .btn, .btn-ghost, .btn-outline, .tip-bubble, .skip-link,
  [data-palette], [data-shortcuts], .grid-toolbar, .app__scrim,
  [data-print-hide], form[role="search"] { display: none !important; }
  /* Quick links are navigation, not content: hide the PATTERN (a .ql-head and
     the grid that follows it), never the class -- /admin/personas reuses
     .ql-grid for the persona catalogue, which must print. */
  .ql-head, .ql-head + .ql-grid { display: none !important; }
  /* A footer whose element children are ALL buttons has nothing left once .btn
     is hidden, and printed as an empty grey strip. One that holds prose, a
     <span class="ops-note"> or a .pagination keeps printing. */
  .card__footer:has(> .btn):not(:has(> *:not(.btn))) { display: none !important; }
  /* Row-action and checkbox columns hold only controls, so the header printed
     over a column of blank cells. */
  .data-table th.col-actions, .data-table td.col-actions,
  .data-table th.col-check, .data-table td.col-check { display: none !important; }
  html, body { background: #fff !important; color: #0f172a; }
  .app__main { margin-left: 0 !important; }
  .app__content { padding: 0 !important; max-width: none !important; }
  .card, .stat-tile, .record-band, .record-amount, .ql-card { box-shadow: none !important; break-inside: avoid; }
  .grid-2, .grid-3 { display: block !important; }
  .grid-2 > *, .grid-3 > *, .ql-grid > * { margin-bottom: 14px; }
  /* The KPI row stays a grid: collapsed to blocks, six numbers ate a third of
     the first page in six full-width 65px rows. */
  .stats-grid, .stats-grid--5, .stats-grid--6 {
    display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)) !important; gap: 10px;
  }
  .table-wrap, .table-scroll, .table-wrap--tall { overflow: visible !important; max-height: none !important; background-image: none !important; }
  .data-table thead { display: table-header-group; }
  .data-table thead th { position: static !important; }
  .data-table tbody td { white-space: normal !important; }
  /* Keep the scope line, drop the controls: a printed page 1 of 266 that says
     nothing about the other 265 is a lie on paper. */
  .pagination { display: block !important; border: 0 !important; padding: 6px 0 !important; }
  .pagination__info { display: inline !important; }
  .pagination__pages, .page-btn { display: none !important; }
  /* A collapsed <details> loses its body on paper (51 JSON payloads on /admin/
     audit, all 38 Help answers). initPrint() in app.js is the ONLY mechanism
     that actually reveals them: its beforeprint pass sets .open on every
     closed <details> inside [data-print-scope] and restores the closed set
     afterprint.

     There is deliberately no CSS fallback here. A closed <details> hides its
     body through the shadow slot, so neither `display:block` on the light-DOM
     child nor `::details-content { content-visibility: visible }` reveals it
     (both measured at height 0 under print media, Chromium 152). Rules that
     look like a scripting-off floor but are not would be worse than none:
     with scripting off the summaries print and the bodies do not, and the
     Export CSV on those screens is the answer for a reader who needs the
     payloads. Only the marker suppression below is real. */
  details > summary { list-style: none; }
  details > summary::marker { content: none; }
  details > summary::-webkit-details-marker { display: none; }
  /* Marks whose ONLY carrier is a background colour vanish when Chrome prints
     with "Background graphics" off (the default): the PU-classification
     composition bar, the hatched no-data LGA flag, and every legend swatch. */
  .pill, .stat-tile::before, .meter > span, .donut-value, .record-band,
  .record-band__badges, .record-band__badges .pill,
  .class-bar, .class-bar > span, .geo-tile, .class-legend .dot, .map-legend .dot, .dot {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
  .record-band { background: var(--primary) !important; }
  .print-meta { display: block !important; }
  a { color: inherit; }
}
.print-meta { display: none; }

/* -- Legacy carry-over (Epic G removes) ------------------------------------
   components.css / enhancements.css / dashboard.css no longer load anywhere
   (Epic B removed the <link>s from layouts/app.php; the files stay on disk
   until Epic G deletes them). Audit of what still depended on them, 2026-09-04:
     * every template class those sheets defined is either unused, defined by
       app.css above, or defined page-scoped (geo explorer .legend/.legend-item,
       register lookup .row-strong -- also added by register-lookup.js -- and the
       cockpit .rank-list/.rec-list/.mover-list);
     * intelligence-chat.js injects <span class="badge badge--ok text-sm">web</span>
       into AI bubbles: intelligence.css shapes .chat-msg-who .badge, the tone
       colours come from the .badge--ok rule kept below;
     * the legacy TOKEN bridge near the top of this file is still live for the
       per-page sheets and the byte-identical svg.php sparkline default
       (var(--green)) -- do not remove it here.
   The remaining rules only ever neutralised the retired sheets or set the
   tone colours those sheets' status classes borrowed; they are now inert
   except .badge--ok and [data-autosubmit-fallback][hidden]. Delete the block
   with the files. */
.btn:not(.btn-ghost):not(.btn-outline):not(.btn-small):not(.btn--sm) { background-image: none; box-shadow: none; }
.btn:not(.btn-ghost):not(.btn-outline):hover { box-shadow: none; filter: none; }
.card:has(> .card__header), .card:has(> .card__body) { padding: 0; }
.stat-tiles > .stat-tile { display: block; }
.stat-tiles > .stat-tile h3 { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted); margin: 0 0 6px; }
/* Keyboard focus: one 2px solid ring everywhere at (0,1,1), matching the halo
   rule the retired sheets painted. .sidebar a:focus-visible (white ring on
   navy) and the field controls (border + ring) keep their own. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--primary-lt); outline-offset: 2px; box-shadow: none; border-radius: var(--radius-sm);
}
/* Status chips/badges use the success tokens (light on the dark tint), never the
   neutral --soft icon tint; "lean" keeps its own (info) hue so it stays
   distinguishable from "stronghold". Only .badge--ok still has a consumer. */
.chip--ok, .chip--stronghold, .badge--ok { background: var(--success-bg); color: var(--success-text); }
.chip--lean { background: var(--info-bg); color: var(--info-text); }
.kpi--ok { --kpi-tone-soft: var(--success-bg); }
/* <form data-autosubmit>: app.js hides the no-JS Save button; .btn's display
   would otherwise beat the UA [hidden] rule. */
[data-autosubmit-fallback][hidden] { display: none; }

/* ---- Command palette, part 2: rows, states, chrome (F1) ------------------
   The .palette / .shortcuts skeletons above landed with the shell (Epic A);
   these are the pieces the live palette actually renders: the grouped option
   rows app.js builds, the empty/loading state, and the shortcut sheet's scrim
   and row internals. Appended (never edited in place) so the earlier rules
   keep their meaning and the cascade decides in source order. */
.palette__row-ico { display: inline-flex; flex-shrink: 0; color: var(--text-faint); }
.palette__grp { list-style: none; }
.palette__grp + .palette__grp { border-top: 1px solid var(--border); margin-top: 4px; }
.palette__option-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.palette__option-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette__option-main .palette__option-sub { margin-left: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette__option-badge { flex-shrink: 0; padding: 1px 7px; border-radius: 999px; background: var(--surface-2); color: var(--text-muted); font-size: 10.5px; font-weight: 700; letter-spacing: .03em; }
.palette__option[aria-selected="true"] .palette__option-badge { background: var(--primary-lt); color: #fff; }
.palette__empty[hidden] { display: none; }
.palette__foot-end { margin-left: auto; }
.palette__empty-title { margin: 0; font-weight: 600; color: var(--text); }
.palette__empty-sub { margin: 6px auto 0; max-width: 42ch; font-size: 12px; color: var(--text-muted); line-height: 1.5; }
@media (max-width: 640px) {
  .palette { padding: 6vh 10px 10px; }
  .palette__list { max-height: 56vh; }
  .palette__foot { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .palette__dialog { animation: none; }
}

/* ---- Keyboard shortcuts overlay, part 2 (F1) ----------------------------- */
.shortcuts__scrim { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; appearance: none; background: none; cursor: pointer; }
.shortcuts__panel { z-index: 1; }
.shortcuts__label { min-width: 0; }
.shortcuts__sep { margin: 0 4px; font-size: 10.5px; color: var(--text-faint); }
.shortcuts__note { margin: 14px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--text-muted); }

/* ---- Live boards (F6) ----------------------------------------------------
   A [data-live] region is re-rendered in place by htmx (load, every 60s, and
   on 'refresh'). #kpi-live is a layout NO-OP -- display:contents keeps its
   four tiles as direct grid items of the .stats-grid around it, so a swap can
   never re-parent or re-flow the KPI row. The stamp spans the whole row when
   it lands inside a grid, and is a plain line anywhere else. */
.kpi-live { display: contents; }
.live-stamp { grid-column: 1 / -1; margin: 4px 0 0; font-size: 12px; color: var(--text-muted); text-align: right; }
.live-stamp time { font-variant-numeric: tabular-nums; }
/* Load shed / rate limit: app.js swaps in the paused message and marks it. */
.live-stamp[data-live-paused] { color: var(--warning-text); }
[data-live][aria-busy="true"] .live-stamp { opacity: .55; }
/* Skeleton placeholders app.js paints into an EMPTY htmx target (never over
   content that is already on screen). Shimmer is off under reduced motion via
   the .skeleton rules above. */
.skeleton[data-skeleton-node] { margin: 0 0 10px; }
.skeleton[data-skeleton-node]:last-child { margin-bottom: 0; }

/* -- Designed empty state -------------------------------------------------
   Every list, table and card that can legitimately have nothing in it uses
   this instead of a blank panel: a faint icon, a one-line heading and a
   sentence saying what would put something here. Hoisted out of 29 page
   <style> blocks (Epic G) so the states cannot drift apart. */
.empty-note {
    text-align: center;
    padding: 34px 20px;
    color: var(--text-muted);
    font-size: 13.5px;
}

.empty-note > svg {
    color: var(--text-faint);
    margin-bottom: 10px;
}

.empty-note h3 {
    margin: 0 0 6px;
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
}

.empty-note p {
    margin: 0;
}

.empty-note--tight { padding: 22px 18px; }
.empty-note--roomy { padding: 44px 20px; }

/* -- Reduced motion, complete (F7) ------------------------------------------
   The scattered per-component blocks above cover the dropdown menu, the help
   drawer, the sheet, the palette, the skeleton shimmer, the env pill and the
   chart tweens. This block closes the four gaps the sweep found and is placed
   LAST so it wins every equal-specificity tie by source order:
     - .notif__menu is declared after .dropdown__menu at the same specificity,
       so the generic rule never reached the bell menu;
     - the card/tile lifts the contract names (a.stat-tile / .stat-tile--link /
       .ql-card) were not covered at all;
     - the mobile .sidebar drawer -- the other "drawer" -- still slid;
     - .spinner is an UNBOUNDED rotation with no escape hatch.
   Selectors mirror the originals exactly (including .app.is-mobile-open
   .sidebar at (0,2,0) and a.stat-tile:hover at (0,2,1)) so each one wins. */
@media (prefers-reduced-motion: reduce) {
  .notif__menu { transition: none; }
  .notif__item { transition: none; }
  a.stat-tile, .stat-tile--link, .ql-card, .ql-card__go,
  .btn, .btn-ghost, .btn-outline, .abn__tab, .topbar__user-caret { transition: none; }
  a.stat-tile:hover, .stat-tile--link:hover, .ql-card:hover { transform: none; }
  .ql-card:hover .ql-card__go { transform: none; }
  .btn:active { transform: none; }
  .spinner { animation: none; }
  .sidebar, .app.is-mobile-open .sidebar, .app__main { transition: none; }
  .htmx-indicator, #progress-bar { transition: none; }
}
