/* Parnex SSM — operations console.
   Calm, dense, high-contrast. Severity is always carried by a glyph and a text label
   as well as by colour. */

:root {
  color-scheme: light dark;

  --bg:            #f6f7f9;
  --surface:       #ffffff;
  --surface-sunk:  #f1f3f6;
  --border:        #d7dbe2;
  --border-strong: #b7bec9;

  --text:          #171a1f;
  --text-muted:    #5b6472;
  --text-inverse:  #ffffff;

  --accent:        #1f5fbf;
  --accent-dark:   #17488f;

  --healthy:       #1c7a4a;
  --healthy-bg:    #e6f4ec;
  --info:          #2b6cb0;
  --info-bg:       #e7f0f9;
  --warning:       #8a5800;
  --warning-bg:    #fdf1dc;
  --critical:      #b0231f;
  --critical-bg:   #fbe9e8;
  --offline:       #4a5160;
  --offline-bg:    #eceef2;
  --unknown:       #5b6472;
  --unknown-bg:    #f0f1f4;

  --radius:   6px;
  --radius-s: 4px;
  --shadow:   0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .10);

  --space-0: 2px;
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px;
  --space-4: 16px; --space-5: 24px; --space-6: 32px; --space-7: 48px;

  /* Operations density. A monitoring console is read, not browsed: rows are scanned
     in bulk and the useful comparison is between rows, so vertical rhythm is tight. */
  --row-h:   28px;
  --spark-h: 24px;

  /* Chart series identity, deliberately NOT the severity ramp above. A red line must
     mean "this is the write series", never "this is bad" — severity colour is reserved
     for severity. Chosen to stay distinguishable under deuteranopia and protanopia;
     dash patterns in dashboard.js carry the same distinction without colour at all. */
  --series-1: #5191e6;
  --series-2: #bf6210;
  --series-3: #0d7567;
  --series-4: #7d30a4;
  --series-5: #573f0f;
  --series-6: #561942;

  /* One measure for a form column and anything that has to line up with it. */
  --form-width: 520px;

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Lifted for dark surfaces; the light values fall below 3:1 against --surface. */
    --series-1: #1e6acf;
    --series-2: #c76610;
    --series-3: #13a894;
    --series-4: #ca9be2;
    --series-5: #e7c277;
    --series-6: #f5dced;

    --bg:            #12151a;
    --surface:       #1a1e25;
    --surface-sunk:  #161a20;
    --border:        #2b313b;
    --border-strong: #3b434f;

    --text:          #e8eaee;
    --text-muted:    #9aa3b2;

    --accent:        #6aa3f0;
    --accent-dark:   #8dbcf7;

    --healthy:       #5fd39a;  --healthy-bg:  #14301f;
    --info:          #7cb3ec;  --info-bg:     #16253a;
    --warning:       #e6b45c;  --warning-bg:  #33270f;
    --critical:      #f08a84;  --critical-bg: #3a1917;
    --offline:       #9aa3b2;  --offline-bg:  #22262e;
    --unknown:       #9aa3b2;  --unknown-bg:  #20242b;

    --shadow: 0 1px 2px rgba(0, 0, 0, .4);
  }
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  /* Tabular figures everywhere. Proportional digits change width as a value updates,
     which makes a column of readings shuffle sideways while being read. */
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.25; letter-spacing: -0.01em; }
h1 { font-size: 20px; }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
p  { margin: 0 0 var(--space-3); }
p:last-child { margin-bottom: 0; }
code { font-family: var(--mono); font-size: 0.92em; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent-dark); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-s);
}

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

.skip-link {
  position: absolute; left: -9999px; top: var(--space-2);
  background: var(--surface); padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius); z-index: 100;
}
.skip-link:focus { left: var(--space-4); }

.muted { color: var(--text-muted); }
.nowrap { white-space: nowrap; }

/* ---------- top bar ---------- */
.topbar {
  display: flex; align-items: center; gap: var(--space-5);
  padding: 0 var(--space-5); height: 52px;
  background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 20;
}
.topbar__brand { display: flex; align-items: center; }
/* The lockup already carries the product name, so the bar does not repeat it as text.
   Only the height is ours to pick; the artwork supplies the width. 36px is the floor —
   the tagline stops resolving below roughly that and reads as a smudge, and the mark
   descends to the tagline's baseline, so it cannot be cropped away to buy height. */
.topbar__logo { display: block; height: 36px; width: auto; }
.topbar__nav { display: flex; gap: var(--space-1); margin-right: auto; }
/* The overflow disclosure is a nav item like any other, so it shares the selector
   rather than being restyled to look similar and drifting later. */
.topbar__nav a,
.topbar__nav > .topbar__more > summary {
  display: inline-flex; align-items: center;
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-s);
  color: var(--text-muted); text-decoration: none; font-weight: 500;
  font-size: inherit; font-family: inherit; line-height: inherit;
  white-space: nowrap;
}
.topbar__nav a:hover,
.topbar__nav > .topbar__more > summary:hover { background: var(--surface-sunk); color: var(--text); }
.topbar__nav a[aria-current="page"],
.topbar__nav > .topbar__more[open] > summary,
.topbar__nav > .topbar__more--active > summary {
  color: var(--text); background: var(--surface-sunk);
}
.topbar__nav a[aria-current="page"],
.topbar__nav > .topbar__more--active > summary {
  box-shadow: inset 0 -2px 0 var(--accent);
}
/* Keyboard focus must stay visible on the summary, which has no default ring here. */
.topbar__nav > .topbar__more > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.topbar__account { display: flex; align-items: center; gap: var(--space-3); }
.topbar__user { display: flex; flex-direction: column; text-decoration: none; color: var(--text); line-height: 1.2; }
.topbar__user-role { font-size: 11px; color: var(--text-muted); }
.topbar__logout { margin: 0; }

/* ---------- layout ---------- */
.main { max-width: 1440px; margin: 0 auto; padding: var(--space-5); }
.footer {
  max-width: 1440px; margin: 0 auto; padding: var(--space-4) var(--space-5) var(--space-6);
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  color: var(--text-muted); font-size: 12px;
}

.page-head {
  display: flex; flex-wrap: wrap; gap: var(--space-4);
  align-items: flex-start; justify-content: space-between;
  margin-bottom: var(--space-5);
}
.page-head__meta { color: var(--text-muted); font-size: 13px; margin: var(--space-1) 0 0; }
.page-head__status { text-align: right; }
.page-head__freshness { font-size: 12px; color: var(--text-muted); margin: var(--space-2) 0 0; max-width: 44ch; }

.section-heading {
  font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-muted); margin: var(--space-6) 0 var(--space-3);
}
.section-heading:first-of-type { margin-top: 0; }

.split { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); }
.row { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }

/* ---------- cards & panels ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: var(--space-4); margin-bottom: var(--space-4);
}
.card--narrow { max-width: 560px; }
.card--danger { border-color: color-mix(in srgb, var(--critical) 40%, var(--border)); }
.card__head { margin-bottom: var(--space-4); }
.card__note { font-size: 12px; color: var(--text-muted); margin: var(--space-1) 0 0; max-width: 68ch; }

.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-6); }
.panel--centered { max-width: 640px; margin: var(--space-7) auto; text-align: center; }
.error__code { font-size: 40px; font-weight: 700; color: var(--text-muted); margin: 0; line-height: 1; }

.empty { color: var(--text-muted); text-align: center; padding: var(--space-5); margin: 0; }

/* ---------- notices ---------- */
.notice {
  display: flex; gap: var(--space-3); align-items: flex-start;
  padding: var(--space-3) var(--space-4); border-radius: var(--radius);
  border: 1px solid var(--border); margin-bottom: var(--space-4);
}
.notice__glyph {
  flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%;
  display: grid; place-items: center; font-size: 11px; font-weight: 700;
  background: currentColor; color: var(--surface);
}
.notice--success { background: var(--healthy-bg);  border-color: var(--healthy);  color: var(--healthy); }
.notice--info    { background: var(--info-bg);     border-color: var(--info);     color: var(--info); }
.notice--warning { background: var(--warning-bg);  border-color: var(--warning);  color: var(--warning); }
.notice--error   { background: var(--critical-bg); border-color: var(--critical); color: var(--critical); }
.notice > span:not(.notice__glyph), .notice > div { color: var(--text); }

/* ---------- severity pills ---------- */
.pill {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 2px var(--space-2); border-radius: 100px;
  font-size: 11px; font-weight: 600; white-space: nowrap;
  border: 1px solid transparent;
}
.pill__glyph { font-size: 10px; line-height: 1; }
.pill--healthy  { background: var(--healthy-bg);  color: var(--healthy);  border-color: color-mix(in srgb, var(--healthy) 30%, transparent); }
.pill--info     { background: var(--info-bg);     color: var(--info);     border-color: color-mix(in srgb, var(--info) 30%, transparent); }
.pill--warning  { background: var(--warning-bg);  color: var(--warning);  border-color: color-mix(in srgb, var(--warning) 35%, transparent); }
.pill--critical { background: var(--critical-bg); color: var(--critical); border-color: color-mix(in srgb, var(--critical) 35%, transparent); }
.pill--offline  { background: var(--offline-bg);  color: var(--offline);  border-color: color-mix(in srgb, var(--offline) 30%, transparent); }
.pill--unknown  { background: var(--unknown-bg);  color: var(--unknown);  border-color: color-mix(in srgb, var(--unknown) 30%, transparent); }

/* ---------- toolbar ---------- */
.toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.toolbar__group { display: flex; align-items: center; gap: var(--space-2); }
.toolbar__label, .toolbar label { font-size: 12px; color: var(--text-muted); font-weight: 500; }
/* Pushes a destructive action to the far end of a toolbar: same bar, deliberate
   distance from the primary button next to it. */
.toolbar__end { margin-left: auto; }
.toolbar__resolution { margin: 0 0 0 auto; font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

.chip {
  padding: 3px var(--space-3); border-radius: 100px; font-size: 12px;
  text-decoration: none; color: var(--text-muted);
  border: 1px solid var(--border);
}
.chip:hover { border-color: var(--border-strong); color: var(--text); }
.chip--active { background: var(--accent); border-color: var(--accent); color: var(--text-inverse); font-weight: 600; }
.chip--active:hover { color: var(--text-inverse); }

/* ---------- KPI grid ---------- */
.kpi-grid { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius); padding: var(--space-3) var(--space-4); box-shadow: var(--shadow);
}
.kpi--healthy  { border-left-color: var(--healthy); }
.kpi--info     { border-left-color: var(--info); }
.kpi--warning  { border-left-color: var(--warning); }
.kpi--critical { border-left-color: var(--critical); }
.kpi--offline  { border-left-color: var(--offline); }
.kpi--unknown  { border-left-color: var(--unknown); }
.kpi__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.kpi__label { font-size: 12px; color: var(--text-muted); font-weight: 500; }
.kpi__value {
  font-size: 26px; font-weight: 600; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; margin: var(--space-2) 0 0;
}
.kpi__context, .kpi__freshness { font-size: 11px; color: var(--text-muted); margin: var(--space-1) 0 0; }

/* ---------- charts ---------- */
.chart-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(460px, 1fr)); }
.chart-card { margin-bottom: 0; display: flex; flex-direction: column; }
.chart-card__canvas { position: relative; height: 220px; margin-bottom: var(--space-3); }
.chart-card__canvas canvas { width: 100% !important; height: 100% !important; }
.chart-card__state {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 12px; color: var(--text-muted); background: var(--surface); margin: 0;
}
.chart-card__state[hidden] { display: none; }
.chart-card__summary { width: 100%; border-collapse: collapse; font-size: 12px; }
.chart-card__summary th, .chart-card__summary td {
  padding: var(--space-1) var(--space-2); text-align: right;
  border-top: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
.chart-card__summary th:first-child, .chart-card__summary td:first-child { text-align: left; font-weight: 500; }
.chart-card__summary thead th { color: var(--text-muted); font-weight: 500; border-top: 0; }

/* ---------- tables ---------- */
.table-scroll { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table th, .table td {
  padding: var(--space-2) var(--space-3); text-align: left;
  border-bottom: 1px solid var(--border); vertical-align: middle;
}
.table thead th {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-muted); font-weight: 600; white-space: nowrap;
  border-bottom: 1px solid var(--border-strong);
}
.table tbody tr:last-child th, .table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--surface-sunk); }
.table--dense th, .table--dense td { padding: var(--space-1) var(--space-2); font-size: 12px; }
.stack { display: flex; flex-direction: column; line-height: 1.35; }
.stack .muted { font-size: 12px; font-weight: 400; }

.meter { position: relative; min-width: 110px; height: 18px; background: var(--surface-sunk); border-radius: var(--radius-s); overflow: hidden; }
.meter__bar { position: absolute; inset: 0 auto 0 0; width: 0; border-radius: var(--radius-s); }
.meter__bar--healthy  { background: color-mix(in srgb, var(--healthy) 35%, transparent); }
.meter__bar--info     { background: color-mix(in srgb, var(--info) 35%, transparent); }
.meter__bar--warning  { background: color-mix(in srgb, var(--warning) 45%, transparent); }
.meter__bar--critical { background: color-mix(in srgb, var(--critical) 45%, transparent); }
.meter__bar--unknown, .meter__bar--offline { background: var(--border); }
.meter__text {
  position: relative; display: block; text-align: center; line-height: 18px;
  font-size: 11px; font-variant-numeric: tabular-nums; font-weight: 600;
}

.tag {
  display: inline-block; padding: 1px var(--space-2); margin: 1px;
  background: var(--surface-sunk); border-radius: var(--radius-s);
  font-size: 11px; font-family: var(--mono); color: var(--text-muted);
}
.audit__context { max-width: 320px; }

/* ---------- forms ---------- */
.form { max-width: var(--form-width); }
/* A form and its action bar are one column even though they are two elements. The
   bar has to sit outside the <form> wherever it carries a destructive action, since
   that needs a POST of its own and HTML forbids nesting forms — without this the bar
   would ignore the form's measure and run the full width of the page. */
.form-stack { max-width: var(--form-width); }
.form-stack > .form { max-width: none; }
.field { margin-bottom: var(--space-4); }
.field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: var(--space-1); }
.field__help { font-size: 12px; color: var(--text-muted); margin: var(--space-1) 0 0; }
.field--check { display: flex; align-items: flex-start; gap: var(--space-2); }
/* Two numbers that are only meaningful together — a threshold and its window, a
   failure count and a recovery count. Side by side so the pair reads as one
   setting; stacked at the same 640px breakpoint the rest of the sheet uses, where
   two number inputs side by side stop being legible. */
.field--pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.field--pair > span { display: block; }
@media (max-width: 640px) { .field--pair { grid-template-columns: 1fr; } }
.field--check label { margin: 0; font-weight: 400; }

input[type="text"], input[type="email"], input[type="password"], input[type="search"],
input[type="number"], select {
  width: 100%; padding: var(--space-2) var(--space-3);
  font: inherit; color: var(--text);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-s);
}
.toolbar input, .toolbar select { width: auto; min-width: 160px; }
input:focus, select:focus { border-color: var(--accent); }
.input--code { font-family: var(--mono); font-size: 20px; letter-spacing: .3em; text-align: center; }

.fieldset { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); margin: 0 0 var(--space-4); }
.fieldset legend { font-size: 12px; font-weight: 600; color: var(--text-muted); padding: 0 var(--space-2); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4); font: inherit; font-weight: 500;
  color: var(--text); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: var(--radius-s);
  cursor: pointer; text-decoration: none;
}
.btn:hover { background: var(--surface-sunk); color: var(--text); }
.btn--primary { background: var(--accent); border-color: var(--accent); color: var(--text-inverse); }
.btn--primary:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: var(--text-inverse); }
.btn--danger { background: var(--critical); border-color: var(--critical); color: #fff; }
.btn--danger:hover { filter: brightness(.92); color: #fff; }
.btn--quiet { background: transparent; border-color: transparent; color: var(--text-muted); }
.btn--quiet:hover { background: var(--surface-sunk); color: var(--text); }
.btn--block { width: 100%; }

.pagination { display: flex; align-items: center; gap: var(--space-3); justify-content: center; padding: var(--space-4) 0; }
.pagination__status { font-size: 12px; color: var(--text-muted); }

/* ---------- auth ---------- */
.auth { display: grid; place-items: center; min-height: 100vh; padding: var(--space-5); }
.auth__card {
  width: 100%; max-width: 400px; padding: var(--space-6);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.auth--wide .auth__card { max-width: 640px; }
.auth__title { margin-bottom: var(--space-1); }
.auth__subtitle { color: var(--text-muted); font-size: 13px; margin-bottom: var(--space-5); }
.auth__hint { font-size: 12px; color: var(--text-muted); margin-top: var(--space-4); }
.auth__recovery { margin-top: var(--space-5); border-top: 1px solid var(--border); padding-top: var(--space-4); }
.auth__recovery summary { cursor: pointer; font-size: 13px; color: var(--accent); }
.auth__cancel { margin-top: var(--space-4); text-align: center; }

.steps { margin: 0; padding-left: var(--space-5); }
.steps > li { margin-bottom: var(--space-5); }
.steps h2 { font-size: 14px; margin-bottom: var(--space-2); }
.steps--compact > li { margin-bottom: var(--space-2); }

.secret {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  padding: var(--space-3); margin-bottom: var(--space-3);
  background: var(--surface-sunk); border: 1px solid var(--border); border-radius: var(--radius);
}
.secret__label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.secret__value { font-family: var(--mono); font-size: 15px; letter-spacing: .08em; word-break: break-all; }

.codes { list-style: none; padding: 0; margin: var(--space-4) 0; display: grid; gap: var(--space-2); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.codes code {
  display: block; padding: var(--space-2); text-align: center;
  background: var(--surface-sunk); border: 1px solid var(--border); border-radius: var(--radius-s);
  font-size: 14px; letter-spacing: .05em;
}

.kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-4); margin: 0 0 var(--space-4); font-size: 13px; }
.kv dt { color: var(--text-muted); }
.kv dd { margin: 0; }
.kv--inline { margin-bottom: var(--space-3); }

/* ---------- section sub-navigation ---------- */
.subnav { display: flex; flex-wrap: wrap; gap: var(--space-1); margin: 0 0 var(--space-4);
          border-bottom: 1px solid var(--border); }
.subnav__link { padding: var(--space-2) var(--space-3); font-size: 13px; text-decoration: none;
                color: var(--text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.subnav__link:hover { color: var(--text); }
/* The active tab carries a weight and a rule, not only a colour. */
.subnav__link--active { color: var(--text); font-weight: 600; border-bottom-color: var(--accent); }

/* ---------- alert summary counters ---------- */
/* Severity is carried by the glyph and the label as well as the colour, so the tint
   here is reinforcement rather than the only signal. */
.stat-row { display: flex; flex-wrap: wrap; gap: var(--space-3); list-style: none; padding: 0; margin: 0 0 var(--space-4); }
.stat { display: flex; flex-direction: column; gap: 2px; min-width: 96px; padding: var(--space-3) var(--space-4);
        border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-sunk); }
.stat__value { font-size: 24px; font-weight: 600; line-height: 1.1; font-variant-numeric: tabular-nums; }
.stat__label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.stat--critical { border-color: var(--critical); background: var(--critical-bg); }
.stat--critical .stat__value { color: var(--critical); }
.stat--warning { border-color: var(--warning); background: var(--warning-bg); }
.stat--warning .stat__value { color: var(--warning); }

/* ---------- responsive ---------- */

/* ---------------------------------------------------------------- triage bar
   Rendered only when something is wrong, so its presence is itself the signal.
   A permanent "all clear" strip becomes furniture and stops being read. */
.triage {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "glyph title" "glyph items";
  gap: 0 var(--space-3);
  align-items: start;
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-strong);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--surface);
}
.triage--critical { border-left-color: var(--critical); background: var(--critical-bg); }
.triage--warning  { border-left-color: var(--warning);  background: var(--warning-bg); }
.triage--offline  { border-left-color: var(--offline);  background: var(--offline-bg); }
.triage--unknown  { border-left-color: var(--unknown);  background: var(--unknown-bg); }
.triage__glyph { grid-area: glyph; font-size: 18px; line-height: 1.4; }
.triage--critical .triage__glyph { color: var(--critical); }
.triage--warning  .triage__glyph { color: var(--warning); }
.triage__title { grid-area: title; margin: 0; font-size: 14px; font-weight: 600; }
.triage__items {
  grid-area: items;
  display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4);
  margin: var(--space-1) 0 0; padding: 0; list-style: none;
}
.triage__items li::before { content: "·"; margin-right: var(--space-2); color: var(--text-muted); }
.triage__items li:first-child::before { content: none; }

/* ------------------------------------------------------------- vitals strip
   Replaces the KPI card grid. Compact enough that eight subsystems fit one row
   on a desktop, so the comparison between them is a glance rather than a scroll. */
.vitals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-2);
  margin: 0 0 var(--space-5); padding: 0;
  list-style: none;
}
.vital {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-top: 3px solid var(--border-strong);
  border-radius: var(--radius-s);
  background: var(--surface);
}
.vital--healthy  { border-top-color: var(--healthy); }
.vital--info     { border-top-color: var(--info); }
.vital--warning  { border-top-color: var(--warning); }
.vital--critical { border-top-color: var(--critical); }
.vital--offline  { border-top-color: var(--offline); }
.vital--unknown  { border-top-color: var(--unknown); }

.vital__label   { grid-column: 1 / -1; font-size: 12px; color: var(--text-muted); }
.vital__value   { font-size: 20px; font-weight: 600; line-height: 1.2; }
.vital__state   { justify-self: end; align-self: center; display: flex; align-items: center; gap: var(--space-1); font-size: 11px; }
.vital__glyph   { font-size: 12px; }
.vital--healthy  .vital__glyph, .vital--healthy  .vital__severity { color: var(--healthy); }
.vital--info     .vital__glyph, .vital--info     .vital__severity { color: var(--info); }
.vital--warning  .vital__glyph, .vital--warning  .vital__severity { color: var(--warning); }
.vital--critical .vital__glyph, .vital--critical .vital__severity { color: var(--critical); }
.vital--offline  .vital__glyph, .vital--offline  .vital__severity { color: var(--offline); }
.vital--unknown  .vital__glyph, .vital--unknown  .vital__severity { color: var(--unknown); }
.vital__context { grid-column: 1 / -1; font-size: 11px; color: var(--text-muted); }
/* Height is reserved so the row does not jump when the sparkline finishes loading. */
.vital__spark   { grid-column: 1 / -1; display: block; position: relative;
                  width: 100%; height: var(--spark-h); margin-top: var(--space-1); }
/* Same containment as .chart-card__canvas, and required for the same reason: a
   responsive Chart.js canvas fills its parent, so the parent must be the thing
   with the height. Without this the sparkline grew its own cell without bound. */
.vital__spark canvas { display: block; width: 100% !important; height: 100% !important; }

/* -------------------------------------------------------------- custom range */
.range-custom { position: relative; display: inline-block; }
.range-custom > summary { cursor: pointer; list-style: none; }
.range-custom > summary::-webkit-details-marker { display: none; }
.range-custom__form {
  position: absolute; z-index: 40; top: calc(100% + var(--space-1)); left: 0;
  display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: flex-end;
  padding: var(--space-3);
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow);
}
.range-custom__field { display: flex; flex-direction: column; gap: var(--space-1); }
.range-custom__field label { font-size: 11px; color: var(--text-muted); }
.range-custom__error {
  position: absolute; z-index: 40; top: calc(100% + var(--space-1)); left: 0;
  margin: 0; max-width: 32ch; color: var(--warning);
}

/* --------------------------------------------------------- availability band
   Discrete segments, not a line. Uptime is a state over time, and a line drawn
   between 100 and 0 implies values in between that were never observed. */
.availability__figure { display: flex; align-items: center; gap: var(--space-3); margin: 0 0 var(--space-2); }
.availability__value  { font-size: 22px; font-weight: 600; }
.availability__band {
  display: flex; gap: 1px; margin: 0 0 var(--space-2); padding: 0;
  list-style: none; height: 20px; border-radius: var(--radius-s); overflow: hidden;
}
.availability__seg { flex: 1 1 auto; min-width: 2px; }
.availability__seg--up    { background: var(--healthy); }
.availability__seg--down  { background: var(--critical); }
.availability__seg--error { background: var(--unknown); }
.availability__note { margin: 0; font-size: 12px; color: var(--text-muted); }

/* ------------------------------------------------------------- dense tables */
.table--dense th, .table--dense td { padding-top: var(--space-1); padding-bottom: var(--space-1); height: var(--row-h); }

/* --------------------------------------------------------------- navigation */
/* The nav scrolls horizontally below 900px; without an edge fade nothing says so. */
.topbar__nav { scrollbar-width: thin; }
.topbar__more { position: relative; display: flex; align-items: center; }
.topbar__more > summary { cursor: pointer; list-style: none; }
.topbar__more > summary::-webkit-details-marker { display: none; }
.topbar__more-menu {
  position: absolute; z-index: 100; right: 0; top: calc(100% + var(--space-1));
  display: flex; flex-direction: column; min-width: 160px;
  padding: var(--space-1);
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow);
}

/* ------------------------------------------------------------- breadcrumbs */
.breadcrumb { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0 0 var(--space-3); padding: 0; list-style: none; font-size: 12px; }
.breadcrumb li + li::before { content: "›"; margin-right: var(--space-2); color: var(--text-muted); }

@media (max-width: 900px) {
  .topbar { flex-wrap: wrap; height: auto; padding: var(--space-3) var(--space-4); gap: var(--space-3); }
  .topbar__nav { order: 3; width: 100%; overflow-x: auto; }
  .main { padding: var(--space-4) var(--space-3); }
  .split, .chart-grid { grid-template-columns: 1fr; }
  .vitals { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
  /* A popover anchored to a chip runs off a narrow screen; in the flow it cannot. */
  .range-custom { position: static; }
  .range-custom__form, .range-custom__error { position: static; max-width: none; }
  .range-custom__form { margin-top: var(--space-2); }
  /* A fade at the trailing edge, so a nav that scrolls looks like one. The spacer
     keeps the last link clear of the fade rather than under it. Masking degrades to
     "no fade" where unsupported, which is the previous behaviour, not a broken one. */
  .topbar__nav {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
  .topbar__nav::after { content: ""; flex: 0 0 var(--space-5); }
  .stat { flex: 1 1 40%; min-width: 0; }
  .chart-card__canvas { height: 200px; }
  .toolbar__resolution { margin-left: 0; width: 100%; }
  .page-head__status { text-align: left; }
}

@media print {
  .topbar, .toolbar, .btn, .skip-link { display: none !important; }
  /* The footer stays, because the ownership line is inside it. Its screen-only parts
     are hidden instead of the whole bar: display:none on the parent would take the
     notice with it, and nothing a child declares can undo that. */
  .footer > :not(.copyright) { display: none !important; }
  .footer {
    margin-top: 1rem; padding: 0.5rem 0 0;
    border-top: 1px solid #999; color: #000; font-size: 8pt;
  }
  body { background: #fff; }
  .card, .kpi { box-shadow: none; border-color: #999; break-inside: avoid; }
}

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

/* ------------------------------------------------------------------ services
   The findings list: one row per thing that is wrong, with the severity pill on
   the left so the column of glyphs can be scanned without reading the sentences.
   Deliberately not a table — these rows are of unequal height by nature, because
   a service can carry more than one finding at once. */
.card--warning { border-color: color-mix(in srgb, var(--warning) 40%, var(--border)); }

.findings { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-2); }
.finding {
  display: grid;
  grid-template-columns: minmax(9rem, auto) 1fr;
  gap: 0 var(--space-3);
  align-items: start;
  padding: var(--space-2) 0;
  border-top: 1px solid var(--border);
}
.finding:first-child { border-top: 0; padding-top: 0; }
.finding__state { grid-column: 1; }
.finding__body  { grid-column: 2; min-width: 0; }
.finding__notes {
  margin: var(--space-1) 0 0; padding: 0; list-style: none;
  font-size: 12px; color: var(--text-muted); max-width: 78ch;
}
.finding__notes li + li { margin-top: 2px; }

/* A value that disagrees with what was configured. Never colour alone: every use
   sits beside the literal word ("disabled", "Never"), and the surrounding row
   carries a severity pill that states the same thing in text. */
.is-wrong { color: var(--warning); font-weight: 600; }

.card__filter { display: flex; align-items: center; gap: var(--space-2); font-size: 12px; }
.card__filter label { color: var(--text-muted); }

/* The unit picker. A datalist rather than a <select>: a host reports several
   hundred units, and typing "ngin" beats scrolling past 500 options — while the
   list still means the value came from what the host published. */
.unit-picker { display: grid; gap: var(--space-1); }
.unit-picker__count { font-size: 12px; color: var(--text-muted); }

@media (max-width: 640px) {
  .finding { grid-template-columns: 1fr; gap: var(--space-1); }
  .finding__state, .finding__body { grid-column: 1; }
}

/* ================================================================================
   Parnex Customization Portal — components this product needs that the base
   operations console did not.

   Everything above this line is inherited unchanged. Everything below uses the same
   tokens, the same density and the same rule that a state is never carried by colour
   alone. Five new components, and the small set of form utilities they need.
   ================================================================================ */

/* ---------- form utilities ----------------------------------------------------
   The base sheet styles inputs by element selector. This application has textareas
   and needs one class it can put on any control, so the two are unified here rather
   than by widening the element selector, which would also catch checkboxes. */
.input,
textarea {
  width: 100%; padding: var(--space-2) var(--space-3);
  font: inherit; color: var(--text);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-s);
}
textarea { resize: vertical; min-height: 4.5em; }
.toolbar .input { width: auto; min-width: 160px; }
input[type="checkbox"], input[type="radio"] { width: auto; margin-top: 3px; }

/* An error sits where the hint would, in the severity colour AND prefixed with a
   glyph, so the field that failed is findable without relying on the red. */
.field__error {
  display: flex; gap: var(--space-1);
  margin: var(--space-1) 0 0; font-size: 12px; color: var(--critical);
}
.field__error::before { content: "✕"; }
.field__legend { display: block; font-size: 13px; font-weight: 500; margin-bottom: var(--space-1); }

/* The action bar. Kept out of .form so a destructive action can live in its own
   form element — HTML forbids nesting forms. */
.form__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; margin-top: var(--space-5); }

.notice__detail { margin: var(--space-1) 0 0; font-size: 13px; }

.plain-list { list-style: none; margin: 0; padding: 0; }
.plain-list > li { padding: var(--space-1) 0; border-top: 1px solid var(--border); }
.plain-list > li:first-child { border-top: 0; }

.break-all { word-break: break-all; }

.tag--warning { background: var(--warning-bg); color: var(--warning); }
.tag-list { display: flex; flex-wrap: wrap; gap: var(--space-1); list-style: none; margin: 0; padding: 0; }
/* A tag that copies is a button, not a styled span: it is operated by keyboard and
   announced as an action. */
button.tag { border: 1px solid var(--border); cursor: pointer; }
button.tag:hover { background: var(--surface); color: var(--text); }

.error__glyph { font-size: 40px; line-height: 1; margin: 0 0 var(--space-3); color: var(--text-muted); }

.auth__brand { display: flex; justify-content: center; margin-bottom: var(--space-5); }
.auth__logo { display: block; width: 100%; max-width: 260px; height: auto; }

/* ---------- .json — raw payload viewer ---------------------------------------
   Webhook bodies are the evidence when a rule misfires, so they are shown exactly
   as received. Capped in height because a position payload can be thousands of
   lines and would otherwise push every other section off the page. */
.json {
  margin: 0; padding: var(--space-3);
  max-height: 460px; overflow: auto;
  background: var(--surface-sunk); border: 1px solid var(--border); border-radius: var(--radius-s);
  font-family: var(--mono); font-size: 12px; line-height: 1.5;
  white-space: pre; tab-size: 2;
}
.json-input { font-family: var(--mono); font-size: 12px; white-space: pre; overflow-wrap: normal; overflow-x: auto; }

/* ---------- .mapping — path-to-field rows -------------------------------------
   Used by the condition editor, the column mapping and the header editors. A grid
   rather than a table: these are inputs, and a table of inputs reads as data the
   user cannot change. */
.mapping { display: grid; gap: var(--space-2); margin-bottom: var(--space-2); }
.mapping__head {
  display: grid; grid-template-columns: 2fr 1.4fr 2fr; gap: var(--space-2);
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted);
}
.mapping__row { display: grid; grid-template-columns: 2fr 1.4fr 2fr; gap: var(--space-2); align-items: start; }
.mapping--three .mapping__head, .mapping--three .mapping__row { grid-template-columns: 1fr 1.6fr 1fr; }
.mapping__row--pair { grid-template-columns: 1fr 2fr; }
.mapping__row--pair ~ .mapping__head { grid-template-columns: 1fr 2fr; }
/* Rows go vertical at the field breakpoint, where three controls side by side stop
   being usable — the same 640px the paired-number fields use. */
@media (max-width: 640px) {
  .mapping__head { display: none; }
  .mapping__row, .mapping--three .mapping__row, .mapping__row--pair { grid-template-columns: 1fr; }
  .mapping__row { padding-bottom: var(--space-2); border-bottom: 1px solid var(--border); }
}

/* ---------- .seq — the process step sequence ----------------------------------
   An ordered list, drawn as one. The position number is the thing being reordered,
   so it is the most prominent element in the row and is repeated verbatim in a run
   trace: "step 3" means the same in both places. */
.seq { list-style: none; margin: 0 0 var(--space-5); padding: 0; display: grid; gap: var(--space-2); }
.seq__step {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
}
/* A disabled step keeps its place in the sequence — removing it would renumber the
   ones after it and make the trace of an older run stop matching. */
.seq__step--disabled { border-left-color: var(--offline); background: var(--surface-sunk); }
.seq__position {
  display: grid; place-items: center;
  width: 26px; height: 26px;
  background: var(--surface-sunk); border-radius: 50%;
  font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--text-muted);
}
.seq__step--disabled .seq__position { background: var(--offline-bg); }
.seq__body { min-width: 0; }
.seq__name { margin: 0; font-size: 14px; display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.seq__type { margin: var(--space-1) 0 0; font-size: 12px; color: var(--text-muted); }
.seq__summary { margin: var(--space-1) 0 0; font-size: 12px; overflow-wrap: anywhere; }
.seq__output { margin: var(--space-1) 0 0; font-size: 11px; }
.seq__controls { display: flex; gap: var(--space-0); }
.seq__controls form { margin: 0; }
.seq__controls .btn { padding: var(--space-1) var(--space-2); }
.seq__controls .btn[disabled] { opacity: .35; cursor: not-allowed; }

@media (max-width: 640px) {
  .seq__step { grid-template-columns: auto 1fr; }
  .seq__controls { grid-column: 1 / -1; justify-content: flex-end; }
}

/* ---------- .trace — one run's execution --------------------------------------
   Deliberately not a table, for the same reason .findings is not: steps are of very
   unequal height, and the useful scan is down the status column for the one that
   failed. The 3px left border repeats the step's severity so that column is
   scannable without reading any of the text. */
.trace { list-style: none; margin: 0 0 var(--space-5); padding: 0; display: grid; gap: var(--space-2); }
.trace__step {
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius);
}
.trace__step--healthy  { border-left-color: var(--healthy); }
.trace__step--info     { border-left-color: var(--info); }
.trace__step--warning  { border-left-color: var(--warning); }
.trace__step--critical { border-left-color: var(--critical); background: var(--critical-bg); }
.trace__step--offline  { border-left-color: var(--offline); background: var(--surface-sunk); }
.trace__step--unknown  { border-left-color: var(--unknown); }

.trace__head { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.trace__position {
  display: grid; place-items: center; width: 22px; height: 22px;
  background: var(--surface-sunk); border-radius: 50%;
  font-family: var(--mono); font-size: 12px; color: var(--text-muted);
}
.trace__name { margin: 0; font-size: 14px; margin-right: auto; }
.trace__timing { font-size: 12px; font-variant-numeric: tabular-nums; }
.trace__error { margin: var(--space-2) 0 0; font-size: 13px; color: var(--critical); }
.trace__detail { margin-top: var(--space-2); }
.trace__detail > summary { cursor: pointer; font-size: 12px; color: var(--text-muted); }
.trace__detail > summary:hover { color: var(--text); }
.trace__detail .json { max-height: 320px; }
/* The two panes stack well below the split breakpoint, and a run trace is often read
   on a phone by whoever is on call. */
.trace__detail .split { margin-top: var(--space-2); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

/* ---------- report charts ------------------------------------------------------ */
.chart-card__caption { margin: 0 0 var(--space-3); font-size: 12px; max-width: 60ch; }
/* The summary table under every chart carries the same numbers as text. It is
   collapsed by default on screen and forced open in print. */
.chart-card__details > summary { cursor: pointer; font-size: 12px; color: var(--text-muted); }
.chart-card__details > summary:hover { color: var(--text); }

.chart-editor {
  padding: var(--space-3); margin-bottom: var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-sunk);
}
.chart-editor .field:last-child { margin-bottom: 0; }

/* ---------- availability band, in severity terms -------------------------------
   The base sheet names these up/down/error. This system has one vocabulary for state
   everywhere, so the segments carry severity names instead of a second set. */
.availability__seg--healthy  { background: var(--healthy); }
.availability__seg--critical { background: var(--critical); }
.availability__seg--warning  { background: var(--warning); }
.availability__seg--offline  { background: var(--offline); }
.availability__seg--info     { background: var(--info); }
/* An hour with nothing received is not zero and not a fault. It reads as an absence:
   a tint rather than a filled bar, so a quiet night does not look like an outage. */
.availability__seg--unknown  { background: var(--unknown-bg); border-top: 1px solid var(--border); }

.legend { display: flex; flex-wrap: wrap; gap: var(--space-4); list-style: none; margin: var(--space-2) 0 0; padding: 0; font-size: 12px; color: var(--text-muted); }
.legend li { display: flex; align-items: center; gap: var(--space-2); }
.legend__swatch { width: 12px; height: 12px; border-radius: 2px; display: inline-block; }
.legend__swatch--healthy  { background: var(--healthy); }
.legend__swatch--critical { background: var(--critical); }
.legend__swatch--unknown  { background: var(--unknown-bg); border: 1px solid var(--border-strong); }

/* ---------- findings, extended -------------------------------------------------
   The base sheet gives .finding its grid and its notes list. The rule tester needs a
   title and a detail line inside the body. */
.finding__title { margin: 0; font-size: 13px; font-weight: 500; }
.finding__detail { margin: var(--space-1) 0 0; font-size: 12px; color: var(--text-muted); }

/* ---------- print --------------------------------------------------------------- */
@media print {
  .topbar, .toolbar, .subnav, .breadcrumb, .footer, .pagination, .skip-link { display: none !important; }
  .card, .panel { border: 1px solid #ccc; box-shadow: none; break-inside: avoid; }
  .seq__step, .trace__step { break-inside: avoid; }
  /* A collapsed summary table would print as a closed triangle and nothing else. */
  .chart-card__details, .trace__detail { display: block; }
  .chart-card__details > summary, .trace__detail > summary { display: none; }
  .json { max-height: none; overflow: visible; }
}

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

/* ---------- enrolment QR --------------------------------------------------------
   The symbol is an inline <svg> the server drew, so there is nothing to load and
   nothing for the CSP to allow. It carries its own white quiet zone, which is why
   it is not given a background here: the quiet zone IS the border a scanner needs,
   and a themed one would eat into it. */
.qr-wrap { margin: var(--space-3) 0; }
.qr {
  display: block;
  width: 100%;
  max-width: 240px;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-s);
}
.qr-fallback { margin-top: var(--space-2); }
.qr-fallback > summary { cursor: pointer; font-size: 12px; color: var(--text-muted); }
.qr-fallback > summary:hover { color: var(--text); }

@media print { .qr { border: 0; } }
