:root {
    --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
    --font-mono: 'JetBrains Mono', 'Fira Code', monospace;

    --bg-base: #0a0c0f;
    --bg-surface: #111418;
    --bg-surface-2: #181c22;
    --bg-border: #1f2530;

    --text-primary: #e8eaed;
    --text-secondary: #8b9099;
    /* 4.5:1 over --bg-surface for the hint, meta and legend text that uses it
       (critique finding: #545b64 read 2.8:1). */
    --text-tertiary: #808894;
    --text-inverse: #0a0c0f;

    --accent: #3b82f6;
    --accent-hover: #2563eb;
    /* The beacon carrying white type. #3b82f6 under #fff is 3.68:1 — the
       primary action fails AA at its own brand colour — so the button's fill
       runs from --accent-hover (5.17:1) to --accent-deep (6.70:1) instead.
       The gradient is still the documented accent gradient; it just starts
       where white can be read on it. */
    --accent-deep: #1d4ed8;

    --color-wind: #4ade80;
    --color-solar: #facc15;
    --color-nuclear: #a78bfa;
    --color-gas: #f97316;
    --color-hydro: #38bdf8;
    --color-coal: #6b7280;
    --color-biomass: #86efac;
    --color-imports: #c084fc;
    --color-storage: #67e8f9;

    --status-excellent: #4ade80;
    --status-good: #a3e635;
    --status-moderate: #facc15;
    --status-poor: #f97316;
    --status-bad: #ef4444;

    --status-positive: #4ade80;
    --status-warning: #facc15;
    --status-negative: #ef4444;
    --status-neutral: #8b9099;

    /* The ramp and the beacon as *ink*.

       Everything above is the ramp as an instrument lamp: a dot, a ring, a
       bar, a 1px rule. A lamp is a graphic, its bar is 3:1, and brightness is
       the whole point of it. But the same nine values are also spent as
       `color:` on type — badges, result lines, summary counts, status pills —
       where the bar is 4.5:1 and brightness works against them. The two jobs
       only agreed because the ramp was drawn once, against the midnight
       stack, and never re-measured on the daylight one. On #f7f9fb the ramp
       as type reads 1.37–3.58:1, so every one of the eighteen text sites
       failed AA — the whole range sits under the 4.5 bar — and the red end
       (#ef4444, 4.41:1 on its own 12% tint) failed on midnight too.

       So the ink is its own layer, per theme, and the lamps keep the ramp.
       This is the generalisation of --status-poor-ink, which was the first of
       these to be found and got a one-off override; the rule it discovered
       holds for the whole set.

       Dark moves two: the red end and the beacon, both a step lighter.
       Everything else already clears 4.5:1 on midnight and is left identical
       to its lamp, so `-ink` is a no-op there rather than a second palette.

       Values are the same hue family, never a new colour: the ramp must stay
       readable as one ordered green→red sequence in both themes. Each is the
       nearest step that clears 4.5:1 on its *worst* ground, which is the pill
       tint, not the panel — a 12% wash of the ink's own hue moves the ground
       toward the ink and costs about 0.2. */
    --accent-ink: #60a5fa;

    --status-good-ink: #a3e635;
    --status-moderate-ink: #facc15;
    --status-poor-ink: #f97316;
    --status-bad-ink: #f87171;

    --status-positive-ink: #4ade80;
    --status-warning-ink: #facc15;
    --status-negative-ink: #f87171;

    --chart-grid: rgba(255, 255, 255, 0.05);
    --chart-tooltip-bg: #1f2530;
    --chart-tooltip-border: #2d3748;

    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-8: 32px;
    --space-10: 40px;
    --space-12: 48px;
    --space-16: 64px;

    --map-filter: saturate(0.55) brightness(0.48) contrast(1.08) hue-rotate(180deg);
    --map-overlay-primary: rgba(10, 12, 15, 0.22);
    --map-overlay-secondary: rgba(10, 12, 15, 0.08);
    --map-overlay-tertiary: rgba(10, 12, 15, 0.44);
    --map-overlay-side-left: rgba(10, 12, 15, 0.36);
    --map-overlay-side-right: rgba(10, 12, 15, 0.5);

    /* Game-window frame. --window-inset is the desk margin around the whole
       app, not spacing between things, so it sits off the --space-N scale on
       purpose. --hud-inset is 10px because that is Leaflet's own control
       margin: it makes the HUD's top edge line up with the zoom cluster's. */
    --desk: #050608;
    --window-inset: 10px;
    --window-radius: 8px;
    --hud-inset: 10px;
    --nav-h: 48px;
    --map-zoom-gutter: 54px;
    /* The editor panel's width, and the width the HUD must leave clear of it:
       the panel plus the --space-4 it is inset from the window edge. */
    --editor-w: 420px;
    --editor-reserve: calc(var(--editor-w) + var(--space-4));
}

html[data-theme="light"] {
    --bg-base: #eef2f6;
    --bg-surface: #f8fafc;
    --bg-surface-2: #eef3f7;
    --bg-border: #d7dee8;

    --text-primary: #101828;
    --text-secondary: #475467;
    --text-tertiary: #667085;
    --text-inverse: #ffffff;

    /* The ink layer, remapped for daylight. The lamps (--accent, --status-*)
       are deliberately NOT overridden here: they are shared with
       sld-layout.js's BUS_TYPE_COLORS and LOADING_RAMP, and a legend that
       advertises a colour the diagram does not draw is the exact defect the
       comment above .legend-dot exists to prevent. Only the ink moves.

       Each is the nearest step down its own hue ramp that clears 4.5:1 on the
       worst ground it lands on, with the sequence still ordered green→red:
       #166534 · #3f6212 · #854d0e · #9a3412 · #b91c1c. The 700 step was one
       measurement short at four of the six (green 4.46, yellow 4.46, lime
       4.53, orange 4.38), so most of these are the 800. */
    --accent-ink: #1d4ed8;

    --status-good-ink: #3f6212;
    --status-moderate-ink: #854d0e;
    --status-poor-ink: #9a3412;
    --status-bad-ink: #b91c1c;

    --status-positive-ink: #166534;
    --status-warning-ink: #854d0e;
    --status-negative-ink: #b91c1c;

    --chart-grid: rgba(16, 24, 40, 0.08);
    --chart-tooltip-bg: #f8fafc;
    --chart-tooltip-border: #d7dee8;

    --map-filter: saturate(0.78) brightness(1.02) contrast(0.96);
    --map-overlay-primary: rgba(255, 255, 255, 0.12);
    --map-overlay-secondary: rgba(255, 255, 255, 0.04);
    --map-overlay-tertiary: rgba(255, 255, 255, 0.18);
    --map-overlay-side-left: rgba(248, 250, 252, 0.18);
    --map-overlay-side-right: rgba(248, 250, 252, 0.22);

    --desk: #d5dce6;
}

*,
*::before,
*::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    transition-property: color, background-color, border-color, opacity;
    transition-duration: 150ms;
    transition-timing-function: ease;
}

html,
body {
    min-height: 100%;
    background: var(--desk);
}

body {
    font-family: var(--font-sans);
    background: var(--desk);
    color: var(--text-primary);
    font-size: 0.875rem;
    line-height: 1.5;
    overflow: hidden;
    font-variant-numeric: tabular-nums;
    -webkit-font-smoothing: antialiased;
}

/* One window around the whole app. The map/SLD is the video; nav and HUD are
   chrome. Do not put a border or a radius on the video — #fallback-map and
   #sld-canvas carry neither, and giving them one turns a single framed window
   into nested frames.

   #control-panel is deliberately not in that list, though an earlier version
   of this comment said it was. It floats over the video like every other HUD
   element (.dashboard-overview, .sld-overlay-card, .loading, #map-hint), and
   all of them carry the same 1px border and 8px radius to separate them from
   the map underneath. Its border is chrome, not a bezel. */
.app-window {
    position: fixed;
    inset: var(--window-inset);
    z-index: 1;
    overflow: hidden;
    border: 1px solid var(--bg-border);
    border-radius: var(--window-radius);
    background: var(--bg-base);
}

.nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-6);
    height: var(--nav-h);
    padding: 0 var(--space-6);
    background: rgba(17, 20, 24, 0.96);
    border-bottom: 1px solid var(--bg-border);
    position: absolute;
    inset: 0 0 auto 0;
    z-index: 100;
    backdrop-filter: blur(12px);
}

.nav-logo {
    margin: 0;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--text-primary);
    letter-spacing: -0.01em;
    white-space: nowrap;
}

/* The mark, left of the wordmark. Both themes are handled inside the SVG
   itself -- the ring is var(--accent), which the light theme does not override,
   and the bolt is currentColor, which resolves to --text-primary in each. So
   there is no light-theme rule for it below and there should not be one.

   Sized and positioned in em so it tracks the wordmark. vertical-align is
   -0.47em rather than `middle` because `middle` aligns to the x-height
   midpoint, which sits 1.55px below the cap-height midpoint the mark should
   centre on; at this size that reads as the mark sagging. The ink fills 76.6%
   of the 32-unit box centred, so centring the box centres the ink. */
.nav-mark {
    height: 1.65em;
    width: auto;
    margin-right: 0.5em;
    vertical-align: -0.47em;
}

/* The transformer symbol separating the name from the strapline, in place of
   the old "//". It is punctuation, so it takes currentColor at reduced opacity
   rather than a colour of its own -- the slashes it replaces were visually
   lighter than the letters around them for the same reason. Sized in em so it
   tracks the logo's font-size at every breakpoint. */
.nav-xfmr {
    height: 0.72em;
    width: auto;
    margin: 0 0.1em;
    vertical-align: -0.04em;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    opacity: 0.55;
}

.theme-toggle {
    padding: 6px 10px;
    border-radius: 4px;
    border: 1px solid var(--bg-border);
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-primary);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1;
}

.theme-toggle:hover {
    background: rgba(255, 255, 255, 0.08);
}

.nav-meta {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

/* Nav status cluster — the live badge and timestamp as a centered instrument
   between the logo and the controls. */
.nav-status {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

.nav-status .badge {
    padding: 3px var(--space-2);
}

.nav-status .nav-status-time {
    margin-left: var(--space-2);
    padding-left: var(--space-2);
    border-left: 1px solid var(--bg-border);
    color: var(--text-secondary);
    font-size: 0.6875rem;
}

/* The bar's narrow-screen behaviour — the absolute centring above comes off at
   960px and the whole bar wraps at 720px — lives with the other breakpoints at
   the end of this file, not here. */

.timestamp,
.loading,
.entity-meta,
textarea,
code {
    font-family: var(--font-mono);
}

/* Persistence banner — the standing warning that the Power System is no longer
   being saved (store.js `storageOk`, worded by storage-status.js).
   It takes its own row under the nav instead of floating over the canvas: the
   condition lasts until the user frees up storage, and every corner of this
   layout is already spoken for — the hero cards and the SLD toolbar at the top,
   the SLD legend and the status pill at the bottom, the editor down the right.
   A floating card would have to cover one of them for the whole session, and
   covering the editor is the worst of the options, since the editor is where a
   user trims the case that would not fit.
   app.js measures the row once per condition change into --storage-alert-h
   (its height depends on how the message wraps) and the panes below shift by
   exactly that, so the banner hides nothing. */
.storage-alert {
    position: absolute;
    top: var(--nav-h);
    left: 0;
    right: 0;
    z-index: 90;
    padding: var(--space-2) var(--space-6);
    border-bottom: 1px solid rgba(239, 68, 68, 0.35);
    background: rgba(239, 68, 68, 0.16);
    color: var(--text-primary);
    font-size: 0.75rem;
    line-height: 1.5;
}

.storage-alert[hidden] {
    display: none;
}

.storage-alert strong {
    margin-right: var(--space-2);
    color: var(--status-negative-ink);
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

html[data-theme="light"] .storage-alert {
    border-bottom-color: rgba(239, 68, 68, 0.32);
    background: rgba(254, 226, 226, 0.94);
}

body.storage-blocked #fallback-map {
    top: calc(var(--nav-h) + var(--storage-alert-h, 0px));
}

body.storage-blocked #sld-canvas {
    top: calc(var(--nav-h) + var(--storage-alert-h, 0px));
}

body.storage-blocked #control-panel {
    top: calc(var(--nav-h) + var(--storage-alert-h, 0px) + var(--space-4));
}

#fallback-map {
    position: absolute;
    inset: var(--nav-h) 0 0;
    z-index: 2;
    display: block;
    background:
        radial-gradient(circle at top left, rgba(59, 130, 246, 0.08), transparent 32%),
        linear-gradient(180deg, rgba(10, 12, 15, 0.9), rgba(10, 12, 15, 0.72));
}

#fallback-map::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background:
        linear-gradient(180deg, var(--map-overlay-primary), var(--map-overlay-secondary) 25%, var(--map-overlay-tertiary)),
        linear-gradient(90deg, var(--map-overlay-side-left), transparent 34%, transparent 66%, var(--map-overlay-side-right));
}

#osm-map,
#fallback-overlay {
    position: absolute;
    inset: 0;
}

#osm-map {
    z-index: 1;
}

/* SLD (Single Line Diagram) canvas */
#sld-canvas {
    position: absolute;
    inset: var(--nav-h) 0 0;
    z-index: 1;
    background: var(--bg-base);
    overflow: auto;
    display: flex;
    align-items: center;
    justify-content: center;
}

#sld-canvas.sld-hidden {
    display: none;
}

#sld-svg {
    min-width: 700px;
    min-height: 600px;
    max-width: 100%;
    max-height: 100%;
}

/* A diagram too big to shrink-to-fit legibly renders at natural size and the
   canvas scrolls (see applySldSizing in app.js, which sets the explicit
   width/height and toggles this class).

   `overflow: auto` above is not enough on its own. #sld-canvas is a centring
   flexbox, so a flex item with an explicit width is still shrunk to fit —
   without `flex-shrink: 0` the SVG stays clamped at the canvas width and the
   labels stay sub-pixel. And centring must become start-alignment, or the
   overflow past the left and top edges cannot be scrolled to. */
#sld-canvas.sld-scrolling {
    align-items: flex-start;
    justify-content: flex-start;
}

#sld-canvas.sld-scrolling #sld-svg {
    flex-shrink: 0;
    max-width: none;
    max-height: none;
}

/* Body class for SLD mode */
body.sld-mode #fallback-map {
    display: none;
}

body.sld-mode #sld-canvas {
    display: flex;
}

/* SLD bus symbol */
.sld-bus {
    cursor: pointer;
    transition: transform 150ms ease;
}

.sld-bus:hover {
    transform: scale(1.1);
}

/* SLD branch line */
.sld-branch {
    transition: opacity 150ms ease;
}

.sld-branch:hover {
    opacity: 1 !important;
}

/* Power system panel elements */
.form-select {
    padding: 8px 12px;
    border: 1px solid var(--bg-border);
    border-radius: 6px;
    background: var(--bg-surface-2);
    color: var(--text-primary);
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    cursor: pointer;
}

.form-select:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

button.primary {
    background: linear-gradient(135deg, var(--accent-hover), var(--accent-deep));
    color: #fff;
    border: 1px solid rgba(59, 130, 246, 0.5);
    padding: 10px 16px;
    border-radius: 6px;
    font-weight: 600;
    cursor: pointer;
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    box-shadow: 0 4px 14px rgba(59, 130, 246, 0.25);
    transition: transform 150ms ease, box-shadow 150ms ease, background 150ms ease;
}

button.primary:hover {
    background: linear-gradient(135deg, var(--accent-deep), var(--accent-hover));
    box-shadow: 0 6px 18px rgba(59, 130, 246, 0.4);
    transform: translateY(-1px);
}

button.primary:active {
    transform: translateY(0);
    box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}

button.full-width {
    width: 100%;
}

/* Large CTA button (Run Load Flow) */
button.btn-lg {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    width: 100%;
    padding: 12px 16px;
    font-size: 0.875rem;
    letter-spacing: 0.01em;
}

.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* A button on its second press — armed to confirm. The editor's Delete
   buttons and the map toolbar's Replace-system button share it. */
button.confirm-armed {
    background: rgba(239, 68, 68, 0.25);
    border-color: rgba(239, 68, 68, 0.5);
    color: var(--status-negative-ink);
    font-weight: 600;
}

/* The armed state is the loudest tint in the interface, and 25% is tuned to
   lift off midnight. The same wash on daylight is a strong pink that closes on
   the ink instead of receding from it: the negative ink lands at 4.45:1 there,
   the only site in the set that misses. Both the other strong tints already
   step down for light (map-dirty 0.18 → 0.12, context-dirty 0.20 → 0.12); this
   one was missed, and the step is what makes the two themes match in loudness
   rather than in number. At 0.16 the ink reads 5.01:1 and the state is still
   unmistakably armed. */
html[data-theme="light"] button.confirm-armed {
    background: rgba(239, 68, 68, 0.16);
    border-color: rgba(239, 68, 68, 0.32);
}

/* Preset selector row: select grows, Load button fixed */
.preset-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-2);
    align-items: stretch;
}

.preset-row .form-select {
    width: 100%;
}

.preset-row button {
    flex: 0 0 auto;
    padding-inline: var(--space-5);
    white-space: nowrap;
}

/* Two-up analysis buttons */
.analysis-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2);
}

.analysis-grid button {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    line-height: 1.25;
    text-align: left;
}

/* The one-line promise under an analysis button — what the Op actually does,
   where the critique found two pairs of near-twins (N-1 vs Screen, Scan vs
   THD) that nothing on the button distinguished. */
.analysis-grid .btn-hint {
    font-size: 0.6875rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-tertiary);
}

/* Grouped form field with label */
.field-group {
    display: grid;
    gap: 6px;
}

.field-label {
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-tertiary);
}

.results-stack {
    display: grid;
    gap: var(--space-2);
}

.results-stack .lf-result:empty {
    display: none;
}

/* Accent strip + badge on the primary section */
.panel-section-primary {
    border-color: rgba(59, 130, 246, 0.35);
    padding-inline-start: calc(var(--space-4) + 3px);
    display: grid;
    gap: var(--space-3);
    overflow: hidden;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02);
}

.panel-section-primary p {
    margin: 0;
}

.panel-section-primary .section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin: 0;
    padding: 0 0 var(--space-3) 0;
    border-bottom: 1px solid var(--bg-border);
    min-height: 0;
}

.panel-section-primary h2 {
    margin: 0;
    padding: 0;
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.5;
    letter-spacing: -0.01em;
    text-transform: none;
    color: var(--text-primary);
}

.panel-section-primary .section-badge {
    align-self: center;
}

.panel-section-primary::before {
    content: '';
    display: block;
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 3px;
    border-radius: 8px 0 0 8px;
    background: linear-gradient(180deg, var(--accent), transparent);
}

.section-badge {
    display: inline-flex;
    align-items: center;
    font-size: 0.625rem;
    font-weight: 600;
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--accent-ink);
    background: rgba(59, 130, 246, 0.12);
    padding: 4px var(--space-2);
    border-radius: 4px;
}

/* Labeled divider between editor groups */
.group-divider {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: var(--space-1) 0;
    color: var(--text-tertiary);
    font-size: 0.625rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.group-divider::before,
.group-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--bg-border);
}

.lf-result {
    margin-top: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: 6px;
    font-family: var(--font-mono);
    font-size: 11px;
    background: var(--bg-surface-2);
    color: var(--text-secondary);
    min-height: 20px;
}

.lf-result.converged {
    color: var(--status-positive-ink);
}

.lf-result.failed {
    color: var(--status-negative-ink);
}

/* Status dot — the status color signals the outcome, drawn, not a glyph. */
.lf-result.converged::before,
.lf-result.failed::before {
    content: '';
    float: left;
    width: 8px;
    height: 8px;
    margin-top: 3px;
    margin-right: var(--space-2);
    border-radius: 999px;
    background: currentColor;
}

/* Result tables for CA / SC */
/* 10.5px was not on the type ramp at all, and its 10px header was the smallest
   text in the product — both flagged as unreadable in the keyboard/low-vision
   pass. Both are 0.6875rem now, which is the ramp's smallest step. */
.result-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.6875rem;
    margin-top: var(--space-2);
}

.result-table th,
.result-table td {
    padding: 3px 6px;
    text-align: left;
    border-bottom: 1px solid var(--bg-border);
}

.result-table th {
    font-weight: 600;
    color: var(--text-secondary);
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.result-table .row-critical td {
    color: var(--status-negative-ink);
}

.result-table .row-warning td {
    color: var(--status-warning-ink);
}

/* Harmonic frequency scan — hand-rolled inline-SVG resonance curve.
   |Z| runs on a log axis, so the decade gridlines carry the reading. */
.harmonic-chart {
    width: 100%;
    height: auto;
    margin: var(--space-2) 0;
    overflow: visible;
}

.harmonic-chart .hc-grid {
    stroke: var(--chart-grid);
    stroke-width: 1;
}

/* The characteristic converter harmonics (5, 7, 11, 13) sit behind the
   curves: a peak landing on one is the finding the scan exists to surface. */
.harmonic-chart .hc-marker {
    stroke: var(--status-warning-ink);
    stroke-width: 1;
    stroke-dasharray: 2 3;
    opacity: 0.35;
}

.harmonic-chart .hc-curve {
    stroke-width: 1.25;
    opacity: 0.75;
    vector-effect: non-scaling-stroke;
}

.harmonic-chart .hc-curve-selected {
    stroke-width: 2.25;
    opacity: 1;
}

.harmonic-chart .hc-peak {
    stroke: var(--bg-surface-2);
    stroke-width: 1;
}

.harmonic-chart .hc-tick {
    fill: var(--text-tertiary);
    font-family: var(--font-mono);
    font-size: 8px;
}

.harmonic-chart .hc-tick-y {
    text-anchor: end;
    dominant-baseline: middle;
}

.harmonic-chart .hc-tick-x {
    text-anchor: middle;
}

.harmonic-chart .hc-axis-label {
    fill: var(--text-secondary);
    font-family: var(--font-sans);
    font-size: 8px;
    text-anchor: middle;
}

.harmonic-chart .hc-unit {
    text-anchor: start;
}

.result-table .swatch {
    margin-right: 4px;
}

/* Harmonic power flow — per-bus voltage-THD bars, worst first. Each bar carries
   a dashed tick at its own IEEE 519 limit on the same scale, so a red bar
   dwarfing its tick reads as "far over limit" without reading the number. */
.thd-chart {
    width: 100%;
    height: auto;
    margin: var(--space-2) 0;
    overflow: visible;
}

.thd-chart .thd-bar-ok {
    fill: var(--status-positive-ink);
    opacity: 0.8;
}

.thd-chart .thd-bar-over {
    fill: var(--status-negative-ink);
    opacity: 0.85;
}

.thd-chart .thd-limit-tick {
    stroke: var(--text-secondary);
    stroke-width: 1;
    stroke-dasharray: 2 2;
    opacity: 0.7;
}

.thd-chart .thd-bus-label {
    fill: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: 9px;
    text-anchor: end;
    dominant-baseline: middle;
}

.thd-chart .thd-value {
    fill: var(--text-tertiary);
    font-family: var(--font-mono);
    font-size: 9px;
    text-anchor: start;
    dominant-baseline: middle;
}

.harmonic-caveat {
    color: var(--text-tertiary);
    line-height: 1.45;
    margin-top: var(--space-2);
}

/* CA summary badges */
.ca-summary, .sc-summary {
    margin-bottom: var(--space-2);
}

.ca-secure { color: var(--status-positive-ink); }

/* Drawn status dots, one per summary line, in the line's own color. */
.ca-secure::before,
.ca-warning::before,
.ca-critical::before {
    content: '';
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 999px;
    margin-right: var(--space-2);
    background: currentColor;
    vertical-align: -1px;
}
/* The contingency screen's verified-of-total line. It qualifies the counts
   above it rather than adding to them, so it is set smaller; the colour is
   already the summary block's and is deliberately not restated here. */
.ca-note {
    font-size: 0.9em;
}
.ca-warning { color: var(--status-warning-ink); }

/* The DC-OPF's AC Verification block (item AM). It qualifies the dispatch
   above it rather than continuing it, so it is set off by a rule. */
.opf-verify {
    margin-top: var(--space-3);
    padding-top: var(--space-2);
    border-top: 1px solid var(--bg-border);
}
.ca-critical { color: var(--status-negative-ink); }

/* Grid helpers for form layouts */
.grid-three {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: var(--space-2);
}

.checkbox-label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.8125rem;
    color: var(--text-secondary);
    cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
    width: 16px;
    height: 16px;
    accent-color: var(--accent);
}

/* Entity list items in PS mode */
.entity-list .entity-item .entity-meta {
    font-size: 10px;
    color: var(--text-tertiary);
    font-family: var(--font-mono);
}

/* CDF textarea */
#cdf-input,
#psse-input {
    width: 100%;
    min-height: 80px;
    font-family: var(--font-mono);
    font-size: 10px;
}

/* PSS/E import report — a list the user can re-read, not a toast */
.file-input-label {
    display: grid;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
    font-size: 11px;
    color: var(--text-secondary);
}

.file-input-label input[type="file"] {
    padding: 8px;
    font-size: 11px;
}

.import-report:empty {
    display: none;
}

.import-report {
    margin-top: var(--space-2);
    font-size: 11px;
    color: var(--text-secondary);
}

.import-report .import-meta {
    margin: var(--space-2) 0 0;
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--text-tertiary);
}

/* A 1px rule, not the 3px one the design scan reads as the side-tab tell. The
   border still carries the available/refused signal; it just stops shouting. */
.import-report .import-ops {
    margin: var(--space-2) 0 0;
    padding-left: var(--space-2);
    border-left: 1px solid var(--bg-border);
}

.import-report .import-ops.available {
    border-left-color: var(--status-positive-ink);
}

.import-report .import-ops.refused {
    border-left-color: var(--status-warning-ink, var(--status-negative-ink));
}

.import-report .import-warnings {
    margin: var(--space-2) 0 0;
    padding-left: 18px;
    display: grid;
    gap: 4px;
    max-height: 220px;
    overflow: auto;
}

.import-report .import-warnings li {
    line-height: 1.45;
}

.leaflet-tile-pane {
    filter: var(--map-filter);
}

#fallback-overlay {
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 20;
    overflow: visible;
}

.dashboard-overview {
    position: absolute;
    top: var(--hud-inset);
    left: var(--map-zoom-gutter);
    z-index: 30;
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0;
    width: auto;
    max-width: calc(100% - var(--map-zoom-gutter) - var(--editor-reserve) - var(--space-3));
    overflow: hidden;
    background: rgba(17, 20, 24, 0.94);
    border: 1px solid var(--bg-border);
    border-radius: 8px;
    backdrop-filter: blur(12px);
}

.dashboard-overview .card,
.dashboard-overview .stat-strip,
.dashboard-overview .stat-card {
    background: transparent;
    border: 0;
    border-radius: 0;
    backdrop-filter: none;
}

.card,
.panel-section,
.stat-card {
    background: rgba(17, 20, 24, 0.94);
    border: 1px solid var(--bg-border);
    border-radius: 8px;
    backdrop-filter: blur(12px);
}

html[data-theme="light"] .nav {
    background: rgba(248, 250, 252, 0.94);
}

/* `.card` and `.stat-card` are deliberately absent here. Every one of them
   lives inside .dashboard-overview, which paints the HUD's single background
   itself; claiming them here too only forced a counter-!important to undo it. */
html[data-theme="light"] .panel-section,
html[data-theme="light"] #control-panel,
html[data-theme="light"] .loading,
html[data-theme="light"] .leaflet-bar,
html[data-theme="light"] .leaflet-control-attribution {
    background: rgba(248, 250, 252, 0.94) !important;
}

/* These three carried raw white alphas — 0.96 / 0.88 / 0.98 — which is a
   second light palette beside the one the theme already defines. The tonal
   relationship they encoded (fields and the active row brighter than a resting
   row) is the surface stack's own, so they use it. */
html[data-theme="light"] input,
html[data-theme="light"] select,
html[data-theme="light"] textarea {
    background: var(--bg-surface);
}

html[data-theme="light"] .entity-item {
    background: var(--bg-surface-2);
}

html[data-theme="light"] .entity-item.active {
    background: var(--bg-surface);
}

html[data-theme="light"] button.secondary,
html[data-theme="light"] button.ghost,
html[data-theme="light"] .theme-toggle {
    background: rgba(16, 24, 40, 0.04);
}

html[data-theme="light"] button.secondary:hover,
html[data-theme="light"] button.ghost:hover,
html[data-theme="light"] .theme-toggle:hover {
    background: rgba(16, 24, 40, 0.08);
}

html[data-theme="light"] #map-hint {
    border-color: rgba(163, 230, 53, 0.28);
    background: rgba(236, 253, 245, 0.88);
}

.card {
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.card-label,
.panel-section h2,
.panel-section h3 {
    font-size: 0.6875rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-secondary);
}

.hero-card {
    flex: 0 1 auto;
    max-width: 220px;
    padding: var(--space-2) var(--space-3);
    gap: 0;
    justify-content: center;
}

.dashboard-overview .hero-card {
    border-right: 1px solid var(--bg-border);
}

#fallback-title span {
    font-size: 0.75rem;
    color: var(--text-secondary);
    line-height: 1.4;
}

.hero-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
}

.stat-strip {
    display: flex;
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
}

html[data-theme="light"] .dashboard-overview {
    background: rgba(248, 250, 252, 0.94);
}

.stat-card {
    flex: 1 1 0;
    padding: var(--space-2) var(--space-3);
    background: transparent;
    border: 0;
    border-radius: 0;
    backdrop-filter: none;
}

.stat-card + .stat-card {
    border-left: 1px solid var(--bg-border);
}

.stat-label {
    font-size: 0.6875rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-secondary);
    margin-bottom: var(--space-1);
}

.stat-value {
    font-family: var(--font-mono);
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--text-primary);
    letter-spacing: -0.02em;
    line-height: 1;
}

.stat-unit {
    font-size: 0.6875rem;
    font-weight: 400;
    color: var(--text-secondary);
    margin-left: var(--space-1);
}

.stat-delta {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    color: var(--text-secondary);
    margin-top: var(--space-1);
}

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    border-radius: 4px;
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.badge-live::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    animation: pulse 2s ease-in-out infinite;
}

.badge-good {
    color: var(--status-good-ink);
    background: rgba(163, 230, 53, 0.12);
}

.badge-moderate {
    color: var(--status-moderate-ink);
    background: rgba(250, 204, 21, 0.12);
}

.badge-bad {
    color: var(--status-bad-ink);
    background: rgba(239, 68, 68, 0.12);
}

#map-hint {
    position: absolute;
    left: var(--space-6);
    bottom: var(--space-4);
    z-index: 30;
    max-width: min(720px, calc(100vw - 80px));
    padding: var(--space-2) var(--space-3);
    border-radius: 4px;
    border: 1px solid rgba(163, 230, 53, 0.24);
    background: rgba(163, 230, 53, 0.08);
    color: var(--text-primary);
    font-size: 0.75rem;
    letter-spacing: 0.01em;
}

#control-panel {
    position: absolute;
    top: calc(var(--nav-h) + var(--space-4));
    right: var(--space-4);
    bottom: var(--space-4);
    width: min(var(--editor-w), calc(100vw - var(--space-8)));
    z-index: 40;
    overflow: auto;
    padding: var(--space-4);
    display: grid;
    gap: var(--space-4);
    background: rgba(17, 20, 24, 0.94);
    border: 1px solid var(--bg-border);
    border-radius: 8px;
    backdrop-filter: blur(12px);
    transition-property: transform, opacity, background-color, border-color;
    transition-duration: 180ms;
    transition-timing-function: ease;
}

body.editor-hidden #control-panel {
    opacity: 0;
    pointer-events: none;
    transform: translateX(calc(100% + var(--space-6)));
}

body.editor-hidden .dashboard-overview {
    width: auto;
    max-width: calc(100% - var(--map-zoom-gutter) - 24px);
}

body.editor-hidden #map-hint {
    max-width: min(920px, calc(100vw - 80px));
}

.panel-section {
    position: relative;
    padding: var(--space-4);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02);
}

.panel-section p,
.panel-section label,
.panel-section button,
.panel-section input,
.panel-section select,
.panel-section textarea {
    font-size: 0.8125rem;
}

.panel-section p {
    color: var(--text-secondary);
}

.section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

.stack {
    display: grid;
    gap: var(--space-3);
}

.grid-two {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
}

label {
    display: grid;
    gap: 6px;
    color: var(--text-secondary);
    font-weight: 500;
}

/* Keep field captions on one tidy line, align sub/sup */
.panel-section label {
    align-content: start;
    font-size: 0.75rem;
    line-height: 1.3;
    min-width: 0;
}

.panel-section label sub,
.panel-section label sup {
    font-size: 0.65em;
    line-height: 0;
}

input,
select,
button,
textarea {
    border-radius: 6px;
    font: inherit;
}

input,
select,
textarea {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--bg-border);
    background: rgba(10, 12, 15, 0.92);
    color: var(--text-primary);
}

input::placeholder,
textarea::placeholder {
    color: var(--text-tertiary);
}

input:focus,
select:focus,
textarea:focus,
button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

textarea {
    min-height: 160px;
    resize: vertical;
}

input[type="color"] {
    padding: 4px;
    min-height: 42px;
    background: rgba(10, 12, 15, 0.92);
}

button {
    cursor: pointer;
    padding: 10px 12px;
    border: 1px solid transparent;
    background: var(--accent-hover);
    color: #fff;
    font-weight: 500;
}

button:hover {
    background: var(--accent-deep);
}

button:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

button.secondary {
    background: rgba(139, 144, 153, 0.12);
    border-color: var(--bg-border);
    color: var(--text-primary);
}

button.secondary:hover,
button.ghost:hover {
    background: rgba(255, 255, 255, 0.08);
}

button.danger {
    background: rgba(239, 68, 68, 0.12);
    border-color: rgba(239, 68, 68, 0.25);
    color: var(--status-negative-ink);
}

button.ghost {
    background: rgba(24, 28, 34, 0.92);
    border-color: var(--bg-border);
    color: var(--text-primary);
}

button.active-toggle {
    background: rgba(250, 204, 21, 0.12);
    border-color: rgba(250, 204, 21, 0.25);
    color: var(--status-moderate-ink);
}

button.map-dirty {
    background: rgba(249, 115, 22, 0.18);
    border-color: rgba(249, 115, 22, 0.35);
    color: #ffd9c7;
}

button.map-dirty:hover {
    background: rgba(249, 115, 22, 0.28);
}

html[data-theme="light"] button.map-dirty {
    background: rgba(249, 115, 22, 0.12);
    border-color: rgba(249, 115, 22, 0.28);
    color: var(--status-poor-ink);
}

button.context-dirty {
    background: rgba(59, 130, 246, 0.2);
    border-color: rgba(59, 130, 246, 0.4);
    color: #dbeafe;
}

button.context-dirty:hover {
    background: rgba(59, 130, 246, 0.3);
}

html[data-theme="light"] button.context-dirty {
    background: rgba(59, 130, 246, 0.12);
    border-color: rgba(59, 130, 246, 0.28);
    color: var(--accent-ink);
}

.panel-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.panel-actions-spaced {
    margin-top: var(--space-3);
}

.panel-actions button {
    flex: 1 1 140px;
}

.entity-list {
    display: grid;
    gap: var(--space-2);
    margin-top: var(--space-3);
    max-height: 220px;
    overflow: auto;
}

.entity-item {
    display: grid;
    gap: var(--space-2);
    padding: var(--space-3);
    border-radius: 6px;
    border: 1px solid var(--bg-border);
    background: rgba(24, 28, 34, 0.88);
    cursor: pointer;
}

.entity-item.active {
    border-color: rgba(59, 130, 246, 0.7);
    background: rgba(24, 28, 34, 0.98);
}

.entity-item:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.entity-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text-primary);
}

.swatch {
    /* Blockified anyway as a flex item in the entity lists; spelled out so it
       also holds its size in ordinary inline flow (the harmonic result table). */
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.18);
    flex: 0 0 auto;
}

.entity-meta {
    color: var(--text-tertiary);
    font-size: 0.75rem;
    line-height: 1.5;
}

.leaflet-control-attribution,
.leaflet-control-zoom {
    z-index: 10;
}

.leaflet-bar,
.leaflet-control-attribution {
    border: 1px solid var(--bg-border) !important;
    background: rgba(17, 20, 24, 0.92) !important;
    color: var(--text-secondary) !important;
    box-shadow: none !important;
}

.leaflet-bar a {
    background: transparent !important;
    color: var(--text-primary) !important;
    border-bottom-color: var(--bg-border) !important;
}

.leaflet-bar a:hover {
    background: rgba(255, 255, 255, 0.06) !important;
}

.leaflet-control-attribution a {
    color: var(--accent-ink) !important;
}

/* The status line in its busy state: an accent pulse dot while an Op runs. */
.loading.busy::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-right: var(--space-2);
    border-radius: 999px;
    background: var(--accent);
    animation: pulse 2s ease-in-out infinite;
}

.loading {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: var(--space-4);
    z-index: 1000;
    pointer-events: none;
    padding: var(--space-2) var(--space-3);
    border-radius: 4px;
    border: 1px solid var(--bg-border);
    background: rgba(17, 20, 24, 0.94);
    color: var(--text-primary);
    font-size: 0.6875rem;
    white-space: nowrap;
}

/* Power infrastructure tooltip */
.leaflet-tooltip.power-tooltip {
    font-family: var(--font-sans) !important;
    font-size: 0.6875rem !important;
    font-weight: 500 !important;
    padding: 5px 10px !important;
    border-radius: 4px !important;
    border: 1px solid #2d3748 !important;
    background: #111418 !important;
    color: #e8eaed !important;
    white-space: nowrap !important;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}

html[data-theme="light"] .leaflet-tooltip.power-tooltip {
    background: #f8fafc !important;
    color: #101828 !important;
    border-color: #d7dee8 !important;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.leaflet-tooltip-top.power-tooltip::before {
    border-top-color: #2d3748 !important;
}

html[data-theme="light"] .leaflet-tooltip-top.power-tooltip::before {
    border-top-color: #d7dee8 !important;
}

@keyframes pulse {
    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.35;
    }
}

@keyframes value-update {
    0% {
        color: var(--accent-ink);
    }

    100% {
        color: var(--text-primary);
    }
}

.value-updating {
    animation: value-update 0.8s ease forwards;
}

::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

::-webkit-scrollbar-track {
    background: rgba(255, 255, 255, 0.04);
    border-radius: 999px;
}

::-webkit-scrollbar-thumb {
    background: rgba(139, 144, 153, 0.35);
    border-radius: 999px;
}

@media (max-width: 1200px) {
    .dashboard-overview {
        max-width: calc(100% - var(--map-zoom-gutter) - var(--editor-reserve) - var(--space-2));
    }
}

/* The timestamp is the first thing the bar gives up, and 1080 is not a round
   number — it is measured. .nav-status is centred by being taken out of flow,
   so nothing reserves room for it and it simply lands on whatever is there.
   With the timestamp it is 313px wide at its widest badge, and clearing both
   the 282px lockup and the 336px controls needs 1081px of bar. Below that it
   overlapped them: at 962px it sat 46px into the controls, text on text.

   Dropping the timestamp alone takes the cluster to 171px and buys the bar down
   to ~939px, which is what lets the lockup keep its strapline until 960. The
   time of the last update is the least load-bearing thing in the bar — the
   badge beside it says what the system is doing, and #loading-status carries
   the message itself. */
@media (max-width: 1080px) {
    .nav-status .nav-status-time {
        display: none;
    }
}

@media (max-width: 960px) {
    .nav {
        padding: 0 var(--space-4);
    }

    /* The lockup gives up its strapline here, and the status gives up its
       centring with it. Timestamp gone, the centred cluster still needs ~939px
       of bar to clear the 282px lockup and the 336px controls; below that the
       three simply do not fit on a centred line, and the failure mode of
       centring is not a squeeze but an overlap, because taking .nav-status out
       of flow means nothing reserves room for it. At 760px it sat 84px into the
       lockup and 137px into the controls, text on text.

       In flow, with space-between doing the placing, the three cannot overlap
       at any width by construction, and shedding the strapline takes the lockup
       from 282px to 89px. That combination carries one row down to ~666px
       (89 + 171 + 324 + 48 of gaps + 32 of padding = 664) — past 720, where the
       bar wraps for other reasons. `[mark] GAHUM` is also where the mark earns
       its keep: it is what leaves a lockup behind rather than bare text.

       .nav-xfmr is punctuation between two things that are no longer both
       present, so it goes with the strapline. It is aria-hidden, so `display:
       none` costs nothing. The strapline is not, so it is clipped instead —
       the h1's accessible name has to stay exactly "GAHUM Power System
       Modeler", and clipping keeps it in the tree where `display: none` would
       not. These are .sr-only's own declarations, spelled out because a class
       cannot be put on an element for one breakpoint only. */
    .nav-status {
        position: static;
        transform: none;
    }

    .nav-xfmr {
        display: none;
    }

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

    /* The HUD is one bar only while there is width for one. Below 960px it
       wraps — hero row, then the stat row — because forcing four cells onto a
       375px screen leaves ~75px each and the selection readout stops being
       readable. "One bar" is a desktop invariant, not a universal one. */
    .dashboard-overview {
        left: var(--map-zoom-gutter);
        right: var(--space-4);
        width: auto;
        max-width: none;
        flex-wrap: wrap;
    }

    .hero-card {
        max-width: none;
    }

    #control-panel {
        top: auto;
        left: var(--space-4);
        right: var(--space-4);
        bottom: var(--space-4);
        width: auto;
        max-height: 48vh;
    }

    /* The panel is bottom-anchored here, so nothing above it needs to move —
       and the banner's shift rule outranks `top: auto` on specificity alone. */
    body.storage-blocked #control-panel {
        top: auto;
    }

    body.editor-hidden #control-panel {
        transform: translateY(calc(100% + var(--space-6)));
    }

    #map-hint {
        bottom: calc(48vh + 28px);
        max-width: calc(100vw - 48px);
    }

    body.editor-hidden #map-hint {
        bottom: var(--space-4);
        max-width: calc(100vw - 48px);
    }
}

@media (max-width: 720px) {
    :root {
        --window-inset: 0px;
        --window-radius: 0px;
        /* Two rows of bar. 80px is 25px of lockup + an 8px row gap + 26px of
           controls, with the same ~10px of air above and below that the 48px
           bar gives its single row. Nothing reads --nav-h from JS — every
           consumer is a CSS `top`/`inset` on something below the bar — so
           redefining the token here is the whole of what moves the panes, the
           map and the storage banner down with it. */
        --nav-h: 80px;
    }

    /* The bar wraps. One row survives to ~666px once the centring is off (see
       the 960 block), but not to 375px: there are 341px of content width there
       against 171px for the widest badge ("Click map to add bus") and 324px for
       the four controls. Icon-only controls would not have closed that either,
       and were the wrong lever regardless — there is no icon language in this
       interface to draw four glyphs from, and inventing one to rescue a nav is
       the tail wagging the dog.

       So the controls take a row of their own and the lockup keeps the badge
       company on the first: 260px of worst case against 341px at 375px. Row
       assignment is DOM order (logo, status, meta) plus the flex-basis below —
       the `order` declarations this block used to carry only restated it.

       Below ~358px the control labels wrap to two lines rather than overflow.
       That is the intended give: the bar has room for it (71px of 80px) and
       every control stays hit-testable, where shrinking the set would not. */
    .nav {
        flex-wrap: wrap;
        align-content: center;
        gap: var(--space-2) var(--space-3);
    }

    .nav-meta {
        gap: var(--space-2);
    }

    /* Row two, on its own line, aligned to the lockup's left edge. All four
       controls stay: on a phone this button is the only Undo there is (there is
       no Ctrl+Z), the only way back from an auto-hidden editor, and the only
       route to the SLD. */
    .nav .nav-meta {
        flex-basis: 100%;
    }

    .dashboard-overview {
        top: var(--hud-inset);
        left: var(--map-zoom-gutter);
        right: var(--space-3);
        flex-wrap: wrap;
    }

    /* .stat-strip used to be listed here too, back when it was a grid. It is a
       flex row now and the three stat cards are meant to stay side by side —
       they get ~100px each at 375px, which reads fine — so the declaration was
       inert and the stacking it once did is not wanted. */
    .grid-two {
        grid-template-columns: 1fr;
    }

    #control-panel {
        left: var(--space-3);
        right: var(--space-3);
        bottom: var(--space-3);
        padding: var(--space-3);
        max-height: 54vh;
    }

    #map-hint {
        left: var(--space-3);
        right: var(--space-3);
        bottom: calc(54vh + 22px);
        max-width: none;
    }

    body.editor-hidden #map-hint {
        bottom: var(--space-3);
    }

    .panel-actions button {
        flex-basis: 100%;
    }
}

/* --- Continuous solve / live visualization --- */
.checkbox-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.8125rem;
    color: var(--text-secondary);
    cursor: pointer;
    margin-top: var(--space-2);
}

.checkbox-row input {
    accent-color: var(--accent, #3b82f6);
}

.field-hint {
    font-size: 0.75rem;
    color: var(--text-tertiary);
    margin: 4px 0 0;
}

/* The branch form's length + estimate row (backlog item Q). The input grows and
   the two buttons take what they need, all sharing a baseline — which is why
   this is not .grid-two (equal columns) or .panel-actions (no input to align
   against). */
.estimate-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: var(--space-2);
    align-items: end;
}

.sld-branch {
    cursor: pointer;
}

.sld-branch.sld-out line {
    opacity: 0.45;
}


/* --- UI audit pass ------------------------------------------------------ */

/* Any empty result box disappears instead of showing a gray sliver */
.lf-result:empty {
    display: none;
}

/* The mode switch is the primary nav action — make it read as one */
#view-mode-toggle {
    background: rgba(59, 130, 246, 0.16);
    border-color: rgba(59, 130, 246, 0.45);
    color: var(--accent-ink);
    font-weight: 600;
}

#view-mode-toggle:hover {
    background: rgba(59, 130, 246, 0.26);
}

/* The map's own editing controls are irrelevant while in SLD mode */
body.sld-mode #map-editor-group {
    display: none;
}

#map-editor-group {
    display: grid;
    gap: var(--space-4);
}

/* SLD overlay cards (toolbar + legend) */
.sld-overlay-card {
    position: absolute;
    z-index: 30;
    background: rgba(17, 20, 24, 0.94);
    border: 1px solid var(--bg-border);
    border-radius: 8px;
    backdrop-filter: blur(12px);
    padding: var(--space-2) var(--space-3);
}

html[data-theme="light"] .sld-overlay-card {
    background: rgba(248, 250, 252, 0.94);
}

#sld-toolbar {
    top: var(--space-4);
    left: var(--space-6);
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

.sld-toolbar-title {
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--text-primary);
    letter-spacing: -0.01em;
}

.sld-toolbar-meta {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    color: var(--text-tertiary);
}

.sld-status {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 600;
    padding: 4px var(--space-2);
    border-radius: 4px;
    white-space: nowrap;
}

.sld-status.ok {
    color: var(--status-positive-ink);
    background: rgba(74, 222, 128, 0.12);
}

.sld-status.bad {
    color: var(--status-negative-ink);
    background: rgba(239, 68, 68, 0.12);
}

.sld-status.idle {
    color: var(--text-tertiary);
    background: rgba(139, 144, 153, 0.1);
}

.sld-status::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 999px;
    margin-right: var(--space-1);
    background: currentColor;
    vertical-align: -1px;
}

#sld-legend {
    bottom: var(--space-4);
    left: var(--space-6);
    display: grid;
    gap: 5px;
    font-size: 0.6875rem;
    color: var(--text-secondary);
    max-width: 300px;
}

.legend-title {
    font-size: 0.625rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-tertiary);
}

.legend-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    line-height: 1.35;
}

/* Exposed to assistive tech, never painted. The SLD's describedby note is the
   only user so far: role="img" hides the SVG subtree, so the text that says
   where the diagram's interaction lives has to sit outside it. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.legend-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex: 0 0 auto;
}

/* The legend publishes the status ramp, so it reads the ramp's own tokens
   instead of restating them inline — which is how it came to advertise a green
   (#22c55e) and an amber (#f59e0b) that were not the system's, next to the
   system's own. `sld-layout.js` draws from the same four steps. */
.legend-dot.ramp-excellent { background: var(--status-excellent); }
.legend-dot.ramp-moderate { background: var(--status-moderate); }
.legend-dot.ramp-poor { background: var(--status-poor); }
.legend-dot.ramp-bad { background: var(--status-bad); }

/* Bus-type rings, matching sld-layout.js's BUS_TYPE_COLORS. */
.legend-ring.ring-slack { border-color: var(--status-bad); }
.legend-ring.ring-pv { border-color: var(--status-excellent); }
.legend-ring.ring-pq { border-color: var(--accent); }

.legend-ring {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2.5px solid;
    flex: 0 0 auto;
}

.legend-sep {
    height: 1px;
    background: var(--bg-border);
    margin: 2px 0;
}

.legend-note {
    color: var(--text-tertiary);
}

.legend-hint {
    color: var(--accent-ink);
    font-weight: 500;
}

@media (max-width: 720px) {
    #sld-legend {
        display: none;
    }

    #sld-toolbar {
        left: var(--space-3);
        right: var(--space-3);
        top: var(--space-3);
        justify-content: space-between;
    }
}
