/* NMS static bundle — extracted from layout_plain + layout_app (v-busted by ?v=VERSION).
   Edit CSS HERE, not inline in the templates. brand-accent stays inline (per-tenant). */

/* ===== layout_plain ===== */

        :root {
            color-scheme: light;
            /* brand rust from the wordmark — reads on both the light page and the dark sidebar */
            --bg: #eef1f5;
            --surface: #ffffff;
            --surface-alt: #f7f8fa;
            --border: #dfe3ea;
            --border-strong: #c6ccd6;
            --text: #191b20;
            --text-muted: #5d6470;
            --text-faint: #959cab;
            --primary: #2563eb;
            --primary-hover: #1d4ed8;
            --primary-soft: #eef4ff;
            --success: #16a34a;
            --success-strong: #15803d;
            --success-soft: #e8f7ee;
            --warning: #d97706;
            --warning-soft: #fdf3e3;
            --danger: #dc2626;
            --danger-hover: #b91c1c;
            --danger-soft: #fdecec;
            --sidebar-bg: #14161c;
            --sidebar-text: #b7bcc7;
            --sidebar-text-active: #ffffff;
            --sidebar-hover: #1f222b;
            /* series palette — CVD-validated (ΔE 18.5); green/red stay RESERVED for status */
            --chart-in: #2563eb;
            --chart-out: #7e57c2;
            --chart-err-in: #ec4899;
            --chart-err-out: #f59e0b;
            --chart-disc-in: #38bdf8;
            --chart-disc-out: #a78bfa;
            --chart-grid: rgba(16, 24, 40, 0.06);
            /* Per-port series palette — 6 independent identities, validated in BOTH modes
               (scripts/validate_palette.js: lightness band, chroma floor, adjacent CVD dE 36.9,
               contrast >= 3:1). Deliberately NOT the --chart-* vars above: those are three in/out
               PAIRS, so their dark halves fail the categorical checks — one reads gray, one falls
               outside the lightness band. Capped at 6: a 7th port folds into "others" rather than
               inventing a hue nobody validated. */
            --series-1: #2563eb;
            --series-2: #b45309;
            --series-3: #0d9488;
            --series-4: #7e57c2;
            --series-5: #db2777;
            --series-6: #4d7c0f;
            --card-tint-l: 96.5%;
            --card-tint-s: 60%;
            --radius: 8px;
            --radius-sm: 5px;
            --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
            --shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08);
            --space-1: 4px;
            --space-2: 8px;
            --space-3: 12px;
            --space-4: 16px;
            --space-5: 24px;
            --space-6: 32px;
            /* ---- bridge tokens: names the templates already use. Aliases only, no new colours.
               --surface-2 had 19 uses and 14 of them rendered TRANSPARENT (no fallback). ---- */
            --font-mono: ui-monospace, "Cascadia Code", "SF Mono", Menlo, Consolas, monospace;
            --mono: var(--font-mono);          /* settings.html ×10; the NOC skins re-declare it in scope */
            --surface-2: var(--surface-alt);   /* node 8, ipam 5, combined 3, settings/interface/credentials_list 1 */
            --accent: var(--primary);          /* ipam 3, ddos 2, shifts, shift_detail, settings, node_options */
            --panel: var(--surface);           /* ddos 2, _interface_actions 2, netflow 1, app.css .nf-whois-pop */
            --ok: var(--success);              /* node.html:234, ddos.html:37 */
            --chart-tick: var(--text-faint);   /* Chart.js tick labels; grid stays --chart-grid */
            --topbar-h: 46px;                  /* logs.html sticky head hard-codes 46px */
            /* DEBT-1: the "terminal" look (log wells). Was the GitHub-Dark palette (values noted, sans #) pasted in 7 files. */
            --term-bg: var(--sidebar-bg);      /* gh-dark 0d1117 → the one surface darker than --bg */
            --term-fg: var(--text);            /* gh-dark c9d1d9 message text */
            --term-muted: var(--text-muted);   /* gh-dark 8b949e info / notice / sync */
            --term-dim: var(--text-faint);     /* gh-dark 484f58 timestamps, debug */
            --term-ok: var(--success);         /* gh-dark 3fb950 connect / poller name */
            --term-warn: var(--warning);       /* gh-dark e3b341 warning AND hostname (operators read it) */
            --term-err: var(--danger);         /* gh-dark f85149 severity <= 3, disconnect */
            --term-info: var(--primary);       /* gh-dark 79c0ff polling */
            --term-alt: var(--chart-out);      /* gh-dark d2a8ff discovery */
        }
        /* The app is designed and tested in dark; the light default above was never finished (the
           header/breadcrumb layout breaks under it), and users whose browser preferred light got
           that broken page. So dark is FORCED for everyone — `@media all` always matches, so these
           values always win over the light :root above, no matter the OS/browser preference.
           (The light tokens stay as the base only so nothing is undefined.) */
        @media all {
            :root {
                color-scheme: dark;
                --bg: #0f1319;
                --surface: #171c24;
                --surface-alt: #1c222c;
                --border: #29303c;
                --border-strong: #3a4350;
                --text: #e4e8ee;
                --text-muted: #9aa3b2;
                --text-faint: #6b7482;
                --primary: #5b8def;
                --primary-hover: #7da4f2;
                --primary-soft: #1b2740;
                --success: #34c065;
                --success-strong: #4ade80;
                --success-soft: #12301d;
                --warning: #f0a13c;
                --warning-soft: #33260f;
                --danger: #f26d6d;
                --danger-hover: #f58f8f;
                --danger-soft: #3a1717;
                --sidebar-bg: #0b0e13;
                --sidebar-hover: #171c24;
                --chart-grid: rgba(228, 232, 238, 0.07);
                --card-tint-l: 17%;
                --card-tint-s: 35%;
                --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
                --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
            }
        }
        * { box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            margin: 0;
            background: var(--bg);
            color: var(--text);
            font-size: 13.5px;
            line-height: 1.45;
            -webkit-font-smoothing: antialiased;
        }
        a { color: var(--primary); text-decoration: none; }
        a:hover { color: var(--primary-hover); text-decoration: underline; }

        h1, h2, h3 { margin: 0 0 var(--space-4); font-weight: 650; letter-spacing: -0.01em; }
        h2 { font-size: 20px; }
        h3 { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; margin-bottom: var(--space-2); }

        .card {
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius);
            padding: var(--space-4);
            box-shadow: var(--shadow-sm);
        }
        a.card { display: block; color: var(--text); transition: box-shadow .12s ease, border-color .12s ease; }
        a.card:hover { box-shadow: var(--shadow-md); border-color: var(--border-strong); text-decoration: none; }

        /* ---- Panels (LibreNMS-style sections) ---- */
        .panel {
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius);
            box-shadow: var(--shadow-sm);
            margin-bottom: var(--space-4);
            overflow: hidden;
        }
        .panel-head {
            display: flex;
            align-items: center;
            gap: 8px;
            /* a head wider than its column (ports head ≈900px in 612px) wraps to a 2nd line instead of
               clipping its filter pills; the head grows in height when it wraps — intended */
            flex-wrap: wrap;
            row-gap: 4px;
            padding: 7px 14px;
            background: var(--surface-alt);
            border-bottom: 1px solid var(--border);
            font-size: 11px;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.07em;
            color: var(--text-muted);
        }
        /* global, not head-only: .bk-bar/.bk-head/backups use it too. Skins that set .spacer{flex:1} coexist
           (flex-grow eats the free space first, the auto margin resolves to 0) */
        .spacer { margin-left: auto; }
        .panel-body { padding: var(--space-4); }

        /* ---- PRTG-style status banner ---- */
        .statusbar {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 8px 14px;
            border-radius: var(--radius-sm);
            color: #fff;
            font-weight: 700;
            font-size: 13px;
            margin-bottom: var(--space-3);
            letter-spacing: 0.02em;
        }
        .statusbar.ok { background: linear-gradient(180deg, #26b45f, #16a34a); }
        .statusbar.nodata { background: linear-gradient(180deg, #a2a9b6, #868e9c); }
        .statusbar .statusbar-value {
            margin-left: auto;
            font-size: 15px;
            font-variant-numeric: tabular-nums;
            font-weight: 700;
        }

        /* ---- PRTG-style stats bar ---- */
        .statsbar {
            display: flex;
            flex-wrap: wrap;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: var(--radius-sm);
            box-shadow: var(--shadow-sm);
            margin-bottom: var(--space-4);
            overflow: hidden;
        }
        .stat-cell {
            padding: 7px 16px 8px;
            border-right: 1px solid var(--border);
            min-width: 105px;
        }
        .stat-cell:last-child { border-right: none; }
        .stat-label {
            font-size: 10px;
            text-transform: uppercase;
            letter-spacing: 0.07em;
            color: var(--text-faint);
            font-weight: 700;
        }
        .stat-value {
            font-size: 14px;
            font-weight: 650;
            margin-top: 1px;
            font-variant-numeric: tabular-nums;
            white-space: nowrap;
        }

        /* ---- Range tab strip (PRTG) ---- */
        .range-tabs {
            display: inline-flex;
            border: 1px solid var(--border-strong);
            border-radius: var(--radius-sm);
            overflow: hidden;
            background: var(--surface);
        }
        .range-tab {
            padding: 5px 13px;
            font-size: 12px;
            font-weight: 650;
            color: var(--text-muted);
            background: transparent;
            border: none;
            border-right: 1px solid var(--border);
            border-radius: 0;
            cursor: pointer;
            white-space: nowrap;   /* a tab label never breaks mid-word; an overlong strip gets .wrap */
        }
        .range-tab:last-child { border-right: none; }
        .range-tab:hover { background: var(--bg); color: var(--text); }
        .range-tab.active { background: var(--primary); color: #fff; }
        .range-tab.active:hover { background: var(--primary-hover); }

        /* ---- Channel chips (PRTG channel toggles) ---- */
        .chips { display: flex; flex-wrap: wrap; gap: 6px; }
        .chip {
            display: inline-flex;
            align-items: center;
            gap: 7px;
            padding: 3px 10px 4px;
            border: 1px solid var(--border);
            border-radius: var(--radius-sm);
            background: var(--surface);
            font-size: 12px;
            font-weight: 600;
            color: var(--text);
            cursor: pointer;
            user-select: none;
        }
        .chip:hover { border-color: var(--border-strong); background: var(--surface-alt); }
        .chip input { display: none; }
        .chip .swatch {
            width: 10px; height: 10px;
            border-radius: 2px;
            flex-shrink: 0;
            background: var(--chip-color, var(--text-faint));
        }
        .chip.off { color: var(--text-faint); background: var(--bg); }
        .chip.off .swatch { background: var(--border-strong); }

        label { display: block; font-size: 12.5px; font-weight: 600; color: var(--text-muted); margin-bottom: var(--space-1); }
        /* input:not([type]) — 79 typeless <input> in 8 templates rendered as bare UA boxes; same
           specificity as the attribute forms, so every later wrapper rule (.form-row, table.edit) still wins */
        input[type=email], input[type=password], input[type=text], input[type=number], input[type=search], input:not([type]), select, textarea {
            display: block;
            width: 100%;
            box-sizing: border-box;
            padding: 8px 11px;
            margin: 0 0 var(--space-4);
            border: 1px solid var(--border-strong);
            border-radius: var(--radius-sm);
            font-size: 13.5px;
            font-family: inherit;
            background: var(--surface);
            color: var(--text);
        }
        /* ---- .form-row : a cleanly-aligned horizontal form. label-over-input columns bottom-aligned,
           one shared control height on inputs/selects/buttons (the base input rule's bottom margin
           otherwise lifts inputs above buttons in a flex-end row; typeless inputs also miss the
           attribute-selector styling). Use on any inline add/edit form to keep fields on one baseline. */
        .form-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3) var(--space-4); margin: 0; }
        .form-row > div { display: flex; flex-direction: column; }
        .form-row label { display: block; margin: 0 0 var(--space-1); font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
        .form-row input:not([type=checkbox]):not([type=radio]):not([type=hidden]),
        .form-row select {
            box-sizing: border-box; height: 36px; width: auto; margin: 0;
            padding: 0 11px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
            font-size: 13.5px; font-family: inherit; background: var(--surface); color: var(--text);
        }
        .form-row button, .form-row .btn { box-sizing: border-box; height: 36px; margin: 0; padding: 0 14px; }
        /* a textarea keeps its own (taller) height, but must lose the base rule's bottom margin and
           width:100% or it lifts its whole column off the row's baseline */
        .form-row textarea {
            box-sizing: border-box; width: auto; margin: 0;
            padding: 8px 11px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
            font-size: 13.5px; font-family: inherit; background: var(--surface); color: var(--text);
        }
        /* checkbox column: a control-height row so its box + text bottom-align with neighbouring inputs */
        .form-row label.form-check { display: flex; flex-direction: row; align-items: center; gap: 6px; height: 36px; margin: 0; }
        .form-row label.form-check input[type=checkbox] { width: auto; height: auto; margin: 0; }

        input:focus, select:focus, textarea:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 3px var(--primary-soft);
        }

        button, .btn {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            background: var(--primary);
            color: white;
            /* transparent, not none: primary/danger then measure the same as .secondary (which has a real
               border) so mixed action cells share one height without per-cell hacks. +2px everywhere. */
            border: 1px solid transparent;
            padding: 8px 14px;
            border-radius: var(--radius-sm);
            cursor: pointer;
            font-size: 13px;
            font-weight: 600;
            font-family: inherit;
            transition: background .12s ease, border-color .12s ease, box-shadow .12s ease, color .12s ease;
        }
        /* Every button carries an icon from _icons.html, tinted by the button's own accent in --c.
           Primary and danger leave --c unset (the icon follows the label colour: white, red);
           secondary sets it inline per action, so the same action wears the same colour in
           every view (Backups is always #4ade80, Syslog always #fbbf24, ...). Hover is decided
           here, once, for every variant: it takes --c (or the variant token) into the border and
           a translucent fill — never a size change. A disabled button gets none of it. */
        button > svg, .btn > svg { color: var(--c, currentColor); flex-shrink: 0; }
        /* :where() keeps these at the specificity the old button:hover had, so a control with its
           own :hover (.range-tab, chips, tiles) still wins; disabled is simply never matched. */
        button:where(:not(:disabled)):hover { background: var(--primary-hover); }
        .btn:where(:not([aria-disabled="true"])):hover { background: var(--primary-hover); color: white; text-decoration: none; }
        button:focus-visible, .btn:focus-visible, a:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
        button.secondary, .btn.secondary { background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); }
        button.secondary:where(:not(:disabled)):hover, .btn.secondary:where(:not([aria-disabled="true"])):hover {
            color: var(--text);
            border-color: var(--c, var(--primary));
            box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c, var(--primary)) 55%, transparent);
            background: color-mix(in srgb, var(--c, var(--primary)) 12%, var(--surface));
        }
        button.danger, .btn.danger { background: var(--danger-soft); color: var(--danger); }
        button.danger:where(:not(:disabled)):hover, .btn.danger:where(:not([aria-disabled="true"])):hover { background: var(--danger); color: white; }
        button:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; box-shadow: none; }
        /* the bare "x" that closes a modal header: no chrome, but still a button that answers hover */
        .x-btn { background: none; border: none; padding: 0; color: var(--text-muted); line-height: 1; }
        .x-btn:where(:not(:disabled)):hover { background: none; color: var(--text); }

        .error {
            color: var(--danger);
            background: var(--danger-soft);
            border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);   /* was a light-theme pink */
            border-radius: var(--radius-sm);
            padding: var(--space-3) var(--space-4);
            margin-bottom: var(--space-4);
            font-size: 13px;
        }
        .muted { color: var(--text-muted); }
        .faint { color: var(--text-faint); }
        .num { font-variant-numeric: tabular-nums; }
        .zero { color: var(--text-faint); }

        table { width: 100%; border-collapse: collapse; font-size: 13px; }
        table th {
            text-align: left;
            color: var(--text-muted);
            font-weight: 700;
            font-size: 11px;
            text-transform: uppercase;
            letter-spacing: 0.05em;
            padding: 7px var(--space-3);
            border-bottom: 1px solid var(--border);
            background: var(--surface-alt);
            white-space: nowrap;
        }
        table td { padding: 6px var(--space-3); border-bottom: 1px solid var(--border); vertical-align: middle; }
        table tr:last-child td { border-bottom: none; }
        table.zebra tbody tr:nth-child(even) td { background: var(--surface-alt); }
        table.compact td { padding: 4px var(--space-3); font-size: 12.5px; }
        table.hover tbody tr:hover td { background: var(--primary-soft); }
        th.sortable { cursor: pointer; user-select: none; }
        th.sortable:hover { color: var(--text); background: var(--bg); }
        th.sortable .sort-arrow { opacity: 0.4; font-size: 9px; margin-left: 3px; }

        .badge {
            display: inline-flex;
            align-items: center;
            gap: 5px;
            font-size: 11.5px;
            font-weight: 650;
            padding: 1px 8px;
            border-radius: 999px;
            letter-spacing: 0;
            text-transform: none;
            /* A pill is one token: "3264 up" never splits into two lines. Inside a flex row
               it must not shrink below its content either — the row wraps, the pill does not.
               Tabular digits keep neighbouring cards' counts aligned. */
            white-space: nowrap;
            flex-shrink: 0;
            font-variant-numeric: tabular-nums;
        }
        .badge.ok { background: var(--success-soft); color: var(--success-strong); }
        .badge.warn { background: var(--warning-soft); color: var(--warning); }
        .badge.danger { background: var(--danger-soft); color: var(--danger); }
        .badge.neutral { background: var(--bg); color: var(--text-muted); }

        .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
        .dot.ok { background: var(--success); box-shadow: 0 0 0 2px var(--success-soft); }
        .dot.warn { background: var(--warning); box-shadow: 0 0 0 2px var(--warning-soft); }
        .dot.danger { background: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
        .dot.neutral { background: var(--text-faint); }
        /* administratively shut: hollow grey ring — reads as "off by config", not a failure */
        .dot.shut { background: transparent; box-shadow: inset 0 0 0 2px var(--text-faint); opacity: .7; }

        /* ---- Key/value rows + inline meters (device info card) ---- */
        .kv-row {
            display: flex;
            align-items: baseline;
            gap: 10px;
            padding: 5px 14px;
            border-bottom: 1px solid var(--border);
            font-size: 12.5px;
        }
        .kv-row:last-child { border-bottom: none; }
        .kv-key { width: var(--kv-key-w, 88px); flex-shrink: 0; color: var(--text-muted); font-weight: 600; font-size: 11.5px; }
        .kv-val { min-width: 0; overflow-wrap: anywhere; }

        .meter {
            position: relative;
            height: 6px;
            flex: 1;
            min-width: 50px;
            max-width: 110px;
            background: var(--bg);
            border: 1px solid var(--border);
            border-radius: 3px;
            overflow: hidden;
            align-self: center;
        }
        .meter > span {
            position: absolute;
            left: 0; top: 0; bottom: 0;
            border-radius: 2px;
            background: var(--success);
        }
        .meter.warn > span { background: var(--warning); }
        .meter.danger > span { background: var(--danger); }
    

/* ===== layout_app ===== */

    .app-shell { display: flex; min-height: 100vh; }
    .sidebar {
        width: 360px; flex-shrink: 0;
        background: var(--sidebar-bg); color: var(--sidebar-text);
        padding: var(--space-4) 0 var(--space-3);
        display: flex; flex-direction: column;
        overflow: hidden;
        /* pin the sidebar to the viewport so it never scrolls away with the page — the tree inside
           scrolls on its own. align-self so it doesn't stretch to the (taller) page height. */
        position: sticky; top: 0; align-self: flex-start;
        height: 100vh; box-sizing: border-box;
    }
    .brand {
        padding: 0 var(--space-4) var(--space-4);
        margin-bottom: var(--space-2);
        display: flex; align-items: center; gap: 10px;
        border-bottom: 1px solid #23262f;
    }
    /* the mark carries the brand; the wordmark stays HTML text so it's crisp and legible on the
       dark sidebar (the artwork's own subtitle is dark-on-white and would disappear here) */
    .brand-mark { width: 34px; height: 34px; flex-shrink: 0; border-radius: 50%; }
    .brand-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
    .brand-name {
        font-weight: 700; font-size: 15px; letter-spacing: 0.12em;
        color: var(--brand-accent); text-transform: uppercase;
    }
    .brand-version { font-size: 10.5px; color: #6b7280; letter-spacing: 0.02em; }
    .tree-label {
        padding: var(--space-3) var(--space-3) var(--space-1) 14px;   /* 14px: meets the tree caret's edge */
        font-size: 10px; font-weight: 700; letter-spacing: 0.1em;
        text-transform: uppercase; color: #565d6b;
        display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
    }
    /* the counts are reference, not a heading — keep them quiet next to the label */
    .tree-totals { font-weight: 600; letter-spacing: 0.02em; color: #6b7280; text-transform: none; font-size: 10.5px; }
    .tree-totals-sep { color: #454b57; }
    .tree { overflow-y: auto; overflow-x: hidden; flex: 1; min-height: 0; padding-bottom: var(--space-3); }
    /* search results: sits between the box and the tree, and only when there's something to say */
    #search-results { margin-top: 6px; border-radius: 6px; overflow: hidden; background: #171a21; border: 1px solid #262a33; }
    .sr-item { display: block; padding: 5px 9px; color: var(--sidebar-text); font-size: 12px; line-height: 1.3; border-left: 2px solid transparent; }
    .sr-item:hover, .sr-item.sel { background: var(--sidebar-hover); color: #fff; text-decoration: none; border-left-color: var(--primary); }
    .sr-label { font-weight: 600; }
    .sr-sub { font-size: 10.5px; color: #6b7280; }
    .sr-kind { float: right; font-size: 9px; text-transform: uppercase; letter-spacing: .06em; color: #565d6b; margin-top: 2px; }
    .sr-empty { padding: 6px 9px; font-size: 11.5px; color: #6b7280; }
    .tree-item {
        display: flex; align-items: flex-start; gap: 7px;
        padding: 5px var(--space-3) 5px 12px;
        color: var(--sidebar-text); font-size: 13px; line-height: 1.35;
        border-left: 2px solid transparent;
        white-space: normal; word-break: break-word;
    }
    .tree-item:hover { background: var(--sidebar-hover); color: white; text-decoration: none; }
    .tree-item.active { background: var(--sidebar-hover); color: var(--sidebar-text-active); border-left-color: var(--primary); font-weight: 600; }
    /* Row indent by depth: 12px + 14px per level, as a short class per level (_tree.html) instead of
       an inline style on every one of a thousand rows. A row deeper than 15 carries the inline
       style instead, so nothing is capped — the classes are only the cheap common case. */
    .tree-item.d0 { padding-left: 12px; }  .tree-item.d1 { padding-left: 26px; }  .tree-item.d2 { padding-left: 40px; }
    .tree-item.d3 { padding-left: 54px; }  .tree-item.d4 { padding-left: 68px; }  .tree-item.d5 { padding-left: 82px; }
    .tree-item.d6 { padding-left: 96px; }  .tree-item.d7 { padding-left: 110px; } .tree-item.d8 { padding-left: 124px; }
    .tree-item.d9 { padding-left: 138px; } .tree-item.d10 { padding-left: 152px; } .tree-item.d11 { padding-left: 166px; }
    .tree-item.d12 { padding-left: 180px; } .tree-item.d13 { padding-left: 194px; } .tree-item.d14 { padding-left: 208px; }
    .tree-item.d15 { padding-left: 222px; }
    .tree-icon { flex-shrink: 0; font-size: 11px; opacity: 0.85; line-height: 1.6; display: inline-flex; }
    /* collapse caret: clickable triangle before a folder's icon; a spacer keeps leaf rows aligned */
    .tree-caret { flex-shrink: 0; width: 24px; text-align: center; font-size: 18px; line-height: 1; opacity: 0.7;
        display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
        transition: transform 0.12s ease; border-radius: 4px; }
    .tree-caret:hover { opacity: 1; background: var(--sidebar-hover); }
    .tree-caret.collapsed { transform: rotate(-90deg); }
    /* A row with no caret (a device, an empty folder) used to carry an empty 24px spacer span to
       keep its dot or icon aligned under the carets — 37 bytes on ~850 rows. The indent is CSS
       now: the caret's 24px plus the row's 7px gap, on whichever child comes first. The dot keeps
       its own 3px side margin on top, so it lands on exactly the pixel the spacer put it on. */
    .tree-item > .tree-dot:first-child { margin-left: 34px; }
    .tree-item > .tree-icon:first-child { margin-left: 31px; }
    .tree-children.collapsed { display: none; }
    .tree-dot { flex-shrink: 0; width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); margin: 0 3px; }
    .tree-dot.up { background: var(--success); }
    .tree-dot.down { background: var(--danger); }
    /* Paused = deliberately not polled, so it must read as "stood down" — not the amber a failing
       backup uses, which it used to share. A hollow slate ring (distinct from every FILLED dot)
       plus a dimmed name: the whole row looks switched off at a glance. */
    .tree-dot.paused { background: transparent; box-shadow: inset 0 0 0 1.6px var(--text-muted, #8a93a3); }
    .tree-dot.paused + span { opacity: 0.5; }
    /* Device is reachable but its config backup is failing. Must come AFTER .tree-dot.up to win the
       cascade (same specificity), and is only ever combined with .up — a device that is DOWN stays
       red, because unreachable is the bigger problem and the backup failing is its consequence. */
    .tree-dot.up.bkfail { background: var(--warning, #d9a441); }
    /* pink input-errors counter, right after the node name (not right-aligned like the up/down
       rollup): the operator asked for it next to the name, and it must not be confused with the
       green/red device counts. Device row = ports with errors; folder row = devices with errors. */
    /* Security posture shield in the tree — its own badge, never the up/down dot: a device can be
       perfectly reachable and still be wide open, and merging the two would hide exactly that.
       Only rendered where config auditing is switched on, so it is invisible for clients who
       have not enabled it. Opaque backgrounds, same rule as the error badge. */
    /* A span, not an anchor — see the comment in layout_app.html. It still behaves like a link, so
       it carries the pointer cursor an <a> gave it for free. */
    .tree-sec { flex-shrink: 0; margin-left: 6px; font-size: 10px; line-height: 1.6;
        border-radius: 999px; padding: 0 5px; text-decoration: none; white-space: nowrap;
        cursor: pointer; }
    .tree-sec.sev-critical { color: #ff8f86; background: color-mix(in srgb, #e5534b 26%, #12141a); }
    .tree-sec.sev-high     { color: #f6b071; background: color-mix(in srgb, #e8843c 24%, #12141a); }
    .tree-sec.sev-medium   { color: #e6c877; background: color-mix(in srgb, #d9a441 22%, #12141a); }
    .tree-sec.sev-low      { color: #9cc7ea; background: color-mix(in srgb, #5a9bd8 20%, #12141a); }
    .tree-sec.sev-info     { color: #9aa1ac; background: #1d202a; }
    .tree-errin { flex-shrink: 0; margin-left: 6px; font-size: 10.5px; font-weight: 700;
        color: #f9a8d0; background: color-mix(in srgb, #ec4899 20%, #12141a);
        border-radius: 999px; padding: 0 6px; line-height: 1.6; white-space: nowrap; }
    .tree-updown { margin-left: auto; display: inline-flex; gap: 4px; font-size: 12px; font-variant-numeric: tabular-nums; }
    /* 1.6 = the same line box as .tree-count / .tree-errin, so every sidebar pill sits on one line */
    .tree-updown i { font-style: normal; font-weight: 700; border-radius: 7px; padding: 0 5px; line-height: 1.6; }
    .tree-updown i.u { color: var(--success); background: rgba(52, 192, 101, 0.14); }
    .tree-updown i.d { color: #ff8484; background: rgba(242, 109, 109, 0.16); }
    /* devices up but with a failing config backup — same amber as the dot they roll up */
    .tree-updown i.b { color: #e0a83a; background: rgba(217, 164, 65, 0.16); }
    /* paused devices under a folder: a neutral slate pill, never confused with up/down/backup. */
    .tree-updown i.p { color: #9aa6b6; background: rgba(138, 147, 163, 0.16); }
    .tree-count {
        margin-left: auto; flex-shrink: 0;
        font-size: 10.5px; font-weight: 600; color: #6b7280;
        background: #1d202a; border-radius: 999px; padding: 0 7px;
        line-height: 1.6; align-self: flex-start;   /* pins to the first text line, like the up/down pills */
    }
    .tree-item.active .tree-count, .tree-item:hover .tree-count { color: #9aa1ac; }

    .topbar {
        background: var(--surface); border-bottom: 1px solid var(--border);
        padding: 0 var(--space-5);
        min-height: var(--topbar-h);
        /* wrap: at a high browser zoom the breadcrumb + nav don't fit one row. Wrapping drops the
           nav to its own line instead of the two colliding — the breadcrumb itself never wraps
           (see below), so the whole header degrades gracefully instead of stacking into a mess. */
        display: flex; justify-content: space-between; align-items: center;
        gap: var(--space-4); flex-wrap: wrap; row-gap: 0;
        /* the page scrolls on the body, so this sticks to the viewport: on a device with 95 ports
           the breadcrumb and the nav used to scroll away and you had to go back up to leave.
           Opaque background is load-bearing — content passes underneath. Below the tree's
           context menu (200) so that still opens over it. */
        position: sticky; top: 0; z-index: 50;
    }
    .breadcrumbs {
        /* NEVER wrap the crumbs — a wrapped breadcrumb stacks each level onto its own line and
           overruns the nav (the bug at high zoom). One line, scroll it if it's too long. */
        display: flex; align-items: center; flex-wrap: nowrap; white-space: nowrap; gap: 6px;
        font-size: 13px; min-width: 0; padding: 10px 0; overflow-x: auto; scrollbar-width: none;
    }
    .breadcrumbs::-webkit-scrollbar { display: none; }
    .breadcrumbs .crumb { color: var(--text-muted); }
    .breadcrumbs .crumb:hover { color: var(--primary); text-decoration: none; }
    .breadcrumbs .crumb.current { color: var(--text); font-weight: 650; }
    .crumb-sep { color: var(--text-faint); font-size: 11px; user-select: none; }
    /* own padding: when the nav drops under the breadcrumb its row keeps the 10/10 rhythm (8/8) instead of 2/2 */
    .topbar-nav { display: flex; align-items: center; gap: var(--space-3); font-size: 12.5px; flex-wrap: wrap; padding: var(--space-2) 0; }
    .topbar-nav a { color: var(--text-muted); }
    .topbar-nav a:hover { color: var(--text); text-decoration: none; }
    .topbar-divider { width: 1px; height: 16px; background: var(--border); }
    .topbar-user { color: var(--text-faint); font-size: 12.5px; }
    /* the user's name opens a small dropdown (My account, Logout). <details> so it needs no script
       under the nonce CSP; the panel borrows the .ctx-menu surface so every menu reads the same. */
    .topbar-menu { position: relative; display: inline-block; }
    .topbar-menu > summary { color: var(--text-faint); font-size: 12.5px; cursor: pointer; list-style: none; }
    .topbar-menu > summary::-webkit-details-marker { display: none; }
    .topbar-menu > summary::marker { content: ""; }
    .topbar-menu > summary:hover, .topbar-menu[open] > summary { color: var(--text); }
    .topbar-menu-panel { position: absolute; left: 0; top: calc(100% + 4px); z-index: 200; min-width: 150px; padding: 4px 0; display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm); box-shadow: var(--shadow-md); }
    .topbar-menu-panel a { padding: 6px 12px; color: var(--text-muted); white-space: nowrap; }
    .topbar-menu-panel a:hover { color: var(--text); background: var(--sidebar-hover); text-decoration: none; }

    .main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
    /* No width cap: the panels grow into the window instead of the ports table scrolling sideways
       with half the screen left empty beside it. This page is tables and graphs, which are read by
       scanning across — the long-line-length argument for capping width is about prose, and the
       few places that have any (Settings' help text) cap themselves. */
    .content { padding: var(--space-5) var(--space-5) var(--space-6); width: 100%; min-width: 0; }

    .fab {
        position: fixed; bottom: 24px; right: 24px;
        width: 46px; height: 46px; border-radius: 50%;
        background: var(--primary); color: white; border: none;
        font-size: 18px; cursor: pointer; box-shadow: var(--shadow-md);
        display: flex; align-items: center; justify-content: center;
    }


        /* bottom-corner toast for fire-and-forget actions (folder Poll now) — a click needs a
           visible result, and a full progress modal is overkill when nothing is awaited */
        .nms-toast { position: fixed; bottom: 20px; right: 20px; z-index: 10001; max-width: 360px;
            padding: 11px 16px; border-radius: 10px; font-size: 13px; background: var(--surface);
            border: 1px solid var(--border-strong); box-shadow: 0 8px 28px rgba(0,0,0,.4);
            opacity: 0; transform: translateY(8px); pointer-events: none; transition: opacity .2s, transform .2s; }
        .nms-toast.show { opacity: 1; transform: translateY(0); }
        .nms-toast.ok { border-left: 3px solid var(--success); }
        .nms-toast.err { border-left: 3px solid var(--danger); }
        .pnl-modal { position: fixed; inset: 0; z-index: 10000; background: rgba(0,0,0,.55); display: none; align-items: center; justify-content: center; }
        .pnl-card { width: 420px; max-width: 92vw; background: var(--surface); border: 1px solid var(--border-strong); border-radius: 12px; padding: 18px 20px; }
        .pnl-title { font-size: 16px; font-weight: 750; margin-bottom: 10px; }
        .pnl-step { display: flex; align-items: center; gap: 10px; padding: 9px 4px; font-size: 14px; border-bottom: 1px solid var(--border); }
        .pnl-icon { width: 22px; text-align: center; font-weight: 800; font-size: 15px; color: var(--text-faint); display: inline-block; }
        .pnl-step.run .pnl-icon { color: var(--primary); animation: pnl-spin 1s linear infinite; }
        .pnl-step.ok .pnl-icon { color: var(--success); }
        .pnl-step.fail .pnl-icon { color: var(--danger); }
        .pnl-step.idle { opacity: .45; }
        @keyframes pnl-spin { to { transform: rotate(360deg); } }
        .pnl-msg { font-size: 12.5px; min-height: 16px; margin: 10px 0 4px; }
        .pnl-actions { display: flex; justify-content: flex-end; margin-top: 6px; }
    

        .ctx-item { display:block; padding:7px 16px; color:var(--text); cursor:pointer; font-size:13px; }
        .ctx-item:hover { background:var(--primary-soft); text-decoration:none; }
        .ctx-item.ctx-danger { color:var(--danger); }
    

.trn{position:relative;height:44px;margin-top:10px;border:1px solid var(--border,#2a2f3a);border-radius:5px;overflow:hidden;background:var(--panel-2,rgba(127,127,127,.06));cursor:crosshair;user-select:none;-webkit-user-select:none;touch-action:none;}
.trn svg{position:absolute;inset:0;width:100%;height:100%;display:block;}
.trn-fill{fill:var(--chart-in,#3b82f6);opacity:.28;}
.trn-line{fill:none;stroke:var(--chart-in,#3b82f6);stroke-width:1;opacity:.7;vector-effect:non-scaling-stroke;}
.trn-mask{position:absolute;top:0;bottom:0;background:rgba(15,19,28,.5);pointer-events:none;}
.trn-sel{position:absolute;top:0;bottom:0;cursor:move;background:rgba(96,150,240,.14);border-left:2px solid var(--chart-in,#3b82f6);border-right:2px solid var(--chart-in,#3b82f6);box-sizing:border-box;}
.trn-h{position:absolute;top:0;bottom:0;width:11px;background:var(--chart-in,#3b82f6);opacity:.92;cursor:ew-resize;}
.trn-h.l{left:-2px;border-radius:4px 0 0 4px;}
.trn-h.r{right:-2px;border-radius:0 4px 4px 0;}
.trn-h::after{content:"";position:absolute;top:50%;left:50%;width:1px;height:15px;margin:-7px 0 0 -1px;background:#fff;opacity:.8;box-shadow:2px 0 0 #fff,-2px 0 0 #fff;}
.trn-hint{position:absolute;right:7px;bottom:3px;font-size:10px;line-height:1;color:var(--text-muted,#8b93a3);opacity:.65;pointer-events:none;}


    .ver-bar { position: fixed; left: 50%; transform: translateX(-50%); bottom: 22px; z-index: 10002;
        display: none; align-items: center; gap: 12px; padding: 10px 16px; border-radius: 10px;
        background: var(--primary, #2563eb); color: #fff; font-size: 13px; font-weight: 600;
        box-shadow: 0 8px 28px rgba(0,0,0,.45); }
    .ver-bar button { background: #fff; color: var(--primary, #2563eb); border: 0; border-radius: 7px;
        padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; }
    .ver-bar a { color: #fff; opacity: .8; font-size: 12px; text-decoration: underline; cursor: pointer; }


  .whois[data-ip]{cursor:help;border-bottom:1px dotted transparent;}
  .whois[data-ip]:hover{border-bottom-color:var(--border,#3a4a55);}
  /* an IP that is ALSO a drill-down link keeps its click affordance — pointer, not help */
  a.whois[data-ip]{cursor:pointer;}
  .nf-whois-pop{position:fixed;z-index:10000;max-width:340px;background:var(--panel,#0E141A);
    border:1px solid var(--border-strong,#2a3a44);border-radius:7px;padding:9px 11px;font-size:12px;
    color:var(--text,#C9D6DF);box-shadow:0 10px 30px rgba(0,0,0,.55);pointer-events:none;line-height:1.55;}
  .nf-whois-pop b{font-size:12.5px;}
  .nf-whois-pop .k{display:inline-block;min-width:42px;color:var(--text-muted,#6b7a86);
    text-transform:uppercase;font-size:10px;letter-spacing:.04em;}

/* ===== shared components (0.9.745 alignment pass) =====
   Appended LAST on purpose: several rules below tie the base input/button rules on specificity and win by
   source order. Every colour is a token; the two hex values in .errin-badge already existed in .tree-errin. */

/* ---- text & table utilities ---- */
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }          /* 93 markup uses, was defined only in 5 skins */
th.r, td.r { text-align: right; }                                                   /* replaces 110 inline text-align:right; .num keeps tabular digits only */
.tscroll { overflow-x: auto; }                                                      /* was cluster.html-only; 5 markup uses + 61 inline overflow-x:auto */
.t-ok { color: var(--success); } .t-warn { color: var(--warning); } .t-danger { color: var(--danger); }
.t-primary { color: var(--primary); }                                             /* a downward change on the shifts pages: informative, not a status */
td.actions { text-align: right; white-space: nowrap; }
td.actions form, form.inline { display: inline; margin: 0; }                        /* 45 <form style="display:inline;margin:0"> */
td.actions > * + * { margin-left: 4px; }

/* ---- panel head: children never inherit the uppercase/tracking; notes and controls are sized once ----
   Retires 49 inline "text-transform:none;letter-spacing:0" copies in 16 templates and defines .ph-note
   (14 markup sites that rendered UPPERCASE). No margin-left:auto on .ph-note — use .spacer before it. */
.panel-head :is(.range-tabs, .chips, .chip, .faint, .muted, .plain, .ph-note, .count, label, form, a, select, input) { text-transform: none; letter-spacing: 0; }
.panel-head .ph-note { font-weight: 400; font-size: 11px; color: var(--text-faint); }
.panel-head .count { font-weight: 600; color: var(--text-faint); }
.panel-head :is(input, select) { display: inline-block; width: auto; max-width: 280px; height: 24px; margin: 0; padding: 0 8px; font-size: 12px; line-height: 22px; font-weight: 400; }
.panel-head .range-tabs + .chips, .panel-head .chips + .range-tabs { margin-left: var(--space-3); }

/* ---- range tabs: wrapping strip, period stepper label, small variant ---- */
.range-tabs.wrap { display: flex; flex-wrap: wrap; }
.range-tabs.wrap .range-tab { border-bottom: 1px solid var(--border); margin-bottom: -1px; }
.range-tabs .range-label { display: inline-flex; align-items: center; justify-content: center; min-width: 56px; padding: 0 4px; font-size: 11px; color: var(--text-faint); border-right: 1px solid var(--border); }
.range-tabs.sm .range-tab { padding: 1px 7px; font-size: 10px; }
.range-tab.live { color: var(--danger); }
.range-tab .badge { margin: 0; }

/* ---- one small control size (buttons, inputs, selects). 26px: matches table.edit cells; .panel-head controls are 24. ----
   Replaces 77 inline "padding:Npx Npx;font-size:12px" recipes and the three local .btn-sm (3 different metrics). */
button.sm, .btn.sm { height: 26px; padding: 0 10px; font-size: 12px; }
button.sm > svg, .btn.sm > svg { width: 12px; height: 12px; }
input.sm, select.sm { display: inline-block; width: auto; height: 26px; margin: 0; padding: 0 8px; font-size: 12px; }
/* inside a .form-row the row's own control sizing (0,4,1 on inputs, later than button.sm) used to win and .sm
   rendered at 36px. The :not() chain is repeated only to out-rank it (0,5,1); nothing else in the app is that specific. */
.form-row :is(input:not([type=checkbox]):not([type=radio]):not([type=hidden]), select).sm { height: 26px; padding: 0 8px; font-size: 12px; }
.form-row :is(button, .btn).sm { height: 26px; padding: 0 10px; font-size: 12px; }

/* ---- editable table rows (opt-in): every control in the row shares one 26px box ---- */
table.edit td { height: 36px; }   /* a read-only row (ipam DNS NS/SOA) matches an editable one: 26px control + 4px compact padding each side + collapsed borders (measured: 34 left it 2px short) */
table.edit td :is(input:not([type=checkbox]):not([type=radio]):not([type=hidden]), select) { display: inline-block; width: 100%; height: 26px; margin: 0; padding: 0 8px; font-size: 12.5px; }
table.edit td input[type=checkbox] { width: auto; margin: 0; }
table.edit td :is(button, .btn) { height: 26px; padding: 0 10px; font-size: 12px; }

/* ---- forms ---- */
label.form-check { display: flex; align-items: center; gap: 8px; margin: 0 0 var(--space-2); font-weight: 500; color: var(--text); }
label.form-check input { width: auto; height: auto; margin: 0; }                    /* .form-row label.form-check (36px) still wins inside a row */
label .label-link { font-weight: 400; font-size: 11.5px; margin-left: 6px; }
.help { margin: 0 0 var(--space-4); font-size: 12px; color: var(--text-muted); max-width: 70ch; }
:is(input, select, textarea) + .help { margin-top: calc(-1 * var(--space-3)); }      /* pulls the help under ITS field (base control keeps its 16px bottom margin) */
input:disabled, select:disabled, textarea:disabled { color: var(--text-muted); background: var(--surface-alt); cursor: not-allowed; }
.toolbar-sep { align-self: stretch; width: 1px; background: var(--border); margin: 2px 0; }
.inline-control { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; text-transform: none; letter-spacing: 0; font-weight: 400; }

/* ---- notices / empty states / tab panes ---- */
.notice { padding: 10px 16px; border: 1px solid var(--border); border-left: 3px solid var(--primary); border-radius: var(--radius-sm); background: var(--surface); font-size: 13px; margin-bottom: var(--space-4); }
.notice.ok { border-left-color: var(--success); }
.notice.warn { border-left-color: var(--warning); }
.notice.danger { border-left-color: var(--danger); }
.empty-state { padding: var(--space-6) 0; text-align: center; color: var(--text-muted); font-size: 12.5px; }
.tab-pane { display: none; } .tab-pane.active { display: block; }                   /* was declared locally in ddos.html and settings.html */
.panel-section { padding: 0 var(--space-4) var(--space-4); }

/* ---- page header ---- */
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3) var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-4); }
.page-head h2 { margin: 0; }
.page-head .page-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; font-weight: 700; color: var(--text-faint); }
.page-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-left: auto; justify-content: flex-end; }   /* pins right on BOTH lines when the row wraps */
.page-actions form { display: contents; }   /* a POST button lives inside its own <form>; contents makes it a flex item like its siblings, so all buttons share one height */
.page-intro { margin: 0 0 var(--space-4); font-size: 12.5px; color: var(--text-muted); max-width: 80ch; }
.page-head + .page-intro { margin-top: calc(-1 * var(--space-2)); }
.back-link { display: block; font-size: 12.5px; margin: 0 0 var(--space-2); }

/* ---- stats / charts / meters ---- */
.stat-value.lg { font-size: 22px; }
.stat-value.warn { color: var(--warning); } .stat-value.danger { color: var(--danger); }
.stat-sub { font-size: 11px; color: var(--text-faint); margin-top: 1px; }
.kpi { padding: var(--space-3) var(--space-4); }
.kpi-value { font-size: 22px; font-weight: 650; font-variant-numeric: tabular-nums; }
.kpi-unit { font-size: 13px; color: var(--text-faint); font-weight: 400; }
.kpi-spark { display: block; width: 100%; height: 24px; margin-top: 6px; }
.chartbox { position: relative; height: 260px; }
.chartbox.short { height: 200px; }
.meter.block { display: block; max-width: none; flex: none; }

/* ---- device-page pills that the folder view and the sidebar also need ---- */
.errin-badge { display: inline-flex; align-items: center; gap: 4px; vertical-align: middle; font-size: 12px; font-weight: 700; padding: 2px 9px; border-radius: 999px; margin-left: 8px; background: color-mix(in srgb, #ec4899 20%, #12141a); color: #f9a8d0; white-space: nowrap; text-decoration: none; }
.errin-badge:hover { background: color-mix(in srgb, #ec4899 32%, #12141a); }

/* ---- log rows (device syslog/events) and the terminal well (DEBT-1) ---- */
.log-row { --sev: var(--border-strong); display: flex; gap: 10px; align-items: baseline; padding: 6px var(--space-4); border-bottom: 1px solid var(--border); border-left: 3px solid var(--sev); }
.log-row.sev-err { --sev: var(--danger); } .log-row.sev-warn { --sev: var(--warning); } .log-row.sev-ok { --sev: var(--success); }
.log-ts { font-size: 11px; white-space: nowrap; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.log-msg { font-size: 12.5px; overflow-wrap: anywhere; }
.term { background: var(--term-bg); color: var(--term-fg); font-family: var(--font-mono); font-size: 11.5px; line-height: 1.7; padding: 8px 0; overflow-y: auto; }
.term-line { padding: 0 14px; white-space: pre-wrap; }
.term-ts { color: var(--term-dim); }
.term-sev { font-weight: 600; color: var(--term-muted); }
.term-sev.err { color: var(--term-err); } .term-sev.warn { color: var(--term-warn); } .term-sev.debug { color: var(--term-dim); }
.term-src { color: var(--term-ok); font-weight: 600; }
a.term-host { color: var(--term-warn); font-weight: 600; text-decoration: none; }
/* after .term-sev on purpose: pollers puts both classes on one span and the kind colour must win by source order */
.term-k-ok { color: var(--term-ok); } .term-k-err { color: var(--term-err); } .term-k-warn { color: var(--term-warn); }
.term-k-info { color: var(--term-info); } .term-k-alt { color: var(--term-alt); } .term-k-muted { color: var(--term-muted); }

/* ---- modals: header row + wide card (full modal unification is round 2) ---- */
.pnl-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 6px; }
.pnl-card.wide { width: 660px; max-width: 96vw; }
.code-word { display: block; width: 100%; margin: 0; padding: 8px; font-family: var(--font-mono); font-size: 22px; letter-spacing: 4px; text-align: center; text-transform: uppercase; }

/* ---- shell: context menu, sidebar search, topbar nav ---- */
.ctx-menu { position: fixed; z-index: 200; min-width: 170px; padding: 4px 0; overflow: hidden; font-size: 13px; background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm); box-shadow: var(--shadow-md); }
.tree-search { padding: 0 var(--space-3) var(--space-2) 14px; }
#tree-filter { width: 100%; margin: 0; padding: 5px 9px; font-size: 12px; outline: none; border: 1px solid var(--sidebar-hover); border-radius: var(--radius-sm); background: var(--sidebar-hover); color: var(--sidebar-text-active); }
.topbar-nav a.current { color: var(--text); font-weight: 650; }
/* Abnormal's count pills: the sidebar's .tree-updown pills, borrowed whole so the topbar and the
   tree say the same thing with the same colour. Only two sidebar-specific bits are undone — the
   `margin-left:auto` that pins a folder's pills to the right edge (here they belong against their
   own word) and the baseline, so the pill sits on the nav's text line instead of lifting it. */
.topbar-nav .tree-updown.nav-badge { margin-left: 5px; vertical-align: baseline; }
/* the tz select is a nav item: same line, colour and height as the links around it (the "Riyadh" complaint).
   Measured (topbar harness, inline style removed): select/EN/Logout centres 57.8 = 57.8 = 57.8 at 1280 and 19.4 at 1794;
   nav one row (38px, was 67); select 108px wide (was 150); .topbar with the admin strip 103.8px at 1280 (was 137)
   and 65.8 at 1794 (was 96) — the 1280 figure includes the nav's own 8px padding. */
.topbar-nav select { display: inline-block; width: auto; max-width: 150px; height: 22px; margin: 0; padding: 0 4px; font-size: 12.5px; line-height: 20px; color: var(--text-muted); background: transparent; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.topbar-nav select:hover { color: var(--text); border-color: var(--border-strong); }

/* ---- admin live status strip (moved from layout_app.html; inert without the admin-gated markup) ---- */
.nms-live-strip {
    flex: 1 0 100%; order: 3; min-width: 0;
    display: flex; align-items: center; flex-wrap: wrap; column-gap: var(--space-4); row-gap: 0;
    /* full-bleed ledge across the header's horizontal padding */
    margin: 0 calc(-1 * var(--space-5)); padding: 3px var(--space-5);
    border-top: 1px solid var(--border); background: var(--surface-alt);
    font-size: 12px; line-height: 18px; white-space: nowrap;
}
.nms-live-item {
    display: inline-flex; align-items: baseline; gap: 5px;
    color: var(--text-muted); text-decoration: none;
}
.nms-live-item:hover { color: var(--text); text-decoration: none; }
.nms-live-item:hover .nms-live-num { color: var(--primary); }
/* tabular figures: a value that ticks from 99 to 100 must not shove its neighbours */
.nms-live-num { font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
.nms-live-num.warn { color: var(--warning); }
.nms-live-sub { color: var(--text-faint); font-size: 11px; font-variant-numeric: tabular-nums; }
.nms-live-sub .danger { color: var(--danger); font-weight: 650; }
.nms-live-strip.is-stale .nms-live-item { opacity: .55; }
.nms-live-stale {
    display: none; margin-left: auto; align-items: center; gap: 6px;
    color: var(--warning); font-size: 11px;
}
.nms-live-strip.is-stale .nms-live-stale { display: inline-flex; }
@media (prefers-reduced-motion: no-preference) { .nms-live-item { transition: opacity .25s; } }

/* ---- checkpack panel: the "Host checks (SSH)" panel of the device page (node.html) ----
   Colour is state, never decoration: the row's `cp-<state>` class comes from checkpack_rules.line_state
   (ok / warn / bad / unread / info) and the pack's `cp-tone-<state>` edge from checkpack_rules.pack_tone.
   A refused read is a grey hollow ring + "no value — reason", never a zero and never green; a row nobody
   judges (a text, a number with no threshold) is a faint hollow ring, not a green one. Tokens only, so
   the light palette renders the same meaning. Reuses .panel/.panel-head/.kv-row/.badge/.health-*. */
.cp-panel .kv-row.cp-row { padding: 4px 14px 4px 12px; align-items: center; gap: 8px; }
.cp-pack { border-left: 3px solid var(--border-strong); }
.cp-pack + .cp-pack { border-top: 1px solid var(--border); }
.cp-tone-ok { border-left-color: var(--success); }
.cp-tone-warn { border-left-color: var(--warning); }
.cp-tone-bad { border-left-color: var(--danger); }
.cp-tone-unread { border-left-color: var(--text-faint); }
.cp-pack-head { display: flex; flex-direction: column; gap: 3px; padding: 8px 14px 7px 12px;
    background: var(--surface-alt); border-bottom: 1px solid var(--border); }
.cp-pack-title { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.cp-pack-name { font-weight: 650; font-size: 13px; color: var(--text); }
.cp-summary { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-left: auto; }
.cp-summary .badge { font-size: 10.5px; padding: 0 7px; }
.cp-badge-unread { border: 1px dashed var(--border-strong); }
.cp-pack-sentence { font-size: 11.5px; overflow-wrap: anywhere; }
/* sub-blocks: a small tracked label, a hairline between blocks */
.cp-group-title { padding: 8px 14px 2px 12px; font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--text-faint); }
.cp-group + .cp-group { border-top: 1px solid var(--border); }
.cp-row .health-name a, .cp-row .health-name > span { color: var(--text); font-weight: 600; }
.cp-row .health-name a:hover { text-decoration: underline; }
/* the state dot: filled for a verdict, hollow when there is none */
.cp-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
    background: transparent; box-shadow: inset 0 0 0 1.5px var(--border-strong); }
.cp-ok .cp-dot { background: var(--success); box-shadow: 0 0 0 2px var(--success-soft); }
.cp-warn .cp-dot { background: var(--warning); box-shadow: 0 0 0 2px var(--warning-soft); }
.cp-bad .cp-dot { background: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
.cp-unread .cp-dot { background: transparent; box-shadow: inset 0 0 0 2px var(--text-faint); opacity: .8; }
/* a breached row is tinted so the eye lands on it before reading a number */
.cp-row.cp-warn { background: color-mix(in srgb, var(--warning-soft) 55%, transparent); }
.cp-row.cp-bad { background: color-mix(in srgb, var(--danger-soft) 65%, transparent); }
.cp-warn .cp-val { color: var(--warning); }
.cp-bad .cp-val { color: var(--danger); }
.cp-val { text-align: right; }
/* an unread row carries prose (the reason): it wraps under the name instead of squeezing it */
.cp-row.cp-unread { flex-wrap: wrap; }
.cp-unread .health-name { flex: 1 1 auto; min-width: 40%; }
.cp-unread .cp-val { flex: 1 1 55%; min-width: 0; white-space: normal; font-weight: 500; }
.cp-reason { color: var(--text-faint); font-weight: 400; }
.cp-text { font-weight: 500; color: var(--text-muted); font-family: var(--font-mono); font-size: 12px; }
/* status pills: Normal / Fault / no value */
.cp-pill { display: inline-block; padding: 0 7px; border-radius: 999px; font-size: 11px; font-weight: 650; line-height: 17px; white-space: nowrap; }
.cp-pill.ok { background: var(--success-soft); color: var(--success-strong); }
.cp-pill.bad { background: var(--danger-soft); color: var(--danger); }
.cp-pill.unread { background: var(--bg); color: var(--text-muted); border: 1px dashed var(--border-strong); }
/* dropped vs queued: two bars on one scale, and the share in words */
.cp-relation { display: grid; gap: 3px; padding: 4px 14px 9px 28px; font-size: 11px; }
.cp-relbar { display: flex; align-items: center; gap: 8px; }
.cp-rellabel { width: 52px; flex-shrink: 0; color: var(--text-muted); font-weight: 600; }
.cp-reltrack { flex: 1; height: 6px; background: var(--bg); border: 1px solid var(--border); border-radius: 3px; overflow: hidden; }
.cp-reltrack > i { display: block; height: 100%; border-radius: 2px; }
.cp-reltrack > i.q { background: var(--primary); }
.cp-reltrack > i.d { background: var(--danger); }
.cp-relnum { min-width: 48px; text-align: right; font-variant-numeric: tabular-nums; font-weight: 650; }
.cp-relshare { padding-top: 2px; font-weight: 650; color: var(--text-muted); }
.cp-relshare-warn { color: var(--warning); }
@media (max-width: 480px) {
    .cp-relation { padding-left: 12px; }
    .cp-summary { margin-left: 0; }
    .cp-val { white-space: normal; }
}

/* ===== table pages on a diet (phase 2 of the page-weight work) =====
   A row rendered a thousand times pays for every inline style a thousand times: what the big
   tables (inventory, backups, IPAM) used to write on each row lives here once. .ico is the sprite
   glyph's alignment (_icons.html icon_use — the same two declarations icon() writes inline). */
.ico { vertical-align: -2px; flex-shrink: 0; }
.bk-view { --c: #38bdf8; }
.bk-pick { margin: 0; width: auto; }
.bk-act { white-space: nowrap; }
.bk-dev { font-weight: 600; }
.bk-platform { font-size: 11.5px; }
/* the footer of a table served in two parts (table_page_rules): "Showing N of M" + Load all */
.tp-foot { display: flex; align-items: center; gap: var(--space-3); padding: 8px 14px; border-top: 1px solid var(--border);
           font-size: 12px; color: var(--text-muted); }
/* the DNS records pager (ipam.html #dns-foot): page buttons, jump box and page-size select on one wrapping line */
.dns-foot { flex-wrap: wrap; row-gap: 6px; }
.dns-foot.busy { opacity: .6; }
.dns-pager { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.dns-pager #dns-pages { display: inline-flex; align-items: center; gap: 2px; }
.dns-pager button.on { border-color: var(--primary); color: var(--text); font-weight: 600; }
.dns-foot label.faint { display: inline-flex; align-items: center; gap: 4px; }
.inv-serial { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11.5px; }
.inv-serial .faint { font-size: 11px; }
.inv-serial .inv-typed { font-family: inherit; font-size: 10px; }
