/* ==========================================================================
 * ds.css - HAVAIC design system: tokens + component classes.
 *
 * Contract document: docs/ui-design-system.md  (read it before adding UI).
 *
 * Direction ("quarterly close at a desk"): calm, dense, numeric. One blue
 * primary, hairline borders instead of shadows (shadows only on floating
 * layers), tabular numbers everywhere, status carried by tinted chips.
 *
 *   1. Tokens (light + dark)           5. Tables
 *   2. Base / a11y                     6. Feedback (badge, alert, empty...)
 *   3. Buttons                         7. Layout helpers (page header...)
 *   4. Forms                           8. Legacy normalisation layer
 *
 * Colours are stored as space-separated RGB triples so that they can be used
 * both from CSS (`rgb(var(--ds-primary) / .5)`) and from Tailwind
 * (`bg-primary/50`, mapped in base.html's tailwind.config).
 * Theme switching: <html class="dark" data-theme="dark"> - set BEFORE first
 * paint by the inline script in base.html <head>.
 * The app shell (sidebar, header, drawer, toasts, dialogs) lives in
 * ds-shell.css.
 * ========================================================================== */

/* ---------------------------------------------------------------- 1. Tokens */
:root {
  color-scheme: light;

  /* surfaces */
  --ds-bg: 255 255 255;
  --ds-surface: 255 255 255;
  --ds-surface-subtle: 249 250 251;
  --ds-surface-sunken: 243 244 246;
  --ds-overlay: 17 24 39;

  /* lines */
  --ds-line: 229 231 235;
  --ds-line-strong: 209 213 219;

  /* ink (text). muted/subtle both pass WCAG AA (4.5:1) on every surface above AND on the tinted status surfaces
     (primary / success / warning / danger -soft). The contract is enforced by tests/services/test_ds_tokens_contrast.py */
  --ds-ink: 17 24 39;
  --ds-ink-muted: 75 85 99;
  --ds-ink-subtle: 100 107 120;
  --ds-ink-disabled: 156 163 175;
  --ds-on-primary: 255 255 255;

  /* the ONE primary */
  --ds-primary: 37 99 235;
  --ds-primary-hover: 29 78 216;
  --ds-primary-active: 30 64 175;
  --ds-primary-soft: 239 246 255;
  --ds-primary-border: 191 219 254;
  --ds-link: 29 78 216;
  --ds-ring: 59 130 246;

  /* status tones: solid = fill behind white text (>= 4.5:1), text = on soft/surface */
  --ds-success: 21 128 61;
  --ds-success-text: 21 128 61;
  --ds-success-soft: 240 253 244;
  --ds-success-border: 187 247 208;
  --ds-warning: 180 83 9;
  --ds-warning-text: 146 64 14;
  --ds-warning-soft: 255 251 235;
  --ds-warning-border: 253 230 138;
  --ds-danger: 185 28 28;
  --ds-danger-text: 185 28 28;
  --ds-danger-soft: 254 242 242;
  --ds-danger-border: 254 202 202;
  --ds-info: var(--ds-primary);
  --ds-info-text: var(--ds-link);
  --ds-info-soft: var(--ds-primary-soft);
  --ds-info-border: var(--ds-primary-border);

  /* radius: sm = badges/inline inputs, md = buttons/inputs, lg = cards/modals */
  --ds-radius-sm: 4px;
  --ds-radius-md: 8px;
  --ds-radius-lg: 12px;
  --ds-radius-full: 9999px;

  /* spacing (4px base) */
  --ds-space-1: 4px;
  --ds-space-2: 8px;
  --ds-space-3: 12px;
  --ds-space-4: 16px;
  --ds-space-6: 24px;
  --ds-space-8: 32px;
  --ds-space-12: 48px;
  --ds-gutter: 16px;             /* page gutter, 24px from lg */
  --ds-control-h: 36px;          /* 44px on coarse pointers (see section 2) */
  --ds-control-h-sm: 30px;

  /* type scale (hard minimum 12px) */
  --ds-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --ds-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --ds-fs-xs: 12px;   --ds-lh-xs: 16px;
  --ds-fs-sm: 13px;   --ds-lh-sm: 18px;
  --ds-fs-base: 14px; --ds-lh-base: 20px;
  --ds-fs-md: 16px;   --ds-lh-md: 24px;
  --ds-fs-lg: 18px;   --ds-lh-lg: 26px;
  --ds-fs-xl: 20px;   --ds-lh-xl: 28px;
  --ds-fs-2xl: 24px;  --ds-lh-2xl: 32px;

  /* elevation: borders carry structure, shadows only lift floating layers */
  --ds-shadow-card: 0 1px 2px rgb(0 0 0 / .04);
  --ds-shadow-pop: 0 8px 24px -4px rgb(17 24 39 / .16), 0 2px 6px rgb(17 24 39 / .06);
  --ds-shadow-modal: 0 24px 48px -12px rgb(17 24 39 / .30), 0 0 0 1px rgb(17 24 39 / .04);

  /* z-index scale: header < side panel < sidebar < dropdown < modal < toast */
  --ds-z-header: 20;
  --ds-z-panel: 25;
  --ds-z-scrim: 39;
  --ds-z-sidebar: 40;
  --ds-z-dropdown: 50;
  --ds-z-modal: 60;
  --ds-z-toast: 70;

  --ds-header-h: 56px;
  --ds-sidebar-w: 256px;
  --ds-ease: cubic-bezier(.23, 1, .32, 1);
}

:root.dark,
:root[data-theme="dark"] {
  color-scheme: dark;

  --ds-bg: 17 24 39;
  --ds-surface: 31 41 55;
  --ds-surface-subtle: 43 53 68;
  --ds-surface-sunken: 24 32 46;
  --ds-overlay: 0 0 0;

  --ds-line: 55 65 81;
  --ds-line-strong: 75 85 99;

  --ds-ink: 243 244 246;
  --ds-ink-muted: 171 178 190;
  --ds-ink-subtle: 158 166 179;
  --ds-ink-disabled: 107 114 128;

  --ds-primary: 37 99 235;        /* fill stays blue-600: white text = 5.2:1 */
  --ds-primary-hover: 59 130 246;
  --ds-primary-active: 96 165 250;
  --ds-primary-soft: 32 50 82;
  --ds-primary-border: 33 58 109;
  --ds-link: 104 170 250;         /* blue-400 on gray-800 = 6.1:1; >= 4.5:1 on every tinted surface too */
  --ds-ring: 96 165 250;

  --ds-success: 21 128 61;
  --ds-success-text: 74 222 128;
  --ds-success-soft: 32 64 61;
  --ds-success-border: 32 88 67;
  --ds-warning: 180 83 9;
  --ds-warning-text: 251 191 36;
  --ds-warning-soft: 63 59 48;
  --ds-warning-border: 95 76 42;
  --ds-danger: 185 28 28;
  --ds-danger-text: 252 129 129;  /* was 248 113 113: 4.48:1 on surface-subtle (danger-ghost "Remove" in a subtle panel) */
  --ds-danger-soft: 62 45 57;
  --ds-danger-border: 93 49 59;

  --ds-shadow-card: none;
  --ds-shadow-pop: 0 0 0 1px rgb(255 255 255 / .08), 0 12px 28px -6px rgb(0 0 0 / .6);
  --ds-shadow-modal: 0 0 0 1px rgb(255 255 255 / .10), 0 28px 56px -12px rgb(0 0 0 / .7);
}

@media (min-width: 1024px) {
  :root { --ds-gutter: 24px; }
}
@media (pointer: coarse) {
  :root { --ds-control-h: 44px; --ds-control-h-sm: 36px; }
}

/* ---------------------------------------------------------- 2. Base / a11y */
*, *::before, *::after { box-sizing: border-box; }   /* same as Tailwind preflight: standalone pages (login) need it too */
[hidden] { display: none !important; }
html {
  font-family: var(--ds-font);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-padding-top: calc(var(--ds-header-h) + 12px);   /* anchors / focus never hide under the sticky header */
}
body {
  margin: 0;
  background: rgb(var(--ds-bg));
  color: rgb(var(--ds-ink));
  font-family: var(--ds-font);
  font-size: var(--ds-fs-base);
  line-height: var(--ds-lh-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
/* kill the 1px of horizontal scroll caused by the vertical scrollbar + 100vw users */
html, body { max-width: 100%; }

/* visible focus ring for everything keyboard-reachable */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid rgb(var(--ds-ring));
  outline-offset: 2px;
  border-radius: var(--ds-radius-sm);
}
:where(input, select, textarea):focus-visible { outline-offset: 0; border-radius: var(--ds-radius-md); }

/* skip link: first tab stop, visible on focus */
.ds-skip-link {
  position: fixed; left: 12px; top: -64px; z-index: 100;
  padding: 10px 16px; border-radius: var(--ds-radius-md);
  background: rgb(var(--ds-primary)); color: rgb(var(--ds-on-primary));
  font-weight: 600; text-decoration: none;
  transition: top .15s var(--ds-ease);
}
.ds-skip-link:focus { top: 12px; }

.ds-sr-only {
  position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* numbers: right aligned + tabular so columns line up */
.ds-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
input.ds-num, .ds-input.ds-num { min-width: 9ch; }
.ds-tabular { font-variant-numeric: tabular-nums; }
.ds-muted { color: rgb(var(--ds-ink-muted)); }
.ds-subtle { color: rgb(var(--ds-ink-subtle)); }
.ds-nowrap { white-space: nowrap; }
.ds-pre-line { white-space: pre-line; }   /* a text message whose line breaks matter (HavaicUI dialogs and toasts use it) */
.ds-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ds-scroll-x { overflow-x: auto; max-width: 100%; }
/* a link inside a sentence must not rely on colour alone (WCAG 1.4.1, axe link-in-text-block): underlined at rest */
.ds-link { color: rgb(var(--ds-link)); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; cursor: pointer; }
.ds-link:hover { text-decoration-thickness: 2px; }
.ds-kbd {
  display: inline-block; padding: 1px 6px; border: 1px solid rgb(var(--ds-line-strong));
  border-bottom-width: 2px; border-radius: var(--ds-radius-sm); background: rgb(var(--ds-surface-subtle));
  font: 600 12px/16px var(--ds-font-mono); color: rgb(var(--ds-ink-muted));
}

/* report / PDF previews must render as paper in BOTH themes */
.ds-paper {
  color-scheme: light;
  background: #fff; color: #111827;
  border: 1px solid rgb(var(--ds-line)); border-radius: var(--ds-radius-md);
}
.ds-paper * { border-color: #e5e7eb; }

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

/* -------------------------------------------------------------- 3. Buttons */
.ds-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: var(--ds-control-h); padding: 0 14px;
  border: 1px solid transparent; border-radius: var(--ds-radius-md);
  font: 500 var(--ds-fs-base)/1 var(--ds-font);
  white-space: nowrap; text-decoration: none; cursor: pointer; user-select: none;
  background: transparent; color: rgb(var(--ds-ink));
  transition: background-color .12s var(--ds-ease), border-color .12s var(--ds-ease),
              color .12s var(--ds-ease), transform .1s var(--ds-ease);
}
.ds-btn:active:not(:disabled):not([aria-disabled="true"]) { transform: scale(.98); }
.ds-btn:disabled, .ds-btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.ds-btn > i, .ds-btn > svg { flex: none; }

.ds-btn--primary { background: rgb(var(--ds-primary)); color: rgb(var(--ds-on-primary)); }
.ds-btn--primary:hover:not(:disabled) { background: rgb(var(--ds-primary-hover)); }
.ds-btn--primary:active:not(:disabled) { background: rgb(var(--ds-primary-active)); }

.ds-btn--secondary {
  background: rgb(var(--ds-surface)); color: rgb(var(--ds-ink));
  border-color: rgb(var(--ds-line-strong));
}
.ds-btn--secondary:hover:not(:disabled) { background: rgb(var(--ds-surface-subtle)); border-color: rgb(var(--ds-ink-disabled)); }

.ds-btn--ghost { color: rgb(var(--ds-ink-muted)); }
.ds-btn--ghost:hover:not(:disabled) { background: rgb(var(--ds-surface-sunken)); color: rgb(var(--ds-ink)); }

.ds-btn--danger { background: rgb(var(--ds-danger)); color: #fff; }
.ds-btn--danger:hover:not(:disabled) { background: rgb(153 27 27); }
/* quiet destructive: for row-level delete next to safe actions */
.ds-btn--danger-ghost { color: rgb(var(--ds-danger-text)); }
.ds-btn--danger-ghost:hover:not(:disabled) { background: rgb(var(--ds-danger-soft)); }

.ds-btn--link { height: auto; padding: 0; color: rgb(var(--ds-link)); }
.ds-btn--link:hover:not(:disabled) { text-decoration: underline; }

.ds-btn--sm { height: var(--ds-control-h-sm); padding: 0 10px; font-size: var(--ds-fs-sm); gap: 6px; }
.ds-btn--block { display: flex; width: 100%; }

/* loading: add aria-busy="true" - label stays, spinner replaces the icon slot */
.ds-btn[aria-busy="true"] { position: relative; pointer-events: none; opacity: .8; }
.ds-btn[aria-busy="true"]::after {
  content: ""; width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: ds-spin .7s linear infinite;
}

/* icon-only button: ALWAYS needs aria-label (macro icon_btn enforces it) */
.ds-icon-btn {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--ds-control-h); height: var(--ds-control-h); padding: 0;
  border: 1px solid transparent; border-radius: var(--ds-radius-md);
  background: transparent; color: rgb(var(--ds-ink-muted)); cursor: pointer;
  transition: background-color .12s var(--ds-ease), color .12s var(--ds-ease);
  position: relative;
}
.ds-icon-btn:hover:not(:disabled) { background: rgb(var(--ds-surface-sunken)); color: rgb(var(--ds-ink)); }
.ds-icon-btn:disabled { opacity: .5; cursor: not-allowed; }
.ds-icon-btn--sm { width: var(--ds-control-h-sm); height: var(--ds-control-h-sm); font-size: 13px; }
.ds-icon-btn--danger:hover:not(:disabled) { background: rgb(var(--ds-danger-soft)); color: rgb(var(--ds-danger-text)); }
.ds-icon-btn--primary:hover:not(:disabled) { background: rgb(var(--ds-primary-soft)); color: rgb(var(--ds-link)); }

@keyframes ds-spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------- 4. Forms */
.ds-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ds-label { font-size: var(--ds-fs-sm); line-height: var(--ds-lh-sm); font-weight: 500; color: rgb(var(--ds-ink)); }
.ds-required { color: rgb(var(--ds-danger-text)); margin-left: 2px; }
.ds-hint { font-size: var(--ds-fs-xs); line-height: var(--ds-lh-xs); color: rgb(var(--ds-ink-muted)); }
.ds-error {
  display: flex; gap: 6px; align-items: flex-start;
  font-size: var(--ds-fs-xs); line-height: var(--ds-lh-xs); color: rgb(var(--ds-danger-text)); font-weight: 500;
}
.ds-error::before { content: "\f06a"; font: 900 12px/16px "Font Awesome 6 Free"; flex: none; }

.ds-input, .ds-select, .ds-textarea {
  width: 100%; min-width: 0; height: var(--ds-control-h);
  padding: 0 12px;
  border: 1px solid rgb(var(--ds-line-strong)); border-radius: var(--ds-radius-md);
  background: rgb(var(--ds-surface)); color: rgb(var(--ds-ink));
  font: 400 var(--ds-fs-base)/var(--ds-lh-base) var(--ds-font);
  transition: border-color .12s var(--ds-ease), box-shadow .12s var(--ds-ease);
}
.ds-textarea { height: auto; min-height: 88px; padding: 8px 12px; resize: vertical; }
.ds-select, .ds-context select {
  -webkit-appearance: none; appearance: none; text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%236b7280' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px;
}
:root.dark .ds-select, :root.dark .ds-context select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%239ca3af' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E");
}
.ds-select { padding-right: 32px; }
.ds-input::placeholder, .ds-textarea::placeholder { color: rgb(var(--ds-ink-subtle)); opacity: 1; }
.ds-input:hover:not(:disabled), .ds-select:hover:not(:disabled), .ds-textarea:hover:not(:disabled) { border-color: rgb(var(--ds-ink-disabled)); }
.ds-input:focus, .ds-select:focus, .ds-textarea:focus {
  outline: none; border-color: rgb(var(--ds-ring)); box-shadow: 0 0 0 3px rgb(var(--ds-ring) / .25);
}
.ds-input:disabled, .ds-select:disabled, .ds-textarea:disabled, .ds-input[readonly] {
  background-color: rgb(var(--ds-surface-sunken)); color: rgb(var(--ds-ink-muted)); cursor: not-allowed;  /* not the shorthand: it reset the select chevron */
}
.ds-input[aria-invalid="true"], .ds-select[aria-invalid="true"], .ds-textarea[aria-invalid="true"],
.ds-field.has-error .ds-input, .ds-field.has-error .ds-select, .ds-field.has-error .ds-textarea {
  border-color: rgb(var(--ds-danger-text));
}
.ds-input[aria-invalid="true"]:focus, .ds-field.has-error .ds-input:focus { box-shadow: 0 0 0 3px rgb(var(--ds-danger-text) / .25); }
/* currency / percent affixes */
.ds-input-group { position: relative; display: flex; align-items: stretch; }
.ds-input-group > .ds-input { flex: 1; }
.ds-input-group__affix {
  display: inline-flex; align-items: center; padding: 0 10px; font-size: var(--ds-fs-sm);
  color: rgb(var(--ds-ink-muted)); background: rgb(var(--ds-surface-sunken));
  border: 1px solid rgb(var(--ds-line-strong));
}
.ds-input-group__affix:first-child { border-right: 0; border-radius: var(--ds-radius-md) 0 0 var(--ds-radius-md); }
.ds-input-group__affix:last-child { border-left: 0; border-radius: 0 var(--ds-radius-md) var(--ds-radius-md) 0; }
.ds-input-group > .ds-input:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.ds-input-group > .ds-input:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; }

.ds-check { display: inline-flex; align-items: center; gap: 8px; font-size: var(--ds-fs-base); cursor: pointer; }
.ds-check input { width: 16px; height: 16px; accent-color: rgb(var(--ds-primary)); }

.ds-form-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
/* long forms: keep Save/Cancel reachable */
.ds-form-actions {
  position: sticky; bottom: 0; z-index: 5; display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;
  padding: 12px var(--ds-gutter); margin: 24px calc(var(--ds-gutter) * -1) 0;
  background: rgb(var(--ds-surface)); border-top: 1px solid rgb(var(--ds-line));
}

/* ------------------------------------------------------------------ cards */
.ds-card {
  background: rgb(var(--ds-surface)); border: 1px solid rgb(var(--ds-line));
  border-radius: var(--ds-radius-lg); box-shadow: var(--ds-shadow-card);
  min-width: 0;
}
.ds-card__header {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px;
  padding: 14px 16px; border-bottom: 1px solid rgb(var(--ds-line));
}
.ds-card__title { margin: 0; font-size: var(--ds-fs-md); line-height: var(--ds-lh-md); font-weight: 600; color: rgb(var(--ds-ink)); min-width: 0; }
.ds-card__subtitle { margin: 2px 0 0; font-size: var(--ds-fs-sm); color: rgb(var(--ds-ink-muted)); }
.ds-card__actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ds-card__body { padding: 16px; min-width: 0; }
.ds-card__footer { padding: 12px 16px; border-top: 1px solid rgb(var(--ds-line)); background: rgb(var(--ds-surface-subtle)); border-radius: 0 0 var(--ds-radius-lg) var(--ds-radius-lg); }
.ds-card--flush > .ds-card__body { padding: 0; }
@media (min-width: 1024px) {
  .ds-card__header, .ds-card__footer { padding-left: 24px; padding-right: 24px; }
  .ds-card__body { padding: 24px; }
  .ds-card--flush > .ds-card__body { padding: 0; }
}

/* ------------------------------------------------------------------ tables */
/* usage: <div class="ds-table-wrap"><table class="ds-table ds-table--zebra"> ... */
/* position:relative makes the wrapper the containing block of absolutely positioned cell content
   (.sr-only labels, sticky-column helpers). Without it those escape the overflow clip, land far to the
   right and - in mobile Chromium - widen the layout viewport so the whole page zooms out (seen on /approvals). */
:where(.overflow-x-auto, .overflow-auto, .overflow-x-scroll, .overflow-scroll):has(table) { position: relative; }
.ds-table-wrap {
  position: relative; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch;
  border: 1px solid rgb(var(--ds-line)); border-radius: var(--ds-radius-lg); background: rgb(var(--ds-surface));
}
.ds-table-wrap--scroll-y { max-height: min(70vh, 720px); overflow-y: auto; }
.ds-card > .ds-table-wrap { border: 0; border-radius: 0 0 var(--ds-radius-lg) var(--ds-radius-lg); }
.ds-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--ds-fs-base); line-height: var(--ds-lh-base); color: rgb(var(--ds-ink)); }
.ds-table thead th {
  position: sticky; top: 0; z-index: 2;
  padding: 10px 16px; text-align: left; white-space: nowrap;
  font-size: var(--ds-fs-xs); line-height: var(--ds-lh-xs); font-weight: 600; letter-spacing: .03em; text-transform: uppercase;
  color: rgb(var(--ds-ink-muted)); background: rgb(var(--ds-surface-subtle));
  border-bottom: 1px solid rgb(var(--ds-line));
}
.ds-table td { padding: 12px 16px; vertical-align: middle; border-bottom: 1px solid rgb(var(--ds-line)); }
.ds-table tbody tr:last-child td { border-bottom: 0; }
.ds-table tbody tr:hover > td { background: rgb(var(--ds-surface-subtle)); }
.ds-table--zebra tbody tr:nth-child(even) > td { background: rgb(var(--ds-surface-subtle) / .6); }
.ds-table--compact td { padding: 8px 12px; }
.ds-table--compact thead th { padding: 8px 12px; }
.ds-table th.ds-num, .ds-table td.ds-num, .ds-table th.num, .ds-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.ds-table td.ds-actions, .ds-table th.ds-actions { text-align: right; white-space: nowrap; width: 1%; }
.ds-table--sticky-col th:first-child, .ds-table--sticky-col td:first-child {
  position: sticky; left: 0; z-index: 1; background: rgb(var(--ds-surface));
  box-shadow: 1px 0 0 rgb(var(--ds-line));
}
.ds-table--sticky-col thead th:first-child { z-index: 3; background: rgb(var(--ds-surface-subtle)); }
.ds-table tfoot td { font-weight: 600; background: rgb(var(--ds-surface-subtle)); border-top: 1px solid rgb(var(--ds-line-strong)); }
.ds-table-caption { padding: 12px 16px; font-size: var(--ds-fs-sm); color: rgb(var(--ds-ink-muted)); }
/* <640px: rows become stacked cards; every <td> needs data-label="Column name" */
@media (max-width: 639px) {
  .ds-table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .ds-table--stack, .ds-table--stack tbody, .ds-table--stack tr, .ds-table--stack td { display: block; width: 100%; }
  .ds-table--stack tr { padding: 8px 0; border-bottom: 1px solid rgb(var(--ds-line-strong)); }
  .ds-table--stack td { display: flex; justify-content: space-between; gap: 16px; padding: 6px 16px; border: 0; text-align: right; }
  .ds-table--stack td::before { content: attr(data-label); font-size: var(--ds-fs-xs); font-weight: 600; color: rgb(var(--ds-ink-muted)); text-align: left; flex: none; }
  .ds-table--stack td.ds-actions { justify-content: flex-end; width: 100%; }
  .ds-table--stack td.ds-actions::before { display: none; }
}
/* pagination footer */
.ds-pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; padding: 10px 16px; border-top: 1px solid rgb(var(--ds-line)); font-size: var(--ds-fs-sm); color: rgb(var(--ds-ink-muted)); }
.ds-pagination__pages { display: inline-flex; gap: 4px; }

/* -------------------------------------------------- 6. Feedback components */
.ds-badge {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 2px 8px; border: 1px solid transparent; border-radius: var(--ds-radius-full);
  font-size: var(--ds-fs-xs); line-height: var(--ds-lh-xs); font-weight: 500; white-space: nowrap;
  background: rgb(var(--ds-surface-sunken)); color: rgb(var(--ds-ink-muted));
}
.ds-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.ds-badge--neutral { background: rgb(var(--ds-surface-sunken)); color: rgb(var(--ds-ink-muted)); border-color: rgb(var(--ds-line)); }
.ds-badge--info    { background: rgb(var(--ds-info-soft)); color: rgb(var(--ds-info-text)); border-color: rgb(var(--ds-info-border)); }
.ds-badge--success { background: rgb(var(--ds-success-soft)); color: rgb(var(--ds-success-text)); border-color: rgb(var(--ds-success-border)); }
.ds-badge--warning { background: rgb(var(--ds-warning-soft)); color: rgb(var(--ds-warning-text)); border-color: rgb(var(--ds-warning-border)); }
.ds-badge--danger  { background: rgb(var(--ds-danger-soft)); color: rgb(var(--ds-danger-text)); border-color: rgb(var(--ds-danger-border)); }

.ds-alert {
  display: flex; gap: 12px; align-items: flex-start; padding: 12px 16px;
  border: 1px solid rgb(var(--ds-info-border)); border-radius: var(--ds-radius-md);
  background: rgb(var(--ds-info-soft)); color: rgb(var(--ds-ink));
  font-size: var(--ds-fs-base); line-height: var(--ds-lh-base);
}
.ds-alert__icon { flex: none; margin-top: 2px; color: rgb(var(--ds-info-text)); }
.ds-alert__body { min-width: 0; flex: 1; }
.ds-alert__title { font-weight: 600; margin: 0 0 2px; }
.ds-alert__close { flex: none; margin: -4px -8px 0 0; }
.ds-alert--success { background: rgb(var(--ds-success-soft)); border-color: rgb(var(--ds-success-border)); }
.ds-alert--success .ds-alert__icon { color: rgb(var(--ds-success-text)); }
.ds-alert--warning { background: rgb(var(--ds-warning-soft)); border-color: rgb(var(--ds-warning-border)); }
.ds-alert--warning .ds-alert__icon { color: rgb(var(--ds-warning-text)); }
.ds-alert--danger  { background: rgb(var(--ds-danger-soft)); border-color: rgb(var(--ds-danger-border)); }
.ds-alert--danger .ds-alert__icon { color: rgb(var(--ds-danger-text)); }

.ds-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px;
  padding: 40px 24px; color: rgb(var(--ds-ink-muted));
}
.ds-empty__icon {
  display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px;
  border-radius: 50%; background: rgb(var(--ds-surface-sunken)); color: rgb(var(--ds-ink-subtle)); font-size: 20px;
}
.ds-empty--error .ds-empty__icon { background: rgb(var(--ds-danger-soft)); color: rgb(var(--ds-danger-text)); }
.ds-empty__title { margin: 4px 0 0; font-size: var(--ds-fs-md); font-weight: 600; color: rgb(var(--ds-ink)); }
.ds-empty__body { margin: 0; max-width: 44ch; font-size: var(--ds-fs-base); }
.ds-empty__cta { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }

.ds-skeleton {
  display: block; border-radius: var(--ds-radius-sm);
  background: linear-gradient(90deg, rgb(var(--ds-surface-sunken)) 25%, rgb(var(--ds-surface-subtle)) 37%, rgb(var(--ds-surface-sunken)) 63%);
  background-size: 400% 100%; animation: ds-shimmer 1.4s ease infinite;
}
.ds-skeleton--text { height: 12px; width: 100%; margin: 6px 0; }
.ds-skeleton--title { height: 20px; width: 40%; }
.ds-skeleton--row { height: 40px; margin: 8px 0; border-radius: var(--ds-radius-md); }
.ds-skeleton--card { height: 96px; border-radius: var(--ds-radius-lg); }
.ds-skeleton--circle { width: 36px; height: 36px; border-radius: 50%; }
@keyframes ds-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

.ds-spinner {
  display: inline-block; width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid rgb(var(--ds-ink-disabled)); border-top-color: rgb(var(--ds-primary));
  animation: ds-spin .7s linear infinite; vertical-align: -3px;
}

.ds-progress { height: 8px; border-radius: var(--ds-radius-full); background: rgb(var(--ds-surface-sunken)); overflow: hidden; }
.ds-progress__bar { height: 100%; border-radius: inherit; background: rgb(var(--ds-primary)); transition: width .3s var(--ds-ease); }
.ds-progress__bar--success { background: rgb(var(--ds-success)); }
.ds-progress__bar--warning { background: rgb(var(--ds-warning)); }
.ds-progress__bar--danger { background: rgb(var(--ds-danger)); }

.ds-stat { padding: 16px; border: 1px solid rgb(var(--ds-line)); border-radius: var(--ds-radius-lg); background: rgb(var(--ds-surface)); min-width: 0; }
.ds-stat__label { font-size: var(--ds-fs-xs); line-height: var(--ds-lh-xs); font-weight: 500; letter-spacing: .03em; text-transform: uppercase; color: rgb(var(--ds-ink-muted)); }
.ds-stat__value { margin-top: 4px; font-size: var(--ds-fs-2xl); line-height: var(--ds-lh-2xl); font-weight: 600; letter-spacing: -.01em; font-variant-numeric: tabular-nums; color: rgb(var(--ds-ink)); overflow-wrap: anywhere; }
.ds-stat__meta { margin-top: 2px; font-size: var(--ds-fs-xs); color: rgb(var(--ds-ink-muted)); }
.ds-stat--success .ds-stat__value { color: rgb(var(--ds-success-text)); }
.ds-stat--warning .ds-stat__value { color: rgb(var(--ds-warning-text)); }
.ds-stat--danger .ds-stat__value { color: rgb(var(--ds-danger-text)); }

.ds-tabs { display: flex; gap: 4px; overflow-x: auto; border-bottom: 1px solid rgb(var(--ds-line)); -webkit-overflow-scrolling: touch; }
.ds-tab {
  position: relative; flex: none; padding: 10px 14px; border: 0; background: transparent; cursor: pointer;
  font: 500 var(--ds-fs-base)/var(--ds-lh-base) var(--ds-font); color: rgb(var(--ds-ink-muted)); white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px; text-decoration: none;
}
.ds-tab:hover { color: rgb(var(--ds-ink)); }
.ds-tab[aria-selected="true"], .ds-tab.is-active { color: rgb(var(--ds-link)); border-bottom-color: rgb(var(--ds-primary)); }

.ds-filterbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; margin-bottom: 16px; }
.ds-filterbar__search { flex: 1 1 220px; min-width: 0; max-width: 360px; }
.ds-filterbar__spacer { flex: 1 1 0; }
.ds-filterbar .ds-field { flex: 0 1 180px; }
@media (max-width: 639px) { .ds-filterbar .ds-field, .ds-filterbar__search { flex: 1 1 100%; max-width: none; } }

/* ------------------------------------------------ 7. Page layout helpers */
.ds-page { max-width: 1280px; margin: 0 auto; width: 100%; }
.ds-page--narrow { max-width: 880px; }
.ds-page--wide { max-width: none; }
.ds-stack > * + * { margin-top: var(--gap, 24px); }
/* real list markers (Tailwind preflight removes them) */
.ds-list { margin: 0; padding-left: 20px; list-style: decimal; }
.ds-list--bullets { list-style: disc; }
.ds-list > li + li { margin-top: 6px; }
.ds-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.ds-grid--stats { grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); }

.ds-page-header {
  display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between;
  gap: 12px 24px; margin-bottom: 24px; min-width: 0;
}
.ds-page-header__main { min-width: 0; flex: 1 1 320px; }
.ds-page-header__crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 0 0 4px; padding: 0; list-style: none; font-size: var(--ds-fs-sm); color: rgb(var(--ds-ink-muted)); }
.ds-page-header__crumbs a { color: rgb(var(--ds-ink-muted)); text-decoration: none; }
.ds-page-header__crumbs a:hover { color: rgb(var(--ds-link)); text-decoration: underline; }
.ds-page-header__crumbs li + li::before { content: "/"; margin-right: 8px; color: rgb(var(--ds-ink-disabled)); }
.ds-page-header__titlerow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; min-width: 0; }
.ds-page-header__title {
  margin: 0; min-width: 0; font-size: var(--ds-fs-xl); line-height: var(--ds-lh-xl); font-weight: 600; letter-spacing: -.01em;
  color: rgb(var(--ds-ink)); overflow-wrap: anywhere; text-wrap: balance;
}
.ds-page-header__subtitle { margin: 4px 0 0; font-size: var(--ds-fs-base); color: rgb(var(--ds-ink-muted)); max-width: 72ch; text-wrap: pretty; }
.ds-page-header__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; }
@media (max-width: 639px) {
  .ds-page-header__actions { width: 100%; justify-content: flex-start; }
  .ds-page-header__actions > .ds-btn--primary { flex: 1 1 auto; }
}

/* ------------------------------------------ 8. Legacy normalisation layer
 * Pages written before the design system use raw Tailwind palette classes.
 * This layer makes them converge on the tokens WITHOUT editing the pages:
 *   - one primary (indigo / purple / teal become the blue primary)
 *   - filled buttons keep >= 4.5:1 contrast with white text
 *   - dark-mode text that used light-mode greys stays readable
 * It only targets buttons/links/text colours, never layout. When a page is
 * migrated to ds-btn / ds-badge / tokens these rules simply stop matching.
 * Delete the block once `grep -rn "bg-indigo-\|bg-purple-\|bg-teal-" templates`
 * is empty.
 * ------------------------------------------------------------------------ */
button.bg-indigo-600, a.bg-indigo-600, button.bg-purple-600, a.bg-purple-600,
button.bg-teal-600, a.bg-teal-600, button.bg-blue-600, a.bg-blue-600 { background-color: rgb(var(--ds-primary)); }
button.hover\:bg-indigo-700:hover, a.hover\:bg-indigo-700:hover, button.hover\:bg-purple-700:hover, a.hover\:bg-purple-700:hover,
button.hover\:bg-teal-700:hover, a.hover\:bg-teal-700:hover, button.hover\:bg-blue-700:hover, a.hover\:bg-blue-700:hover { background-color: rgb(var(--ds-primary-hover)); }
button.bg-green-600, a.bg-green-600, button.bg-emerald-600, a.bg-emerald-600 { background-color: rgb(var(--ds-success)); }
button.hover\:bg-green-700:hover, a.hover\:bg-green-700:hover, button.hover\:bg-emerald-700:hover, a.hover\:bg-emerald-700:hover { background-color: rgb(20 83 45); }
button.bg-orange-600, a.bg-orange-600, button.bg-amber-600, a.bg-amber-600, button.bg-yellow-600, a.bg-yellow-600,
button.bg-orange-500, a.bg-orange-500, button.bg-amber-500, a.bg-amber-500, button.bg-yellow-500, a.bg-yellow-500 { background-color: rgb(var(--ds-warning)); color: #fff; }
button.hover\:bg-orange-700:hover, a.hover\:bg-orange-700:hover, button.hover\:bg-amber-700:hover, a.hover\:bg-amber-700:hover,
button.hover\:bg-yellow-700:hover, a.hover\:bg-yellow-700:hover, button.hover\:bg-orange-600:hover, a.hover\:bg-orange-600:hover { background-color: rgb(146 64 14); }
button.bg-red-600, a.bg-red-600 { background-color: rgb(var(--ds-danger)); }
button.hover\:bg-red-700:hover, a.hover\:bg-red-700:hover { background-color: rgb(153 27 27); }

/* Elements that carry their OWN background class (`bg-green-100 text-green-700` badges) are never remapped: their
 * text/background pair was designed together. Only text that sits on the page / card surface is touched. */

/* light surfaces: gray-400 text = 2.5:1, green-600 = 3.3:1, red-500 = 3.6:1; icons keep their soft tone */
:root:not(.dark) .text-gray-400:not([class*="fa-"]):not(svg):not(svg *):not([class*="bg-"]) { color: rgb(var(--ds-ink-subtle)); }
:root:not(.dark) :is(.text-green-500, .text-green-600):not([class*="bg-"]) { color: rgb(var(--ds-success-text)); }
:root:not(.dark) :is(.text-red-500):not([class*="bg-"]) { color: rgb(var(--ds-danger-text)); }
:root:not(.dark) :is(.text-yellow-500, .text-yellow-600, .text-amber-500, .text-amber-600, .text-orange-500, .text-orange-600):not([class*="bg-"]) { color: rgb(var(--ds-warning-text)); }

/* dark mode: greys/blues authored for light backgrounds fail on gray-800 (links 2.8:1, gray-500 3.0:1, gray-600 1.9:1).
 * The `dark:text-gray-500/600` variants (3.0:1 / 1.9:1 on gray-800) are lifted too: the extra :root raises specificity
 * above the Tailwind-generated `.dark .dark\:text-gray-500` rule. */
:root.dark :is(.text-gray-500, .text-gray-600, .dark\:text-gray-500, .dark\:text-gray-600):not([class*="bg-"]) { color: rgb(var(--ds-ink-muted)); }
:root.dark :is(.text-blue-600, .text-blue-700, .text-indigo-600, .text-indigo-700):not([class*="bg-"]) { color: rgb(var(--ds-link)); }
:root.dark :is(.text-green-600, .text-green-700):not([class*="bg-"]) { color: rgb(var(--ds-success-text)); }
:root.dark :is(.text-red-500, .text-red-600, .text-red-700):not([class*="bg-"]) { color: rgb(var(--ds-danger-text)); }
:root.dark :is(.text-yellow-600, .text-yellow-700, .text-amber-600, .text-amber-700, .text-orange-600):not([class*="bg-"]) { color: rgb(var(--ds-warning-text)); }
/* ...and paper surfaces (report previews) keep their light-theme palette in dark mode */
:root.dark .ds-paper .text-gray-500 { color: #6b7280; }
:root.dark .ds-paper .text-gray-600 { color: #4b5563; }
:root.dark .ds-paper .text-blue-600, :root.dark .ds-paper .text-blue-700 { color: #1d4ed8; }
:root.dark .ds-paper .text-green-600, :root.dark .ds-paper .text-green-700 { color: #15803d; }
:root.dark .ds-paper .text-red-600, :root.dark .ds-paper .text-red-700 { color: #b91c1c; }
