/* ============================================================
   Catenix Design System v3.0
   ============================================================
   Single source of truth for the Catenix platform UI.
   Loaded by both the dashboard (/) and the admin portal (/admin).
   Defines design tokens (custom properties) + a thin component
   layer built on those tokens. All component classes here are
   ADDITIVE — they refine and unify what the dashboard and admin
   portal already use, but never break existing class names.

   Sections:
     1. Tokens — colour, typography, spacing, radius, shadow, z
     2. Reset + base
     3. Buttons (.btn, .btn-primary, .btn-secondary, …)
     4. Cards & panels
     5. Stat cards
     6. Badges
     7. Tables
     8. Forms
     9. Modals & toasts
    10. Empty state + skeleton
    11. Focus rings + accessibility
    12. Animations + reduced-motion

   IMPORTANT — SaMD safety:
     This file styles only. It NEVER decides whether a clinical
     value is normal/abnormal/critical. Status colours (.badge-red
     etc.) are applied by the application based on data already
     received from devices / clinic-authored registry rules.
   ============================================================ */


/* ====================================================
   1. DESIGN TOKENS
   ==================================================== */

/* ---- Dark theme (opt-in only) ----
   LIGHT is the default token set (see :root + [data-theme="light"] below),
   because the dashboard and admin portal default to data-theme="light" for
   clinical readability. Dark is applied only when the user opts in via the
   theme toggle (data-theme="dark"). */
[data-theme="dark"] {
  /* Surface hierarchy — navy ramp, Catenix brand */
  --surface-0: #050b18;
  --surface-1: #0a1322;
  --surface-2: #111c33;
  --surface-3: #182542;
  --surface-4: #213056;

  /* Backward-compatible aliases */
  --bg:           var(--surface-1);
  --card-bg:      var(--surface-2);
  --sidebar-bg:   #0c1626;
  --topbar-bg:    rgba(12, 22, 38, .88);
  --input-bg:     var(--surface-1);
  /* Subtle fill behind quieter blocks (assistant replies, inline detail
     panels). It was being referenced by the portal without ever being
     defined, so those blocks fell back to a hard-coded light grey and kept it
     in dark mode, under text that had gone near-white. */
  --bg-subtle:    var(--surface-3);

  /* Borders & subtle backgrounds */
  --border:        #1f2c47;
  --border-strong: #2b3c5c;
  --border-hover:  rgba(11, 123, 121, .35);
  --hover:         rgba(11, 123, 121, .08);
  --shadow:        rgba(0, 0, 0, .35);
  --table-stripe:  rgba(255, 255, 255, .025);

  /* Text */
  --text:           #e6edf7;
  --text-secondary: #9aa9c2;
  --text-muted:     #607089;
  --text-inverse:   #0a1322;

  /* Brand — canonical Catenix teal ramp (#0B7B79 family) as primary.
     Ramp: 50 #E6F2F1 · 100 #C2E0DD · 200 #8FC7C2 · 300 #57A8A2 ·
           400 #2A8F88 · 500 #0B7B79 (BRAND) · 600 #0A6B69 (text/links) ·
           700 #08544F · 800 #063E3B · 900 #042C2A.
     On dark surfaces a lighter step (-400) reads better for hover. */
  --brand:        var(--teal-500);
  --brand-hover:  var(--teal-400);
  --brand-active: var(--teal-600);
  --brand-soft:   rgba(11, 123, 121, .14);

  /* Semantic colours (dark-tuned)
     `primary` is an alias of brand so existing var(--primary) lookups
     in the dashboard automatically inherit the Catenix teal. */
  --primary:       var(--teal-500);
  --primary-hover: var(--teal-400);
  --primary-muted: rgba(11, 123, 121, .14);

  --success:       #22c55e;
  --success-hover: #16a34a;
  --success-muted: rgba(34, 197, 94, .14);

  --warning:       #f59e0b;
  --warning-hover: #d97706;
  --warning-muted: rgba(245, 158, 11, .14);

  --danger:        #ef4444;
  --danger-hover:  #dc2626;
  --danger-muted:  rgba(239, 68, 68, .14);

  --info:          #38bdf8;
  --info-hover:    #0ea5e9;
  --info-muted:    rgba(56, 189, 248, .14);

  --purple:        #a78bfa;
  --purple-hover:  #8b5cf6;
  --purple-muted:  rgba(167, 139, 250, .14);

  --teal:          var(--teal-500);
  --teal-hover:    var(--teal-400);
  --teal-muted:    rgba(11, 123, 121, .14);

  /* Component-level shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .35), 0 1px 3px rgba(0, 0, 0, .25);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .35), 0 2px 4px -2px rgba(0, 0, 0, .25);
  --shadow-lg: 0 12px 24px -6px rgba(0, 0, 0, .45), 0 6px 12px -4px rgba(0, 0, 0, .35);
  --shadow-xl: 0 24px 48px -10px rgba(0, 0, 0, .55), 0 12px 18px -6px rgba(0, 0, 0, .35);
}


/* ---- Light theme (DEFAULT) ----
   Bound to :root as well so any element without a data-theme attribute
   renders light. [data-theme="light"] is kept as an explicit opt-in that
   carries the identical token set. */
:root,
[data-theme="light"] {
  color-scheme: light;
  --surface-0: #f1f5f4;
  --surface-1: #f6f9f8;
  --surface-2: #ffffff;
  --surface-3: #f1f5f4;
  --surface-4: #e6ecea;

  --bg:           var(--surface-1);
  --card-bg:      var(--surface-2);
  --sidebar-bg:   #ffffff;
  --topbar-bg:    rgba(255, 255, 255, .94);
  --input-bg:     #f8fafa;
  --bg-subtle:    var(--surface-3);

  --border:        #dde7e5;
  --border-strong: #c6d4d1;
  --border-hover:  rgba(11, 123, 121, .35);
  --hover:         rgba(11, 123, 121, .06);
  --shadow:        rgba(15, 26, 32, .08);
  --table-stripe:  rgba(15, 26, 32, .025);

  --text:           #0f1b2d;
  --text-secondary: #475569;
  --text-muted:     #6b7d8a;
  --text-inverse:   #ffffff;

  /* Canonical Catenix teal ramp. On white, --teal-500 gives ~5.2:1 (AA
     for buttons); inline text links use the darker --teal-600. */
  --brand:        var(--teal-500);
  --brand-hover:  var(--teal-600);
  --brand-active: var(--teal-700);
  --brand-soft:   rgba(11, 123, 121, .10);

  --primary:       var(--teal-500);
  --primary-hover: var(--teal-600);
  --primary-muted: rgba(11, 123, 121, .10);

  --success:       #16a34a;
  --success-hover: #15803d;
  --success-muted: rgba(22, 163, 74, .10);

  --warning:       #d97706;
  --warning-hover: #b45309;
  --warning-muted: rgba(217, 119, 6, .10);

  --danger:        #dc2626;
  --danger-hover:  #b91c1c;
  --danger-muted:  rgba(220, 38, 38, .10);

  --info:          #0284c7;
  --info-hover:    #0369a1;
  --info-muted:    rgba(2, 132, 199, .10);

  --purple:        #7c3aed;
  --purple-hover:  #6d28d9;
  --purple-muted:  rgba(124, 58, 237, .10);

  --teal:          var(--teal-500);
  --teal-hover:    var(--teal-600);
  --teal-muted:    rgba(11, 123, 121, .10);

  --shadow-sm: 0 1px 2px rgba(15, 26, 32, .04), 0 1px 3px rgba(15, 26, 32, .06);
  --shadow-md: 0 4px 6px -1px rgba(15, 26, 32, .06), 0 2px 4px -2px rgba(15, 26, 32, .06);
  --shadow-lg: 0 12px 24px -6px rgba(15, 26, 32, .12), 0 6px 12px -4px rgba(15, 26, 32, .06);
  --shadow-xl: 0 24px 48px -10px rgba(15, 26, 32, .18), 0 12px 18px -6px rgba(15, 26, 32, .08);
}


/* ---- Shared tokens (both themes) ---- */
:root {
  /* ----------------------------------------------------------------
     CANONICAL TEAL RAMP — the Catenix brand palette.
     Exposed as discrete steps so any owner (dashboard, admin portal)
     can pull a consistent tint/shade without hand-mixing hex. The
     theme-level --brand / --primary vars below point at -500/-600. */
  --teal-50:  #E6F2F1;
  --teal-100: #C2E0DD;
  --teal-200: #8FC7C2;
  --teal-300: #57A8A2;
  --teal-400: #2A8F88;
  --teal-500: #0B7B79;   /* BRAND */
  --teal-600: #0A6B69;
  --teal-700: #08544F;
  --teal-800: #063E3B;
  --teal-900: #042C2A;

  /* ----------------------------------------------------------------
     SUPPORTING PALETTE
     A cool slate-navy neutral ramp (cards, borders, chrome accents)
     plus a small set of tasteful data-viz accent hues. The viz hues
     are for charts/legends/sparklines ONLY — they are decorative and
     carry no clinical meaning (SaMD-safe). */
  --slate-50:  #F4F7F9;
  --slate-100: #E6ECF1;
  --slate-200: #CBD6E0;
  --slate-300: #A7B6C5;
  --slate-400: #7B8DA1;
  --slate-500: #586A7E;
  --slate-600: #435162;
  --slate-700: #313D4B;
  --slate-800: #1F2A38;
  --slate-900: #121A26;

  /* Data-viz accents — chosen to be distinguishable + harmonious next
     to the teal brand. Use in declared series order. Decorative only. */
  --viz-1: #0B7B79;   /* brand teal      */
  --viz-2: #3F6CC9;   /* cool indigo     */
  --viz-3: #C9842B;   /* amber ochre     */
  --viz-4: #8B5CF6;   /* violet          */
  --viz-5: #2A9D8F;   /* sea green       */
  --viz-6: #B45463;   /* dusty rose      */
  --viz-1-soft: rgba(11, 123, 121, .14);
  --viz-2-soft: rgba(63, 108, 201, .14);
  --viz-3-soft: rgba(201, 132, 43, .14);
  --viz-4-soft: rgba(139, 92, 246, .14);
  --viz-5-soft: rgba(42, 157, 143, .14);
  --viz-6-soft: rgba(180, 84, 99, .14);

  /* Typography — Inter is loaded by the host pages */
  --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-mono:   ui-monospace, SFMono-Regular, 'JetBrains Mono', Menlo, Monaco, monospace;

  --font-xs:   10px;
  --font-sm:   11px;
  --font-base: 13px;
  --font-md:   14px;
  --font-lg:   16px;
  --font-xl:   18px;
  --font-2xl:  24px;
  --font-3xl:  32px;
  --font-4xl:  40px;

  --font-normal:     400;
  --font-medium:     500;
  --font-semibold:   600;
  --font-bold:       700;
  --font-extrabold:  800;

  /* Spacing — strict 4/8 system */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-7:  28px;
  --space-8:  32px;
  --space-9:  36px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* Radius */
  --radius-xs:   4px;
  --radius-sm:   6px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-xl:   16px;
  --radius-2xl:  20px;
  --radius-full: 9999px;

  /* Transitions */
  --transition-fast:   .12s ease;
  --transition-base:   .2s  ease;
  --transition-slow:   .35s ease;
  --transition-spring: .4s cubic-bezier(.34, 1.56, .64, 1);

  /* Z-index scale */
  --z-dropdown: 40;
  --z-sticky:   50;
  --z-overlay:  900;
  --z-modal:    1000;
  --z-toast:    1100;
  --z-cmd:      2000;

  /* Tap targets (WCAG AA / 2.5.5) */
  --tap-min: 36px;

  /* ----------------------------------------------------------------
     STATUS TOKENS — connectivity (device fleet) + QC / run state.

     SaMD SAFETY (read before using):
       These colours describe OPERATIONAL / TECHNICAL state only —
       whether a device is reachable, or whether a control material's
       run passed/failed/locked. They are assigned by the application
       from device telemetry, the device/operator registry, or QC
       outcomes that the analyser/LIS already reported. They are NEVER
       computed from a patient clinical value and NEVER express that a
       result is normal/abnormal/critical. Catenix stores + displays;
       it does not classify clinical meaning.
     ---------------------------------------------------------------- */

  /* Connectivity — assigned by the app from device/registry telemetry */
  --conn-online:    #15803D;   /* device reachable + reporting        */
  --conn-degraded:  #B45309;   /* reachable but latent / partial      */
  --conn-offline:   #7C8C93;   /* not seen within heartbeat window    */
  --conn-error:     #B91C1C;   /* transport / handshake fault         */
  --conn-online-soft:   rgba(21, 128, 61, .12);
  --conn-degraded-soft: rgba(180, 83, 9, .12);
  --conn-offline-soft:  rgba(124, 140, 147, .14);
  --conn-error-soft:    rgba(185, 28, 28, .12);

  /* QC / run state — reflects QC outcome already reported, not a
     clinical interpretation of a patient result */
  --qc-pass:    #15803D;
  --qc-warn:    #B45309;
  --qc-fail:    #B91C1C;
  --qc-pending: #0369A1;
  --qc-locked:  #44525A;
  --qc-pass-soft:    rgba(21, 128, 61, .12);
  --qc-warn-soft:    rgba(180, 83, 9, .12);
  --qc-fail-soft:    rgba(185, 28, 28, .12);
  --qc-pending-soft: rgba(3, 105, 161, .12);
  --qc-locked-soft:  rgba(68, 82, 90, .14);
}

/* dark theme surface/ink overrides (added 2026-05-30; chrome tokens added
   2026-05-30 PM).
   IMPORTANT — why this block exists and must stay LAST:
   The light token block is written as `:root, [data-theme="light"]{…}`. The
   `:root` selector ALWAYS matches <html>, even when data-theme="dark", and it
   sits AFTER the first [data-theme="dark"] block with equal specificity — so
   in dark mode it re-applies the LIGHT values for any token it declares. This
   block, placed last, re-wins them for dark. It therefore MUST redeclare every
   token that the light block sets with a literal value, or that token leaks
   light in dark mode. The chrome tokens (--sidebar-bg/--topbar-bg/--input-bg)
   were missing here, which is why the body went dark but the sidebar, topbar
   and inputs stayed white. Do not remove these. */
[data-theme="dark"] {
  /* Tell the browser to render native form controls (notably <select>
     dropdown lists) with dark chrome — without this, option text was white
     on the OS-default white popup, i.e. unreadable. */
  color-scheme: dark;
  --surface-0: #0f1729;
  --surface-1: #131d33;
  --surface-2: #1a2540;
  --surface-3: #22304d;
  --surface-4: #2b3a5c;
  --bg: #0a0e1a;
  --bg-alt: #0f1729;
  --bg-card: #111c33;
  --card-bg: #1a2540;
  /* App chrome — these were the leak: re-win them for dark. */
  --sidebar-bg: #0c1424;
  --topbar-bg: rgba(12, 20, 36, .88);
  --input-bg: #0f1830;
  --hover: rgba(11, 123, 121, .10);
  --ink: #e8edf6;
  --ink-2: #cdd7e6;
  --ink-dim: #9aa8bf;
  --ink-faint: #7d8aa3;
  --text: #e8edf6;
  --text-primary: #e8edf6;
  --text-secondary: #94a3b8;
  --text-muted: #64748b;
  --text-inverse: #0a1322;
  --border: #22304d;
  --border-strong: #2f415f;
  --border-subtle: #1a2540;
  --text-on-light: #e8edf6;
  /* primary/brand hover differ per theme; re-win the dark steps. */
  --primary-hover: var(--teal-400);
  --brand-hover: var(--teal-400);
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.30);
  --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.40), 0 2px 4px -2px rgba(0,0,0,0.30);
  --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.45), 0 4px 6px -4px rgba(0,0,0,0.30);
  --shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.55), 0 8px 10px -6px rgba(0,0,0,0.35);

  /* Status, brand and subtle-surface tokens. Same trap as the chrome
     tokens above: the light block is `:root, [data-theme="light"]`, so
     every token it declares was re-winning in dark mode. That put the
     light palette's darker status colours - meant to sit on white - onto
     navy, where a warning or a danger label reads as muddy brown or
     maroon rather than as a colour. Redeclared here so dark gets the
     brighter ramp the block above intended. */
  /* Surfaces and edges */
  --bg-subtle: var(--surface-3);
  --border-hover: rgba(11, 123, 121, .35);
  --shadow: rgba(0, 0, 0, .35);
  --table-stripe: rgba(255, 255, 255, .025);
  /* Brand */
  --brand: var(--teal-500);
  --brand-active: var(--teal-600);
  --brand-soft: rgba(11, 123, 121, .14);
  --primary: var(--teal-500);
  --primary-muted: rgba(11, 123, 121, .14);
  --teal: var(--teal-500);
  --teal-hover: var(--teal-400);
  --teal-muted: rgba(11, 123, 121, .14);
  /* Status */
  --success: #22c55e;
  --success-hover: #16a34a;
  --success-muted: rgba(34, 197, 94, .14);
  --warning: #f59e0b;
  --warning-hover: #d97706;
  --warning-muted: rgba(245, 158, 11, .14);
  --danger: #ef4444;
  --danger-hover: #dc2626;
  --danger-muted: rgba(239, 68, 68, .14);
  --info: #38bdf8;
  --info-hover: #0ea5e9;
  --info-muted: rgba(56, 189, 248, .14);
  --purple: #a78bfa;
  --purple-hover: #8b5cf6;
  --purple-muted: rgba(167, 139, 250, .14);

}



/* ====================================================
   2. RESET & BASE
   ==================================================== */
*,
*::before,
*::after { box-sizing: border-box; }

html,
body {
  font-family: var(--font-family);
  background: var(--bg);
  color: var(--text);
  font-size: var(--font-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  transition: background .25s, color .25s;
}


/* ====================================================
   3. BUTTONS
   ==================================================== */
.btn,
.ds-btn {
  --btn-fg:        #fff;
  --btn-bg:        var(--primary);
  --btn-bg-hover:  var(--primary-hover);
  --btn-shadow:    0 2px 6px rgba(0, 0, 0, .14);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 16px;
  min-height: var(--tap-min);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: 12px;
  font-weight: var(--font-semibold);
  letter-spacing: .2px;
  color: var(--btn-fg);
  background: var(--btn-bg);
  cursor: pointer;
  user-select: none;
  position: relative;
  outline: none;
  transition: background var(--transition-fast),
              border-color var(--transition-fast),
              color var(--transition-fast),
              box-shadow var(--transition-fast),
              transform var(--transition-fast);
  box-shadow: var(--btn-shadow);
}
.btn:hover,
.ds-btn:hover { background: var(--btn-bg-hover); transform: translateY(-1px); box-shadow: 0 4px 10px rgba(0,0,0,.16); }
.btn:active,
.ds-btn:active { transform: scale(.98); box-shadow: var(--btn-shadow); }
.btn:disabled,
.ds-btn:disabled,
.btn[aria-disabled="true"] { opacity: .55; pointer-events: none; }

.btn-primary   { --btn-bg: var(--primary); --btn-bg-hover: var(--primary-hover); background: linear-gradient(135deg, var(--primary), var(--primary-hover)); }
.btn-primary:hover { background: linear-gradient(135deg, var(--primary-hover), var(--primary-hover)); }
.btn-success   { --btn-bg: var(--success); --btn-bg-hover: var(--success-hover); background: linear-gradient(135deg, var(--success), var(--success-hover)); }
.btn-success:hover { background: linear-gradient(135deg, var(--success-hover), var(--success-hover)); }
.btn-danger    { --btn-bg: var(--danger);  --btn-bg-hover: var(--danger-hover);  background: linear-gradient(135deg, var(--danger),  var(--danger-hover)); }
.btn-danger:hover  { background: linear-gradient(135deg, var(--danger-hover),  var(--danger-hover)); }
.btn-warning   { --btn-fg: #0f1b2d; --btn-bg: var(--warning); --btn-bg-hover: var(--warning-hover); background: linear-gradient(135deg, var(--warning), var(--warning-hover)); color: #0f1b2d; }
.btn-warning:hover { background: linear-gradient(135deg, var(--warning-hover), var(--warning-hover)); }

.btn-secondary {
  background: var(--card-bg);
  color: var(--text-secondary);
  border: 1px solid var(--border);
  box-shadow: none;
}
.btn-secondary:hover {
  background: var(--hover);
  color: var(--text);
  border-color: var(--border-hover);
  box-shadow: var(--shadow-sm);
}

.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid transparent;
  box-shadow: none;
  padding: 6px 10px;
}
.btn-ghost:hover {
  background: var(--hover);
  color: var(--text);
  border-color: transparent;
  box-shadow: none;
}

.btn-sm { padding: 5px 12px; min-height: 28px; font-size: var(--font-sm); }
.btn-xs { padding: 3px 8px;  min-height: 22px; font-size: var(--font-xs); border-radius: var(--radius-sm); }
.btn-lg { padding: 10px 24px; min-height: 44px; font-size: var(--font-md); }

.btn-block { width: 100%; }


/* ====================================================
   4. CARDS & PANELS
   ==================================================== */
.card,
.ds-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  transition: border-color var(--transition-base), box-shadow var(--transition-base);
}
.card:hover,
.ds-card.interactive:hover {
  border-color: var(--border-hover);
  box-shadow: var(--shadow-sm);
}

.panel {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  margin-bottom: var(--space-4);
  transition: border-color var(--transition-base), box-shadow var(--transition-base);
}
.panel:hover { border-color: var(--border-hover); box-shadow: var(--shadow-sm); }
.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
  gap: var(--space-2);
}
.panel-title {
  font-size: var(--font-md);
  font-weight: var(--font-bold);
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 6px;
  letter-spacing: -.01em;
}
.panel-subtitle {
  font-size: var(--font-sm);
  color: var(--text-muted);
  margin-top: 2px;
  font-weight: var(--font-normal);
}


/* ====================================================
   5. STAT CARDS
   ==================================================== */
.stat-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  position: relative;
  overflow: hidden;
  transition: transform var(--transition-base),
              border-color var(--transition-base),
              box-shadow var(--transition-base);
}
.stat-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--primary);
}
.stat-card:hover {
  transform: translateY(-2px);
  border-color: var(--border-hover);
  box-shadow: var(--shadow-lg);
}
.stat-card.success::before { background: var(--success); }
.stat-card.warning::before { background: var(--warning); }
.stat-card.danger::before  { background: var(--danger); }
.stat-card.info::before    { background: var(--info); }
.stat-card.brand::before   { background: var(--brand); }

.stat-label {
  font-size: var(--font-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  font-weight: var(--font-semibold);
}
.stat-value {
  font-size: 28px;
  font-weight: var(--font-extrabold);
  margin: var(--space-1) 0;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.5px;
  line-height: 1.1;
}
.stat-desc {
  font-size: var(--font-sm);
  color: var(--text-muted);
}


/* ====================================================
   6. BADGES
   ==================================================== */
.badge,
.badge-green, .badge-yellow, .badge-red,
.badge-blue, .badge-grey, .badge-purple, .badge-teal {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-size: var(--font-xs);
  font-weight: var(--font-bold);
  letter-spacing: .2px;
  line-height: 1;
  border: 1px solid transparent;
  white-space: nowrap;
}

.badge-green  { background: var(--success-muted); color: var(--success); border-color: rgba(34, 197, 94, .22); }
.badge-yellow { background: var(--warning-muted); color: var(--warning); border-color: rgba(245, 158, 11, .22); }
.badge-red    { background: var(--danger-muted);  color: var(--danger);  border-color: rgba(239, 68, 68, .25); }
.badge-blue   { background: var(--info-muted);    color: var(--info);    border-color: rgba(56, 189, 248, .22); }
.badge-grey   { background: rgba(107, 114, 128, .14); color: #9aa9c2; border-color: rgba(107, 114, 128, .22); }
.badge-purple { background: var(--purple-muted);  color: var(--purple); border-color: rgba(167, 139, 250, .22); }
.badge-teal   { background: var(--teal-muted);    color: var(--teal);   border-color: rgba(26, 184, 166, .25); }
.badge-brand  { background: var(--brand-soft);    color: var(--brand);  border-color: rgba(26, 123, 110, .25); }

.badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }

/* ---- Status badges: connectivity + QC/run ----
   Same pill geometry as the badges above (they inherit the shared
   .badge rules below via the grouped selector). SaMD-safe: these
   classes are applied by the app from device/registry/QC telemetry,
   NEVER from a computed clinical value. */
.badge-conn-online,  .badge-conn-degraded, .badge-conn-offline, .badge-conn-error,
.badge-qc-pass, .badge-qc-warn, .badge-qc-fail, .badge-qc-pending, .badge-qc-locked {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-size: var(--font-xs);
  font-weight: var(--font-bold);
  letter-spacing: .2px;
  line-height: 1;
  border: 1px solid transparent;
  white-space: nowrap;
}

/* Connectivity */
.badge-conn-online   { background: var(--conn-online-soft);   color: var(--conn-online);   border-color: rgba(21, 128, 61, .25); }
.badge-conn-degraded { background: var(--conn-degraded-soft); color: var(--conn-degraded); border-color: rgba(180, 83, 9, .25); }
.badge-conn-offline  { background: var(--conn-offline-soft);  color: var(--conn-offline);  border-color: rgba(124, 140, 147, .28); }
.badge-conn-error    { background: var(--conn-error-soft);    color: var(--conn-error);    border-color: rgba(185, 28, 28, .28); }

/* QC / run state */
.badge-qc-pass    { background: var(--qc-pass-soft);    color: var(--qc-pass);    border-color: rgba(21, 128, 61, .25); }
.badge-qc-warn    { background: var(--qc-warn-soft);    color: var(--qc-warn);    border-color: rgba(180, 83, 9, .25); }
.badge-qc-fail    { background: var(--qc-fail-soft);    color: var(--qc-fail);    border-color: rgba(185, 28, 28, .28); }
.badge-qc-pending { background: var(--qc-pending-soft); color: var(--qc-pending); border-color: rgba(3, 105, 161, .25); }
.badge-qc-locked  { background: var(--qc-locked-soft);  color: var(--qc-locked);  border-color: rgba(68, 82, 90, .28); }

/* Optional pulsing dot for live "online" devices (respects reduced-motion
   via the global rule in section 12). The dot colour follows the badge. */
.badge-conn-online .badge-dot { animation: ds-pulse 2s ease-in-out infinite; }


/* ====================================================
   7. TABLES
   ==================================================== */
table.ds-table,
.ds-table { width: 100%; border-collapse: collapse; }

table th,
.ds-table th {
  text-align: left;
  padding: 10px 12px;
  font-size: var(--font-xs);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--text-muted);
  font-weight: var(--font-bold);
  border-bottom: 2px solid var(--border);
  background: var(--card-bg);
  position: sticky;
  top: 0;
  z-index: 1;
  white-space: nowrap;
}
table td,
.ds-table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--text);
  transition: background var(--transition-fast);
}
/* Whole-table tabular figures so every numeric column aligns by default
   (counts, IDs, TAT, QC values — stored/displayed as-is, no clinical
   computation). Per-cell .num below still controls alignment. */
.ds-table { font-variant-numeric: tabular-nums; }

/* Zebra striping — improves row tracking on dense fleet/result grids.
   Hover (below) overrides the stripe. */
.ds-table tbody tr:nth-child(even) td { background: var(--table-stripe); }

.ds-table tbody tr:hover td,
tr:hover td { background: var(--hover); }

.ds-table.dense th,
.ds-table.dense td { padding: 6px 10px; font-size: var(--font-sm); }

/* Numeric cells — tabular figures so columns align (counts, IDs, TAT,
   QC values stored/displayed as-is; no clinical computation). Apply to
   <td>/<th> or any inline numeric span. */
.num,
td.num, th.num,
.ds-table td.num, .ds-table th.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  text-align: right;
}
.num.left, td.num.left, th.num.left { text-align: left; }

/* Error / failed-fetch state for a table body. Render one full-width row
   (e.g. <tr class="table-error"><td colspan="N"> … </td></tr>) when a data
   fetch fails, so the table never silently shows a misleading empty grid. */
.ds-table .table-error td,
tr.table-error td {
  text-align: center;
  padding: var(--space-8) var(--space-5);
  color: var(--danger);
  background: var(--danger-muted);
  font-weight: var(--font-semibold);
  border-bottom: 1px solid var(--border);
}
.ds-table .table-error .table-error-detail,
tr.table-error .table-error-detail {
  display: block;
  margin-top: var(--space-2);
  color: var(--text-muted);
  font-weight: var(--font-normal);
  font-size: var(--font-sm);
}

/* Loading state for a table body (paired with .skeleton rows or a spinner) */
.ds-table.is-loading tbody { opacity: .55; pointer-events: none; }


/* ---- Device fleet: offline row/card state ----
   Visually de-emphasises a device the app has marked offline (assigned
   from heartbeat telemetry, NOT from any clinical value). Pair with
   .badge-conn-offline. */
.fleet-row.offline td,
.ds-table tr.is-offline td {
  color: var(--text-muted);
  background: var(--conn-offline-soft);
}
.fleet-card.offline,
.device-card.is-offline {
  border-color: var(--conn-offline);
  background: var(--conn-offline-soft);
  opacity: .85;
}
.fleet-card.offline .fleet-card-title,
.device-card.is-offline .device-card-title {
  color: var(--text-muted);
}
/* Error/fault state for a device card (transport/handshake fault) */
.fleet-card.conn-error,
.device-card.is-error {
  border-color: var(--conn-error);
  background: var(--conn-error-soft);
}


/* ====================================================
   8. FORMS
   ==================================================== */
.form-group { margin-bottom: var(--space-4); }
.form-group label {
  display: block;
  font-size: var(--font-sm);
  color: var(--text-secondary);
  margin-bottom: 5px;
  text-transform: uppercase;
  letter-spacing: .5px;
  font-weight: var(--font-semibold);
}
.form-group input,
.form-group select,
.form-group textarea,
/* ---- Base form controls: themed by default -------------------------
   68 selects in the portal carry no class and no inline style, so they had
   NO colour rules at all and fell back to the browser's own field colours.
   In dark mode that is where "the dropdown text is invisible" came from: a
   light UA field with inherited light text. Element selectors keep this at
   the bottom of the cascade, so every existing class or inline style still
   wins; this only fills the gap. */
select,
textarea,
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]) {
  background: var(--input-bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 6px);
  font-family: inherit;
}
select option,
select optgroup {
  background: var(--input-bg);
  color: var(--text);
}
input::placeholder, textarea::placeholder { color: var(--text-muted); opacity: 1; }
input:disabled, select:disabled, textarea:disabled { color: var(--text-muted); opacity: .75; }
input[type="checkbox"], input[type="radio"], progress, input[type="range"] { accent-color: var(--primary); }

.ds-input, .ds-select, .ds-textarea {
  width: 100%;
  background: var(--input-bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 12px;
  border-radius: var(--radius-md);
  font-size: 12px;
  font-family: inherit;
  outline: none;
  transition: border-color var(--transition-base),
              box-shadow var(--transition-base),
              background var(--transition-base);
}
.form-group input:hover,
.form-group select:hover,
.form-group textarea:hover {
  border-color: var(--border-strong);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.ds-input:focus, .ds-select:focus, .ds-textarea:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-muted);
}
.form-group input.error,
.form-group select.error {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-muted);
}
.form-group .form-hint  { font-size: var(--font-xs); color: var(--text-muted); margin-top: 4px; }
.form-group .form-error { font-size: var(--font-xs); color: var(--danger);     margin-top: 4px; }


/* ====================================================
   9. MODALS & TOASTS (shared structure)
   ==================================================== */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(5, 11, 24, .65);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  z-index: var(--z-modal);
  justify-content: center;
  align-items: flex-start;
  padding: 40px;
}
.modal-overlay.active { display: flex; }
.modal {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  width: 90%;
  max-width: 800px;
  max-height: 85vh;
  overflow-y: auto;
  padding: var(--space-7);
  position: relative;
  box-shadow: var(--shadow-xl);
  animation: ds-modalIn .2s ease;
}

/* Wide detail modal — the canonical shell for multi-section detail/edit
   views (per the "wide centered modal, not narrow slide-over" pattern).
   Pair the body with .modal-wide-body for a 2-column layout that collapses
   to a single column on narrow viewports. */
.modal-wide { max-width: 1080px; }
.modal-wide-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5) var(--space-6);
  align-items: start;
}
/* A section that should span the full width of the 2-col body */
.modal-wide-body .span-2,
.modal-wide-body .col-span-full { grid-column: 1 / -1; }
@media (max-width: 820px) {
  .modal-wide-body { grid-template-columns: 1fr; }
}

.toast-container {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-2);
  pointer-events: none;
}


/* ====================================================
  10. EMPTY STATES + SKELETONS
   ==================================================== */
.empty-state {
  text-align: center;
  padding: var(--space-12) var(--space-5);
  color: var(--text-muted);
}
.empty-state-icon  { font-size: 48px; margin-bottom: var(--space-4); opacity: .55; }
.empty-state-title { font-size: var(--font-lg); font-weight: var(--font-semibold); color: var(--text); margin-bottom: var(--space-2); }
.empty-state-desc  { font-size: var(--font-base); color: var(--text-muted); max-width: 380px; margin: 0 auto var(--space-5); line-height: 1.6; }
.empty-state .empty-actions { display: flex; gap: var(--space-3); justify-content: center; }

.skeleton {
  background: var(--surface-3);
  border-radius: var(--radius-md);
  position: relative;
  overflow: hidden;
}
.skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
              transparent,
              rgba(255, 255, 255, .045),
              transparent);
  animation: ds-shimmer 1.5s infinite;
}
:root .skeleton::after,
[data-theme="light"] .skeleton::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .6), transparent);
}
[data-theme="dark"] .skeleton::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .045), transparent);
}
.skeleton-text   { height: 14px; margin-bottom: var(--space-2); border-radius: var(--radius-sm); }
.skeleton-text.sm { width: 40%; }
.skeleton-text.md { width: 65%; }
.skeleton-text.lg { width: 85%; }
.skeleton-circle { border-radius: var(--radius-full); }
.skeleton-card   { height: 100px; border-radius: var(--radius-lg); }

/* Multi-bar block for a colspan cell / any loading container — 3 shimmer
   lines, no JS. Use in a table's initial <td colspan="N"> loading row:
   <td colspan="N"><div class="skel-block"><i></i><i></i><i></i></div></td> */
.skel-block { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-1) 0; width: 100%; }
.skel-block > i {
  display: block;
  height: 12px;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  position: relative;
  overflow: hidden;
}
.skel-block > i:nth-child(1) { width: 82%; }
.skel-block > i:nth-child(2) { width: 60%; }
.skel-block > i:nth-child(3) { width: 71%; }
.skel-block > i::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
  animation: ds-shimmer 1.5s infinite;
}
[data-theme="dark"] .skel-block > i::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .05), transparent);
}


/* ====================================================
  11. FOCUS RINGS + ACCESSIBILITY
   ==================================================== */
:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
button:focus-visible,
.btn:focus-visible,
.ds-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--primary-muted),
              0 0 0 1px var(--primary);
}
/* Inputs: replace the generic outline with the brand focus treatment —
   EVERY input gets it, not just those inside .form-group (a bare topbar
   search or login field must still show a visible indicator: WCAG 2.4.7). */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-muted);
}

/* High-contrast tweak.
   :root is light by default, so it takes the light high-contrast border;
   dark is the explicit opt-in. */
@media (prefers-contrast: more) {
  :root, [data-theme="light"] { --border: #6b7d8a; }
  [data-theme="dark"]         { --border: #4a5b7c; }
  .btn { border: 1px solid currentColor; }
}


/* ====================================================
  12. ANIMATIONS + REDUCED MOTION
   ==================================================== */
@keyframes ds-modalIn  { from { opacity: 0; transform: translateY(-12px) scale(.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes ds-shimmer  { to { transform: translateX(100%); } }
@keyframes ds-fadeIn   { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes ds-pulse    { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}


/* ====================================================
  13. SCROLLBAR (subtle, themed)
   ==================================================== */
::-webkit-scrollbar       { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--radius-sm); }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
/* Firefox equivalent */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }


/* ====================================================
  14. v3.1 ADDITIONS — polish & micro-components
   ==================================================== */

/* Brand-tinted text selection */
::selection { background: rgba(11, 123, 121, .22); }

/* Consistent, theme-correct placeholder contrast */
input::placeholder,
textarea::placeholder { color: var(--text-muted); opacity: 1; }

/* --- Keyboard hint chip (shortcut help, command palette footer) --- */
.ds-kbd {
  display: inline-block;
  min-width: 16px;
  padding: 2px 5px;
  margin: 0 2px;
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--surface-3);
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1;
  color: var(--text-secondary);
  text-align: center;
}

/* --- Callout: inline notice band (info default) ---
   <div class="callout warning"><span>⚠️</span><div>…copy…</div></div> */
.callout {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-left: 3px solid var(--info);
  border-radius: var(--radius-md);
  background: var(--info-muted);
  color: var(--text);
  font-size: var(--font-base);
  line-height: 1.55;
  margin-bottom: var(--space-4);
}
.callout.success { border-left-color: var(--success); background: var(--success-muted); }
.callout.warning { border-left-color: var(--warning); background: var(--warning-muted); }
.callout.danger  { border-left-color: var(--danger);  background: var(--danger-muted); }
.callout.neutral { border-left-color: var(--border-strong); background: var(--surface-3); }
.callout .callout-title { font-weight: var(--font-semibold); display: block; margin-bottom: 2px; }

/* --- Button busy state: <button class="btn btn-primary is-loading"> ---
   Keeps width stable (label goes transparent, ring spins on top). */
.btn.is-loading,
.ds-btn.is-loading {
  color: transparent !important;
  pointer-events: none;
  position: relative;
}
.btn.is-loading::after,
.ds-btn.is-loading::after {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  top: calc(50% - 7px);
  left: calc(50% - 7px);
  border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: #fff;
  border-radius: var(--radius-full);
  animation: ds-spin .6s linear infinite;
}
.btn-secondary.is-loading::after,
.btn-ghost.is-loading::after {
  border-color: var(--primary-muted);
  border-top-color: var(--primary);
}

/* --- Sortable table headers: set aria-sort on the <th> ---
   (aria attribute drives both the a11y tree and the arrow) */
th[aria-sort] { cursor: pointer; user-select: none; }
th[aria-sort]::after { content: '↕'; margin-left: 6px; font-size: 10px; opacity: .4; }
th[aria-sort="ascending"]::after  { content: '↑'; opacity: .95; color: var(--primary); }
th[aria-sort="descending"]::after { content: '↓'; opacity: .95; color: var(--primary); }

/* --- Entrance utility for freshly-loaded panes (respects reduced motion
   via the global section-12 override) --- */
.ds-fade-in { animation: ds-fadeIn .25s ease both; }

/* --- Toast entrance (pages already position/colour their toasts;
   this only adds motion when the shared .toast class is used) --- */
.toast { animation: ds-toastIn .28s cubic-bezier(.21, 1.02, .73, 1) both; }
@keyframes ds-toastIn { from { opacity: 0; transform: translateX(24px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes ds-spin { to { transform: rotate(360deg); } }

/* The hidden attribute must actually hide.
 *
 * A row action menu was written as <span hidden style="...display:flex...">.
 * The user agent stylesheet gives [hidden] display:none, but an inline display
 * beats it, so every row's More menu painted at once and stacked down the
 * results table. This makes the attribute win, which is what anyone writing it
 * expects, and covers every other element that toggles the same way.
 */
[hidden] { display: none !important; }
