/* ══════════════════════════════════════════════════════════════════════════
   07 — Design tokens & components (Docket direction: ink, paper, one orange
   signal colour). Spec: _notes/release-specialisation/07-design-tokens.md

   Load order matters: this file is linked in _BootstrapLayout.cshtml AFTER
   aistyle.css so its :root block wins the cascade for the aliased old
   variable names below (07.1 "alias the old variable names to the new
   tokens so existing pages don't break") without editing aistyle.css itself.

   Three levels, top to bottom: primitive -> semantic -> component.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Primitive ──────────────────────────────────────────────────────── */
  --dt-ink-950: #0f172a;
  --dt-ink-700: #1f2937;
  --dt-paper-0: #ffffff;
  --dt-paper-50: #fafbfc;
  --dt-paper-100: #f3f4f6;
  --dt-line-200: rgba(229, 231, 235, 0.6);
  --dt-orange-500: #ff6b35;
  --dt-orange-600: #d1582b;
  --dt-orange-700: #b93c0c;

  /* ── Semantic (light — default) ────────────────────────────────────── */
  --canvas: var(--dt-paper-50);
  --surface: var(--dt-paper-0);
  --ink: var(--dt-ink-950);
  --muted: #6b7280;
  --rule: var(--dt-line-200);
  --signal: var(--dt-orange-500);
  /* colony-visual-audit 2026-09-22 — white text on --signal fails contrast at 2.84:1; ink text
     on it passes at 6.30:1 (re-measured 2026-09-24; 6.70 was a mis-quote). Every component below that fills with --signal must use
     --signal-text, never a hardcoded white. Meaning of the name: --signal-text is the text colour ON a
   --signal fill (the pair is fill and its text). For orange text on a light page use --signal-ink. */
  --signal-text: var(--dt-ink-950);
  /* Orange used AS TEXT or as a small icon colour on a light surface (a link, an outline-button label,
     a row icon). The bright --signal fails there (2.84:1 on white); this darker orange measures
     5.65:1 on white, 5.46:1 on --canvas and 5.0:1 on a 10% orange tint, so it passes AA text and the
     3:1 icon rule. Never use it as a fill. */
  --signal-ink: var(--dt-orange-700);
  /* Reusing the already contrast-checked *-text colours aistyle.css defines (--success-text
     #047857 4.8:1, --warning-text #b45309 4.6:1, --info-text #0e7490 4.7:1, --danger-text
     #dc2626 4.8:1) instead of picking new, unchecked hex values — see aistyle.css's own
     comments next to those variables for the ratios. */
  --good: var(--success-text, #047857);
  --warn: var(--warning-text, #b45309);
  --danger: var(--danger-text, #dc2626);
  --info: var(--info-text, #0e7490);
  /* Text on a --danger fill: white on #dc2626 is 4.83:1. */
  --dt-on-danger: #ffffff;
  /* Visible edge for small outline controls (row action buttons): 3.32:1 on white, 3.21:1 on
     --canvas — WCAG 1.4.11 wants 3:1 for a control's boundary. */
  --dt-control-edge: #868e96;
  --dt-control-edge-strong: #6b7280;

  /* Dark mode (2026-10-01): neutral ladder + extras. LIGHT values are the exact hex values that
     aistyle.css used to hard-code, so light mode renders byte-for-byte the same; aistyle.css now
     reads them as var(--dt-gray-200, #e5e7eb) etc. Every token in this block has a dark value in
     the dark set below (CRMAdmin.Tests/DarkModeTokenTests enforces that). */
  --dt-gray-50: #f9fafb;
  --dt-slate-50: #f8fafc;
  --dt-gray-75: #f5f6f8;
  --dt-gray-100: #f3f4f6;
  --dt-slate-100: #f1f5f9;
  --dt-gray-200: #e5e7eb;
  --dt-slate-200: #e2e8f0;
  --dt-gray-300: #d1d5db;
  --dt-gray-400: #9ca3af;
  --dt-gray-600: #4b5563;
  --dt-slate-600: #475569;
  --dt-gray-700: #374151;
  --dt-gray-900: #111827;
  --dt-hairline: #f5f5f7;
  --dt-page-a: #f8fafc;
  --dt-page-b: #ffffff;
  --dt-gray-800: #1f2937;
  --dt-chat-ink: #333333;
  --dt-chat-muted: #666666;
  --dt-chat-bg: #f0f2f5;
  --dt-glass-2-rgb: 249 250 251;
  --dt-bs-light: #f8f9fa;
  --dt-bs-track: #e9ecef;
  /* Space-separated r g b of the translucent "glass" panels: rgb(var(--dt-glass-rgb) / 0.9). */
  --dt-glass-rgb: 255 255 255;
  --dt-input-bg: #ffffff;
  --dt-input-border: #d1d5db;
  --dt-table-hover: #f8fafc;
  --dt-table-stripe: rgba(15, 23, 42, 0.02);
  --dt-overlay: rgba(15, 23, 42, 0.5);
  --dt-chart-text: #222222;              /* Google Charts' own default axis text */
  --dt-chart-grid: rgba(15, 23, 42, 0.08);
  --dt-scrollbar-thumb: #c1c8d1;
  --dt-scrollbar-track: #f3f4f6;

  /* Status stamps (07.3) — light pairs, all ≥ 6.4:1 (see the .dt-status block below). */
  --dt-status-draft-fg: #374151;       --dt-status-draft-bg: #f3f4f6;
  --dt-status-scheduled-fg: #3730a3;   --dt-status-scheduled-bg: #eef2ff;
  --dt-status-sent-fg: #075985;        --dt-status-sent-bg: #e0f2fe;
  --dt-status-viewed-fg: #6b21a8;      --dt-status-viewed-bg: #f3e8ff;
  --dt-status-part-paid-fg: #92400e;   --dt-status-part-paid-bg: #fef3c7;
  --dt-status-paid-fg: #065f46;        --dt-status-paid-bg: #d1fae5;
  --dt-status-overdue-fg: #991b1b;     --dt-status-overdue-bg: #fee2e2;
  --dt-status-in-progress-fg: #155e75; --dt-status-in-progress-bg: #cffafe;
  --dt-status-done-fg: #065f46;        --dt-status-done-bg: #d1fae5;
  --dt-status-accepted-fg: #166534;    --dt-status-accepted-bg: #dcfce7;
  --dt-status-cancelled-fg: #4b5563;   --dt-status-cancelled-bg: #f3f4f6;

  --dt-space-1: 4px;
  --dt-space-2: 8px;
  --dt-space-3: 12px;
  --dt-space-4: 16px;
  --dt-space-5: 24px;
  --dt-space-6: 32px;
  --dt-radius-sm: 4px;
  --dt-radius-md: 8px;
  --dt-radius-lg: 10px;
  --dt-elevation-1: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 20px rgba(0, 0, 0, 0.06);
  --dt-elevation-2: 0 8px 40px rgba(0, 0, 0, 0.12);

  /* 07.4 — type scale. Sizes only (aistyle.css's body/h1-h6 rule already applies
     --font-display/--font-body by element; these are for components that need a size off the
     scale without owning a heading tag — a card title, a KPI figure, a caption). */
  --dt-text-xs: 0.75rem;
  --dt-text-sm: 0.875rem;
  --dt-text-md: 1rem;
  --dt-text-lg: 1.25rem;
  --dt-text-xl: 1.5rem;
  --dt-text-2xl: 2rem;
  --dt-text-3xl: 2.5rem;

  /* ── Old names aliased to the new tokens (07.1) ────────────────────── */
  /* Deliberately only the colour/surface/text/shadow names the spec calls out — NOT
     --radius-lg/--radius-xl, which existing components size at 16px/24px; realiasing those to
     the new 4-10px scale would be a visible, unintended layout change on every current page. */
  --primary-violet: var(--signal);
  --primary-violet-dark: var(--dt-orange-600);
  --surface-light: var(--canvas);
  --text-primary: var(--ink);
  --text-secondary: var(--muted);
  --text-muted: var(--muted);
  --border-subtle: var(--rule);
  --shadow-soft: var(--dt-elevation-1);
  --shadow-elevated: var(--dt-elevation-2);
}

/* ── Dark theme ────────────────────────────────────────────────────────────────────────────────
   Dark mode toggle (Ari 2026-10-01). Two ways in, ONE set of values: the block below appears twice,
   identically, because CSS cannot share a declaration list between a plain rule and a media query.
   CRMAdmin.Tests/DarkModeTokenTests asserts the two copies are identical, so edit both together.

   1. :root[data-theme="dark"]  an explicit choice (the navbar toggle, stored per browser).
   2. prefers-color-scheme: dark, for internal pages only (html has data-colony-app, set by
      _BootstrapLayout.cshtml) and only when the user has not chosen light. This is how "follow
      system" works. Habitat storefront pages, the customer portal and every public page do NOT
      carry data-colony-app, so they stay light whatever the visitor's OS says.

   Light mode is untouched: nothing here applies unless one of those two selectors matches.
   A literal close-comment sequence must never appear inside a CSS comment (it silently drops the
   next rule), so every comment in this file is checked by DarkModeTokenTests. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --canvas: #0b1220;
  --surface: #111827;
  --ink: #f1f5f9;
  --muted: #94a3b8;
  --rule: rgba(255, 255, 255, 0.12);
  --signal: var(--dt-orange-500);
  --signal-text: var(--dt-ink-950);
  --signal-ink: var(--dt-orange-500);   /* 6.26:1 on --surface #111827, 6.60:1 on --canvas */
  --good: #34d399;                       /* 9.23:1 on --surface */
  --warn: #fbbf24;                       /* 10.63:1 */
  --danger: #f87171;                     /* 6.41:1 */
  --info: #38bdf8;                       /* 8.28:1 */
  /* aistyle.css's own AA text colours are tuned for white; on the dark surface they fall to about
     3.4:1, so the same four names resolve to the dark status colours above. */
  --success-text: #34d399;
  --warning-text: #fbbf24;
  --info-text: #38bdf8;
  --danger-text: #f87171;
  /* 07 audit: white on the dark --danger (#f87171) is 2.77:1; ink on it is 6.45:1. */
  --dt-on-danger: var(--dt-ink-950);
  --dt-control-edge: #64748b;            /* 3.73:1 on --surface #111827 */
  --dt-control-edge-strong: #94a3b8;     /* 6.92:1 on --surface #111827 */

  /* Neutral ladder. Surfaces step lighter than --surface (a raised or hover tone); text steps are
     measured on --surface: 900 16.2, 700 11.9, 600 9.5, 400 6.0 (all AA). 300 and the 200s are
     used as borders and tracks (about 2.3 to 2.5:1: decorative, not the only cue). */
  --dt-gray-50: #172033;
  --dt-slate-50: #172033;
  --dt-gray-75: #1a2438;
  --dt-gray-100: #1c2840;
  --dt-slate-100: #1c2840;
  --dt-gray-200: #2a3750;
  --dt-slate-200: #2a3750;
  --dt-gray-300: #475569;
  --dt-gray-400: #8b97ab;
  --dt-gray-600: #b4bfce;
  --dt-slate-600: #b4bfce;
  --dt-gray-700: #cbd5e1;
  --dt-gray-900: #f1f5f9;
  --dt-hairline: #1c2535;
  --dt-page-a: #0b1220;
  --dt-page-b: #0d1626;
  --dt-gray-800: #e2e8f0;
  --dt-chat-ink: #e2e8f0;
  --dt-chat-muted: #a3b0c2;
  --dt-chat-bg: #172033;
  --dt-glass-2-rgb: 23 32 51;
  --dt-bs-light: #172033;
  --dt-bs-track: #2a3750;
  --dt-glass-rgb: 17 24 39;
  --dt-input-bg: #0f1726;
  --dt-input-border: #3b475c;            /* 2.0:1 edge on --surface; the control also has a fill */
  --dt-table-hover: #172033;
  --dt-table-stripe: rgba(255, 255, 255, 0.03);
  --dt-overlay: rgba(0, 0, 0, 0.65);
  --dt-chart-text: #94a3b8;              /* 6.92:1 on --surface */
  --dt-chart-grid: rgba(255, 255, 255, 0.1);
  --dt-scrollbar-thumb: #3b475c;
  --dt-scrollbar-track: #0b1220;
  --dt-elevation-1: 0 1px 3px rgba(0, 0, 0, 0.5), 0 4px 20px rgba(0, 0, 0, 0.35);
  --dt-elevation-2: 0 8px 40px rgba(0, 0, 0, 0.6);
  scrollbar-color: #3b475c #0b1220;

  /* Bootstrap 5.3 reads these on <html>; colony-theme.js sets data-bs-theme to match, so modals,
     dropdowns, tables, form controls and list groups follow. --bs-body-bg is --surface (the light
     value is also white). */
  --bs-body-bg: #111827;
  --bs-body-bg-rgb: 17, 24, 39;
  --bs-body-color: #f1f5f9;
  --bs-body-color-rgb: 241, 245, 249;
  --bs-emphasis-color: #ffffff;
  --bs-emphasis-color-rgb: 255, 255, 255;
  --bs-secondary-color: #94a3b8;
  --bs-secondary-color-rgb: 148, 163, 184;
  --bs-secondary-bg: #172033;
  --bs-secondary-bg-rgb: 23, 32, 51;
  --bs-tertiary-color: #94a3b8;
  --bs-tertiary-bg: #0b1220;
  --bs-tertiary-bg-rgb: 11, 18, 32;
  --bs-border-color: #2a3750;
  --bs-border-color-translucent: rgba(255, 255, 255, 0.12);
  --bs-link-color: #ff8a5c;              /* 7.64:1 on --surface */
  --bs-link-color-rgb: 255, 138, 92;
  --bs-link-hover-color: #ffa27f;
  --bs-link-hover-color-rgb: 255, 162, 127;

  /* Status stamps (07.3) — dark pairs, all >= 6.9:1. */
  --dt-status-draft-fg: #e5e7eb;       --dt-status-draft-bg: #374151;
  --dt-status-scheduled-fg: #c7d2fe;   --dt-status-scheduled-bg: #312e81;
  --dt-status-sent-fg: #bae6fd;        --dt-status-sent-bg: #0c4a6e;
  --dt-status-viewed-fg: #e9d5ff;      --dt-status-viewed-bg: #581c87;
  --dt-status-part-paid-fg: #fde68a;   --dt-status-part-paid-bg: #78350f;
  --dt-status-paid-fg: #a7f3d0;        --dt-status-paid-bg: #064e3b;
  --dt-status-overdue-fg: #fecaca;     --dt-status-overdue-bg: #7f1d1d;
  --dt-status-in-progress-fg: #a5f3fc; --dt-status-in-progress-bg: #164e63;
  --dt-status-done-fg: #a7f3d0;        --dt-status-done-bg: #064e3b;
  --dt-status-accepted-fg: #bbf7d0;    --dt-status-accepted-bg: #14532d;
  --dt-status-cancelled-fg: #d1d5db;   --dt-status-cancelled-bg: #374151;
}

@media (prefers-color-scheme: dark) {
  :root[data-colony-app]:not([data-theme="light"]) {
    color-scheme: dark;
    --canvas: #0b1220;
    --surface: #111827;
    --ink: #f1f5f9;
    --muted: #94a3b8;
    --rule: rgba(255, 255, 255, 0.12);
    --signal: var(--dt-orange-500);
    --signal-text: var(--dt-ink-950);
    --signal-ink: var(--dt-orange-500);   /* 6.26:1 on --surface #111827, 6.60:1 on --canvas */
    --good: #34d399;                       /* 9.23:1 on --surface */
    --warn: #fbbf24;                       /* 10.63:1 */
    --danger: #f87171;                     /* 6.41:1 */
    --info: #38bdf8;                       /* 8.28:1 */
    /* aistyle.css's own AA text colours are tuned for white; on the dark surface they fall to about
       3.4:1, so the same four names resolve to the dark status colours above. */
    --success-text: #34d399;
    --warning-text: #fbbf24;
    --info-text: #38bdf8;
    --danger-text: #f87171;
    /* 07 audit: white on the dark --danger (#f87171) is 2.77:1; ink on it is 6.45:1. */
    --dt-on-danger: var(--dt-ink-950);
    --dt-control-edge: #64748b;            /* 3.73:1 on --surface #111827 */
    --dt-control-edge-strong: #94a3b8;     /* 6.92:1 on --surface #111827 */

    /* Neutral ladder. Surfaces step lighter than --surface (a raised or hover tone); text steps are
       measured on --surface: 900 16.2, 700 11.9, 600 9.5, 400 6.0 (all AA). 300 and the 200s are
       used as borders and tracks (about 2.3 to 2.5:1: decorative, not the only cue). */
    --dt-gray-50: #172033;
    --dt-slate-50: #172033;
    --dt-gray-75: #1a2438;
    --dt-gray-100: #1c2840;
    --dt-slate-100: #1c2840;
    --dt-gray-200: #2a3750;
    --dt-slate-200: #2a3750;
    --dt-gray-300: #475569;
    --dt-gray-400: #8b97ab;
    --dt-gray-600: #b4bfce;
    --dt-slate-600: #b4bfce;
    --dt-gray-700: #cbd5e1;
    --dt-gray-900: #f1f5f9;
    --dt-hairline: #1c2535;
  --dt-page-a: #0b1220;
  --dt-page-b: #0d1626;
  --dt-gray-800: #e2e8f0;
  --dt-chat-ink: #e2e8f0;
  --dt-chat-muted: #a3b0c2;
  --dt-chat-bg: #172033;
  --dt-glass-2-rgb: 23 32 51;
    --dt-bs-light: #172033;
    --dt-bs-track: #2a3750;
    --dt-glass-rgb: 17 24 39;
    --dt-input-bg: #0f1726;
    --dt-input-border: #3b475c;            /* 2.0:1 edge on --surface; the control also has a fill */
    --dt-table-hover: #172033;
    --dt-table-stripe: rgba(255, 255, 255, 0.03);
    --dt-overlay: rgba(0, 0, 0, 0.65);
    --dt-chart-text: #94a3b8;              /* 6.92:1 on --surface */
    --dt-chart-grid: rgba(255, 255, 255, 0.1);
    --dt-scrollbar-thumb: #3b475c;
    --dt-scrollbar-track: #0b1220;
    --dt-elevation-1: 0 1px 3px rgba(0, 0, 0, 0.5), 0 4px 20px rgba(0, 0, 0, 0.35);
    --dt-elevation-2: 0 8px 40px rgba(0, 0, 0, 0.6);
    scrollbar-color: #3b475c #0b1220;

    /* Bootstrap 5.3 reads these on <html>; colony-theme.js sets data-bs-theme to match, so modals,
       dropdowns, tables, form controls and list groups follow. --bs-body-bg is --surface (the light
       value is also white). */
    --bs-body-bg: #111827;
    --bs-body-bg-rgb: 17, 24, 39;
    --bs-body-color: #f1f5f9;
    --bs-body-color-rgb: 241, 245, 249;
    --bs-emphasis-color: #ffffff;
    --bs-emphasis-color-rgb: 255, 255, 255;
    --bs-secondary-color: #94a3b8;
    --bs-secondary-color-rgb: 148, 163, 184;
    --bs-secondary-bg: #172033;
    --bs-secondary-bg-rgb: 23, 32, 51;
    --bs-tertiary-color: #94a3b8;
    --bs-tertiary-bg: #0b1220;
    --bs-tertiary-bg-rgb: 11, 18, 32;
    --bs-border-color: #2a3750;
    --bs-border-color-translucent: rgba(255, 255, 255, 0.12);
    --bs-link-color: #ff8a5c;              /* 7.64:1 on --surface */
    --bs-link-color-rgb: 255, 138, 92;
    --bs-link-hover-color: #ffa27f;
    --bs-link-hover-color-rgb: 255, 162, 127;

    /* Status stamps (07.3) — dark pairs, all >= 6.9:1. */
    --dt-status-draft-fg: #e5e7eb;       --dt-status-draft-bg: #374151;
    --dt-status-scheduled-fg: #c7d2fe;   --dt-status-scheduled-bg: #312e81;
    --dt-status-sent-fg: #bae6fd;        --dt-status-sent-bg: #0c4a6e;
    --dt-status-viewed-fg: #e9d5ff;      --dt-status-viewed-bg: #581c87;
    --dt-status-part-paid-fg: #fde68a;   --dt-status-part-paid-bg: #78350f;
    --dt-status-paid-fg: #a7f3d0;        --dt-status-paid-bg: #064e3b;
    --dt-status-overdue-fg: #fecaca;     --dt-status-overdue-bg: #7f1d1d;
    --dt-status-in-progress-fg: #a5f3fc; --dt-status-in-progress-bg: #164e63;
    --dt-status-done-fg: #a7f3d0;        --dt-status-done-bg: #064e3b;
    --dt-status-accepted-fg: #bbf7d0;    --dt-status-accepted-bg: #14532d;
    --dt-status-cancelled-fg: #d1d5db;   --dt-status-cancelled-bg: #374151;
  }
}

/* ── Quick wins (07.5) ────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* Remove the glass page header (translucent fill + backdrop-filter blur) and the two-colour
   gradient icon tile — aistyle.css's shared .page-header/.header-icon classes, used by every
   internal page's header. Overridden centrally here (tokens.css loads after aistyle.css) rather
   than per-page, so this reaches every page without a page-by-page edit. Ink-on-signal for the
   icon tile, same reasoning as the button/status-stamp contrast fix above. */
.page-header {
  background: var(--surface);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border: 1px solid var(--rule);
  box-shadow: var(--dt-elevation-1);
}
.page-header::before { background: var(--signal); opacity: 1; }
.header-icon {
  background: var(--signal);
  color: var(--signal-text);
  box-shadow: none;
}

/* Sidebar in brand ink — already true (--sidebar-bg #0f172a is the same colour as --ink above,
   see aistyle.css's own "matches --surface-dark, not a new color" comment on that variable), so
   nothing to override here. Left as a comment, not a no-op rule, so a future reader doesn't
   wonder why 07.5 has no sidebar CSS. */

/* 07.4 — type scale utilities. Sizing classes off --dt-text-*, for a component that needs a
   scale size without owning a heading tag. .dt-tabular is the "money uses tabular numbers" half
   of the acceptance test: digits line up in a column instead of each digit claiming its own
   natural width (a "1" and a "0" render the same width). Apply it to any element showing money
   or another number meant to be scanned in a column — .dt-result-value (07.6) already has it
   inline; this class is for everything built later (invoices/quotes/Projects, Waves 2-3) that
   isn't built yet to apply it directly. */
.dt-text-xs { font-size: var(--dt-text-xs); }
.dt-text-sm { font-size: var(--dt-text-sm); }
.dt-text-md { font-size: var(--dt-text-md); }
.dt-text-lg { font-size: var(--dt-text-lg); font-family: var(--font-display, inherit); font-weight: 700; }
.dt-text-xl { font-size: var(--dt-text-xl); font-family: var(--font-display, inherit); font-weight: 700; }
.dt-text-2xl { font-size: var(--dt-text-2xl); font-family: var(--font-display, inherit); font-weight: 800; }
.dt-text-3xl { font-size: var(--dt-text-3xl); font-family: var(--font-display, inherit); font-weight: 800; }
.dt-tabular { font-variant-numeric: tabular-nums; }

/* 2026-09-23 — Ari reported the top navbar rendering white/transparent live. Root cause found in
   aistyle.css itself, near line 9798: a comment reading "--sidebar-*(slash)--primary-violet" (a
   shorthand meaning "these two token groups") contained that punctuation with no space around
   it — which is also, unintentionally, the two-character sequence that closes a CSS comment. It
   closed that comment early; everything from there up to the next literal "{" (including the
   comment's own real closing marker, now just stray text) got parsed as one garbage selector for
   the `.colony-topnav { background: ...; }` rule that followed, so the whole rule was silently
   dropped — the rest of the file parsed fine since nothing else in it happened to contain that
   exact two-character sequence outside of a real comment close. Fixed at the source (a space
   added) rather than duplicated here, so there's one copy of this rule, not two. */

/* Same session — the small round icon buttons (.tbl-btn/.action-btn/.fav-btn/.entry-action-btn,
   used for "view all" links on dashboard cards, row actions in tables, etc.) render correctly
   per DevTools (white-to-pale-grey gradient fill, a themed icon colour, a 25%-opacity coloured
   border) but read as "invisible" against a white/near-white card — a real, pre-existing
   low-contrast issue those hardcoded hex values have always had, not a missing-CSS bug like the
   navbar above. Token-driven replacement: a visible --rule border and --canvas fill instead of a
   white-on-white gradient, so the button reads as a button before you even notice its icon
   colour. Doesn't touch the per-action icon colours (.meeting/.view/.del/...) — only the shared
   base shape all of them sit on top of. */
.action-btn,
.tbl-btn,
.fav-btn,
.entry-action-btn {
  background: var(--canvas);
}
/* 07 audit 2026-09-24: --rule as the edge measured 1.13:1 against white (worse than the old
   #e2e5eb). Now a 3:1+ neutral edge plus a subtle shadow. :is() + :not(:hover) gives this rule
   specificity 0,2,0 so the resting edge is the same on every variant — previously ".tbl-btn.view"
   (0,2,0) kept its faint 25%-opacity coloured border while ".action-btn.view" (".view" is 0,1,0)
   lost it to this file, so the same variant looked different per base class. Hover is left to
   aistyle.css: each coloured variant's :hover (.view:hover, .del:hover, ...) paints its own solid
   colour fill and edge, and the plain :hover edge there uses --dt-control-edge-strong. Icon colours
   (.view/.del/.meeting/...) are untouched. */
:is(.action-btn, .tbl-btn, .fav-btn, .entry-action-btn):not(:hover) {
  border-color: var(--dt-control-edge);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
}

/* ══════════════════════════════════════════════════════════════════════════
   Component level
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Four button types (07.2), each with a built-in loading state ──────── */
.dt-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--dt-space-2);
  font-family: var(--font-body, inherit);
  font-weight: 600;
  font-size: 0.875rem;
  line-height: 1.2;
  padding: 0.625rem 1.25rem;
  border-radius: var(--dt-radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: filter 0.15s ease, opacity 0.15s ease;
}
.dt-btn:disabled,
.dt-btn.is-loading {
  cursor: default;
  opacity: 0.75;
  pointer-events: none;
}
.dt-btn .dt-btn-spinner {
  width: 0.9em;
  height: 0.9em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: dt-spin 0.6s linear infinite;
  display: none;
}
.dt-btn.is-loading .dt-btn-spinner { display: inline-block; }
.dt-btn.is-loading .dt-btn-label { opacity: 0.85; }
@keyframes dt-spin { to { transform: rotate(360deg); } }
/* wwwroot/js/dt-busy.js — window.dtBusy(button, on) adds the spinner/label markup above and
   toggles .is-loading + disabled + aria-busy. A legacy button without .dt-btn (.btn-generate,
   .btn-forge, a Bootstrap .btn ...) gets .dt-busy-host so the spinner still shows on it. */
.dt-busy-host .dt-btn-spinner {
  display: none;
  width: 0.9em;
  height: 0.9em;
  margin-right: 0.4em;
  vertical-align: -0.125em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: dt-spin 0.6s linear infinite;
}
.dt-busy-host.is-loading { cursor: default; pointer-events: none; }
.dt-busy-host.is-loading .dt-btn-spinner { display: inline-block; }

/* Primary — signal fill, ink text (never white — see --signal-text above). */
.dt-btn-primary { background: var(--signal); color: var(--signal-text); }
.dt-btn-primary:hover:not(:disabled) { filter: brightness(0.94); }

/* Secondary — outline. */
.dt-btn-secondary { background: transparent; color: var(--ink); border-color: var(--dt-control-edge); }
.dt-btn-secondary:hover:not(:disabled) { background: var(--canvas); }

/* Quiet — text only, no fill or border. */
.dt-btn-quiet { background: transparent; color: var(--muted); }
.dt-btn-quiet:hover:not(:disabled) { color: var(--ink); background: var(--canvas); }

/* Danger. */
.dt-btn-danger { background: var(--danger); color: var(--dt-on-danger); }
.dt-btn-danger:hover:not(:disabled) { filter: brightness(0.92); }

/* ── Old button classes mapped onto the four types (07.2) ─────────────────
   "Map the old classes onto the four; don't delete them in this release." Done here as aliases so
   no cshtml needs editing. Each group states its target type. Text contrast is checked: ink on
   signal 6.3:1, ink on white 16:1, danger fill with white text 4.83:1, --signal-ink on white
   5.65:1. Toggle-style option pills (heat, energy, status strip, contact channel) are not
   buttons in this sense; only their text colours are corrected further down. */

/* PRIMARY (signal fill, ink text): every old purple, teal, blue or green gradient primary action.
   btn-forge and btn-generate are primary already in aistyle.css (signal fill, ink text) and are
   left without !important so a page can still override one inline. */
.btn-new-forge,
.btn-hive,
.btn-refine,
.btn-final.primary,
.btn-task-primary,
.btn-task-save,
.btn-task-add-sub,
.btn-task-send,
.btn-convert-task-header,
.collab-add-btn,
.collab-role-btn.active {
  background: var(--signal) !important;
  color: var(--signal-text) !important;
  border-color: transparent !important;
}
.btn-refine:hover:not(:disabled),
.btn-final.primary:hover:not(:disabled),
.btn-task-primary:hover:not(:disabled),
.btn-task-save:hover:not(:disabled),
.btn-task-add-sub:hover:not(:disabled),
.btn-task-send:hover:not(:disabled),
.btn-convert-task-header:hover:not(:disabled),
.collab-add-btn:hover:not(:disabled) {
  filter: brightness(0.94);
}

/* SECONDARY (outline, ink text, visible edge) */
.btn-secondary,
.btn-table-action,
.btn-remove-header,
.btn-add-header,
.btn-preview-action,
.preview-action-btn,
.preview-control-btn,
.quick-action-btn,
.btn-clear-filters,
.btn-refresh,
.btn-final.secondary,
.btn-task-ghost,
.add-field-btn,
.upload-btn,
.btn-edit {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--dt-control-edge);
}
.btn-secondary:hover:not(:disabled),
.btn-table-action:hover,
.btn-remove-header:hover:not(:disabled),
.btn-add-header:hover,
.btn-preview-action:hover:not(:disabled),
.preview-action-btn:hover,
.preview-control-btn:hover,
.quick-action-btn:hover,
.btn-clear-filters:hover,
.btn-refresh:hover,
.btn-final.secondary:hover,
.btn-task-ghost:hover,
.add-field-btn:hover,
.upload-btn:hover {
  background: var(--canvas);
  color: var(--ink);
  border-color: var(--dt-control-edge-strong);
}
/* The quick-action icon was purple; keep it readable in ink. */
.quick-action-btn i { color: var(--ink); }
/* btn-edit swaps to a signal fill on hover (aistyle.css); its resting label is orange text. */
.btn-edit { color: var(--signal-ink); border-color: var(--signal-ink); }

/* QUIET (text only) */
.btn-quiet,
.btn-pw-toggle,
.collab-remove-btn,
.collab-role-btn {
  background: transparent;
  color: var(--muted);
}
.btn-quiet:hover,
.btn-pw-toggle:hover,
.collab-role-btn:hover { color: var(--ink); }
.collab-remove-btn:hover { color: var(--danger); background: transparent; }
.btn-modal-cancel {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--rule);
}

/* DANGER (danger fill, white text; ink text on the dark theme through --dt-on-danger) */
.btn-modal-delete,
.profile-hero-btn.danger {
  background: var(--danger);
  color: var(--dt-on-danger);
  border: none;
}
.btn-modal-delete:hover,
.profile-hero-btn.danger:hover {
  background: var(--danger);
  color: var(--dt-on-danger);
  filter: brightness(0.92);
}

/* Profile hero buttons: white text on 0.8-0.9 alpha teal, cyan, green and pink gradients measured
   3.7:1 or worse. Meeting, ticket, assign, invite-portal and task become secondary buttons on the
   hero (surface fill, ink text). Sale, save and contact were already signal or amber with ink. */
.profile-hero-btn.meeting,
.profile-hero-btn.ticket,
.profile-hero-btn.assign,
.profile-hero-btn.invite-portal,
.profile-hero-btn.task {
  background: var(--surface);
  color: var(--ink);
}
.profile-hero-btn.meeting:hover,
.profile-hero-btn.ticket:hover,
.profile-hero-btn.assign:hover,
.profile-hero-btn.invite-portal:hover,
.profile-hero-btn.task:hover {
  background: var(--canvas);
  color: var(--ink);
}

/* Bootstrap buttons (used on most internal pages): Bootstrap 5.3 reads its --bs-btn-* variables,
   so the four types are set through those. btn-primary and btn-success are the primary type,
   btn-secondary and btn-light and the outline pair are secondary, btn-danger and
   btn-outline-danger are danger, btn-link is quiet. */
.btn-primary,
.btn-success {
  --bs-btn-color: var(--signal-text);
  --bs-btn-bg: var(--signal);
  --bs-btn-border-color: var(--signal);
  --bs-btn-hover-color: var(--signal-text);
  --bs-btn-hover-bg: var(--signal);
  --bs-btn-hover-border-color: var(--signal);
  --bs-btn-active-color: var(--signal-text);
  --bs-btn-active-bg: var(--signal);
  --bs-btn-active-border-color: var(--signal);
  --bs-btn-disabled-color: var(--signal-text);
  --bs-btn-disabled-bg: var(--signal);
  --bs-btn-disabled-border-color: var(--signal);
  --bs-btn-focus-shadow-rgb: 255, 107, 53;
}
.btn-primary:hover:not(:disabled),
.btn-success:hover:not(:disabled) { filter: brightness(0.94); }
.btn-outline-primary,
.btn-outline-secondary,
.btn-light {
  --bs-btn-color: var(--ink);
  --bs-btn-bg: var(--surface);
  --bs-btn-border-color: var(--dt-control-edge);
  --bs-btn-hover-color: var(--ink);
  --bs-btn-hover-bg: var(--canvas);
  --bs-btn-hover-border-color: var(--dt-control-edge-strong);
  --bs-btn-active-color: var(--ink);
  --bs-btn-active-bg: var(--canvas);
  --bs-btn-active-border-color: var(--dt-control-edge-strong);
  --bs-btn-disabled-color: var(--muted);
  --bs-btn-disabled-bg: var(--surface);
  --bs-btn-disabled-border-color: var(--dt-control-edge);
}
.btn-danger {
  --bs-btn-color: var(--dt-on-danger);
  --bs-btn-bg: var(--danger);
  --bs-btn-border-color: var(--danger);
  --bs-btn-hover-color: var(--dt-on-danger);
  --bs-btn-hover-bg: var(--danger);
  --bs-btn-hover-border-color: var(--danger);
  --bs-btn-active-color: var(--dt-on-danger);
  --bs-btn-active-bg: var(--danger);
  --bs-btn-active-border-color: var(--danger);
  --bs-btn-disabled-color: var(--dt-on-danger);
  --bs-btn-disabled-bg: var(--danger);
  --bs-btn-disabled-border-color: var(--danger);
}
.btn-danger:hover:not(:disabled) { filter: brightness(0.92); }
.btn-outline-danger {
  --bs-btn-color: var(--danger);
  --bs-btn-border-color: var(--danger);
  --bs-btn-hover-color: var(--dt-on-danger);
  --bs-btn-hover-bg: var(--danger);
  --bs-btn-hover-border-color: var(--danger);
  --bs-btn-active-color: var(--dt-on-danger);
  --bs-btn-active-bg: var(--danger);
  --bs-btn-active-border-color: var(--danger);
  --bs-btn-disabled-color: var(--danger);
  --bs-btn-disabled-border-color: var(--danger);
}
.btn-link {
  --bs-btn-color: var(--signal-ink);
  --bs-btn-hover-color: var(--ink);
  --bs-btn-active-color: var(--ink);
}

/* ── Orange as text or icon: use --signal-ink (re-audit "other orange items") ──────────────────
   aistyle.css uses --primary-violet (now the bright orange, 2.84:1 on white) and #ff6b35 for the
   text and icon colour of many controls. The named ones are corrected here; the same rule applies
   to any new CSS: orange text on a light surface is --signal-ink, orange fills carry --signal-text. */
.ticket-action-btn.assign,
.act-quick-btn.email,
.save-pill.saving,
.accordion-modern .accordion-button:not(.collapsed) { color: var(--signal-ink); }
.score-adj-btn:hover { color: var(--signal-ink); border-color: var(--signal-ink); }
.act-quick-btn.email { border-color: var(--signal-ink); }
.act-quick-btn.email:hover { color: var(--signal-text); }
.fc .fc-button:hover { color: var(--signal-ink) !important; }
/* Row icons: orange (2.84:1) and the two other sub-3:1 icon colours. The :not(:hover) guard keeps
   the hover state (signal fill, ink icon) from aistyle.css. */
.allocate:not(:hover),
.tbl-btn.allocate:not(:hover),
.customer:not(:hover),
.tbl-btn.customer:not(:hover) { color: var(--signal-ink); }
.view-stats:not(:hover) { color: var(--info); }

/* Other option-pill and quick-action text below AA on their light fills. */
.act-quick-btn.call { color: var(--info); }
.act-quick-btn.sms { color: var(--good); }
.act-quick-btn.meeting { color: var(--warn); }
.heat-btn.warm { color: var(--warn); }
.status-strip-btn.active[data-val="inprogress"] { color: var(--warn); }
.status-strip-btn.active[data-val="done"] { color: var(--good); }
.energy-btn.active[data-val="low"] { color: var(--good); }
.energy-btn.active[data-val="medium"] { color: var(--warn); }
.ticket-action-btn.note { color: var(--warn); }
.ticket-action-btn.snooze { color: var(--info); }
.ticket-action-btn.close-ticket { color: var(--danger); }
.btn-remove-header:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); }

/* Bulk selection bar (Landing Entries): white text on a signal-to-purple gradient failed. Now a
   solid signal bar with ink text and icons; its buttons are ink-tinted outline buttons and the
   destructive one is a surface fill with danger text. */
.bulk-actions-bar {
  background: var(--signal);
  box-shadow: var(--dt-elevation-1);
}
.bulk-selected-count,
.btn-bulk-action,
.btn-bulk-action-secondary { color: var(--signal-text); }
.btn-bulk-action {
  background: rgba(15, 23, 42, 0.1);
  border-color: rgba(15, 23, 42, 0.45);
  backdrop-filter: none;
}
.btn-bulk-action:hover { background: rgba(15, 23, 42, 0.18); }
.btn-bulk-action-secondary { border-color: rgba(15, 23, 42, 0.55); }
.btn-bulk-action-secondary:hover { background: rgba(15, 23, 42, 0.1); }
.btn-bulk-action.danger {
  background: var(--surface);
  color: var(--danger);
  border-color: var(--danger);
}
.btn-bulk-action.danger:hover { background: var(--canvas); }

/* ── Status stamps (07.3) — draft/sent/viewed/part-paid/paid/overdue/scheduled/in
   progress/done ─────────────────────────────────────────────────────────── */
.dt-status {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.25rem 0.7rem;
  border-radius: 50px;
  white-space: nowrap;
}
.dt-status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
/* Opus re-audit 2026-09-24: the first version used translucent tints over the page with the
   shared --warn/--danger/--info text colours — 5 of 9 states measured 4.0–4.5:1 (WCAG AA needs
   4.5:1 at this size). Each state now has its own opaque fg/bg token pair, checked in both themes:
   light 6.4–9.4:1, dark 6.9–8.3:1. Distinct hues so sent/viewed/part-paid no longer look alike.
   Use through the shared helper (wwwroot/js/status-stamps.js), not hand-written markup. */
.dt-status-draft       { background: var(--dt-status-draft-bg);       color: var(--dt-status-draft-fg); }
.dt-status-scheduled   { background: var(--dt-status-scheduled-bg);   color: var(--dt-status-scheduled-fg); }
.dt-status-sent        { background: var(--dt-status-sent-bg);        color: var(--dt-status-sent-fg); }
.dt-status-viewed      { background: var(--dt-status-viewed-bg);      color: var(--dt-status-viewed-fg); }
.dt-status-part-paid   { background: var(--dt-status-part-paid-bg);   color: var(--dt-status-part-paid-fg); }
.dt-status-paid        { background: var(--dt-status-paid-bg);        color: var(--dt-status-paid-fg); }
.dt-status-overdue     { background: var(--dt-status-overdue-bg);     color: var(--dt-status-overdue-fg); }
.dt-status-in-progress { background: var(--dt-status-in-progress-bg); color: var(--dt-status-in-progress-fg); }
.dt-status-done        { background: var(--dt-status-done-bg);        color: var(--dt-status-done-fg); }
.dt-status-accepted    { background: var(--dt-status-accepted-bg);    color: var(--dt-status-accepted-fg); }
.dt-status-cancelled   { background: var(--dt-status-cancelled-bg);   color: var(--dt-status-cancelled-fg); text-decoration: line-through; }

/* ── New components the specialisation screens need (07.6) ─────────────── */

/* Side panel (assistant, 09) */
.dt-panel {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(400px, 100vw);
  background: var(--surface);
  border-left: 1px solid var(--rule);
  box-shadow: var(--dt-elevation-2);
  display: flex;
  flex-direction: column;
  z-index: 1040;
}
.dt-panel-header { padding: var(--dt-space-4); border-bottom: 1px solid var(--rule); display: flex; align-items: center; justify-content: space-between; }
.dt-panel-body { flex: 1; overflow-y: auto; padding: var(--dt-space-4); }

/* Confirm card (assistant write actions, 09) */
.dt-confirm-card {
  border: 1px solid var(--rule);
  border-radius: var(--dt-radius-lg);
  padding: var(--dt-space-4);
  background: var(--surface);
  box-shadow: var(--dt-elevation-1);
}
.dt-confirm-card-title { font-weight: 700; color: var(--ink); margin-bottom: var(--dt-space-2); }
.dt-confirm-card-actions { display: flex; gap: var(--dt-space-2); margin-top: var(--dt-space-3); }

/* Result card (tool output with a formula line, 10) */
.dt-result-card { border: 1px solid var(--rule); border-radius: var(--dt-radius-lg); padding: var(--dt-space-4); background: var(--surface); }
.dt-result-value { font-size: 1.75rem; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
.dt-result-formula { font-size: 0.78rem; color: var(--muted); margin-top: var(--dt-space-1); font-family: ui-monospace, "SF Mono", Consolas, monospace; }

/* Findings list (document review, 11) */
.dt-finding { display: flex; gap: var(--dt-space-3); padding: var(--dt-space-3) 0; border-bottom: 1px solid var(--rule); }
.dt-finding:last-child { border-bottom: none; }
.dt-finding-severity { flex-shrink: 0; width: 8px; border-radius: 4px; }
.dt-finding-severity.info { background: var(--info); }
.dt-finding-severity.check { background: var(--warn); }
.dt-finding-severity.important { background: var(--danger); }
.dt-finding-title { font-weight: 600; color: var(--ink); }
.dt-finding-detail { font-size: 0.85rem; color: var(--muted); margin-top: 2px; }

/* Timer (Projects visits/time entries, 06) */
.dt-timer { display: inline-flex; align-items: center; gap: var(--dt-space-2); font-variant-numeric: tabular-nums; font-weight: 700; font-size: 1.1rem; color: var(--ink); }
.dt-timer.running .dt-timer-dot { background: var(--good); animation: dt-pulse 1.2s ease-in-out infinite; }
.dt-timer-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
@keyframes dt-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* Stage board column (Projects board, 06) */
.dt-stage-column { background: var(--canvas); border-radius: var(--dt-radius-lg); padding: var(--dt-space-3); min-width: 260px; display: flex; flex-direction: column; gap: var(--dt-space-2); }
.dt-stage-column-header { display: flex; align-items: center; justify-content: space-between; font-weight: 700; color: var(--ink); padding: 0 var(--dt-space-1) var(--dt-space-2); }
.dt-stage-column-count { font-size: 0.75rem; color: var(--muted); background: var(--surface); border-radius: 50px; padding: 0.1rem 0.55rem; }


/* "Me" badge (Ari, 2026-09-25) — marks the signed-in user in any user picker (js/colony-me.js).
   Ink text on the signal colour: the same pairing the primary button uses, so it passes AA. */
.dt-me-badge {
    display: inline-block;
    margin-left: .4rem;
    padding: .05rem .4rem;
    border-radius: 999px;
    background: var(--signal);
    color: var(--signal-text);
    font-size: .68rem;
    font-weight: 700;
    line-height: 1.4;
    letter-spacing: .02em;
    vertical-align: middle;
}
/* ── Plain usages of the splashy AI buttons render as the standard primary button ──
   .btn-generate, .btn-forge and .btn-hive (aistyle.css) are large call-to-action buttons made for
   AI-generate actions (padding 1rem 2rem, lift on hover, glow). Many pages reused them as the
   ordinary primary button, so ordinary actions looked oversized. Here every usage is brought back to
   the standard size (same box as .dt-btn-primary). The splashy look is kept only where the markup
   opts in with .btn-ai or data-ai-action. Inline style attributes on a button still win, because
   nothing below uses the important flag. Text stays ink on the signal fill (AA). */
.btn-generate:not(.btn-ai):not([data-ai-action]),
.btn-forge:not(.btn-ai):not([data-ai-action]),
.btn-hive:not(.btn-ai):not([data-ai-action]) {
  gap: var(--dt-space-2);
  padding: 0.625rem 1.25rem;
  font-size: 0.875rem;
  line-height: 1.2;
  border-radius: var(--dt-radius-md);
  box-shadow: none;
  transition: filter 0.15s ease, opacity 0.15s ease;
}
.btn-generate:not(.btn-ai):not([data-ai-action]):hover:not(:disabled),
.btn-forge:not(.btn-ai):not([data-ai-action]):hover:not(:disabled),
.btn-hive:not(.btn-ai):not([data-ai-action]):hover:not(:disabled) {
  transform: none;
  box-shadow: none;
  filter: brightness(0.94);
}
.btn-generate:not(.btn-ai):not([data-ai-action]):active,
.btn-forge:not(.btn-ai):not([data-ai-action]):active,
.btn-hive:not(.btn-ai):not([data-ai-action]):active {
  transform: none;
}
.btn-generate:not(.btn-ai):not([data-ai-action]) i,
.btn-forge:not(.btn-ai):not([data-ai-action]) i,
.btn-hive:not(.btn-ai):not([data-ai-action]) i {
  font-size: 1em;
}
.btn-generate:not(.btn-ai):not([data-ai-action]):hover i,
.btn-forge:not(.btn-ai):not([data-ai-action]):hover i,
.btn-hive:not(.btn-ai):not([data-ai-action]):hover i {
  transform: none;
}
/* The shine overlay is part of the splashy look only. */
.btn-generate:not(.btn-ai):not([data-ai-action])::before,
.btn-forge:not(.btn-ai):not([data-ai-action])::before,
.btn-hive:not(.btn-ai):not([data-ai-action])::before {
  display: none;
}
