:root {
    /* Native scrollbars, form controls and the <select> dropdown follow the
       theme instead of rendering light-grey chrome on the dark UI. */
    color-scheme: dark;

    --bg-main: #111111;
    --bg-panel: #151515;
    --bg-card: #252525;
    /* Fleet Map station cards — kept as its own variable (currently equal to
       --bg-card) so the cards can be re-tinted independently of drawer/modal
       surfaces without the two losing contrast against each other. */
    --bg-station-card: #252525;
    --bg-container: #1a1a1a;
    --border-color: #333333;
    --text-main: #ffffff;
    --text-sub: #aaaaaa;
    --panel-header: #000000;
    --detail-text: #ffffff;

    /* Evidence modal (station history) header + operator-review buttons.
       Kept as their OWN variables — not --panel-header — so tuning them
       doesn't also change the tab bar / other drawer and modal headers that
       share --panel-header. Dark theme uses a slate/grey family (same idea
       as the SuspendedEV card color, #5d6b80) rather than navy, which read
       as flat/mismatched against the near-black dark background. To change
       these yourself: edit the three lines below (dark) and their light-
       theme counterparts further down in this file. */
    --evidence-head-bg:  #4a5456;  /* modal title bar */
    --action-neutral-bg: #5d6b80;  /* "No issue — was transient" button */
    --action-neutral-fg: #ffffff;  /* its text color (5.4:1 on the slate; dark text only reached 3.3:1) */
    /* "Real issue confirmed" — a deeper shade of the Issue Confirmed red, so
       the button reads as the state it produces and can never be mistaken for
       the neutral "clear" action beside it (the two used to be near-identical
       greys). Deepened from #ce5750 so white text clears 4.5:1. */
    --action-confirm-bg: #b5443e;
    --action-confirm-fg: #ffffff;

    /* -------------------------------------------------------------------
       Semantic UI colors. The 13 status colors (legend, markers, card
       borders) are a fixed convention and stay exactly as they are; these
       are for TEXT and interface state, which must stay legible on both
       themes. Dark theme uses the status hue itself wherever it clears
       4.5:1; light theme uses a deeper shade of the same hue, because the
       status colors as text on the pale light surfaces drop to 2-3:1.
       ------------------------------------------------------------------- */
    --text-heading: #ffffff;
    --text-danger:  #e7635c;   /* Faulted hue */
    --text-ok:      #4faf7c;   /* Available hue */
    --text-warn:    #e0a021;   /* amber — analyser findings, approximations */
    --text-info:    #5e9aab;   /* Charging hue — an open session */
    --text-slate:   #8a97ab;   /* SuspendedEV family, lifted for dark-bg legibility */

    /* One interactive accent, used for primary actions, focus and the active
       tab — deliberately NOT a status hue, so a button never reads as a
       station state. Replaces four drifting blues (#3498db, #2e9fe0, #5dade2,
       #6fa8dc). --accent-bright sits on the tab bar, which is dark in both
       themes. */
    --accent:        #3498db;
    --accent-bright: #3498db;

    /* Top tab bar. Its own tokens rather than --panel-header, which the drawer
       and modal headers share - retinting the bar must not retint those. */
    --tabbar-bg:        var(--panel-header);
    --tabbar-indicator: var(--accent-bright);   /* active underline + focus ring */
    --tabbar-shadow:    none;
    --accent-fill:   #2b78bd;   /* filled buttons: white text 4.6:1 */
    --accent-fill-hover: #2468a6;
    --focus-ring: 0 0 0 2px var(--bg-main), 0 0 0 4px var(--accent);

    /* -------------------------------------------------------------------
       Type system. Inter + JetBrains Mono — the standard pairing across
       modern dashboards/dev tools (Linear, Vercel, GitHub, Figma all use
       this combination). Loaded via Google Fonts in the HTML <head>, with
       a system-font fallback stack so text still renders correctly if the
       webfont fails to load. Replaces 'Calibri', which is Windows/Office-
       only — it silently fell back to a different font on Mac/Linux,
       which was part of why the type felt inconsistent.

       Sizes are a deliberate scale, not ad hoc values — but the ones used
       inside the virtualized station-card grid (.card and its children)
       are kept numerically UNCHANGED from before: grid.js positions each
       card with a fixed pixel height (ROW_HEIGHT) computed from the
       current text size, so enlarging that text would clip inside its
       slot. Everywhere else (drawers, tables, page headers, tiles) was
       free to move onto the proper scale.
       ------------------------------------------------------------------- */
    --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --font-mono: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace;

    --fs-2xs: 10px;  /* card address line — tightest meta text */
    --fs-xs:  11px;  /* table cells/headers, card status, list-row meta */
    --fs-sm:  12px;  /* secondary body text, buttons, badges */
    --fs-base:13px;  /* default UI text — drawer body, search, tab bar */
    --fs-lg:  16px;  /* drawer/section titles */
    --fs-2xl: 24px;  /* page headers, stat numbers */
    --fs-3xl: 32px;  /* hero stat numbers (Diagnostics tiles) */

    --fw-regular:  400;
    --fw-medium:   500;
    --fw-semibold: 600;
    --fw-bold:     700;
}
[data-theme="light"] {
    color-scheme: light;

    --bg-main: #f5f5f5;
    --bg-panel: #eef1f8;
    --bg-card: #dde3ee;
    --bg-station-card: #eef1f8;
    --bg-container: #e4e9f2;
    --border-color: #dddddd;
    --text-main: #222222;
    --text-sub: #5c5c5c;   /* #666 dipped to 4.4:1 on --bg-card */
    --detail-text: #222222;

    /* The light theme's one dark blue. Every navy surface and heading points
       here, so it can be retuned in one place. It replaced three near-black
       navies that had drifted apart (#00082d on the bars and headers, #001f5b
       on headings, #1a3a6b on the "No issue" button). White on it is 9.4:1;
       as heading text it holds 7.7:1 even on --bg-container. */
    --navy: #1d4877;

    --panel-header: var(--navy);
    --tabbar-bg:    var(--navy);
    /* The accent blue sits at 3:1 against the navy, too faint for an underline,
       hence the pale-sky indicator (6:1). The shadow separates the bar from
       map tiles that would otherwise run straight into it. */
    --tabbar-indicator: #a9d4ff;
    --tabbar-shadow:    0 1px 2px rgba(12, 32, 58, 0.16), 0 4px 12px rgba(12, 32, 58, 0.12);

    --evidence-head-bg:  var(--navy);
    --action-neutral-bg: var(--navy);
    --action-neutral-fg: #ffffff;

    --text-heading: var(--navy);
    --text-danger:  #b3372f;
    --text-ok:      #27704a;
    --text-warn:    #865a00;
    --text-info:    #2b6272;
    --text-slate:   #56647a;
    --accent:       #2a66a3;
}

body { margin: 0; padding: 0; background: var(--bg-main); font-family: var(--font-sans); color: var(--text-main); display: flex; flex-direction: column; height: 100vh; height: 100dvh; transition: background 0.3s, color 0.3s; }

/* Browser surfaces follow the palette too: selection, native checkbox/radio
   tint, and one visible keyboard-focus ring used by every control. */
::selection { background: rgba(52,152,219,0.35); }
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent-fill); }
:focus { outline: none; }
:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Counts, timestamps and telemetry line up column to column. */
#stats-display, .legend-tile-count, .diag-table, .la-table, .card .status, .dcard .metric,
.settings-preview, .usage-axis { font-variant-numeric: tabular-nums; }

/* Inline SVG icons (stroke-based, one weight) — replace the emoji/Unicode
   glyphs that used to stand in for them. */
.icon { width: 1em; height: 1em; display: block; fill: none; stroke: currentColor; stroke-width: 2;
        stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }

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

/* Form controls don't inherit font-family from the page by default (browser
   UA stylesheets give them a system font) — this is the standard reset so
   every button/input/textarea picks up var(--font-sans) instead of each
   needing its own font-family declaration repeated throughout the file. */
button, input, textarea, select { font: inherit; }

/* Top tab bar — switches #view-map / #view-diagnostics. Both views (and every
   drawer) stay mounted at all times; this only toggles which is visible, so
   there's no page reload and the WebSocket connection never drops. */
/* position + z-index so the light theme's shadow paints over the map below
   (Leaflet's controls sit at 1000); drawers and modals are at 9990+. */
#app-tabs { flex-shrink: 0; height: 44px; background: var(--tabbar-bg); box-shadow: var(--tabbar-shadow);
            position: relative; z-index: 1100; display: flex; padding: 0 12px; gap: 4px;
            transition: background 0.3s, box-shadow 0.3s; }
/* 0.72, not 0.65: inactive labels hold 5.8:1 on the light bar's navy rather
   than scraping 5.0. Still clearly quieter than the white active tab. */
.tab-btn { background: transparent; border: none; color: rgba(255,255,255,0.72); white-space: nowrap;
           font-size: var(--fs-base); font-weight: var(--fw-semibold); padding: 0 18px; cursor: pointer; position: relative; transition: color 0.15s; }
.tab-btn:hover { color: #fff; }
.tab-btn.active { color: #fff; }

/* One inset plate serves hover and keyboard focus, so both states have the
   same shape. z-index -1 puts it under the label and above the bar's
   background (#app-tabs is the stacking context). */
.tab-btn::before, .tab-icon-btn::before {
    content: ''; position: absolute; inset: 7px 6px; z-index: -1; border-radius: 6px;
    background: rgba(255,255,255,0.09); opacity: 0; transition: opacity 0.15s, box-shadow 0.15s; }
.tab-btn:hover::before, .tab-icon-btn:hover::before { opacity: 1; }
.tab-btn:focus-visible, .tab-icon-btn:focus-visible { outline: none; }
.tab-btn:focus-visible::before, .tab-icon-btn:focus-visible::before {
    opacity: 1; box-shadow: inset 0 0 0 2px var(--tabbar-indicator); }

.tab-btn.active::after { content: ''; position: absolute; left: 12px; right: 12px; bottom: 0; height: 3px;
                         background: var(--tabbar-indicator); border-radius: 2px 2px 0 0; }
@media (max-width: 480px) {
    #app-tabs { padding: 0 4px; gap: 0; }
    .tab-btn { padding: 0 10px; }
    .tab-btn.active::after { left: 6px; right: 6px; }
}

/* Views fill the remaining height below the tab bar. Proportions that used
   to be viewport-relative (65vh/35vh) are now flex-grow ratios of the same
   65:35 split, so they still work now that the tab bar takes real space. */
.app-view { display: none; }
.app-view.active { display: flex; flex-direction: column; flex: 1; min-height: 0; }

#top-container { flex: 65 1 0%; width: 100%; display: flex; position: relative; min-height: 0; }
#map { height: 100%; flex-grow: 1; }

/* OSM only ships one (light) tile style — see js/map.js for why. Dark theme
   (the default, no [data-theme] attribute) inverts the tile pane to fake a
   dark basemap instead; light theme leaves the tiles unmodified. */
#map .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9) saturate(0.7); }
[data-theme="light"] #map .leaflet-tile-pane { filter: none; }
#map { background: var(--bg-container); font-family: var(--font-sans); }

/* Leaflet's own chrome (zoom buttons, popups, attribution) ships white in
   every theme — themed here so it belongs to the dashboard. */
#map .leaflet-bar { border: 1px solid var(--border-color); box-shadow: 0 2px 6px rgba(0,0,0,0.3); border-radius: 8px; overflow: hidden; }
#map .leaflet-bar a { background: var(--bg-panel); color: var(--text-main); border-bottom-color: var(--border-color); }
#map .leaflet-bar a:hover { background: var(--bg-card); }
#map .leaflet-bar a.leaflet-disabled { color: var(--text-sub); background: var(--bg-panel); }
#map .leaflet-popup-content-wrapper, #map .leaflet-popup-tip { background: var(--bg-panel); color: var(--text-main);
    box-shadow: 0 6px 20px rgba(0,0,0,0.35); }
#map .leaflet-popup-content-wrapper { border-radius: 8px; border: 1px solid var(--border-color); }
#map .leaflet-popup-content { margin: 10px 14px; font-size: var(--fs-sm); line-height: 1.5; }
#map .leaflet-popup-content .pop-addr { font-weight: var(--fw-semibold); }
#map .leaflet-popup-content .pop-meta { color: var(--text-sub); }
#map a.leaflet-popup-close-button { color: var(--text-sub); }
#map .leaflet-control-attribution { background: rgba(21,21,21,0.75); color: var(--text-sub); }
#map .leaflet-control-attribution a { color: var(--accent); }
[data-theme="light"] #map .leaflet-control-attribution { background: rgba(255,255,255,0.8); }

/* Cluster bubbles. Leaflet.markercluster's default green/yellow/orange
   encodes only how MANY stations a bubble holds — and those hues read as
   Available / Flagged on this map. Bubbles are neutral instead; a ring in a
   status color appears only when the cluster hides a problem station
   (worst-status-wins, same rule as a multi-connector card — see map.js). */
.fleet-cluster { background: transparent; }
.fleet-cluster span { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;
    box-sizing: border-box; border-radius: 50%; background: var(--bg-panel); color: var(--text-main);
    border: 2px solid var(--border-color); font-size: var(--fs-sm); font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums; box-shadow: 0 2px 8px rgba(0,0,0,0.35); }
.fleet-cluster.has-alert span { border: 3px solid var(--ring); }

/* -----------------------------------------------------------------------
   Drawer chrome. The station detail panel (Fleet Map) is the only drawer
   left after the Diagnostics redesign moved to status cards + the evidence
   modal, but the shared .drawer-* classes are kept generic in case another
   is added. Left-anchored per the map-UI convention: a workflow panel
   belongs on the left with the map staying dominant.
   ----------------------------------------------------------------------- */
.drawer-overlay {
    display: none; /* JS sets display:flex before adding .open */
    position: fixed; inset: 0;
    justify-content: flex-start;
    background: rgba(0,0,0,0);
    backdrop-filter: blur(0px);
    transition: background 0.28s ease, backdrop-filter 0.28s ease;
}
.drawer-overlay.open { background: rgba(0,0,0,0.45); backdrop-filter: blur(3px); }
.drawer-box {
    background: var(--bg-panel);
    max-width: 92vw; height: 100vh;
    display: flex; flex-direction: column; overflow: hidden;
    box-shadow: 6px 0 32px rgba(0,0,0,0.45);
    border-radius: 0 12px 12px 0;
    transform: translateX(-100%);
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
.drawer-overlay.open .drawer-box { transform: translateX(0); }
.drawer-head {
    background: var(--panel-header); padding: 14px 18px; color: #fff;
    display: flex; align-items: center; justify-content: space-between;
    border-radius: 0 12px 0 0; flex-shrink: 0;
}
.drawer-head .title { font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.drawer-head .addr  { font-size: var(--fs-sm); opacity: 0.8; margin-top: 2px; }
.drawer-close {
    background: rgba(255,255,255,0.15); border: none; color: #fff; font-size: var(--fs-lg);
    width: 30px; height: 30px; border-radius: 6px; cursor: pointer; line-height: 1;
    display: flex; align-items: center; justify-content: center; padding: 0;
    transition: background 0.15s; flex-shrink: 0;
}
.drawer-close:hover { background: rgba(255,255,255,0.28); }
/* Title bars are dark in both themes, so ring against white. */
.drawer-close:focus-visible { box-shadow: 0 0 0 2px #fff; }

/* Station detail drawer */
#detail-overlay { z-index: 9992; }
#detail-box { width: 380px; }
#detail-content { padding: 12px; font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--detail-text); overflow-y: auto; flex-grow: 1; }

/* Live fault-reason banner shown at the top of the detail drawer when a
   station has one or more connectors currently Faulted. */
.detail-fault-banner { background: rgba(231,99,92,0.12); border: 1px solid #e7635c;
                       border-radius: 6px; padding: 10px 12px; margin-bottom: 12px; }
.detail-fault-banner .fault-title { color: var(--text-danger); font-weight: var(--fw-bold); font-size: var(--fs-base); margin-bottom: 6px; }
.detail-fault-banner .fault-row { font-size: var(--fs-sm); color: var(--text-main); padding: 2px 0; }
.detail-fault-banner .fault-conn { font-weight: var(--fw-bold); }

.detail-charging-banner { background: rgba(94,154,171,0.12); border: 1px solid #5e9aab;
                          border-radius: 6px; padding: 10px 12px; margin-bottom: 12px; }
.detail-charging-banner .charge-title { color: var(--text-info); font-weight: var(--fw-bold); font-size: var(--fs-base); margin-bottom: 6px; }
.detail-charging-banner .charge-row { font-size: var(--fs-sm); color: var(--text-main); padding: 2px 0; }
.detail-charging-banner .charge-conn { font-weight: var(--fw-bold); }

.detail-unused-banner { background: rgba(224,193,69,0.14); border: 1px solid #e0c145;
                        border-radius: 6px; padding: 10px 12px; margin-bottom: 12px; }
.detail-unused-banner .unused-title { color: var(--text-warn); font-weight: var(--fw-bold); font-size: var(--fs-base); margin-bottom: 6px; }
.detail-unused-banner .unused-row { font-size: var(--fs-sm); color: var(--text-main); padding: 2px 0; }
.detail-unused-banner .unused-conn { font-weight: var(--fw-bold); }

/* VPN section, at the top of the detail panel (js/vpnSection.js). */
.detail-vpn-banner { background: rgba(94,154,171,0.08); border: 1px solid var(--border-color);
                     border-radius: 6px; padding: 10px 12px; margin-bottom: 12px; }
.detail-vpn-banner.vpn-banner-anomaly { background: rgba(231,99,92,0.10); border-color: #e7635c; }
.detail-vpn-banner .vpn-title { color: var(--text-heading); font-weight: var(--fw-bold); font-size: var(--fs-base); margin-bottom: 6px; }
.detail-vpn-banner .vpn-warning { color: var(--text-danger); font-weight: var(--fw-semibold); font-size: var(--fs-sm); margin-bottom: 6px; }
.detail-vpn-banner .vpn-entry + .vpn-entry { border-top: 1px solid var(--border-color); margin-top: 8px; padding-top: 8px; }
.detail-vpn-banner .vpn-where { font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-main); }
.detail-vpn-banner .vpn-row { font-size: var(--fs-sm); color: var(--text-main); padding: 2px 0; }
.detail-vpn-banner .vpn-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }
.detail-vpn-banner .vpn-link,
.detail-vpn-banner .vpn-switch,
.detail-vpn-banner .log-btn,
.detail-vpn-banner .vpn-refresh { font: inherit; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-main);
                                  background: transparent; border: 1px solid var(--text-sub); border-radius: 4px;
                                  padding: 3px 10px; text-decoration: none; cursor: pointer; }
.detail-vpn-banner .vpn-link:hover,
.detail-vpn-banner .vpn-switch:hover:not(:disabled),
.detail-vpn-banner .log-btn:hover:not(:disabled),
.detail-vpn-banner .vpn-refresh:hover:not(:disabled) { background: var(--text-sub); color: var(--bg-card); }
.detail-vpn-banner .vpn-link:focus-visible,
.detail-vpn-banner .vpn-switch:focus-visible,
.detail-vpn-banner .log-btn:focus-visible,
.detail-vpn-banner .vpn-refresh:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.detail-vpn-banner .vpn-refresh:disabled,
.detail-vpn-banner .vpn-switch:disabled,
.detail-vpn-banner .log-btn:disabled { opacity: 0.5; cursor: wait; }
/* Outcome of the last command sent to the station (js/vpnSection.js). */
.detail-vpn-banner .vpn-command-running { color: var(--text-info); }
.detail-vpn-banner .vpn-command-done    { color: var(--text-ok); }
.detail-vpn-banner .vpn-command-failed,
.detail-vpn-banner .vpn-command-refused { color: var(--text-danger); }
.detail-vpn-banner .vpn-checked { font-size: var(--fs-xs); color: var(--text-sub); }

.detail-line { display: block; padding: 2px 4px; border-radius: 2px; transition: background-color 0.5s ease; }

#controls-overlay { position: absolute; top: 10px; left: 50px; z-index: 1000; background: var(--bg-panel); padding: 10px 15px; border-radius: 8px; border: 1px solid var(--border-color); font-size: var(--fs-sm); display: flex; align-items: center; gap: 20px; box-shadow: 0 2px 6px rgba(0,0,0,0.3); transition: background 0.3s, border-color 0.3s; }
#stats-display { color: var(--text-sub); display: flex; flex-wrap: wrap; row-gap: 4px; }
#stats-display .stat { white-space: nowrap; padding: 0 10px; }
#stats-display .stat:first-child { padding-left: 0; }
#stats-display .stat + .stat { border-left: 1px solid var(--border-color); }
#stats-display b { color: var(--text-main); font-weight: var(--fw-bold); margin-left: 3px; }

/* Proxy connection indicator — green dot when live, red "Reconnecting…"
   while the auto-reconnect loop is retrying. */
#conn-status { font-size: var(--fs-xs); font-weight: var(--fw-semibold); padding: 3px 9px; border-radius: 10px; white-space: nowrap; }
#conn-status.connected    { color: var(--text-ok);     background: rgba(79,175,124,0.14); }
#conn-status.disconnected { color: var(--text-danger); background: rgba(231,99,92,0.14); }
#conn-status.connecting   { color: var(--text-sub); background: rgba(127,127,127,0.12); }

/* Status key. A <details> so it can fold away on a phone, where 13 rows
   would cover most of the map; it starts open everywhere else (see map.js). */
#map-legend { position: absolute; bottom: 20px; left: 20px; z-index: 1000; background: var(--bg-panel); padding: 10px; border-radius: 6px; border: 1px solid var(--border-color); font-size: var(--fs-xs); box-shadow: 0 2px 6px rgba(0,0,0,0.3); transition: background 0.3s, border-color 0.3s; }
#map-legend summary { cursor: pointer; font-weight: var(--fw-semibold); color: var(--text-sub); list-style: none;
    display: flex; align-items: center; gap: 6px; border-radius: 4px; }
#map-legend summary::-webkit-details-marker { display: none; }
#map-legend summary .icon { transition: transform 0.15s; }
#map-legend:not([open]) summary .icon { transform: rotate(-90deg); }
#map-legend[open] summary { margin-bottom: 8px; }
.legend-items { display: flex; flex-direction: column; gap: 5px; }
.legend-item { display: flex; align-items: center; gap: 8px; color: var(--text-main); }
.legend-color { width: 12px; height: 12px; border-radius: 50%; border: 1px solid #fff; }

.theme-switch-wrapper { display: flex; align-items: center; gap: 8px; font-weight: var(--fw-bold); color: var(--text-main); cursor: pointer; }
.theme-switch { position: relative; display: inline-block; width: 36px; height: 20px; }
/* Visually hidden but still focusable — the ring is drawn on the slider. */
.theme-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #8a8a8a; transition: .3s; border-radius: 20px; pointer-events: none; }
.slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }
input:checked + .slider { background-color: var(--accent-fill); }
input:checked + .slider:before { transform: translateX(16px); }
input:focus-visible + .slider { box-shadow: var(--focus-ring); }

/* Phone: the overlay wraps inside the map instead of running off-screen. */
@media (max-width: 600px) {
    #controls-overlay { left: 50px; right: 10px; flex-wrap: wrap; gap: 8px 14px; padding: 8px 12px; }
    #map-legend { bottom: 12px; left: 10px; }
}

#stations-container { flex: 35 1 0%; min-height: 0; background: var(--bg-container); border-top: 3px solid var(--border-color); padding: 15px; box-sizing: border-box; display: flex; flex-direction: column; transition: background 0.3s, border-color 0.3s; }
/* Section title — one step below the page headers (h1, --fs-2xl) that the
   other tabs carry, so a list title never competes with a page title. */
#stations-title { font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--text-heading); }
#stations-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; margin-bottom: 10px; }

/* Station search: filters the cards; Enter opens the first match on the map. */
#station-search { position: relative; flex: 0 1 320px; min-width: 200px; }
#station-search .icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
    width: 14px; height: 14px; color: var(--text-sub); pointer-events: none; }
#station-search-input { width: 100%; box-sizing: border-box; padding: 6px 10px 6px 31px;
    background: var(--bg-card); color: var(--text-main); border: 1px solid var(--border-color);
    border-radius: 6px; font-size: var(--fs-sm); transition: border-color 0.15s, box-shadow 0.15s; }
#station-search-input::placeholder { color: var(--text-sub); }
#station-search-input:hover { border-color: var(--text-sub); }
#station-search-input:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
#station-search-count { font-size: var(--fs-xs); color: var(--text-sub); font-variant-numeric: tabular-nums; }
#stations-no-match { color: var(--text-sub); font-size: var(--fs-base); padding: 20px 4px; }

/* VPN filter chips and the VPN reading line, on their own row under the search. */
#vpn-filter-bar { flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
#vpn-filter-bar[hidden] { display: none; }
#vpn-filters { display: flex; flex-wrap: wrap; gap: 6px; }
.vpn-chip { font: inherit; font-size: var(--fs-sm); color: var(--text-main); background: transparent;
            border: 1px solid var(--border-color); border-radius: 999px; padding: 3px 10px; cursor: pointer; }
.vpn-chip:hover { border-color: var(--text-sub); }
.vpn-chip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.vpn-chip[aria-pressed="true"] { background: var(--accent-fill); border-color: var(--accent-fill); color: #ffffff; }
.vpn-chip[hidden] { display: none; }
.vpn-chip .chip-count { opacity: 0.75; font-variant-numeric: tabular-nums; }
.vpn-chip.chip-anomaly:not([aria-pressed="true"]) { color: var(--text-danger); border-color: var(--text-danger); }
#vpn-sources { font-size: var(--fs-xs); color: var(--text-sub); }
#vpn-sources.warn { color: var(--text-warn); }
#stations-no-match[hidden] { display: none; }
/* Virtualized grid: #stations-grid is the scroll viewport, #stations-sizer
   gives it the correct scrollable height, and individual .card elements
   are absolutely positioned within it — only the visible ones exist in the DOM. */
#stations-grid { position: relative; overflow-y: auto; flex-grow: 1; padding-right: 5px; }
#stations-sizer { position: relative; width: 100%; }

/* Every card is exactly one grid slot tall (grid.js sets height to
   ROW_HEIGHT minus the gap), laid out as a column with the button pinned to
   the bottom: a status that wraps to two lines used to make its card taller
   than its neighbours, leaving ragged rows. The status clamps at two lines.
   Line-height is explicit so the stack is deterministic inside the slot. */
.card { position: absolute; box-sizing: border-box; background: var(--bg-station-card); padding: 10px; border-radius: 5px; border-top: 4px solid #555; text-align: center; font-size: var(--fs-sm); line-height: 1.2; transition: background 0.3s, border-color 0.3s, transform 0.1s; cursor: pointer; color: var(--text-main);
        display: flex; flex-direction: column; }
.card:hover { background: var(--bg-card); }
.card .sn { font-weight: var(--fw-bold); margin-bottom: 2px; }
/* The serial is the card's real control (keyboard + screen reader); its
   ::after stretches over the whole card so a click anywhere still opens it. */
.card .card-open { all: unset; cursor: pointer; }
.card .card-open::after { content: ''; position: absolute; inset: 0; border-radius: 5px; }
.card .card-open:focus-visible { box-shadow: none; }
.card .card-open:focus-visible::after { box-shadow: var(--focus-ring); }
/* On MQTT and connected to a company VPN. */
.card .vpn-tag { display: inline-block; margin-left: 5px; padding: 0 4px; border: 1px solid var(--text-info); border-radius: 3px;
                 color: var(--text-info); font-size: var(--fs-2xs); font-weight: var(--fw-semibold); line-height: 1.4; vertical-align: 1px; }
.card .vpn-tag[hidden] { display: none; }
.card .addr { color: var(--text-sub); font-size: var(--fs-2xs); margin-bottom: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card .status { color: var(--text-sub); font-size: var(--fs-xs);
                display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* The button beneath a card in VPN — Panel (the Station tab) or SPV on a
   SeleBox, SSH on a CST; diagnosis/review tools moved to the Diagnostics tab,
   and Download Log and SPV to the detail panel. Panel is a <button>, the
   others links: the same look. */
.card .card-btns { display: flex; gap: 5px; margin-top: auto; padding-top: 4px; }
.card .card-btns a, .card .card-btns button {
                     flex: 1; padding: 3px 0; position: relative; z-index: 1; /* above .card-open::after */
                     font-family: inherit; line-height: inherit; text-align: center;
                     font-size: var(--fs-2xs); font-weight: var(--fw-semibold); color: var(--text-main);
                     background: transparent; border: 1px solid var(--text-sub);
                     border-radius: 4px; cursor: pointer; text-decoration: none; }
.card .card-btns a:hover, .card .card-btns button:hover { background: var(--text-sub); color: var(--bg-card); }
.card .card-btns a:focus-visible, .card .card-btns button:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Status colors — keep in sync with CATEGORY_MARKER in js/colors.js and the
   --tile-color values in the HTML legend. Every OCPP status has its own class
   so none of them fall back to .waiting, which means "no data yet". */
.waiting   { border-top-color: #e2e2e2 !important; }
.available { border-top-color: #4faf7c !important; }
.preparing { border-top-color: #afd8c3 !important; }
.charging  { border-top-color: #5e9aab !important; box-shadow: 0 0 8px rgba(94,154,171,0.5); }
.finishing { border-top-color: #8593c4 !important; }
.faulted   { border-top-color: #e7635c !important; }
.suspended { border-top-color: #5d6b80 !important; opacity: 0.7; }
.suspended-evse { border-top-color: #7c8ca3 !important; opacity: 0.7; }
.reserved  { border-top-color: #d7d8e0 !important; }
/* Out of service: dimmed like .offline — it is deliberately not operational,
   as opposed to merely idle. */
.unavailable { border-top-color: #8d7f70 !important; opacity: 0.55; }
.offline   { border-top-color: #454f63 !important; opacity: 0.4; }
.suspicious          { border-top-color: #efa94f !important; box-shadow: 0 0 8px rgba(239,169,79,0.4); }
.suspicious-confirmed{ border-top-color: #ce5750 !important; box-shadow: 0 0 8px rgba(206,87,80,0.4); }
/* Unused connector (connector-level rule). Its own amber, yellower than
   Flagged's orange, so the two lists are never mistaken for each other. */
.unused              { border-top-color: #e0c145 !important; box-shadow: 0 0 8px rgba(224,193,69,0.35); }
/* Stations only the VPN knows (not on MQTT). Dashed, so neither reads as an
   OCPP status: a SeleBox there is an anomaly, a CST is normal. */
.card.vpn-anomaly { border-top-color: #ce5750 !important; border-top-style: dashed; }
.card.vpn-only    { border-top-color: #8a97ab !important; border-top-style: dashed; }

/* Live-update flashes: green = a telemetry value changed (Available hue),
   blue = the card received new data (the UI accent). */
@keyframes flash-green  { 0% { background-color: rgba(79,175,124,0.55); } 100% { background-color: transparent; } }
@keyframes flash-blue   { 0% { background-color: rgba(52,152,219,0.45); } 100% { background-color: var(--bg-station-card); } }
.value-changed-flash { animation: flash-green  0.8s ease-out; }
.card-data-flash     { animation: flash-blue   0.4s ease-out; }

/* Operator check button in detail panel. Flagged amber carries dark text
   (8.9:1); the confirmed variant uses the review button's deep red. */
#check-btn { margin: 10px 12px 12px; padding: 8px; background: #efa94f; color: #14181c;
             font-weight: var(--fw-semibold); font-size: var(--fs-sm); flex-shrink: 0;
             border: none; border-radius: 5px; cursor: pointer; width: calc(100% - 24px); transition: filter 0.15s; }
#check-btn:hover { filter: brightness(1.08); }
#check-btn.confirmed { background: var(--action-confirm-bg); color: var(--action-confirm-fg); }
#forget-btn { margin: 10px 12px 12px; padding: 8px; background: var(--action-neutral-bg); color: var(--action-neutral-fg);
              font-weight: var(--fw-semibold); font-size: var(--fs-sm); flex-shrink: 0;
              border: none; border-radius: 5px; cursor: pointer; width: calc(100% - 24px); transition: filter 0.15s; }
#forget-btn:hover:not(:disabled) { filter: brightness(1.15); }
#forget-btn:disabled { opacity: 0.6; cursor: default; }

/* Action buttons (shared by diagnosis review) */
.modal-btns   { display: flex; gap: 10px; }
.modal-btns button { flex: 1; padding: 9px; border: none; border-radius: 5px;
                     font-weight: var(--fw-semibold); font-size: var(--fs-sm); cursor: pointer; }
#btn-no-issue  { background: var(--action-neutral-bg); color: var(--action-neutral-fg); transition: filter 0.15s; }
#btn-confirmed { background: var(--action-confirm-bg); color: var(--action-confirm-fg); transition: filter 0.15s; }
#btn-no-issue:hover, #btn-confirmed:hover { filter: brightness(1.15); }

/* Evidence modal — station history (faults / connectivity / reviews), opened
   by clicking a card on the Diagnostics tab. Uses the shared .modal-* chrome;
   this only overrides width, the header color, and the footer's stacked layout. */
#evidence-box { width: 560px; }
#evidence-box .modal-head { background: var(--evidence-head-bg); }
#evidence-body { min-height: 120px; }
.diag-loading { color: var(--text-sub); font-size: var(--fs-sm); }
/* Operator-review footer, beneath the table. .modal-foot is a flex row (for
   the Settings modal); here we want the note stacked above its buttons, so
   override to block. Empty + hidden when the station needs no review. */
#evidence-footer { display: block; }
#evidence-footer.empty { display: none; }
#evidence-note { width: 100%; box-sizing: border-box; padding: 7px; border-radius: 5px;
             border: 1px solid var(--border-color); background: var(--bg-card); color: var(--text-main);
             font-size: var(--fs-sm); resize: none; margin-bottom: 10px; }
.diag-empty  { color: var(--text-sub); font-size: var(--fs-sm); padding: 6px 0; }

/* Charging-sessions-per-day chart at the top of the station modal. Single
   hue (charging blue), theme-aware axis/baseline via CSS vars. */
/* --- Station twin: the station's pillars, top of the history modal -------
   Drawn in stationTwin.js. Metal and face tones are scoped here so the pillar
   reads as a physical object in both themes; socket fills are the status
   palette itself (colors.js), so a socket and a station card agree. */
.twin { --twin-metal-edge: #5f656c; --twin-metal-hi: #aeb4ba; --twin-face: #111417;
        --twin-screen: #2b3a40; --twin-base: #2b3035; --twin-ring: #0b0d0f;
        margin-bottom: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--border-color); }
[data-theme="light"] .twin { --twin-metal-edge: #9ba2a9; --twin-metal-hi: #eef0f2; --twin-base: #1e2327; }
.twin-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.twin-title { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-main); }
.twin-sub { font-size: var(--fs-2xs); color: var(--text-sub); }
.twin.is-offline .twin-sub { color: var(--text-warn); font-weight: var(--fw-semibold); }
/* 58px columns: the biggest station in the fleet (15 connectors = 8 pillars)
   fits one row of the 560px modal. Anything larger wraps. */
.twin-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px 6px; }
.twin-pillar { width: 58px; display: flex; flex-direction: column; align-items: center; }
.twin-svg { width: 56px; height: 154px; display: block; }
.twin.is-offline .twin-svg { opacity: 0.5; }
.twin-base   { fill: var(--twin-base); }
.twin-face   { fill: var(--twin-face); }
.twin-screen { fill: var(--twin-screen); stroke: #3c4d54; stroke-width: 0.6; }
.twin-holster { fill: var(--twin-ring); }
.twin-socket { cursor: help; }
.twin-socket-ring { fill: none; stroke: var(--twin-ring); stroke-width: 2; }
.twin-socket-num { font-family: var(--font-sans); font-size: 9px; font-weight: 700; text-anchor: middle; pointer-events: none; }
.twin-socket-empty { fill: #1d2125; stroke: #3a4046; stroke-width: 1.2; stroke-dasharray: 2 2; }
.twin-inferred { fill: none; stroke: var(--text-sub); stroke-width: 1; stroke-dasharray: 3 3; }
.twin-labels { width: 100%; margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
.twin-label { display: grid; grid-template-columns: 7px minmax(0, 1fr); column-gap: 4px; align-items: center;
    font-size: var(--fs-2xs); line-height: 1.3; padding: 1px 2px; border-radius: 3px; }
.twin-dot { width: 7px; height: 7px; border-radius: 50%; }
.twin-key { font-family: var(--font-mono); font-weight: var(--fw-bold); color: var(--text-main);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Status and power span the whole 58px column: indented under the dot they
   lost 11px, and "Charging" truncated. */
.twin-state { grid-column: 1 / -1; color: var(--text-sub); font-variant-numeric: tabular-nums;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Live values: the teal of the history table's in-progress energy, and a
   rounded box for the change flash to fill. */
.twin-power { grid-column: 1 / -1; color: var(--text-info); font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.twin-state, .twin-power { border-radius: 3px; }
.twin-note { margin: 10px 0 0; font-size: var(--fs-2xs); color: var(--text-sub); text-align: center; }

.usage-chart { margin-bottom: 14px; }
.usage-chart-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; }
.usage-chart-title { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-main); }
.usage-chart-sub { font-size: var(--fs-2xs); color: var(--text-sub); }
.usage-chart svg { width: 100%; height: auto; display: block; }
.usage-bar { fill: #5e9aab; transition: filter 0.12s; }
.usage-bar:hover { filter: brightness(1.2); }
.usage-axis { fill: var(--text-sub); font-size: 9px; font-family: var(--font-sans); }
.usage-y-label { dominant-baseline: middle; }
.usage-gridline { stroke: var(--border-color); stroke-width: 1; opacity: 0.5; }
.usage-gridline.usage-baseline { opacity: 1; }
.usage-empty { color: var(--text-sub); font-size: var(--fs-sm); padding: 10px 0 14px; }

/* Evidence — segmented control switching between Faults / Connectivity /
   Reviews, one table visible at a time instead of three permanently stacked. */
.evidence-tabs { display: flex; gap: 4px; margin-bottom: 10px; background: var(--bg-card);
                 border-radius: 7px; padding: 3px; }
.evidence-tab { flex: 1; background: transparent; border: none; border-radius: 5px;
                padding: 7px 8px; font-size: var(--fs-xs); font-weight: var(--fw-semibold);
                color: var(--text-sub); cursor: pointer; transition: background 0.15s, color 0.15s; }
.evidence-tab:hover { color: var(--text-main); }
.evidence-tab:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
.evidence-tab.active { background: var(--bg-panel); color: var(--text-main); }
.evidence-tab-count { color: var(--text-sub); font-weight: var(--fw-regular); }
.evidence-tab.active .evidence-tab-count { color: inherit; opacity: 0.7; }
.evidence-panel { max-height: 260px; overflow-y: auto; }

/* Diagnosis tables — compact, fixed layout so they always fit the drawer. */
.diag-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: var(--fs-xs); margin-bottom: 6px; }
.diag-table th { text-align: left; font-weight: var(--fw-bold); color: var(--text-sub); text-transform: uppercase;
                 font-size: var(--fs-xs); letter-spacing: 0.4px; padding: 4px 6px; border-bottom: 1px solid var(--border-color); }
.diag-table td { padding: 5px 6px; border-bottom: 1px solid var(--border-color);
                 color: var(--text-main); vertical-align: top; word-break: break-word; }
.diag-table th:first-child, .diag-table td:first-child { width: 92px; }
.diag-table th:nth-child(2), .diag-table td:nth-child(2) { width: 84px; white-space: nowrap; }
.diag-table tr.ev-fault td:nth-child(2) { color: var(--text-danger); font-weight: var(--fw-semibold); }
.diag-table tr.ev-on    td:nth-child(2) { color: var(--text-ok); }
.diag-table tr.ev-off   td:nth-child(2) { color: var(--text-slate); }
/* An open session is a charging session, so it takes the Charging hue. */
.diag-table .ev-open  { color: var(--text-info); font-weight: var(--fw-semibold); }
/* Live energy of an open session. Padding (cancelled by the negative margin)
   gives the change flash some room around the figure instead of a tight box. */
.diag-table .ev-live-energy { color: var(--text-info); font-weight: var(--fw-semibold);
    padding: 1px 4px; margin: -1px -4px; border-radius: 3px; cursor: help; }
/* Open session whose connector is not actually charging (SuspendedEV /
   SuspendedEVSE) — distinct from the "In progress" so a paused car is
   never mistaken for one drawing power. */
.diag-table .ev-suspended { color: var(--text-slate); font-weight: var(--fw-semibold); cursor: help; }
.diag-table .ev-trunc { color: var(--text-warn); cursor: help; }
/* Start time that is an upper bound, not the real start — the session was
   already charging when monitoring picked it up. Dimmed so it reads as
   "approximate" next to the exact timestamps in the same column. */
.diag-table .ev-start-est { opacity: 0.75; cursor: help; }

/* -----------------------------------------------------------------------
   Diagnostics page: status tiles (bigger, clickable, live counts) — the
   interactive counterpart to the map's static reference legend above.
   Solid-colored stat cards — eyebrow label, big number, one-line
   description, pill call-to-action — one system across the app.
   ----------------------------------------------------------------------- */
/* The tab is a flex column: tiles up top (#diag-top), the selected status's
   station cards filling the bottom (#diag-stations-container) — the same
   split idea as the Fleet Map's map + stations. */
#view-diagnostics { padding: 0; }
#diag-top { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 24px 28px; }
#diag-page-header h1 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); margin: 0 0 4px; color: var(--text-heading); }
#diag-page-header p { margin: 0 0 20px; font-size: var(--fs-base); color: var(--text-sub); }

/* auto-fit (not auto-fill): auto-fill reserves a column TRACK for every width
   that fits the container, even ones no tile ever occupies — with 13 tiles
   that left a full-height empty column on wide windows and undersized every
   real tile to make room for it. auto-fit collapses tracks with zero items
   in every row and lets the real columns' 1fr absorb that width instead.
   No max-width: the grid used to cap at 1200px regardless of screen size,
   which on any wider window left the leftover width as plain blank page
   past the grid's right edge — a second, separate empty-space cause from
   the auto-fill one above. Letting it fill the container means it just
   grows another column (or widens the existing ones) instead. */
#diag-legend { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
/* Tile text is dark ink by default: on 11 of the 13 fixed status colors
   white text fell below 3:1 (Flagged was 2.0:1), while ink clears 4.3–13.8:1.
   Only the two dark slates (SuspendedEV, Offline) keep white text —
   .tile-dark — where white is the stronger of the two (5.4 and 8.2:1). */
.legend-tile {
    background: var(--tile-color, #555); color: #14181c; text-align: left; cursor: pointer;
    border-radius: 14px; padding: 18px 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.18);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.legend-tile:hover { transform: translateY(-3px); box-shadow: 0 8px 22px rgba(0,0,0,0.3); }
.legend-tile.tile-dark { color: #ffffff; }
/* The tile whose stations are listed below. */
.legend-tile.is-selected { box-shadow: 0 0 0 3px var(--bg-main), 0 0 0 5px var(--text-main), 0 8px 22px rgba(0,0,0,0.3); }
.legend-tile:focus-visible { box-shadow: var(--focus-ring), 0 8px 22px rgba(0,0,0,0.3); }

.legend-tile-eyebrow { font-size: var(--fs-xs); font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: 0.6px;
                       margin-bottom: 10px; }
.legend-tile-count { font-size: var(--fs-3xl); font-weight: var(--fw-bold); line-height: 1; margin-bottom: 8px; }
.legend-tile-desc { font-size: var(--fs-sm); margin-bottom: 16px; }
.legend-tile-cta { display: inline-block; font-size: var(--fs-xs); font-weight: var(--fw-semibold); padding: 6px 12px;
                   border-radius: 20px; background: rgba(0,0,0,0.12); transition: background 0.15s; }
.legend-tile:hover .legend-tile-cta { background: rgba(0,0,0,0.2); }
.legend-tile.tile-dark .legend-tile-cta { background: rgba(255,255,255,0.18); }
.legend-tile.tile-dark:hover .legend-tile-cta { background: rgba(255,255,255,0.3); }

/* -----------------------------------------------------------------------
   Diagnostics station-cards container — the bottom half of the Diagnostics
   tab, populated when a status tile is clicked. Same look as the Fleet Map's
   #stations-container/#stations-grid, but a plain (non-virtualized) CSS grid
   since it only ever holds the stations of one status.
   ----------------------------------------------------------------------- */
#diag-stations-container { flex: 1 1 0; min-height: 0; background: var(--bg-container);
    border-top: 3px solid var(--border-color); padding: 15px; box-sizing: border-box;
    display: flex; flex-direction: column; transition: background 0.3s, border-color 0.3s; }
#diag-stations-title { font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--text-heading); margin-bottom: 10px; }
/* padding: room for the focus ring, which would otherwise be clipped by the
   scroll container on the cards at its edges. */
#diag-stations-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 10px; overflow-y: auto; flex-grow: 1; align-content: start; padding: 4px 5px 4px 4px; }
.diag-cards-empty { grid-column: 1 / -1; color: var(--text-sub); font-size: var(--fs-base); padding: 20px 4px; }

/* Unused connectors: station filter above the grid, and one full-width header
   per station inside it so a station's connectors read as one group. */
#unused-filter { display: flex; align-items: center; gap: 8px; margin: -2px 0 12px;
    font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-sub); }
#unused-filter[hidden] { display: none; }   /* the flex above would otherwise beat [hidden] */
#unused-station { flex: 0 1 420px; min-width: 0; background: var(--bg-card); color: var(--text-main);
    border: 1px solid var(--border-color); border-radius: 6px; padding: 6px 9px; font-size: var(--fs-sm);
    cursor: pointer; transition: border-color 0.15s; }
#unused-station:hover { border-color: var(--text-sub); }
#unused-station:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.unused-group { grid-column: 1 / -1; display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
    padding: 14px 2px 6px; border-bottom: 1px solid var(--border-color); }
.unused-group:first-child { padding-top: 2px; }
.unused-group-sn { font-size: var(--fs-base); font-weight: var(--fw-bold); color: var(--text-heading); }
.unused-group-addr { min-width: 0; font-size: var(--fs-xs); color: var(--text-sub);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.unused-group-count { margin-left: auto; font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-warn); }

/* Phone: one scrolling page instead of two half-height scroll panes, and
   two tile columns instead of thirteen stacked full-width tiles. */
@media (max-width: 600px) {
    #view-diagnostics.active { overflow-y: auto; }
    #diag-top { flex: none; overflow: visible; padding: 16px; }
    #diag-legend { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
    .legend-tile { padding: 14px; border-radius: 12px; }
    .legend-tile-count { font-size: var(--fs-2xl); }
    .legend-tile-desc { margin-bottom: 12px; }
    #diag-stations-container { flex: none; min-height: 60vh; }
    #diag-stations-grid { overflow: visible; }
}

/* Diagnostics card — mirrors the Fleet Map .card look, but laid out by the
   CSS grid above (not absolutely positioned like the virtualized map cards).
   The status color classes (.suspicious, .available, …) apply their
   border-top-color to it exactly as they do to .card. */
.dcard { box-sizing: border-box; background: var(--bg-station-card); padding: 10px; border-radius: 5px;
    border-top: 4px solid #555; text-align: center; font-size: var(--fs-sm); cursor: pointer;
    color: var(--text-main); transition: background 0.15s; }
.dcard:hover { background: var(--bg-card); }
.dcard .sn { font-weight: var(--fw-bold); margin-bottom: 3px; }
.dcard .addr { color: var(--text-sub); font-size: var(--fs-2xs); margin-bottom: 4px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dcard .status { color: var(--text-sub); font-size: var(--fs-xs); }
/* Suspicious extras (fault/flip counts, triggered rule) — same small font as
   the rest of the card details. Only the counts line (.metric-counts) is
   called out, in the heading color; the triggered-rule line stays plain grey. */
.dcard .metric { color: var(--text-sub); font-size: var(--fs-2xs); margin-top: 3px; }
.dcard .metric-counts { color: var(--text-heading); font-weight: var(--fw-semibold); }

/* -----------------------------------------------------------------------
   Settings gear + centered modal (Suspicion Rules). Modals differ from the
   drawers: centered, scale/fade in, for a focused configure-then-confirm
   task rather than a browse-and-select workflow.
   ----------------------------------------------------------------------- */
.tab-icon-btn { margin-left: auto; background: transparent; border: none; color: rgba(255,255,255,0.72);
                font-size: 18px; cursor: pointer; padding: 0 13px; transition: color 0.15s;
                position: relative; display: flex; align-items: center; }
.tab-icon-btn:hover { color: #fff; }
/* Only the FIRST icon button pushes the group to the right edge. Without this,
   a second one gets its own margin-left:auto, the free space is split between
   them, and the first ends up marooned mid-bar. */
.tab-icon-btn + .tab-icon-btn { margin-left: 0; }

.modal-overlay {
    display: none; /* JS sets display:flex before adding .open */
    position: fixed; inset: 0; z-index: 9995;
    align-items: center; justify-content: center;
    background: rgba(0,0,0,0); backdrop-filter: blur(0px);
    transition: background 0.2s ease, backdrop-filter 0.2s ease;
}
.modal-overlay.open { background: rgba(0,0,0,0.5); backdrop-filter: blur(3px); }
.modal-box {
    background: var(--bg-panel); width: 520px; max-width: 92vw; max-height: 88vh;
    display: flex; flex-direction: column; overflow: hidden; border-radius: 12px;
    box-shadow: 0 12px 48px rgba(0,0,0,0.5);
    transform: translateY(12px) scale(0.98); opacity: 0;
    transition: transform 0.2s ease, opacity 0.2s ease;
}
.modal-overlay.open .modal-box { transform: none; opacity: 1; }
.modal-head { background: var(--panel-header); color: #fff; padding: 14px 18px; flex-shrink: 0;
              display: flex; align-items: center; justify-content: space-between; }
.modal-head .title { font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.modal-body { padding: 18px; overflow-y: auto; }
.modal-foot { display: flex; align-items: center; gap: 10px; padding: 14px 18px;
              border-top: 1px solid var(--border-color); flex-shrink: 0; }
.foot-spacer { flex: 1; }

.settings-intro { font-size: var(--fs-sm); color: var(--text-sub); margin: 0 0 16px; line-height: 1.5; }

.rule-card { background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 8px;
             padding: 14px; margin-bottom: 14px; transition: opacity 0.15s; }
.rule-card.rule-disabled { opacity: 0.5; }
.rule-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.rule-title { font-size: var(--fs-base); font-weight: var(--fw-bold); color: var(--text-main); }
.rule-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
            font-size: var(--fs-sm); color: var(--text-main); margin: 8px 0; cursor: default; }
label.rule-row { cursor: pointer; }
.rule-num { width: 58px; text-align: center; padding: 5px 6px; border-radius: 5px;
            border: 1px solid var(--border-color); background: var(--bg-panel); color: var(--text-main); }
.rule-num:focus { border-color: var(--accent); }
.rule-num:disabled { opacity: 0.5; cursor: not-allowed; }
.rule-num.invalid { border-color: var(--text-danger); }
.rule-note { font-size: var(--fs-2xs); color: var(--text-sub); margin-top: 8px; line-height: 1.45; }

.settings-preview { font-size: var(--fs-sm); color: var(--text-sub); background: var(--bg-card);
                    border: 1px solid var(--border-color); border-radius: 8px; padding: 10px 12px; margin-top: 4px; }
.settings-preview.error { color: var(--text-danger); border-color: var(--text-danger); }
.settings-preview b { color: var(--text-main); }

.btn-primary { background: var(--accent-fill); color: #fff; border: none; border-radius: 6px; padding: 8px 18px;
               font-size: var(--fs-sm); font-weight: var(--fw-semibold); cursor: pointer; transition: background 0.15s; }
.btn-primary:hover:not(:disabled) { background: var(--accent-fill-hover); }
.btn-primary:disabled { background: var(--bg-card); color: var(--text-sub); cursor: not-allowed; }
.btn-ghost { background: transparent; color: var(--text-sub); border: 1px solid var(--border-color);
             border-radius: 6px; padding: 8px 16px; font-size: var(--fs-sm); font-weight: var(--fw-semibold);
             cursor: pointer; transition: color 0.15s, border-color 0.15s; }
.btn-ghost:hover { color: var(--text-main); border-color: var(--text-sub); }

/* =======================================================================
   Log Analyser tab
   -----------------------------------------------------------------------
   Operate surface, read top to bottom:

     1. #la-facts     the period's record, as counted by the parser
     2. #la-body      the evidence itself, tables left, summary right

   Three colour ideas are load-bearing:

   1. Row severity (.la-row-*) is the ANALYSER's judgement, not the device's
      log level — the firmware logs "Firmware upgrade successful!" at level E
      and a dead connector at level D, so colouring by level would mislead.
      It is encoded three ways (dot, hairline, wash) so it survives both
      themes and colour-blind readers without leaning on hue.
   2. Amber means "major, or money at risk" and nothing else. It used to also
      mark collapsed-run counts and the AI badge, which put the loudest
      colour on screen onto a volume fact; .la-count is now neutral and the
      badge is an outline.
   3. The *-ink variables exist because the badge hues are tuned as fills on
      dark panels and fall under 4.5:1 as text on the light theme.

   #la-facts is deliberately flat — equal weight, no accent, modest numbers:
   it is the record, not a ranking of what matters.
   ======================================================================= */
#view-loganalyser {
    --la-critical: #e7635c;
    --la-major:    #e0a021;
    --la-warning:  #8593c4;
    --la-accent:   var(--accent);

    --la-critical-ink: #e7635c;
    --la-major-ink:    #d69a1e;
    --la-warning-ink:  #9aa6d2;

    /* Secondary text sitting on --bg-card (the section header) rather than on a
       panel. --text-sub lands at 4.46:1 there in the light theme, just under AA,
       so that surface gets its own slightly darker ink. */
    --la-meta-ink: var(--text-sub);
}
[data-theme="light"] #view-loganalyser {
    --la-critical-ink: #b4322b;
    --la-major-ink:    #8a5d00;
    --la-warning-ink:  #46528c;
    --la-meta-ink:     #555555;
}

#la-top { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 24px 28px 48px; }
/* One centred measure for every band, so the tables stop stretching to the
   full width of an ultrawide monitor. */
#la-top > * { max-width: 1680px; margin-left: auto; margin-right: auto; }

/* --- Browser surfaces --------------------------------------------------
   The parts nobody draws: selection, scrollbar, focus ring, and the numerals
   in every table. Data columns get tabular figures so digits line up down the
   column instead of jittering row to row. */
#la-top ::selection { background: rgba(46,159,224,0.28); color: var(--text-main); }
#la-top { scrollbar-width: thin; scrollbar-color: var(--border-color) transparent; }
#la-top ::-webkit-scrollbar { width: 10px; height: 10px; }
#la-top ::-webkit-scrollbar-track { background: transparent; }
#la-top ::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 999px;
    border: 2px solid transparent; background-clip: content-box; }
#la-top ::-webkit-scrollbar-thumb:hover { background: var(--text-sub); background-clip: content-box; }
/* Focus uses the app-wide ring (see :focus-visible at the top); the section
   header clips it (.la-section is overflow:hidden), so it rings inward. */
.la-section-head:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
#la-facts, .la-table, .la-md-table { font-variant-numeric: tabular-nums; }

/* Icons are drawn, one stroke weight throughout — no emoji standing in. */
.la-icon { width: 20px; height: 20px; flex: none; fill: none; stroke: currentColor;
    stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

#la-page-header h1 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); margin: 0 0 4px; color: var(--text-heading);
    letter-spacing: -0.01em; }
#la-page-header p { margin: 0 0 22px; font-size: var(--fs-base); color: var(--text-sub); max-width: 68ch; }

/* Once a result is up, the picker and the explanation have done their job and
   stop occupying the top of every scroll. */
#la-top.has-result #la-inputs,
#la-top.has-result #la-page-header p { display: none; }
#la-top.has-result #la-page-header h1 { margin-bottom: 16px; }
#la-top:not(.has-result) #la-results { display: none; }

/* --- Inputs ----------------------------------------------------------- */
#la-inputs { display: grid; grid-template-columns: 1fr auto 1fr; gap: 24px; align-items: stretch; margin-bottom: 32px; }
@media (max-width: 900px) { #la-inputs { grid-template-columns: 1fr; gap: 16px; } }

#la-dropzone { border: 1px dashed var(--border-color); border-radius: 10px; padding: 28px 20px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    text-align: center; cursor: pointer; background: var(--bg-panel);
    transition: border-color .18s, background .18s; }
#la-dropzone:hover, #la-dropzone:focus-visible { border-color: var(--la-accent); background: rgba(46,159,224,0.05); }
#la-dropzone.dragging { border-color: var(--la-accent); border-style: solid; background: rgba(46,159,224,0.10); }
#la-dropzone.busy { opacity: .5; pointer-events: none; }
.la-drop-icon { width: 26px; height: 26px; color: var(--text-sub); margin-bottom: 8px; transition: color .18s; }
#la-dropzone:hover .la-drop-icon, #la-dropzone.dragging .la-drop-icon { color: var(--la-accent); }
.la-drop-main { font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--text-main); }
.la-drop-sub  { font-size: var(--fs-sm); color: var(--text-sub); margin-top: 4px; }
.la-drop-sub code { font-family: var(--font-mono); font-size: var(--fs-xs); }

/* A rule through the middle, with the word sitting in the gap. */
.la-or { display: flex; flex-direction: column; align-items: center; gap: 8px;
    font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .1em; color: var(--text-sub); }
.la-or::before, .la-or::after { content: ""; flex: 1; width: 1px; background: var(--border-color); }
@media (max-width: 900px) {
    .la-or { flex-direction: row; }
    .la-or::before, .la-or::after { width: auto; height: 1px; }
}

#la-pull { display: flex; flex-direction: column; justify-content: center; }
#la-pull label { display: block; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-main); margin-bottom: 8px; }
.la-pull-row { display: flex; gap: 8px; }
#la-station { flex: 1; min-width: 0; background: var(--bg-card); color: var(--text-main);
    border: 1px solid var(--border-color); border-radius: 6px; padding: 8px 10px;
    font-family: var(--font-sans); font-size: var(--fs-base); transition: border-color .15s; }
#la-station:hover { border-color: var(--text-sub); }

/* --- Status line ------------------------------------------------------- */
.la-status { margin: 0 0 20px; padding: 11px 14px; border-radius: 8px; font-size: var(--fs-base);
    border: 1px solid var(--border-color); display: flex; align-items: center; gap: 10px; }
.la-status-busy  { background: rgba(46,159,224,0.08); border-color: rgba(46,159,224,0.5); color: var(--text-main); }
.la-status-error { background: rgba(231,99,92,0.08);  border-color: rgba(231,99,92,0.55); color: var(--text-main); }
.la-status-idle  { display: none; }
/* A class selector beats the UA sheet's [hidden] rule, so the flex above would
   otherwise keep an empty bordered bar on screen between analyses. */
.la-status[hidden] { display: none; }
/* Determinate progress would be a lie — the proxy reports no percentage — so
   this reads as "working", not "38% done". */
.la-status-busy::before { content: ""; width: 13px; height: 13px; flex: none; border-radius: 50%;
    border: 2px solid rgba(46,159,224,0.30); border-top-color: var(--la-accent); animation: la-spin .7s linear infinite; }
@keyframes la-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .la-status-busy::before { animation-duration: 2.4s; } }

/* --- Source bar (replaces the picker once a result is up) --------------- */
#la-source-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
#la-title { font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--text-main); margin: 0;
    font-family: var(--font-mono); letter-spacing: -0.01em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#la-title .la-title-src { color: var(--text-sub); font-weight: var(--fw-regular); font-family: var(--font-sans);
    letter-spacing: 0; }
#la-reset { margin-left: auto; flex: none; padding: 6px 13px; }

/* Severity dot, used in the Type cell of every event row. */
.la-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }

/* --- Facts band -------------------------------------------------------- */
/* 132px, not 158: seven facts is the maximum the parser produces, and this
   keeps all seven on one row down to ~1080px so the last one does not orphan
   onto a second row with a divider the row above does not have. */
#la-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
    border-top: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color); margin-bottom: 24px; }
.la-fact { padding: 13px 18px; border-left: 1px solid var(--border-color); }
.la-fact:first-child { border-left: none; padding-left: 0; }
.la-fact-label { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .08em;
    color: var(--text-sub); font-weight: var(--fw-bold); }
.la-fact-value { font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text-main);
    margin-top: 5px; line-height: 1.3; }
.la-fact-sub { font-size: var(--fs-xs); color: var(--text-sub); margin-top: 3px; line-height: 1.45; }
.la-nowrap { white-space: nowrap; }
/* Once the band wraps, a divider can no longer mean "next column" — the first
   fact of each wrapped row would carry a stray rule — so spacing takes over. */
@media (max-width: 720px) {
    #la-facts { column-gap: 18px; }
    .la-fact { border-left: none; padding-left: 0; padding-right: 0; }
}

/* --- Body: evidence left, summary right -------------------------------- */
#la-body { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 24px; align-items: start; }
#la-evidence { min-width: 0; }

/* Segmented control: same vocabulary as the rest of the page — panel ground,
   the pressed option lifted onto --bg-card. */
#la-evidence-bar { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
#la-viewswitch { display: inline-flex; gap: 2px; padding: 3px;
    border: 1px solid var(--border-color); border-radius: 8px; background: var(--bg-panel); }
#la-viewswitch button { border: 0; background: transparent; color: var(--text-sub); cursor: pointer;
    font-size: var(--fs-sm); font-weight: var(--fw-semibold); padding: 5px 13px; border-radius: 6px;
    transition: background .15s, color .15s; }
#la-viewswitch button:hover { color: var(--text-main); }
#la-viewswitch button[aria-pressed="true"] { background: var(--bg-card); color: var(--text-main); }
@media (max-width: 1180px) { #la-body { grid-template-columns: minmax(0, 1fr); } }

/* --- Summary (AI) ------------------------------------------------------ */
#la-summary { background: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 10px;
    padding: 16px 18px; position: sticky; top: 0; max-height: calc(100vh - 140px); overflow-y: auto; }
@media (max-width: 1180px) { #la-summary { position: static; max-height: none; } }
.la-summary-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.la-summary-head h3 { margin: 0; font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--text-main); }
/* Provenance, not a warning: the tables beside this block are exact and this is
   a model's reading of them. Plain attribution text; the amber fill it once had
   said "something is wrong", which was the wrong signal. */
.la-ai-badge { font-size: var(--fs-xs); color: var(--text-sub); }
.la-summary-missing p { margin: 0 0 6px; font-size: var(--fs-sm); color: var(--text-main); }
.la-summary-missing .la-sub { color: var(--text-sub); font-size: var(--fs-sm); }

.la-md { font-size: var(--fs-sm); color: var(--text-main); line-height: 1.62; }
.la-md > *:first-child { margin-top: 0; }
.la-md h2 { font-size: var(--fs-base); margin: 18px 0 6px; font-weight: var(--fw-bold); }
.la-md h3, .la-md h4 { font-size: var(--fs-xs); margin: 18px 0 6px; font-weight: var(--fw-bold);
    text-transform: uppercase; letter-spacing: .07em; color: var(--text-sub); }
.la-md p { margin: 0 0 9px; }
.la-md ul, .la-md ol { margin: 0 0 10px; padding-left: 18px; }
.la-md li { margin-bottom: 5px; }
.la-md strong { font-weight: var(--fw-semibold); }
.la-md code { font-family: var(--font-mono); font-size: var(--fs-xs); background: var(--bg-card);
    padding: 1px 5px; border-radius: 4px; }
.la-md-table { width: 100%; border-collapse: collapse; font-size: var(--fs-xs); margin: 6px 0 12px; }
.la-md-table th { text-align: left; font-weight: var(--fw-bold); color: var(--text-sub); text-transform: uppercase;
    font-size: var(--fs-2xs); letter-spacing: .05em; padding: 5px 8px 5px 0; border-bottom: 1px solid var(--border-color); }
.la-md-table td { padding: 5px 8px 5px 0; border-bottom: 1px solid var(--border-color); vertical-align: top; }
.la-md-table th:last-child, .la-md-table td:last-child { padding-right: 0; text-align: right; }

/* --- Per-connector event tables ---------------------------------------- */
.la-section { border: 1px solid var(--border-color); border-radius: 10px; margin-bottom: 12px;
    overflow: hidden; background: var(--bg-panel); }
.la-section-head { display: flex; align-items: center; gap: 10px; padding: 11px 14px; cursor: pointer;
    background: var(--bg-card); user-select: none; transition: background .15s; }
.la-section-head:hover { background: var(--bg-panel); }
.la-section-head h4 { margin: 0; font-size: var(--fs-base); font-weight: var(--fw-bold); color: var(--text-main);
    font-family: var(--font-mono); }
.la-section-meta { margin-left: auto; font-size: var(--fs-xs); color: var(--la-meta-ink); text-align: right;
    font-variant-numeric: tabular-nums; }
.la-caret { color: var(--text-sub); width: 14px; height: 14px; flex: none; transition: transform .18s ease; }
.la-section.collapsed .la-caret { transform: rotate(-90deg); }
.la-section.collapsed .la-section-body { display: none; }
.la-section-body { overflow-x: auto; max-height: 480px; overflow-y: auto; }

.la-scheme { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .05em; font-weight: var(--fw-bold);
    color: var(--la-meta-ink); border: 1px solid var(--border-color); border-radius: 999px; padding: 1px 7px; cursor: help; }

.la-sev { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .06em; font-weight: var(--fw-bold);
    border-radius: 999px; padding: 2px 9px; }
.la-sev-critical { background: var(--la-critical); color: #2a0d0a; }
.la-sev-major    { background: var(--la-major); color: #1a1206; }
.la-sev-warning  { background: var(--la-warning); color: #12181a; }

.la-table { width: 100%; border-collapse: collapse; font-size: var(--fs-xs); }
.la-table th { text-align: left; font-weight: var(--fw-bold); color: var(--text-sub); text-transform: uppercase;
    letter-spacing: .05em; padding: 7px 10px; border-bottom: 1px solid var(--border-color); white-space: nowrap;
    position: sticky; top: 0; background: var(--bg-panel); z-index: 1; }
.la-table td { padding: 7px 10px; border-bottom: 1px solid var(--border-color); vertical-align: top; }
.la-table tr:last-child td { border-bottom: none; }
/* Widths by class, not position: the timeline adds a Connector column, which
   would shift every nth-child rule by one. */
.la-table .la-c-when  { width: 132px; }
.la-table .la-c-conn  { width: 74px; }
.la-table .la-c-type  { width: 112px; }
.la-table .la-c-src   { width: 86px; }
.la-conn { font-family: var(--font-mono); font-weight: var(--fw-semibold); color: var(--text-main); white-space: nowrap; }
/* One table instead of many, so it gets the height the stacked sections had. */
.la-section-timeline .la-section-body { max-height: max(480px, calc(100vh - 240px)); }

.la-when { font-family: var(--font-mono); white-space: nowrap; color: var(--text-main); }
.la-msg  { font-family: var(--font-mono); word-break: break-word; color: var(--text-main); line-height: 1.5; }
.la-sub  { color: var(--text-sub); font-size: var(--fs-xs); }
.la-kind { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-sub); }
.la-kind .la-dot { width: 6px; height: 6px; }
.la-row-critical .la-kind .la-dot { color: var(--la-critical-ink); }
.la-row-major    .la-kind .la-dot { color: var(--la-major-ink); }
.la-row-warning  .la-kind .la-dot { color: var(--la-warning-ink); }
.la-row-info     .la-kind .la-dot { color: var(--border-color); }
/* Neutral on purpose: "x4,218" is a volume fact. Its weight and tabular figures
   make it findable; severity is the row's job, not the count's. */
.la-count { font-family: var(--font-mono); font-weight: var(--fw-bold); color: var(--text-main); }
.la-detail { color: var(--text-sub); }
.la-cycle { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .04em; color: var(--text-sub);
    border: 1px solid var(--border-color); border-radius: 999px; padding: 0 6px; cursor: help; white-space: nowrap; }

/* Severity reaches the row three ways — the dot above, a hairline here, and a
   wash — so none of them has to carry it alone. */
.la-row-critical td:first-child { box-shadow: inset 1px 0 0 var(--la-critical); }
.la-row-major    td:first-child { box-shadow: inset 1px 0 0 var(--la-major); }
.la-row-warning  td:first-child { box-shadow: inset 1px 0 0 var(--la-warning); }
.la-row-critical { background: rgba(231,99,92,0.07); }
.la-row-major    { background: rgba(224,160,33,0.06); }
.la-row-warning  { background: rgba(133,147,196,0.05); }

/* Routine chatter (boot inventory, config assignments, raw OCPP frames) is
   returned by the parser but folded away: it is neither an event nor a state
   change, and on a station like 40010668 it was most of the rows. One toggle
   in the evidence bar reveals it everywhere; its count keeps it discoverable. */
.la-routine-toggle { display: flex; align-items: center; gap: 7px; font-size: var(--fs-sm);
    color: var(--text-sub); cursor: pointer; white-space: nowrap; }
.la-routine-toggle[hidden] { display: none; }
.la-routine-toggle:hover { color: var(--text-main); }
.la-routine-toggle input { cursor: pointer; margin: 0; accent-color: var(--la-accent); }
.la-row-routine { display: none; }
#la-tables.show-routine .la-row-routine { display: table-row; opacity: .7; }


/* Which backend this dashboard is connected to (see handleInstance).
   The shared "testing" instance is quiet; a developer's own instance is
   deliberately loud, so a local tab is never mistaken for the shared one. */
#instance-badge {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 3px 8px;
    border-radius: 999px;
    white-space: nowrap;
}
#instance-badge.instance-shared {
    color: var(--text-sub);
    border: 1px solid var(--border);
}
#instance-badge.instance-other {
    color: #14181c;
    background: #efa94f;
    border: 1px solid #d8933b;
}
/* Who is signed in, and their role (see handleInstance). */
#user-badge {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-sub);
    padding: 3px 8px;
    border: 1px solid var(--border);
    border-radius: 999px;
    white-space: nowrap;
}

/* -----------------------------------------------------------------------
   Live view (js/liveView.js): the switch at the right of the tab bar, and,
   when it is off, the views greyed out under a notice with a big button.
   The notice sits above drawers and modals (9992, 9995) but starts below
   the tab bar, so the switch up there stays in reach.
   ----------------------------------------------------------------------- */
.live-btn { margin-left: auto; align-self: center; display: flex; align-items: center; gap: 6px;
            font: inherit; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: #fff; white-space: nowrap;
            padding: 4px 12px; border-radius: 12px; cursor: pointer;
            border: 1px solid rgba(79,175,124,0.7); background: rgba(79,175,124,0.22); }
.live-btn .live-dot { width: 8px; height: 8px; border-radius: 50%; background: #4faf7c; }
.live-btn[aria-pressed="false"] { border-color: #e7635c; background: #c0443d; }
.live-btn[aria-pressed="false"] .live-dot { background: transparent; border: 1.5px solid #fff; width: 6px; height: 6px; }
.live-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* The switch now pushes the group to the right; the gear must not split the space. */
.live-btn + .tab-icon-btn { margin-left: 0; }
@media (max-width: 480px) { .live-btn .live-label { display: none; } .live-btn { padding: 6px 8px; } }

body.live-off .app-view { filter: grayscale(1); opacity: 0.35; pointer-events: none; }
#live-off { position: fixed; top: 44px; left: 0; right: 0; bottom: 0; z-index: 9998;
            display: flex; align-items: center; justify-content: center; padding: 16px; }
#live-off[hidden] { display: none; }
.live-off-card { max-width: 420px; text-align: center; background: var(--bg-panel); color: var(--text-main);
                 border: 1px solid var(--border-color); border-radius: 10px; padding: 24px;
                 box-shadow: 0 8px 30px rgba(0,0,0,0.35); }
.live-off-title { font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--text-heading); margin-bottom: 8px; }
.live-off-card p { font-size: var(--fs-base); line-height: 1.45; margin: 0 0 18px; }
.live-resume-btn { font: inherit; font-size: var(--fs-lg); font-weight: var(--fw-bold); color: #fff;
                   background: var(--accent-fill); border: none; border-radius: 8px; padding: 12px 26px; cursor: pointer; }
.live-resume-btn:hover { background: var(--accent-fill-hover); }
.live-resume-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
#conn-status.off { color: var(--text-sub); background: rgba(127,127,127,0.12); }


/* -----------------------------------------------------------------------
   Station tab (js/boardTab.js): the selected station's board, read through
   the proxy. Same page frame as Diagnostics and the Log Analyser.
   ----------------------------------------------------------------------- */
#view-board { padding: 0; }
#board-top { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 24px 28px 48px; }
#board-top > * { max-width: 1680px; margin-left: auto; margin-right: auto; }
#board-page-header { position: relative; padding-right: 48px; }
#board-page-header h1 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); margin: 0 0 4px; color: var(--text-heading); }
/* Top right: back to the Fleet Map. */
.board-close { position: absolute; top: 0; right: 0; width: 36px; height: 36px; padding: 0;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
    background: transparent; color: var(--text-sub); border: 1px solid var(--border-color); border-radius: 8px;
    transition: background 0.15s, color 0.15s; }
.board-close:hover { background: rgba(127,127,127,0.14); color: var(--text-main); }
.board-close:focus-visible { outline: none; box-shadow: var(--focus-ring); }
#board-page-header p { margin: 0 0 10px; font-size: var(--fs-base); color: var(--text-sub); }
.board-state { display: inline-block; font-size: var(--fs-sm); font-weight: var(--fw-semibold); padding: 4px 10px;
    border-radius: 10px; margin-bottom: 20px; color: var(--text-sub); background: rgba(127,127,127,0.12); }
.board-state-ready { color: var(--text-ok); background: rgba(79,175,124,0.14); }
.board-state-refused, .board-state-unreachable, .board-state-full,
.board-state-not_configured, .board-state-not_allowed { color: var(--text-danger); background: rgba(231,99,92,0.14); }
.board-state-unsupported { color: var(--text-warn); background: rgba(224,160,33,0.14); }

/* The state at a glance: the board page's ticks, one pill each. */
.board-glance { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; padding: 0; }
.board-glance[hidden] { display: none; }
.board-glance-item { display: inline-flex; align-items: baseline; gap: 6px; padding: 5px 11px; border-radius: 10px;
    font-size: var(--fs-sm); border: 1px solid var(--border-color); background: var(--bg-panel); }
.board-glance-mark { font-weight: var(--fw-bold); }
.board-glance-label { font-weight: var(--fw-semibold); color: var(--text-heading); }
.board-glance-text { color: var(--text-sub); }
.board-glance-ok   { border-color: rgba(79,175,124,0.45); background: rgba(79,175,124,0.10); }
.board-glance-ok .board-glance-mark { color: var(--text-ok); }
.board-glance-ko   { border-color: rgba(231,99,92,0.55); background: rgba(231,99,92,0.12); }
.board-glance-ko .board-glance-mark, .board-glance-ko .board-glance-text { color: var(--text-danger); }
.board-glance-warn { border-color: rgba(224,160,33,0.55); background: rgba(224,160,33,0.12); }
.board-glance-warn .board-glance-mark, .board-glance-warn .board-glance-text { color: var(--text-warn); }
.board-glance-na .board-glance-mark, .board-glance-info .board-glance-mark { color: var(--text-sub); }

.board-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-bottom: 20px; }
.board-card { background: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 10px; padding: 14px 16px; }
.board-card h3 { margin: 0 0 8px; font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--text-heading); }

.board-section { background: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 10px;
    padding: 14px 16px; margin-bottom: 16px; }
.board-section[hidden] { display: none; }
.board-section h2 { margin: 0 0 10px; font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text-heading); }

.board-kv { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.board-kv th { text-align: left; font-weight: var(--fw-regular); color: var(--text-sub); padding: 3px 12px 3px 0;
    width: 45%; vertical-align: top; }
.board-kv td { color: var(--text-main); padding: 3px 0; word-break: break-word; }

.board-grid { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.board-grid th { text-align: left; font-weight: var(--fw-semibold); color: var(--text-sub);
    border-bottom: 1px solid var(--border-color); padding: 6px 10px 6px 0; white-space: nowrap; }
.board-grid td { padding: 5px 10px 5px 0; border-bottom: 1px solid var(--border-color); }
.board-grid tr.board-faulted td { color: var(--text-danger); }

.board-search { width: 100%; max-width: 360px; box-sizing: border-box; padding: 6px 10px; margin-bottom: 10px;
    background: var(--bg-card); color: var(--text-main); border: 1px solid var(--border-color);
    border-radius: 6px; font-size: var(--fs-sm); }
.board-search:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.board-config-group { border-top: 1px solid var(--border-color); padding: 6px 0; }
.board-config-group summary { cursor: pointer; font-size: var(--fs-base); font-weight: var(--fw-semibold); padding: 4px 0; }
.board-config-group .board-kv { margin: 4px 0 6px 16px; width: calc(100% - 16px); }
.board-tag { margin-left: 6px; font-size: var(--fs-xs); color: var(--text-sub); border: 1px solid var(--border-color);
    border-radius: 999px; padding: 0 6px; }
.board-secret { color: var(--text-sub); font-style: italic; }
.board-note { color: var(--text-sub); font-size: var(--fs-sm); }

/* The Station tab's sub-tabs, and Settings: its groups on the left, one shown on the right. */
.board-subtabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border-color); margin-bottom: 16px; }
.board-subtab { font-family: inherit; font-size: var(--fs-base); font-weight: var(--fw-semibold); padding: 8px 14px;
    margin-bottom: -1px; background: transparent; border: none; border-bottom: 2px solid transparent;
    color: var(--text-sub); cursor: pointer; }
.board-subtab:hover { color: var(--text-main); }
.board-subtab:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }
.board-subtab[aria-selected="true"] { color: var(--text-heading); border-bottom-color: var(--accent); }
.board-settings { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 16px; align-items: start; }
.board-settings[hidden] { display: none; }
.board-settings-nav { display: flex; flex-direction: column; gap: 2px; padding: 8px; background: var(--bg-panel);
    border: 1px solid var(--border-color); border-radius: 10px; }
.board-settings-nav:empty { display: none; }
.board-settings-item { font-family: inherit; font-size: var(--fs-base); text-align: left; padding: 7px 10px;
    background: transparent; border: none; border-radius: 6px; color: var(--text-sub); cursor: pointer; }
.board-settings-item:hover { background: rgba(127,127,127,0.12); color: var(--text-main); }
.board-settings-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.board-settings-item[aria-current="true"] { background: rgba(127,127,127,0.18); color: var(--text-heading);
    font-weight: var(--fw-semibold); }
.board-settings-fields th { width: 260px; }
.board-settings-fields td { padding: 5px 0; }
/* A setting being changed: its control, the row marked, and the bar with the review. */
.board-input { font-family: inherit; font-size: var(--fs-sm); padding: 4px 8px; min-width: 220px; max-width: 100%;
    box-sizing: border-box; background: var(--bg-container); color: var(--text-main);
    border: 1px solid var(--border-color); border-radius: 6px; }
.board-input-number { min-width: 0; width: 110px; font-variant-numeric: tabular-nums; }
.board-input:focus-visible, .board-check:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.board-input:disabled, .board-check:disabled { opacity: 0.55; cursor: not-allowed; }
.board-check { width: 16px; height: 16px; accent-color: var(--accent); vertical-align: middle; }
.board-field-unit { margin-left: 6px; color: var(--text-sub); }
.board-settings-fields tr.board-changed th { color: var(--text-heading); font-weight: var(--fw-semibold); }
.board-settings-fields tr.board-changed .board-input { border-color: var(--accent); }
.board-change-bar { display: flex; align-items: center; gap: 8px; margin: 10px 0 4px; padding: 8px 10px;
    border: 1px solid var(--accent); border-radius: 8px; background: rgba(52,152,219,0.08); font-size: var(--fs-sm); }
.board-change-bar[hidden] { display: none; }
.board-change-text { flex: 1; font-weight: var(--fw-semibold); }
.board-change-restart { color: var(--text-warn); }
.board-btn.board-btn-main { background: var(--accent-fill, var(--accent)); border-color: transparent; color: #fff; }
.board-btn.board-btn-main:hover:not(:disabled) { background: var(--accent-fill-hover, var(--accent)); color: #fff; }
.board-confirm-now.board-confirm-changes { color: var(--text-main); font-variant-numeric: tabular-nums; }
.board-field-note { margin-left: 8px; font-size: var(--fs-xs); color: var(--text-sub); }
/* A secret's field, with Show and Remove beside it; the RFID tags, with the field to add one under the list. */
.board-secret-control { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }
.board-secret-option { font-size: var(--fs-sm); color: var(--text-sub); white-space: nowrap; cursor: pointer; }
.board-tags .board-grid { width: auto; min-width: min(100%, 520px); }
.board-tag-add { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
#board-settings-panel .board-note { margin: 0 0 8px; }
/* A certificate pasted on its many lines; a change that cannot be saved yet; what is charging, in a review. */
.board-input.board-cert { width: 420px; max-width: 100%; min-height: 4.5em; font-family: var(--font-mono, monospace);
    font-size: var(--fs-xs); resize: vertical; }
.board-change-text.board-change-problem { color: var(--text-danger); font-weight: var(--fw-regular); }
.board-confirm-now li.board-confirm-warn { color: var(--text-warn); }
/* The connector table changed in place, and one key of Advanced being changed. */
.board-connector-edit .board-grid { width: auto; min-width: min(100%, 640px); }
.board-connector-edit td.board-changed .board-input { border-color: var(--accent); }
.board-connector-edit td.board-changed .board-check { outline: 2px solid var(--accent); outline-offset: 1px; }
.board-input.board-input-tiny { width: 64px; min-width: 0; }
.board-btn.board-btn-small { padding: 2px 8px; font-size: var(--fs-xs); margin-left: 8px; }
.board-key-edit { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
@media (max-width: 820px) {
    .board-settings { grid-template-columns: minmax(0, 1fr); }
    .board-settings-nav { flex-direction: row; flex-wrap: wrap; }
    .board-settings-fields th { width: auto; }
}

/* Station tab commands (step 3): each button where the thing it acts on is
   shown — in its card, under the station's values, on its connector's row. */
.board-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.board-card .board-actions:not(:empty), #board-evse-actions:not(:empty) { margin-top: 10px; }
.board-section h3 { margin: 18px 0 4px; font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--text-heading); }
.board-section h3[hidden] { display: none; }
/* The connectors' table: the status in its colour, the buttons in the last column. */
.board-grid td.board-tone-ok { color: var(--text-ok); font-weight: var(--fw-semibold); }
.board-grid td.board-tone-busy { color: var(--text-info); font-weight: var(--fw-semibold); }
.board-grid td.board-tone-fault { font-weight: var(--fw-semibold); }
.board-grid td .board-actions { gap: 6px; }
#board-connectors { overflow-x: auto; }
.board-btn { font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-semibold); padding: 6px 12px;
    border-radius: 6px; cursor: pointer; color: var(--text-main); background: transparent;
    border: 1px solid var(--text-sub); transition: background 0.15s, color 0.15s; }
.board-btn:hover:not(:disabled) { background: var(--text-sub); color: var(--bg-panel); }
.board-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.board-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.board-btn.board-danger { color: var(--text-danger); border-color: var(--text-danger); }
.board-btn.board-danger:hover:not(:disabled) { background: var(--text-danger); color: #fff; }
.board-grid td .board-btn { padding: 4px 10px; }
/* The command running or last run: under the header, in view while the page
   scrolls (the tint is laid over the page's own background, so it is opaque). */
.board-command { position: sticky; top: 0; z-index: 2; margin-bottom: 16px; font-size: var(--fs-sm);
    padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border-color); color: var(--text-sub);
    background: linear-gradient(rgba(127,127,127,0.12), rgba(127,127,127,0.12)) var(--bg-main); }
.board-command[hidden] { display: none; }
.board-command-done { color: var(--text-ok);
    background: linear-gradient(rgba(79,175,124,0.14), rgba(79,175,124,0.14)) var(--bg-main); }
.board-command-failed, .board-command-refused { color: var(--text-danger);
    background: linear-gradient(rgba(231,99,92,0.14), rgba(231,99,92,0.14)) var(--bg-main); }
.board-history summary { cursor: pointer; font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--text-heading); }
.board-history .board-grid { margin-top: 10px; }
/* Overview > Diagnostics of every service: one small table per part. */
.board-diag-part h3 { margin: 12px 0 4px; font-size: var(--fs-sm); color: var(--text-heading); }
/* Settings > MQTT: the certificate the board has, to read. */
.board-cert-now { margin-top: 6px; font-size: var(--fs-sm); }
.board-cert-now summary { cursor: pointer; color: var(--text-sub); }
.board-cert-text { margin: 6px 0 0; padding: 8px; max-height: 220px; overflow: auto; white-space: pre-wrap;
                   word-break: break-all; font-family: 'JetBrains Mono', monospace; font-size: var(--fs-xs, 11px);
                   background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 6px; }
.board-grid tr.board-result-failed td:last-child, .board-grid tr.board-result-refused td:last-child { color: var(--text-danger); }

.board-confirm { border: 1px solid var(--border-color); border-radius: 12px; padding: 20px 22px; max-width: 460px;
    width: calc(100vw - 32px); box-sizing: border-box; background: var(--bg-panel); color: var(--text-main); }
.board-confirm::backdrop { background: rgba(0,0,0,0.45); backdrop-filter: blur(2px); }
.board-confirm h2 { margin: 0 0 6px; font-size: var(--fs-lg); color: var(--text-heading); }
.board-confirm-station { margin: 0 0 12px; font-weight: var(--fw-semibold); color: var(--text-sub); }
.board-confirm p { font-size: var(--fs-base); }
.board-confirm-now { margin: 0 0 12px; padding-left: 18px; font-size: var(--fs-sm); color: var(--text-warn); }
.board-confirm-now:empty { display: none; }
.board-confirm-btns { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }

/* --- Users tab (js/users.js) ------------------------------------------- */
#users-top { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 24px 28px 48px; }
#users-page-header { margin-bottom: 16px; }
#users-page-header h1 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); margin: 0 0 4px; color: var(--text-heading);
    letter-spacing: -0.01em; }
#users-page-header p { margin: 0 0 12px; font-size: var(--fs-base); color: var(--text-sub); max-width: 80ch; }
#users-result { margin: 0 0 14px; font-size: var(--fs-sm); color: var(--text-ok); }
#users-result.error { color: var(--text-danger); }
.users-table { width: 100%; max-width: 1200px; border-collapse: collapse; font-size: var(--fs-sm); }
.users-table th { text-align: left; font-weight: var(--fw-bold); color: var(--text-sub); text-transform: uppercase;
    font-size: var(--fs-xs); letter-spacing: 0.4px; padding: 6px 8px; border-bottom: 1px solid var(--border-color); }
.users-table td { padding: 7px 8px; border-bottom: 1px solid var(--border-color); color: var(--text-main);
    vertical-align: middle; word-break: break-word; }
.users-table .users-me { color: var(--text-sub); }
.users-table .users-state-pending { color: var(--text-warn); font-weight: var(--fw-semibold); }
.users-table .users-state-disabled { color: var(--text-sub); }
.users-table .users-mail-failed { display: block; color: var(--text-danger); font-size: var(--fs-xs); }
.users-table select { font: inherit; }
@media (max-width: 900px) {
    .users-table th:nth-child(n+5):not(:last-child), .users-table td:nth-child(n+5):not(:last-child) { display: none; }
}

