/* ============================================================================================
   EDaq Central — design system

   Self-contained on purpose. The previous build pulled Bootstrap from a public CDN, which is
   fine on a laptop and wrong here: this application runs on an operations network that often
   cannot reach the internet at all, and a console whose layout collapses because jsdelivr is
   unreachable is a console nobody trusts at three in the morning.

   ── the direction ──────────────────────────────────────────────────────────────────────────
   An annunciator panel, not a dashboard. The visual language is taken from the substation
   control room this data comes from: a navy enamelled panel carrying the navigation, a pale
   working surface for the register data, hairline rules instead of shadows, and lamps that mean
   exactly what lamps mean on real switchgear.

   Colour is status and nothing else. Green is healthy, amber is attention, red is a fault, a
   dark lamp is out of service on purpose. The house colours - a navy panel and an azure
   action - carry no meaning an operator has to decode, which is the point: the only saturated
   colour on the screen that MEANS something is the colour burning on a lamp.
   ============================================================================================ */

/* Only the display face and the mono are fetched. The interface type is the SYSTEM stack - Segoe
   UI on Windows, which is what every operator here is running - so the text an operator reads all
   day needs no download at all, and an air-gapped console loses only the headings. */
@import url('https://fonts.googleapis.com/css2?family=Figtree:wght@500;600;700;800&family=IBM+Plex+Mono:wght@400;500;600&display=swap');

:root {
    /* ------------------------------------------------------------------ panel (the chrome) */
    --navy-950: #111b30;
    --navy-900: #1b3456;
    --navy-800: #22395c;
    --navy-700: #2d4260;
    --navy-500: #64748b;

    /* --------------------------------------------------------- surface (the work surface) */
    --paper: #f1f5f9;
    --surface: #ffffff;
    --surface-sunken: #e9eef5;
    --rule: #e2e8f0;
    --rule-strong: #cbd5e1;

    /* ---------------------------------------------------------------------------- ink */
    --ink: #212529;
    --ink-strong: #1e293b;
    --ink-muted: #64748b;
    --ink-faint: #94a3b8;
    --ink-inverse: #eef2f8;

    /* --------------------------------------------------- azure: actions, never status */
    --azure-900: #0a58ca;
    --azure-700: #0b5ed7;
    --azure-600: #0d6efd;
    --azure-300: #86b7fe;
    --azure-100: #cfe2ff;

    /* -------------------------------------------- brass: the accent, used with restraint */
    --brass-600: #a3721f;
    --brass-500: #c08a2e;
    --brass-100: #f6ecd8;

    /* -------------------------------------------------- lamps: control-room convention */
    --lamp-ok: #17864a;
    --lamp-ok-soft: #e2f2e7;
    --lamp-warn: #b4780f;
    --lamp-warn-soft: #fbf0dc;
    --lamp-fault: #c0392b;
    --lamp-fault-soft: #fae9e7;
    --lamp-idle: #6b7683;
    --lamp-idle-soft: #eceef0;

    /* ------------------------------------------------------------------------- geometry */
    --r-sm: 3px;
    --r: 5px;
    --r-lg: 8px;
    --rail: 244px;

    --shadow-sm: 0 1px 2px rgba(13, 26, 68, .06);
    --shadow: 0 1px 2px rgba(13, 26, 68, .05), 0 6px 18px rgba(13, 26, 68, .07);
    --shadow-lg: 0 2px 6px rgba(13, 26, 68, .08), 0 18px 44px rgba(13, 26, 68, .14);

    /* --------------------------------------------------------------------------- type */
    --font-ui: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --font-display: 'Figtree', system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;
}

/* ============================================================================ foundations */

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

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    min-height: 100vh;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: 14.5px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5 {
    font-family: var(--font-display);
    font-weight: 700;
    letter-spacing: -.015em;
    color: var(--ink-strong);
    margin: 0 0 .5rem;
    line-height: 1.2;
}

h1 { font-size: 1.6rem; }
h2 { font-size: 1.2rem; }
h3 { font-size: 1.02rem; }

p { margin: 0 0 .75rem; }
p:last-child { margin-bottom: 0; }

a { color: var(--azure-600); text-decoration: none; }
a:hover { color: var(--azure-900); text-decoration: underline; }

hr { border: 0; border-top: 1px solid var(--rule); margin: 1.25rem 0; }

/* Every identifier, reading and timestamp is monospaced with tabular figures, so digits line up
   down a column and a transposed one shows as a break in the alignment. */
.mono, code, pre, .num, td.num, th.num, .lamp-label {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

td.num, th.num { text-align: right; }

code {
    background: var(--surface-sunken);
    border: 1px solid var(--rule);
    border-radius: var(--r-sm);
    padding: .05rem .3rem;
    font-size: .85em;
    color: var(--azure-900);
}

/* The eyebrow. Structural, not decorative: it names the band of the page below it. */
.eyebrow {
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: .1em;
    font-size: .68rem;
    font-weight: 700;
    color: var(--brass-600);
    margin: 0 0 .6rem;
    display: flex;
    align-items: center;
    gap: .55rem;
}

.eyebrow::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--rule);
}

.muted { color: var(--ink-muted); }
.faint { color: var(--ink-faint); }
.small { font-size: .82rem; }
.tiny { font-size: .74rem; }
.strong { font-weight: 600; }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.center { text-align: center; }

:focus-visible {
    outline: 2px solid var(--azure-600);
    outline-offset: 2px;
    border-radius: var(--r-sm);
}

/* ============================================================================== app shell */

.app { display: flex; min-height: 100vh; }

/* A rail rather than a top bar. This is a tool an operator keeps open all day and moves between
   six screens in; a persistent rail costs 244px once and gives every screen its full height. */
.rail {
    width: var(--rail);
    flex: 0 0 var(--rail);

    /* Lit at the brand, settling as it falls - the rail reads as one panel rather than a flat
       block of colour beside a pale page. */
    background: linear-gradient(180deg, var(--navy-900) 0%, var(--navy-950) 100%);
    color: var(--ink-inverse);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    z-index: 40;
}

.rail__brand {
    display: flex;
    align-items: center;
    gap: .65rem;
    padding: 1.05rem 1.1rem;
    border-bottom: 1px solid rgba(255, 255, 255, .07);
    color: #fff;
    text-decoration: none;
}

.rail__brand:hover { text-decoration: none; color: #fff; }

.brand-mark {
    width: 30px;
    height: 30px;
    flex: 0 0 30px;
    border-radius: var(--r);
    background: linear-gradient(155deg, var(--azure-600), var(--azure-900));
    display: grid;
    place-items: center;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12);
}

.brand-mark svg { width: 17px; height: 17px; }

.rail__brand-text {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 1.02rem;
    letter-spacing: -.015em;
    line-height: 1.15;
}

.rail__brand-text small {
    display: block;
    font-family: var(--font-ui);
    font-weight: 400;
    font-size: .68rem;
    letter-spacing: .04em;
    color: #93a3c8;
    text-transform: uppercase;
    margin-top: 1px;
}

.rail__nav { flex: 1; overflow-y: auto; padding: .85rem .6rem 1rem; }

.rail__section {
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: .12em;
    font-size: .64rem;
    font-weight: 700;
    color: #6b7ba6;
    padding: .85rem .55rem .4rem;
}

.rail__link {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .48rem .55rem;
    border-radius: var(--r);
    color: #b8c2df;
    font-size: .89rem;
    font-weight: 500;
    text-decoration: none;
    position: relative;
}

.rail__link:hover {
    background: rgba(255, 255, 255, .05);
    color: #eef1fa;
    text-decoration: none;
}

.rail__link.is-active {
    background: var(--navy-700);
    color: #fff;
}

/* The active marker is a bar on the panel edge, the way a selected channel is marked on real
   equipment — not a rounded pill floating in space. */
.rail__link.is-active::before {
    content: '';
    position: absolute;
    left: -.6rem;
    top: 6px;
    bottom: 6px;
    width: 3px;
    background: var(--brass-500);
    border-radius: 0 2px 2px 0;
}

.rail__link svg {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: .9;
}

.rail__badge {
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: .68rem;
    background: var(--lamp-warn);
    color: #1a1200;
    border-radius: 8px;
    padding: 0 .35rem;
    font-weight: 600;
}

/* Signing out lived only inside the account menu in the corner, which meant knowing it was a
   menu at all. It is the one action every operator needs on a shared console at shift change, so
   it also sits here: same shape as a nav item, one click from every screen, and a POST rather
   than a link so a prefetch or a crawler cannot sign anybody out. */
.rail__signout {
    padding: .5rem .6rem;
    border-top: 1px solid rgba(255, 255, 255, .07);
}

button.rail__link {
    font: inherit;
    font-size: .89rem;
    font-weight: 500;
    width: 100%;
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
}

button.rail__link:hover { background: rgba(255, 255, 255, .05); color: #eef1fa; }

.rail__foot {
    padding: .8rem 1.1rem 1rem;
    border-top: 1px solid rgba(255, 255, 255, .07);
    font-size: .72rem;
    color: #7383ab;
}

.env-chip {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: .64rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    padding: .1rem .4rem;
    border: 1px solid rgba(255, 255, 255, .15);
    border-radius: var(--r-sm);
    color: #a3b0d4;
}

/* ---------------------------------------------------------------------------- main column */

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
    height: 52px;
    background: var(--surface);
    border-bottom: 1px solid var(--rule);
    display: flex;
    align-items: center;
    gap: .9rem;
    padding: 0 1.25rem;
    position: sticky;
    top: 0;
    z-index: 30;
}

.crumbs {
    font-family: var(--font-display);
    font-size: .86rem;
    color: var(--ink-faint);
    letter-spacing: .02em;
}

.crumbs strong { color: var(--ink-strong); font-weight: 600; }
.crumbs span { margin: 0 .3rem; color: var(--rule-strong); }

.topbar__right { margin-left: auto; display: flex; align-items: center; gap: .9rem; }

.clock {
    font-family: var(--font-mono);
    font-size: .78rem;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------- account menu */

.who { position: relative; }

.who__button {
    display: flex;
    align-items: center;
    gap: .5rem;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--r);
    padding: .2rem .4rem .2rem .2rem;
    cursor: pointer;
    font-family: inherit;
    color: var(--ink);
}

.who__button:hover { background: var(--surface-sunken); border-color: var(--rule); }

.avatar {
    width: 27px;
    height: 27px;
    border-radius: var(--r);
    background: var(--azure-600);
    color: #fff;
    display: grid;
    place-items: center;
    font-family: var(--font-display);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .02em;
}

.who__name { font-size: .82rem; font-weight: 500; line-height: 1.1; text-align: left; }
.who__role { font-size: .68rem; color: var(--ink-faint); }

.who__menu {
    position: absolute;
    right: 0;
    top: calc(100% + .45rem);
    min-width: 208px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
    padding: .3rem;
    display: none;
}

.who.is-open .who__menu { display: block; }

.who__menu a, .who__menu button {
    display: flex;
    width: 100%;
    align-items: center;
    gap: .5rem;
    padding: .42rem .55rem;
    border: 0;
    background: none;
    border-radius: var(--r);
    font: inherit;
    font-size: .85rem;
    color: var(--ink);
    text-align: left;
    cursor: pointer;
    text-decoration: none;
}

.who__menu a:hover, .who__menu button:hover {
    background: var(--surface-sunken);
    text-decoration: none;
}

.who__menu hr { margin: .3rem 0; }

.content { flex: 1; padding: 1.4rem 1.5rem 2.5rem; max-width: 1560px; width: 100%; }

.page-head {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.15rem;
}

.page-head__text { min-width: 0; }
.page-head__text p { color: var(--ink-muted); font-size: .88rem; margin: .15rem 0 0; }
.page-head__actions { margin-left: auto; display: flex; gap: .5rem; flex-wrap: wrap; }

.app-foot {
    border-top: 1px solid var(--rule);
    padding: .8rem 1.5rem;
    font-size: .76rem;
    color: var(--ink-faint);
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

/* ================================================================================== cards */

.card {
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
}

/* The one card on the dashboard allowed to raise its voice. It replaced a full-width red banner
   that said the same thing as the table beneath it: the accent carries the urgency, the rows
   carry the work, and the two are no longer separate blocks arguing for the same attention. */
.card--attention { border-left: 3px solid var(--lamp-warn); }
.card--attention--fault { border-left-color: var(--lamp-fault); }

.card__head {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .7rem 1rem;
    border-bottom: 1px solid var(--rule);
}

.card__head h2, .card__head h3 { margin: 0; }
.card__head .card__actions { margin-left: auto; display: flex; gap: .4rem; align-items: center; }

.card__body { padding: 1rem; }
.card__body--flush { padding: 0; }

.card__foot {
    padding: .6rem 1rem;
    border-top: 1px solid var(--rule);
    background: var(--surface-sunken);
    border-radius: 0 0 var(--r-lg) var(--r-lg);
    font-size: .82rem;
}

.grid { display: grid; gap: 1rem; }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid--main { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

/* ================================================================== metrics (the KPI row) */

.metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
    gap: 0;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--r-lg);
    overflow: hidden;
}

.metric {
    padding: .85rem 1rem;
    border-right: 1px solid var(--rule);
    position: relative;

    /* A grid item defaults to min-content width, so a long value pushes past its cell instead of
       wrapping and the end of the number is simply cut off - which is worse than a big number,
       because a truncated figure still reads as a figure. */
    min-width: 0;
}

.metric:last-child { border-right: 0; }

.metric__label {
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: .09em;
    font-size: .66rem;
    font-weight: 600;
    color: var(--ink-faint);
}

.metric__value {
    font-family: var(--font-mono);
    font-size: 1.55rem;
    font-weight: 500;
    line-height: 1.15;
    margin-top: .15rem;
    color: var(--ink-strong);
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}

.metric__note { font-size: .75rem; color: var(--ink-muted); margin-top: .1rem; }

.metric--ok .metric__value { color: var(--lamp-ok); }
.metric--warn .metric__value { color: var(--lamp-warn); }
.metric--fault .metric__value { color: var(--lamp-fault); }

/* ====================================================================== the annunciator ==
   The signature element.

   One lamp per substation, laid out the way an alarm annunciator is laid out in the control
   room this data comes from: a dense rectangular grid, read left to right, colour carrying the
   whole message. It answers "is anything wrong" before the operator has focused on any of it,
   and it stays one screenful at a thousand sites, which no table does. */

.annunciator {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
    gap: 3px;
    padding: 3px;
    background: var(--navy-800);
    border-radius: var(--r);
}

.lamp {
    display: block;
    padding: .38rem .5rem .42rem;
    background: var(--navy-700);
    border-radius: 2px;
    border-left: 3px solid var(--lamp-idle);
    color: #c5cfe8;
    text-decoration: none;
    min-width: 0;
    transition: background .12s ease;
}

.lamp:hover {
    background: #2c3f78;
    color: #fff;
    text-decoration: none;
}

.lamp__code {
    font-family: var(--font-mono);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .01em;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lamp__state {
    font-size: .63rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    opacity: .72;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lamp--ok { border-left-color: var(--lamp-ok); }
.lamp--ok .lamp__state { color: #6fd699; opacity: .9; }

.lamp--warn { border-left-color: var(--lamp-warn); background: #3a3121; }
.lamp--warn .lamp__state { color: #e8bd68; opacity: 1; }

.lamp--fault { border-left-color: var(--lamp-fault); background: #3d2723; }
.lamp--fault .lamp__state { color: #f0938a; opacity: 1; }

.lamp--idle { border-left-color: #46567f; opacity: .55; }

/* ---------------------------------------------------------------------------- status pill */

.pill {
    display: inline-flex;
    align-items: center;
    gap: .32rem;
    font-family: var(--font-display);
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    padding: .12rem .42rem;
    border-radius: var(--r-sm);
    border: 1px solid transparent;
    white-space: nowrap;
}

.pill::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 6px;
}

.pill--ok { background: var(--lamp-ok-soft); color: var(--lamp-ok); border-color: #bfe0cb; }
.pill--warn { background: var(--lamp-warn-soft); color: var(--lamp-warn); border-color: #ead9b4; }
.pill--fault { background: var(--lamp-fault-soft); color: var(--lamp-fault); border-color: #eec6c1; }
.pill--idle { background: var(--lamp-idle-soft); color: var(--lamp-idle); border-color: var(--rule); }
.pill--info { background: var(--azure-100); color: var(--azure-900); border-color: #9ec5fe; }
.pill--brass { background: var(--brass-100); color: var(--brass-600); border-color: #e8d5ac; }

.pill--plain::before { display: none; }

/* The live dot. The one animated thing in the interface, and it earns it: a still dot cannot
   tell an operator whether the page is current or was left open overnight. */
.live {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .72rem;
    color: var(--ink-muted);
}

.live::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--lamp-ok);
    box-shadow: 0 0 0 0 rgba(23, 134, 74, .5);
    animation: pulse 2.4s ease-out infinite;
}

@keyframes pulse {
    0% { box-shadow: 0 0 0 0 rgba(23, 134, 74, .45); }
    70% { box-shadow: 0 0 0 7px rgba(23, 134, 74, 0); }
    100% { box-shadow: 0 0 0 0 rgba(23, 134, 74, 0); }
}

@media (prefers-reduced-motion: reduce) {
    .live::before { animation: none; }
    * { transition: none !important; }
}

/* ================================================================================ buttons */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    font-family: var(--font-ui);
    font-size: .855rem;
    font-weight: 500;
    line-height: 1.2;
    padding: .44rem .85rem;
    border-radius: var(--r);
    border: 1px solid var(--rule-strong);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
}

.btn:hover { background: var(--surface-sunken); color: var(--ink); text-decoration: none; }
.btn:disabled, .btn.is-disabled { opacity: .5; pointer-events: none; }

.btn svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }

.btn--primary {
    background: var(--azure-600);
    border-color: var(--azure-600);
    color: #fff;
}

.btn--primary:hover { background: var(--azure-700); border-color: var(--azure-700); color: #fff; }

.btn--danger { background: var(--surface); border-color: #e2b8b2; color: var(--lamp-fault); }
.btn--danger:hover { background: var(--lamp-fault-soft); color: var(--lamp-fault); }

.btn--ghost { border-color: transparent; background: transparent; }
.btn--ghost:hover { background: var(--surface-sunken); }

.btn--sm { font-size: .78rem; padding: .26rem .55rem; }
.btn--block { width: 100%; }

.btn-row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }

/* ================================================================================= tables */

.table-wrap { overflow-x: auto; }

table.data {
    width: 100%;
    border-collapse: collapse;
    font-size: .855rem;
}

table.data th {
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: .07em;
    font-size: .66rem;
    font-weight: 600;
    color: var(--ink-faint);
    text-align: left;
    padding: .5rem .75rem;
    border-bottom: 1px solid var(--rule);
    background: var(--surface-sunken);
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: 1;
}

table.data td {
    padding: .5rem .75rem;
    border-bottom: 1px solid var(--rule);
    vertical-align: middle;
}

table.data tbody tr:hover { background: #fbfcfe; }
table.data tbody tr:last-child td { border-bottom: 0; }

table.data td.tight { width: 1%; white-space: nowrap; }

/* An empty screen is an invitation to act, not a shrug. */
.empty {
    padding: 2.4rem 1.5rem;
    text-align: center;
    color: var(--ink-muted);
}

.empty svg {
    width: 34px;
    height: 34px;
    stroke: var(--rule-strong);
    fill: none;
    stroke-width: 1.4;
    margin-bottom: .6rem;
}

.empty h3 { color: var(--ink); margin-bottom: .25rem; }
.empty p { max-width: 42ch; margin: 0 auto .9rem; font-size: .86rem; }

/* ================================================================================== forms */

.field { margin-bottom: .9rem; }

.field label, .label {
    display: block;
    font-family: var(--font-display);
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: .25rem;
}

.input, input[type=text], input[type=password], input[type=email], input[type=number],
input[type=search], input[type=url], select, textarea {
    width: 100%;
    font-family: var(--font-ui);
    font-size: .9rem;
    padding: .45rem .6rem;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    background: var(--surface);
    color: var(--ink);
}

input:focus, select:focus, textarea:focus {
    outline: 0;
    border-color: var(--azure-300);
    box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25);
}

input[type=file] {
    width: 100%;
    font-size: .85rem;
    padding: .35rem;
    border: 1px dashed var(--rule-strong);
    border-radius: var(--r);
    background: var(--surface-sunken);
}

.input--mono { font-family: var(--font-mono); }

.hint { font-size: .77rem; color: var(--ink-faint); margin-top: .2rem; }

.check { display: flex; align-items: flex-start; gap: .5rem; margin-bottom: .9rem; }
.check input { width: auto; margin-top: .2rem; flex: 0 0 auto; }
.check label { text-transform: none; letter-spacing: 0; font-size: .88rem;
               font-family: var(--font-ui); font-weight: 500; color: var(--ink); margin: 0; }

.field-error, .text-danger { color: var(--lamp-fault); font-size: .78rem; display: block; margin-top: .18rem; }

.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1rem; }

.toolbar {
    display: flex;
    gap: .5rem;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.toolbar input[type=search], .toolbar input[type=text] { width: auto; min-width: 220px; }
.toolbar select { width: auto; }

.seg { display: inline-flex; border: 1px solid var(--rule-strong); border-radius: var(--r); overflow: hidden; }

.seg a {
    padding: .34rem .7rem;
    font-size: .8rem;
    color: var(--ink-muted);
    border-right: 1px solid var(--rule);
    background: var(--surface);
}

.seg a:last-child { border-right: 0; }
.seg a:hover { background: var(--surface-sunken); text-decoration: none; }
.seg a.is-active { background: var(--azure-600); color: #fff; }

/* ================================================================================ notices */

.notice {
    display: flex;
    gap: .65rem;
    padding: .7rem .9rem;
    border-radius: var(--r);
    border: 1px solid;
    font-size: .875rem;
    margin-bottom: 1rem;
    align-items: flex-start;
}

.notice svg { width: 17px; height: 17px; flex: 0 0 17px; margin-top: 1px;
              stroke: currentColor; fill: none; stroke-width: 1.8; }

.notice strong { display: block; margin-bottom: .1rem; }

.notice--ok { background: var(--lamp-ok-soft); border-color: #bfe0cb; color: #0f5c33; }
.notice--warn { background: var(--lamp-warn-soft); border-color: #ead9b4; color: #7a5209; }
.notice--fault { background: var(--lamp-fault-soft); border-color: #eec6c1; color: #8f281d; }
.notice--info { background: var(--azure-100); border-color: #9ec5fe; color: var(--azure-900); }

/* ============================================================================ the key card
   A credential shown once. It is deliberately the loudest thing on any screen it appears on,
   because the one thing that must not happen is somebody navigating away from it. */

.keycard {
    border: 1px solid var(--brass-500);
    border-left: 4px solid var(--brass-500);
    background: linear-gradient(180deg, #fffdf7, var(--brass-100));
    border-radius: var(--r-lg);
    padding: 1rem 1.1rem;
    margin-bottom: 1.25rem;
}

.keycard h3 { color: var(--brass-600); margin-bottom: .2rem; }

.keyvalue {
    display: flex;
    gap: .5rem;
    align-items: center;
    margin: .7rem 0 .55rem;
}

.keyvalue code {
    flex: 1;
    font-size: .95rem;
    padding: .55rem .7rem;
    background: #fff;
    border: 1px solid var(--brass-500);
    color: var(--ink-strong);
    word-break: break-all;
    letter-spacing: .01em;
}

/* ============================================================================ code blocks */

.code {
    background: var(--navy-950);
    color: #d7dff2;
    border-radius: var(--r);
    padding: .8rem .9rem;
    font-family: var(--font-mono);
    font-size: .79rem;
    line-height: 1.6;
    overflow-x: auto;
    white-space: pre;
    margin: 0;
    border: 1px solid var(--navy-800);
}

.code .k { color: #8ab4ff; }
.code .s { color: #e8c07d; }
.code .c { color: #6f8189; font-style: italic; }
.code .n { color: #c8a0e8; }

.code-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .4rem .6rem .4rem .9rem;
    background: var(--navy-800);
    border: 1px solid var(--navy-800);
    border-bottom: 0;
    border-radius: var(--r) var(--r) 0 0;
    font-family: var(--font-mono);
    font-size: .72rem;
    color: #94a3c9;
}

.code-head + .code { border-radius: 0 0 var(--r) var(--r); }
.code-head .btn { margin-left: auto; }

/* ============================================================================== sparkline */

.spark { display: flex; align-items: flex-end; gap: 2px; height: 34px; }

.spark__bar {
    flex: 1;
    min-height: 2px;
    background: var(--azure-600);
    border-radius: 1px 1px 0 0;
    opacity: .82;
}

.spark__bar--empty { background: var(--rule-strong); opacity: .6; }

/* ============================================================================= file browser */

.crumb-bar {
    display: flex;
    align-items: center;
    gap: .3rem;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: .82rem;
    padding: .5rem .75rem;
    background: var(--surface-sunken);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    margin-bottom: 1rem;
}

.crumb-bar .sep { color: var(--rule-strong); }
.crumb-bar .current { color: var(--ink); font-weight: 600; }

.filerow { display: flex; align-items: center; gap: .55rem; min-width: 0; }

.filerow svg {
    width: 16px; height: 16px; flex: 0 0 16px;
    stroke: var(--ink-faint); fill: none; stroke-width: 1.6;
}

.filerow--dir svg { stroke: var(--brass-500); }
.filerow--xlsx svg { stroke: var(--lamp-ok); }

/* ================================================================================ sign-in */

.auth {
    min-height: 100vh;
    display: grid;
    grid-template-columns: 1fr 460px;
    background: var(--paper);
}

.auth__panel {
    background: linear-gradient(160deg, #142969 0%, #1c3c96 55%, #214cb5 100%);
    color: var(--ink-inverse);
    padding: 3rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    position: relative;
    overflow: hidden;
}

/* A single-line diagram, drawn faintly across the panel. It is the one place in the interface
   where an image is allowed, and it is the schematic this whole application is about. */
.auth__panel::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(255, 255, 255, .028) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, .028) 1px, transparent 1px);
    background-size: 34px 34px;
    pointer-events: none;
}

.auth__panel > * { position: relative; z-index: 1; }

.auth__lede {
    font-family: var(--font-display);
    font-size: 2.15rem;
    font-weight: 800;
    line-height: 1.12;
    letter-spacing: -.025em;
    color: #fff;
    max-width: 15ch;
    margin: 0 0 .85rem;
}

.auth__sub { color: #94a3c9; font-size: .95rem; max-width: 44ch; }

.auth__chain {
    display: flex;
    align-items: center;
    gap: .55rem;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: .74rem;
    color: #8291bb;
    margin-top: 1.6rem;
}

.auth__chain span { padding: .2rem .5rem; border: 1px solid rgba(255, 255, 255, .12); border-radius: var(--r-sm); }
.auth__chain em { font-style: normal; color: #556490; }

.auth__form {
    background: var(--surface);
    padding: 3rem 2.5rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    border-left: 1px solid var(--rule);
}

.auth__form h1 { font-size: 1.35rem; margin-bottom: .2rem; }

/* ================================================================================ details */

.kv { display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: .4rem 1rem; font-size: .87rem; }

.kv dt {
    font-family: var(--font-display);
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ink-faint);
    font-weight: 600;
    padding-top: .12rem;
}

.kv dd { margin: 0; min-width: 0; word-break: break-word; }

.stack { display: flex; flex-direction: column; gap: 1rem; }

/* ============================================================================== responsive */

.rail-toggle { display: none; }
.scrim { display: none; }

@media (max-width: 1180px) {
    .grid--main, .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 980px) {
    .auth { grid-template-columns: 1fr; }
    .auth__panel { padding: 2rem; }
    .auth__lede { font-size: 1.7rem; }
    .auth__form { border-left: 0; border-top: 1px solid var(--rule); padding: 2rem 1.5rem; }
}

@media (max-width: 860px) {
    .rail {
        position: fixed;
        left: 0;
        top: 0;
        transform: translateX(-100%);
        transition: transform .18s ease;
        box-shadow: var(--shadow-lg);
    }

    body.nav-open .rail { transform: translateX(0); }

    body.nav-open .scrim {
        display: block;
        position: fixed;
        inset: 0;
        background: rgba(13, 26, 68, .45);
        z-index: 35;
    }

    .rail-toggle {
        display: inline-grid;
        place-items: center;
        width: 32px;
        height: 32px;
        border: 1px solid var(--rule-strong);
        border-radius: var(--r);
        background: var(--surface);
        cursor: pointer;
    }

    .rail-toggle svg { width: 17px; height: 17px; stroke: var(--ink); fill: none; stroke-width: 2; }

    .content { padding: 1rem 1rem 2rem; }
    .grid--2, .grid--3, .grid--4, .grid--main, .form-row { grid-template-columns: minmax(0, 1fr); }
    .kv { grid-template-columns: minmax(0, 1fr); gap: .1rem; }
    .kv dd { margin-bottom: .5rem; }
    .who__name, .who__role { display: none; }
    .clock { display: none; }
}

@media print {
    .rail, .topbar, .app-foot, .page-head__actions, .btn { display: none !important; }
    .content { padding: 0; }
    .card { border: 1px solid #000; box-shadow: none; }
}

/* ============================================================================================
   PART II — the things that make it usable rather than merely correct
   ============================================================================================ */

/* -------------------------------------------------------------------------------- skip link
   First thing in the tab order, invisible until focused. A keyboard user should not have to
   tab through nine navigation links on every single page to reach the table they came for. */

.skip {
    position: absolute;
    left: .5rem;
    top: -3rem;
    z-index: 100;
    background: var(--azure-600);
    color: #fff;
    padding: .5rem .9rem;
    border-radius: var(--r);
    font-size: .85rem;
    font-weight: 500;
    transition: top .12s ease;
}

.skip:focus { top: .5rem; color: #fff; text-decoration: none; }

/* ===================================================================== night shift (dark)
   A substation control room at 02:00 is lit for reading instruments, not spreadsheets. A page
   this bright is genuinely uncomfortable on a night shift, and an operator who turns the
   monitor brightness down to cope loses the amber/red distinction the whole interface depends
   on. So the surfaces darken and the lamps stay exactly as saturated as they were.

   Only the surface and ink variables move. Every status colour is deliberately untouched. */

[data-theme="dark"] {
    /* ── the chrome ── */
    --navy-950: #0a1017;
    --navy-900: #131b28;   /* HeaderBg */
    --navy-800: #182231;
    --navy-700: #1f2b3d;
    --navy-500: #8ca0b8;

    /* ── surfaces, straight off the station ── */
    --paper: #0e141d;          /* WindowBg */
    --surface: #1a2332;        /* CardBg */
    --surface-sunken: #212c3f; /* CardBg2 */
    --rule: #293648;           /* BorderBrush2 */
    --rule-strong: #37475e;

    /* ── ink ── */
    --ink: #e9f1f9;            /* TextPrimary */
    --ink-strong: #f5fafe;
    --ink-muted: #8ca0b8;      /* TextMuted */
    --ink-faint: #6c7f97;
    --ink-inverse: #0e141d;

    /* ── the accent is the station's cyan, not a browser blue ──
       These keep the azure names because a hundred rules point at them and renaming a token to
       change a colour is how a hover state ends up two shades off for a year. */
    --azure-900: #0097a7;
    --azure-700: #22c3d6;      /* AccentHover */
    --azure-600: #00acc1;      /* Accent */
    --azure-300: #4dd0e1;      /* AccentSoft */
    --azure-100: #10323c;      /* CyanCell, near enough */

    --brass-600: #d8b978;
    --brass-500: #c08a2e;
    --brass-100: #2a2417;

    /* ── lamps: the station's own zone colours, unchanged ── */
    --lamp-ok: #22c55e;        /* GreenZone */
    --lamp-ok-soft: #10301e;
    --lamp-warn: #f59e0b;      /* OrangeZone */
    --lamp-warn-soft: #33260c;
    --lamp-fault: #ef4444;     /* RedZone */
    --lamp-fault-soft: #341b1b;
    --lamp-idle: #8ca0b8;
    --lamp-idle-soft: #202a38;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
    --shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 6px 18px rgba(0, 0, 0, .4);
    --shadow-lg: 0 2px 6px rgba(0, 0, 0, .45), 0 18px 44px rgba(0, 0, 0, .55);
}

[data-theme="dark"] h1,
[data-theme="dark"] h2,
[data-theme="dark"] h3 { color: var(--ink-strong); }

[data-theme="dark"] .crumbs strong { color: var(--ink-strong); }
[data-theme="dark"] .metric__value { color: var(--ink-strong); }

/* Cyan, like every link and every accent on the station. */
[data-theme="dark"] a { color: var(--azure-300); }
[data-theme="dark"] a:hover { color: #7ee0ec; }
[data-theme="dark"] code { color: var(--azure-300); }

/* Cyan is light enough that white text on it is unreadable — the station uses the dark ground
   as the label colour on its accent buttons, and so does this. */
[data-theme="dark"] .btn--primary {
    background: var(--azure-600);
    border-color: var(--azure-600);
    color: #06202a;
    font-weight: 600;
}

[data-theme="dark"] .btn--primary:hover {
    background: var(--azure-700);
    border-color: var(--azure-700);
    color: #06202a;
}

[data-theme="dark"] .pill--info { background: var(--azure-100); color: var(--azure-300); border-color: #17505e; }

[data-theme="dark"] table.data tbody tr:hover { background: #202b3d; }
[data-theme="dark"] .keycard { background: linear-gradient(180deg, #26200f, #1c1810); }
[data-theme="dark"] .keyvalue code { background: #101826; color: #f0e4c8; }

/* InputBg, so a field reads as a well rather than as another card. */
[data-theme="dark"] input, [data-theme="dark"] select, [data-theme="dark"] textarea { background: #101826; }
[data-theme="dark"] input[type=file] { background: #101826; }

[data-theme="dark"] .notice--ok { color: #6ee7a0; }
[data-theme="dark"] .notice--warn { color: #fbc86a; }
[data-theme="dark"] .notice--fault { color: #f78d8d; }
[data-theme="dark"] .notice--info { color: var(--azure-300); }

.theme-toggle {
    display: inline-grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border: 1px solid var(--rule);
    border-radius: var(--r);
    background: var(--surface);
    cursor: pointer;
    color: var(--ink-muted);
}

.theme-toggle:hover { background: var(--surface-sunken); color: var(--ink); }
.theme-toggle svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.7; }
.theme-toggle .moon { display: none; }
[data-theme="dark"] .theme-toggle .sun { display: none; }
[data-theme="dark"] .theme-toggle .moon { display: block; }

/* ================================================================== the jump box (Ctrl-K)
   At a thousand substations the single most common interaction is not "browse", it is "get me
   to SONKAR". Three clicks and a page load for that is the wrong cost. This is one keystroke
   from anywhere, types ahead, and is reachable with the mouse for anyone who never learns it. */

.jump-trigger {
    display: flex;
    align-items: center;
    gap: .5rem;
    min-width: 210px;
    padding: .3rem .5rem;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    background: var(--surface-sunken);
    color: var(--ink-faint);
    font: inherit;
    font-size: .82rem;
    cursor: pointer;
    text-align: left;
}

.jump-trigger:hover { border-color: var(--azure-600); color: var(--ink-muted); }
.jump-trigger svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; }

kbd {
    font-family: var(--font-mono);
    font-size: .68rem;
    background: var(--surface);
    border: 1px solid var(--rule-strong);
    border-bottom-width: 2px;
    border-radius: 3px;
    padding: 0 .28rem;
    color: var(--ink-muted);
    margin-left: auto;
}

.jump {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: none;
    padding: 10vh 1rem 1rem;
    background: rgba(13, 26, 68, .55);
    backdrop-filter: blur(2px);
}

.jump.is-open { display: block; }

.jump__panel {
    max-width: 620px;
    margin: 0 auto;
    background: var(--surface);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.jump__input {
    width: 100%;
    border: 0;
    border-bottom: 1px solid var(--rule);
    border-radius: 0;
    padding: .85rem 1rem;
    font-size: 1rem;
    font-family: var(--font-ui);
    background: transparent;
}

.jump__input:focus { outline: 0; box-shadow: none; border-color: var(--rule); }

.jump__results { max-height: 56vh; overflow-y: auto; padding: .3rem; }

.jump__group {
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: .1em;
    font-size: .62rem;
    font-weight: 700;
    color: var(--ink-faint);
    padding: .55rem .6rem .25rem;
}

.jump__item {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .48rem .6rem;
    border-radius: var(--r);
    color: var(--ink);
    text-decoration: none;
    cursor: pointer;
}

.jump__item:hover, .jump__item.is-active { background: var(--surface-sunken); text-decoration: none; }
.jump__item.is-active { box-shadow: inset 2px 0 0 var(--brass-500); }

.jump__code {
    font-family: var(--font-mono);
    font-weight: 600;
    font-size: .84rem;
    flex: 0 0 auto;
}

.jump__meta { color: var(--ink-faint); font-size: .78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jump__tail { margin-left: auto; flex: 0 0 auto; }

.jump__foot {
    display: flex;
    gap: 1rem;
    padding: .5rem .8rem;
    border-top: 1px solid var(--rule);
    background: var(--surface-sunken);
    font-size: .72rem;
    color: var(--ink-faint);
}

.jump__empty { padding: 1.6rem 1rem; text-align: center; color: var(--ink-faint); font-size: .87rem; }

/* ============================================================ getting started checklist
   Shown only while the system is genuinely not set up. A first-run screen that never goes away
   is clutter; one that disappears the moment the work is done is a tutorial nobody had to read. */

.checklist { display: grid; gap: .5rem; }

.check-step {
    display: flex;
    gap: .8rem;
    align-items: flex-start;
    padding: .8rem .9rem;
    border: 1px solid var(--rule);
    border-radius: var(--r);
    background: var(--surface);
}

.check-step--done { opacity: .6; }
.check-step--next { border-color: var(--azure-600); box-shadow: 0 0 0 3px rgba(13, 110, 253, .1); }

.check-step__num {
    width: 24px;
    height: 24px;
    flex: 0 0 24px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-family: var(--font-mono);
    font-size: .74rem;
    font-weight: 600;
    background: var(--surface-sunken);
    color: var(--ink-faint);
    border: 1px solid var(--rule-strong);
}

.check-step--done .check-step__num { background: var(--lamp-ok); color: #fff; border-color: var(--lamp-ok); }
.check-step--next .check-step__num { background: var(--azure-600); color: #fff; border-color: var(--azure-600); }

.check-step__body { flex: 1; min-width: 0; }
.check-step__body h3 { font-size: .95rem; margin: 0 0 .1rem; }
.check-step__body p { font-size: .83rem; color: var(--ink-muted); margin: 0; }
.check-step__action { margin-left: auto; flex: 0 0 auto; }

/* --------------------------------------------------------------------- annunciator filter */

.lamp-filter {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    margin-bottom: .6rem;
}

.annunciator.is-filtered .lamp--ok,
.annunciator.is-filtered .lamp--idle { display: none; }

/* ============================================================== tables that fit a phone
   Nine columns do not fit on a handset, and horizontal scrolling inside a card is a thing
   people do not discover. Below the breakpoint each row becomes a small stack, with the header
   text carried down by data-label. It is the same markup — no second template to keep in step. */

@media (max-width: 760px) {
    table.data thead { display: none; }

    table.data, table.data tbody, table.data tr, table.data td { display: block; width: 100%; }

    table.data tr {
        border-bottom: 1px solid var(--rule);
        padding: .55rem .75rem;
    }

    table.data tr:last-child { border-bottom: 0; }

    table.data td {
        border: 0;
        padding: .16rem 0;
        display: flex;
        gap: .75rem;
        align-items: baseline;
    }

    table.data td::before {
        content: attr(data-label);
        font-family: var(--font-display);
        text-transform: uppercase;
        letter-spacing: .06em;
        font-size: .62rem;
        font-weight: 600;
        color: var(--ink-faint);
        flex: 0 0 34%;
    }

    /* Rows whose only content is a button do not need a label repeating the obvious. */
    table.data td.tight { justify-content: flex-start; padding-top: .45rem; }
    table.data td.tight::before { content: none; }
    table.data td.num { justify-content: space-between; }
    table.data td[data-label=""]::before { content: none; }

    .table-wrap { overflow-x: visible; }
}

/* ------------------------------------------------------------------------- small niceties */

/* Announced by screen readers when a notice appears after a redirect, rather than silently
   changing the page under someone who cannot see it. */
.notice[role="status"], .notice[role="alert"] { scroll-margin-top: 4rem; }

.card__head h2 { display: flex; align-items: center; gap: .5rem; }

/* A count beside a heading, so "Substations 412" reads as one thing. */
.count {
    font-family: var(--font-mono);
    font-size: .74rem;
    font-weight: 500;
    color: var(--ink-faint);
    background: var(--surface-sunken);
    border: 1px solid var(--rule);
    border-radius: 10px;
    padding: 0 .4rem;
}

/* Applied by the jump box and by anchor navigation, so arriving at a row shows you where. */
@keyframes flash {
    from { background: var(--brass-100); }
    to { background: transparent; }
}

.is-target { animation: flash 1.6s ease-out; }

@media (prefers-reduced-motion: reduce) {
    .is-target { animation: none; box-shadow: inset 3px 0 0 var(--brass-500); }
    .skip { transition: none; }
}

/* ================================================================== disclosure

   For the paragraph that earns its place on a page and does not earn being read twice: why an
   address is http rather than https, what to run when a port is shut. Closed it costs one line;
   the page reads as the three things somebody has to do, not as a document. */

.disc {
    border: 1px solid var(--rule);
    border-radius: var(--r-lg);
    background: var(--surface);
    margin-top: .8rem;
}

.disc > summary {
    display: flex;
    align-items: center;
    gap: .55rem;
    cursor: pointer;
    padding: .6rem .8rem;
    font-size: .82rem;
    font-weight: 600;
    color: var(--ink-strong);
    list-style: none;
    border-radius: var(--r-lg);
}

.disc > summary::-webkit-details-marker { display: none; }

/* A chevron, drawn rather than fetched. */
.disc > summary::after {
    content: "";
    width: 6px;
    height: 6px;
    border-right: 2px solid var(--ink-faint);
    border-bottom: 2px solid var(--ink-faint);
    transform: rotate(45deg);
    margin-left: auto;
    transition: transform .15s ease;
}

.disc[open] > summary::after { transform: rotate(-135deg); }
.disc > summary:hover { background: var(--paper); }

.disc[open] > summary {
    border-bottom: 1px solid var(--rule);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
}

/* First in a body, it is the content rather than an afterthought to it. */
.card__body > .disc:first-child { margin-top: 0; }

.disc__body { padding: .8rem; font-size: .82rem; color: var(--ink-muted); }
.disc__body > :last-child { margin-bottom: 0; }
.disc__body .code, .disc__body .code-head { margin-top: .5rem; }

@media (prefers-reduced-motion: reduce) {
    .disc > summary::after { transition: none; }
}

/* ====================================================================== steps

   A numbered list for a sequence somebody performs somewhere else — at the station, in a
   terminal. Distinct from the numbered cards, which are the page's own structure. */

.steps { margin: 0; padding-left: 1.3rem; font-size: .87rem; }
.steps li { margin-bottom: .45rem; }
.steps li:last-child { margin-bottom: 0; }

/* ============================================================ a value to copy

   One thing, large enough to read across a desk, with its button beside it. Deliberately not
   .keyvalue: that one is brass-bordered because it holds a secret shown once, and an address
   dressed as a secret teaches the wrong reflex. */

.bigcode { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }

.bigcode code {
    flex: 1 1 14rem;
    min-width: 0;
    font-size: 1rem;
    padding: .6rem .75rem;
    background: var(--surface-sunken);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-lg);
    color: var(--ink-strong);
    overflow-wrap: anywhere;
}

/* ======================================================== a row that needs finding

   A pill in a cell says a site has no register. An accent on the row means nobody has to read
   the pills to see that one of them is there. */

table.data tbody tr.is-missing td:first-child {
    box-shadow: inset 3px 0 0 var(--lamp-fault);
}

/* A disclosure wrapping a table: the table brings its own edges, so the body must not add padding
   on top of them. */
.disc__body > .table-wrap { margin: 0; }
.disc__body table.data { border-radius: 0 0 var(--r-lg) var(--r-lg); }

/* ============================================================ a KPI that is a ratio

   "3 / 12" is two different facts and only the first is the reading. The denominator is the scale
   it is read against, so it is sized and coloured as scale rather than as another number. */

.metric__of {
    font-size: .58em;
    font-weight: 500;
    color: var(--ink-faint);
    letter-spacing: 0;
}

/* ================================================================== the live strip

   The dot claims the page is current. These say how current, and let somebody stop it while they
   read — a board that reloads under a finger is worse than one that never reloads. */

.livebar {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    vertical-align: middle;
}

.livebar__age { font-size: .72rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

.livebar[data-state="off"] .live::before {
    animation: none;
    background: var(--ink-faint);
    box-shadow: none;
}

.livebar[data-state="off"] .live { color: var(--ink-faint); }

.livebar button {
    font-size: .7rem;
    padding: .1rem .45rem;
    line-height: 1.5;
}

/* A strip that has stopped updating must not go on pulsing as though it had not. */
.livebar[data-state="stale"] .live::before { animation: none; background: var(--lamp-warn); box-shadow: none; }
.livebar[data-state="stale"] .live { color: var(--lamp-warn); }

/* ================================================== a KPI numerator that is good news

   "1 / 2 connected" is two facts of opposite sign in one tile. The tile carries the worse of them
   — a site is down — but painting the count of sites that ARE up in the same amber says the wrong
   thing about the half that is working. Green means online here exactly as it does on a lamp. */

.metric--warn .metric__value .metric__up,
.metric--fault .metric__value .metric__up { color: var(--lamp-ok); }

/* ====================================================== a row that belongs to the one above

   The rewrites for a reading date, folded under the file that superseded them. Indented and set
   back so the eye reads them as a footnote to the row above rather than as another delivery. */

table.data tbody tr.row--sub > td {
    padding: 0 .6rem .6rem 1.6rem;
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--rule);
}

table.data tbody tr.row--sub:hover > td { background: var(--surface-sunken); }
table.data tbody tr.row--sub .disc { margin-top: 0; }

/* ── a register row pointing at a feeder the station does not have ──────────────────────────
   The one mistake the Feeder dropdown exists to prevent, and the one that used to be invisible:
   a tag that matches nothing produces a register that saves and syncs perfectly and leaves two
   columns blank in the workbook. Marked on the control itself so it is found by looking rather
   than by counting down the rows. */
select.is-orphan {
    border-color: var(--lamp-fault);
    background: var(--lamp-fault-soft);
    color: var(--lamp-fault);
}
