/* New Clean Styles for NGON Status Dashboard */
/* Built by extracting only used styles from styles.css.old */

/* Base styling */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background: #0a0a0a;
    color: #e0e0e0;
    line-height: 1.6;
    overflow-x: hidden;
}

/* Header */
.header {
    background: #1a1a1a;
    padding: 20px;
    border-bottom: 2px solid #2a2a2a;
}

.header h1 {
    color: #00ff00;
    font-size: 28px;
    font-weight: 600;
    display: inline-block;
}

/* Dropdown styles */
.dropdown {
    position: relative;
    display: inline-block;
}

.dropdown-title {
    cursor: pointer;
    transition: color 0.2s ease;
    user-select: none;
}

.dropdown-title:hover {
    color: #4a9eff;
}

.dropdown-content {
    display: none;
    position: absolute;
    background-color: #1a1a1a;
    min-width: 350px;
    box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.4);
    z-index: 1000;
    border: 1px solid #2a2a2a;
    border-radius: 8px;
    padding: 10px 0;
    top: 100%;
    left: 0;
}

.dropdown:hover .dropdown-content {
    display: block;
}

/* Mobile dropdown handling */
@media (max-width: 768px) {
    .dropdown-content {
        position: fixed;
        top: 0;
        left: 0;
        right: auto;
        width: 75vw;
        min-width: 0;
        z-index: 2000;
        max-height: 100vh;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        border-radius: 0 0 8px 0;
        border: none;
        border-right: 1px solid #2a2a2a;
        border-bottom: 1px solid #2a2a2a;
    }
    
    .dropdown:hover .dropdown-content {
        display: none; /* Disable hover on mobile */
    }
    
    .dropdown-title {
        position: relative;
    }
    
    /* Add click functionality for mobile */
    .dropdown-content.show {
        display: block !important;
    }
}

.dropdown-section {
    padding: 5px 0;
}

.dropdown-category {
    color: #00ff00;
    font-weight: bold;
    font-size: 14px;
    padding: 8px 15px 5px 15px;
    border-bottom: 1px solid #2a2a2a;
    margin-bottom: 5px;
}

.dropdown-link {
    color: #e0e0e0;
    padding: 6px 25px;
    text-decoration: none;
    display: block;
    font-size: 13px;
    transition: background-color 0.2s ease;
}

.dropdown-link:hover {
    background-color: #2a2a2a;
    color: #4a9eff;
}

.data-rate-info {
    float: right;
    color: #888;
    font-size: 14px;
    line-height: 40px;
}

/* Sites Section */
.sites-section {
    background: #1a1a1a;
    padding: 20px;
    margin: 20px;
    border-radius: 12px;
    border: 1px solid #2a2a2a;
}

.sites-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 15px;
    justify-content: center;
}

/* Container and Columns */
.container {
    width: 100%;  /* Full-width container */
    min-height: 100vh;
    margin: 0 auto;  /* Center the container horizontally */
    padding: 0 0 0 5px;
    display: flex;
    justify-content: flex-start;
    gap: 1rem;
    flex-wrap: nowrap;  /* Allow items to wrap on smaller screens */
    padding-left: 5px;
    box-sizing: border-box;
}

.column {
    background: #1a1a1a;
    border: 1px solid #2a2a2a;
    border-radius: 12px;
    padding: 20px;
    overflow: visible;
}

.column-cards {
    flex: 0 0 18%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    box-sizing: border-box;
    margin-right: 0.5rem;
}

.column-peps {
    flex: 0 0 15%;
    width: 15%;  
    display: flex;
    flex-direction: column;
    align-items: flex-start;  /* Left-align content inside the column */
    box-sizing: border-box;
}

.column-gens {
    flex: 1 1 0;
    min-width: 140px;  
    display: flex;
    flex-direction: column;
    align-items: flex-start;  /* Left-align content inside the column */
    box-sizing: border-box;
}

.column-notes {
    flex: 0 0 20%;  
    display: flex;
    flex-direction: column;
    align-items: flex-start;  /* Left-align content inside the column */
    box-sizing: border-box;
}

/* Summary boxes */
.summary-box {
    background: #2a2a2a;
    border: 1px solid #3a3a3a;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 15px;
    width: 100%;
    box-sizing: border-box;
}

.summary-value {
    font-size: 24px;
    font-weight: bold;
    color: #e0e0e0;
    margin-bottom: 5px;
}

.summary-detail {
    font-size: 12px;
    color: #888;
    margin-top: 8px;
}

/* Progress bars */
.progress-bar {
    width: 100%;
    height: 8px;
    background: #1a1a1a;
    background-image: none !important;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    border: none;
    border-radius: 4px;
    overflow: hidden;
    margin: 8px 0;
    box-shadow: none;
}

.progress-fill {
    height: 100%;
    background: #00ff00;
    border-radius: 4px;
    transition: width 0.3s ease;
}

/* List styling */
ul {
    list-style-type: none;
    padding: 0;
    margin: 0;
    width: 100%;
}

/* Device and Status Classes */
.clickable {
    cursor: pointer;
    color: #fff;
}

.clickable:hover {
    color: #00ff00;
    text-decoration: underline;
}

.device {
    display: flex;
    justify-content: flex-start;  /* Left-align the items in the list */
    align-items: center;
    margin-bottom: 5px;
    width: 100%;  /* Ensure each item takes full width */
}

.device-item {
    padding: 8px 0;
    border-bottom: 1px solid #2a2a2a;
    font-size: 14px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 8px;
    row-gap: 0;
    transition: all 0.2s ease;
}

.device-item:hover {
    background: #2a2a2a;
}

.device-item:last-child {
    border-bottom: none;
}

/* SVG Status Icons - crisp at any zoom level */
.status-icon {
    width: 12px;
    height: 12px;
    display: inline-block;
    margin: 2px;
    vertical-align: middle;
    flex-shrink: 0;
}

.status-icon.dot circle,
.status-icon.dot rect {
    transition: fill 0.2s ease;
}

.status-icon.inline {
    width: 12px;
    height: 12px;
    margin-right: 8px;
}

/* Status colors for SVG fills */
.status-icon.online circle,
.status-icon.online rect {
    fill: #00ff00;
}

.status-icon.offline circle,
.status-icon.offline rect {
    fill: #ff4444;
}

.status-icon.stuck circle,
.status-icon.stuck rect {
    fill: #ff6600;
}

.status-icon.blank circle,
.status-icon.blank rect {
    fill: transparent;
}

/* Legacy CSS classes - kept for any remaining non-SVG usage */
.dot {
    border-radius: 50%;
    width: 12px;
    height: 12px;
    display: inline-block;
    margin: 2px;
}

.dot.blank {
    background: transparent;
}

.square {
    width: 12px;
    height: 12px;
    display: inline-block;
    margin: 2px;
}

.square.blank {
    background: transparent;
}

.status-dot {
    height: 1.2vh;
    width: 1.2vh;
    border-radius: 50%;
    display: inline-block;
    margin-right: 1vh;
}

/* Generator status badge (NGON maintenance status) */
.gen-status-badge {
    display: inline-block;
    padding: 1px 4px;
    border-radius: 2px;
    font-size: 9px;
    font-weight: bold;
    margin-left: 6px;
    vertical-align: middle;
    white-space: nowrap;   /* never wrap a badge's text — a wrapped badge doubled the gen row height */
    flex-shrink: 0;        /* and don't let it get squeezed into wrapping either */
}

/* Site Card Styling */
.site-card {
    background: #2a2a2a;
    border: 1px solid #3a3a3a;
    border-radius: 8px;
    padding: 12px 16px;
    min-width: 120px;
    transition: all 0.2s ease;
    cursor: pointer;
}

.site-card:hover {
    border-color: #00ff00;
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 255, 0, 0.1);
}

.site-name {
    color: #fff;
    font-weight: 600;
    margin-bottom: 8px;
    font-size: 16px;
}

.site-icons {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.icon-row {
    display: flex;
    gap: 3px;
    flex-wrap: wrap;
    justify-content: center;
}

/* Sites-overview pod pixel maps: one pixel per miner at its real physical
   position. Bottom-aligned so pods of different heights share a baseline. */
.icon-row.pods {
    gap: 6px;
    align-items: flex-end;
    justify-content: center;
}
.pod-pixmap {
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    background: #141414;              /* not-installed / empty cells read as dark holes */
    outline: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 1px;
    vertical-align: bottom;
}

/* Generator group styling */
.group-box {
    background: #2a2a2a;
    border: 1px solid #3a3a3a;
    border-radius: 8px;
    margin-bottom: 15px;
}

.group-header {
    background: #3a3a3a;
    padding: 8px 12px;
    font-size: 12px;
    font-weight: bold;
    color: #00ff00;
    border-bottom: 1px solid #4a4a4a;
    cursor: pointer;
    user-select: none;
    transition: background-color 0.2s ease;
    position: relative;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
}

.group-header .capacity-info {
    color: #888;
    font-size: 11px;
    font-weight: normal;
    text-align: right;
}

.group-header:hover {
    background: #4a4a4a;
}

/* Auto-sleep/auto-wake toggle controls row */
.group-toggles {
    display: flex;
    gap: 14px;
    padding: 4px 12px;
    background: #333;
    border-bottom: 1px solid #4a4a4a;
    font-size: 11px;
    color: #888;
}

/* Auto-Restart sits on its OWN line above the R0/AS/AW/EMS toggles. Its phase
   chip is variable-width, and on the shared line it pushed the EMS "on @ M/D
   HH:MM" stamp out past AR, where it read as AR's timestamp. */
.group-ar-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 12px;
    background: #333;
    border-bottom: 1px solid #4a4a4a;
    font-size: 11px;
    color: #888;
}

.toggle-label {
    display: flex;
    align-items: center;
    gap: 5px;
    cursor: default;
    user-select: none;
}

.toggle-label.toggle-off .toggle-switch { background: #aa0000; }

.toggle-switch {
    display: inline-block;
    width: 28px;
    height: 14px;
    background: #555;
    border-radius: 7px;
    position: relative;
    cursor: pointer;
    transition: background 0.2s ease;
}

.toggle-switch.on {
    background: #00aa00;
}

.toggle-switch.sos.on {
    background: #0088cc;
}

/* AR = Auto-Restart. Amber to read as a powerful/attention action, distinct
   from the blue EMS and green R0/AS/AW. */
.toggle-switch.ar.on {
    background: #cc8800;
}

/* AR globally disabled (master kill switch off) — locked, non-interactive. */
.toggle-switch.ar-locked {
    background: #3a3a3a;
    cursor: not-allowed;
    opacity: 0.6;
}
.toggle-switch.ar-locked .toggle-knob { background: #777; }

/* Live AR phase chip next to the AR toggle (fed by /api/gen_autostart/status). */
.ar-chip { display: inline-flex; align-items: center; }
.ar-chip-inner {
    font-size: 10px;
    line-height: 1;
    padding: 3px 7px;
    border-radius: 8px;
    white-space: nowrap;
}

.toggle-switch .toggle-knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 10px;
    height: 10px;
    background: #ccc;
    border-radius: 50%;
    transition: left 0.2s ease;
}

.toggle-switch.on .toggle-knob {
    left: 16px;
    background: #fff;
}

.toggle-switch.saving {
    opacity: 0.5;
    pointer-events: none;
}

/* Disabled gens badge in toggles row */
.disabled-gens-badge {
    color: #ff6600;
    font-size: 11px;
    margin-left: auto;
    white-space: nowrap;
}

.disabled-gens-badge i {
    margin-right: 3px;
}

/* Service-disabled generator row */
.device-item.service-disabled {
    opacity: 0.5;
}

/* --- A TECH IS ON THIS GEN ---------------------------------------------------
   Hard case: by the time someone is working on a gen it has almost always been
   DISABLED first, so the row is already dimmed to 0.5 and any ordinary tag
   arrives greyed out — exactly when it most needs to be seen.

   So this does three things a tag can't. It cancels the dimming (a serviced row
   is the ONE thing on a dim row that must stay legible). It marks the row with a
   PATTERN rather than a colour — every hue on this page is spoken for, and
   hazard striping means "work area" in the physical world these gens live in, so
   it reads instantly and can't collide with a status colour. And the badge
   pulses, because this is the only state on the page where acting on stale
   information can hurt somebody. */
/* The bar is a PSEUDO-ELEMENT, not border-image. border-image paints every
   border the element has, and .device-item already carries a 1px border-bottom
   as its row separator -- so the stripe was landing along the bottom of the row
   as well, which read as a second, horizontal piece of tape. An absolutely
   positioned bar can only ever paint the left edge, and it also survives
   .device-item:hover, whose `background:` shorthand would have blown away a
   background-image version of the same idea. */
.device-item.gen-servicing {
    position: relative;
    opacity: 1;
    background: rgba(255, 179, 0, 0.07);
    padding-left: 10px;
}
.device-item.gen-servicing::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 5px;
    background: repeating-linear-gradient(45deg, #ffb300 0 7px, #20180a 7px 14px);
}
.device-item.gen-servicing:hover { background: rgba(255, 179, 0, 0.13); }
.gen-servicing-badge {
    display: inline-block;
    padding: 1px 6px;
    margin-left: 6px;
    border-radius: 3px;
    font-size: 9px;
    font-weight: bold;
    background: #ffb300;
    color: #17130a;
    animation: gen-servicing-pulse 1.8s ease-in-out infinite;
}
@keyframes gen-servicing-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }

/* Banner in the gen modal: whoever is about to press start reads this BEFORE
   the action, not as a 409 afterwards. */
.gen-servicing-banner {
    margin: 8px 0;
    padding: 9px 11px;
    border-radius: 6px;
    background: rgba(255, 179, 0, 0.12);
    color: #ffd98a;
    font-size: 12px;
    line-height: 1.45;
    position: relative;
    overflow: hidden;
    padding-left: 16px;
}
.gen-servicing-banner::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 5px;
    background: repeating-linear-gradient(45deg, #ffb300 0 7px, #20180a 7px 14px);
}
.gen-servicing-banner b { color: #ffb300; }

/* Gen buttons in service prep modal */
.service-gen-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 6px 10px;
    background: #2a2a2a;
    border: 1px solid #444;
    border-radius: 6px;
    cursor: pointer;
    font-size: 11px;
    color: #ccc;
    min-width: 70px;
    transition: all 0.15s ease;
}

.service-gen-btn:hover {
    border-color: #666;
    background: #333;
}

.service-gen-btn.disabled {
    border-color: #ff6600;
    background: rgba(255, 102, 0, 0.1);
    color: #ff6600;
}

/* Editable kW/gen link in group header */
.kw-edit-link {
    color: #ffaa00;
    cursor: pointer;
    transition: color 0.2s ease;
}

.kw-edit-link:hover {
    color: #ffcc44;
}

.group-content {
    padding: 0;
    transition: max-height 0.3s ease;
    overflow: clip;
    max-height: 5000px;
}

.group-content.collapsed {
    max-height: 0 !important;
}

.group-content .device-item {
    border-bottom: 1px solid #2a2a2a;
    padding: 6px 12px;
}

.group-content .device-item:last-child {
    border-bottom: none;
}

/* Status colors - only for legacy div-based indicators, not SVG */
div.dot.online,
div.square.online {
    background-color: #00ff00;
}

div.dot.offline,
div.square.offline {
    background-color: #ff4444;
}

div.dot.stuck,
div.square.stuck {
    background-color: #ff6600;
}

div.dot.blank,
div.square.blank {
    background-color: transparent;
}

/* Status colors for text */
.zero-hash {
    color: #ff4444;
}

.sleep-target {
    color: #4a9eff;
}

/* Header styles from the old file */
h2 {
    color: #00ff00;
    font-size: 18px;
    margin-bottom: 15px;
    font-weight: 600;
}

/* Generator Action Picker */
.gen-action-item {
    padding: 10px 16px;
    color: #ccc;
    cursor: pointer;
    font-size: 13px;
    display: flex;
    align-items: center;
    gap: 10px;
    transition: background 0.15s;
}
.gen-action-item:hover {
    background: #2a2a2a;
    color: white;
}

/* Gen detail modal two-column layout (Mesa + Live modals).
   Defaults reproduce the original inline desktop styling exactly; the mobile
   media query below stacks them so phones don't need landscape. */
.gen-modal-cols {
    display: flex;
    gap: 40px;
}
.gen-modal-col-left {
    flex: 1;
    max-height: 85vh;
    overflow-y: auto;
}
.gen-modal-col-right {
    flex: 2;
}

/* =====================================================================
   Generator SCHEMATIC view (status_gen_modal.js showSchematic).
   Everything scoped under .gen-schematic / .gsch-* / --gs-* / gsch-* keyframes
   so none of it collides with the rest of the status page.
   ===================================================================== */
.gen-schematic {
    --gs-good:#25f078; --gs-info:#4a9eff; --gs-warn:#ff9d2e; --gs-crit:#ff4d4d;
    --gs-line:#1c2b2c; --gs-wire:#3c554f; --gs-wire-soft:#2b3f3a;
    --gs-text:#c9d8d2; --gs-muted:#6f847c; --gs-faint:#465852;
    --gs-panel:#0f1719; --gs-bg2:#0d1315;
    --gs-mono:ui-monospace,"SF Mono","Cascadia Code","Roboto Mono",Menlo,Consolas,monospace;
    --gs-sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}
/* Schematic view wraps the modal to its content instead of the shared full-bleed
   98%×95vh size (which the data-heavy Mesa/Live modals need but this one doesn't). */
#genModal .modal-content.gsch-modal {
    width:fit-content; max-width:96vw;
    height:fit-content; max-height:92vh;
    margin:4vh auto;
}
.gsch-main { display:flex; gap:16px; align-items:flex-start; }
@media (max-width:900px) { .gsch-main { flex-direction:column; } }
.gsch-stage-wrap { min-width:0; overflow-x:auto; }
.gsch-stage {
    /* Explicit size (not flex-grow) so the fit-content modal can wrap it cleanly.
       Sized by viewport HEIGHT × the 1040:560 ratio the chip %-positions rely on,
       capped at 90vw so it never overflows a narrow window. Bump the vh to resize. */
    position:relative; width:min(calc(64vh * 1040 / 560), 90vw); aspect-ratio:1040/560;
    background:radial-gradient(900px 520px at 54% 44%, #0d1718 0%, #0a1012 70%, #080b0d 100%);
    border:1px solid var(--gs-line); border-radius:14px; overflow:hidden;
}
.gsch-stage svg { position:absolute; inset:0; width:100%; height:100%; display:block; }
.gsch-tray { width:280px; flex:none; background:var(--gs-panel); border:1px solid var(--gs-line); border-radius:12px; padding:12px 13px; }
@media (max-width:900px) { .gsch-tray { width:100%; } }
.gsch-tray-head { display:flex; flex-direction:column; gap:10px; margin-bottom:12px; }
.gsch-h { margin:0; font-size:10.5px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; color:var(--gs-muted); }
.gsch-cnt { font-family:var(--gs-mono); font-size:10px; color:var(--gs-faint); margin-left:6px; letter-spacing:0; text-transform:none; }
.gsch-addwrap { position:relative; }
.gsch-addbtn { width:100%; font-family:var(--gs-mono); font-size:12px; color:var(--gs-good);
    background:color-mix(in oklab, var(--gs-good) 12%, var(--gs-bg2)); border:1px solid color-mix(in oklab, var(--gs-good) 45%, transparent);
    border-radius:9px; padding:9px 13px; cursor:pointer; }
.gsch-addbtn:hover { background:color-mix(in oklab, var(--gs-good) 20%, var(--gs-bg2)); }

/* callout chips (HTML overlay on the stage) */
.gsch-chip { position:absolute; transform:translate(-50%,-50%); display:flex; align-items:baseline; gap:6px;
    white-space:nowrap; padding:4px 8px; border-radius:8px; background:rgba(9,13,14,.92);
    border:1px solid var(--gs-line); box-shadow:0 3px 12px rgba(0,0,0,.4); z-index:3; }
/* Callout chips are HTML divs over the SVG, so the .gsch-svg text rule misses them. */
.gsch-chip { user-select:none; -webkit-user-select:none; cursor:default; }
.gsch-chip .k { font-size:9px; letter-spacing:.1em; text-transform:uppercase; color:var(--gs-muted); }
.gsch-chip .v { font-family:var(--gs-mono); font-size:13px; font-weight:600; color:var(--gs-text); font-variant-numeric:tabular-nums; }
.gsch-chip .u { font-size:9.5px; color:var(--gs-faint); font-family:var(--gs-mono); }
.gsch-chip.state .v { font-size:11px; letter-spacing:.04em; }
/* stacked chip — label wraps onto its own line with the value under it (louvers) */
.gsch-chip.stack { flex-direction:column; align-items:center; gap:1px; text-align:center; }
.gsch-chip.stack .v { font-size:15px; line-height:1.1; }
.gsch-chip.hot .v { color:var(--gs-warn); } .gsch-chip.crit .v { color:var(--gs-crit); } .gsch-chip.on .v { color:var(--gs-good); }

/* watch tiles */
.gsch-tiles { display:grid; grid-template-columns:1fr; gap:9px; max-height:60vh; overflow-y:auto; }
.gsch-tile { position:relative; background:var(--gs-bg2); border:1px solid var(--gs-line); border-radius:10px; padding:10px 11px 9px; }
.gsch-tile .tk { font-size:10px; letter-spacing:.06em; color:var(--gs-muted); display:block; margin-bottom:4px; padding-right:22px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.gsch-tile .tv { font-family:var(--gs-mono); font-size:18px; font-weight:600; color:var(--gs-text); font-variant-numeric:tabular-nums; }
.gsch-tile .tu { font-family:var(--gs-mono); font-size:11px; color:var(--gs-faint); margin-left:4px; }
.gsch-tile .sec { position:absolute; bottom:8px; right:10px; font-size:8.5px; letter-spacing:.08em; text-transform:uppercase; color:var(--gs-faint); }
.gsch-tile .acts { position:absolute; top:6px; right:6px; }
.gsch-tile .acts button { width:20px; height:20px; border:none; background:transparent; color:var(--gs-faint); cursor:pointer; border-radius:5px; font-size:12px; line-height:1; }
.gsch-tile .acts button:hover { background:var(--gs-line); color:var(--gs-text); }
.gsch-empty { color:var(--gs-muted); font-size:12.5px; padding:14px 4px; text-align:center; border:1px dashed var(--gs-line); border-radius:10px; }
.gsch-empty b { color:var(--gs-good); font-family:var(--gs-mono); }

/* field picker popover */
.gsch-picker { position:absolute; top:calc(100% + 6px); right:0; width:326px; max-width:calc(100vw - 40px); z-index:20;
    background:var(--gs-panel); border:1px solid var(--gs-wire-soft); border-radius:12px; box-shadow:0 18px 50px rgba(0,0,0,.6); padding:12px; display:none; }
.gsch-picker.open { display:block; }
.gsch-search { width:100%; font-family:var(--gs-mono); font-size:13px; color:var(--gs-text); background:var(--gs-bg2); border:1px solid var(--gs-line); border-radius:8px; padding:9px 11px; margin-bottom:9px; }
.gsch-search:focus { outline:none; border-color:var(--gs-good); }
.gsch-secchips { display:flex; flex-wrap:wrap; gap:5px; margin-bottom:9px; }
.gsch-secchips button { font-family:var(--gs-mono); font-size:10px; letter-spacing:.05em; color:var(--gs-muted); background:var(--gs-bg2); border:1px solid var(--gs-line); border-radius:20px; padding:4px 9px; cursor:pointer; }
.gsch-secchips button:hover { color:var(--gs-good); border-color:var(--gs-wire); }
.gsch-plist { max-height:280px; overflow-y:auto; display:flex; flex-direction:column; gap:2px; }
.gsch-pgroup { font-size:9.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--gs-faint); padding:8px 4px 3px; }
.gsch-pitem { display:flex; align-items:center; gap:9px; padding:7px 8px; border-radius:7px; cursor:pointer; }
.gsch-pitem:hover { background:var(--gs-bg2); }
.gsch-pitem .box { width:15px; height:15px; border:1.5px solid var(--gs-wire); border-radius:4px; flex:none; display:grid; place-items:center; }
.gsch-pitem.sel .box { background:var(--gs-good); border-color:var(--gs-good); }
.gsch-pitem.sel .box::after { content:"✓"; color:#04120a; font-weight:800; font-size:11px; }
.gsch-pitem .pl { font-size:12.5px; color:var(--gs-text); flex:1; }
.gsch-pitem .pu { font-family:var(--gs-mono); font-size:10.5px; color:var(--gs-faint); }
.gsch-pnone { color:var(--gs-muted); font-size:12px; padding:14px; text-align:center; }

/* svg components */
.gsch-encl { fill:none; stroke:var(--gs-line); stroke-width:1.4; }
.gsch-skid { fill:#0c1213; stroke:var(--gs-wire-soft); stroke-width:1.2; }
.gsch-part { fill:#0e1618; stroke:var(--gs-wire); stroke-width:1.6; stroke-linejoin:round; }
.gsch-soft { fill:none; stroke:var(--gs-wire-soft); stroke-width:1.2; }
.gsch-fin { fill:none; stroke:var(--gs-wire-soft); stroke-width:1; }
.gsch-lbl { fill:var(--gs-faint); font:600 10px var(--gs-mono); letter-spacing:.08em; }
/* backing plate so a label centered ON a part isn't crossed by its detail lines */
.gsch-lblbg { fill:#0e1618; stroke:none; }
/* engine instrument plate (inside the engine block) */
.gsch-plate-h { fill:var(--gs-faint); font:600 9px var(--gs-mono); letter-spacing:.12em; }
.gsch-erbox { fill:#0a1214; stroke:var(--gs-wire-soft); stroke-width:1; }
.gsch-erk { fill:var(--gs-muted); font:600 9px var(--gs-mono); letter-spacing:.1em; }
.gsch-erv { fill:var(--gs-text); font:700 12px var(--gs-mono); }
.gsch-erv.hot { fill:var(--gs-warn); }
.gsch-erv.crit { fill:var(--gs-crit); }
.gsch-eru { fill:var(--gs-faint); font-size:9px; font-weight:600; }
/* headline power plate (above the alternator, inside the body) */
.gsch-pwrbox { fill:#0a1214; stroke:var(--gs-wire); stroke-width:1.2; }
.gsch-pwrk { fill:var(--gs-muted); font:600 9px var(--gs-mono); letter-spacing:.18em; }
.gsch-pwrv { fill:var(--gs-text); font:700 22px var(--gs-mono); transition:fill .3s; }
.gsch-pwrv.on { fill:var(--gs-good); }
.gsch-pwru { fill:var(--gs-faint); font-size:11px; font-weight:600; }
.gsch-leader { stroke:var(--gs-wire); stroke-width:1; stroke-dasharray:2 3; opacity:.7; }
.gsch-anchor { fill:var(--gs-wire); }
.gsch-fanring { fill:#0b1214; stroke:var(--gs-wire); stroke-width:1.6; }
.gsch-hub { fill:#0e1618; stroke:var(--gs-wire); stroke-width:1.6; }
.gsch-lvbox { fill:#070c0d; stroke:var(--gs-wire-soft); stroke-width:1.2; }
/* Slats must read clearly against the near-black louver box — they ARE the status
   (fully open = thin lines, closed = solid stack), so keep them the brightest thing
   in the box. */
.gsch-slat { fill:#5c817a; stroke:#93bdb1; stroke-width:.6; }
.gsch-rad { stroke:var(--gs-wire); stroke-width:1.6; fill:#12262c; }
.gsch-blades path { fill:#1a2a2c; stroke:var(--gs-wire-soft); stroke-width:1; }
.gsch-contact { fill:var(--gs-faint); transition:fill .3s; }
.gsch-glow { opacity:0; transition:opacity .6s; }
.gsch-gasflow { stroke:var(--gs-info); stroke-width:2.4; fill:none; opacity:0; stroke-dasharray:4 7; }
.gsch-puff { opacity:0; transform-box:fill-box; transform-origin:center; }
/* exhaust stack rain flapper — hinged at the high side of the mitered cut, rests
   closed on the mouth and flutters open while the engine is running */
.gsch-flap line { stroke:var(--gs-wire); stroke-width:5; fill:none; }
.gsch-flap { transform-box:fill-box; transform-origin:left top; transition:transform .4s; }
.gsch-hinge { fill:var(--gs-wire); }
.gsch-bus, .gsch-load { stroke:var(--gs-faint); stroke-width:3; fill:none; transition:stroke .3s; }
.gsch-arrow { fill:var(--gs-faint); transition:fill .3s; }
.gsch-bridge { fill:var(--gs-wire); transform-box:fill-box; transform-origin:left center; transform:rotate(-34deg); transition:transform .35s cubic-bezier(.5,1.6,.5,1); }

/* dynamic (fan spin / louver angle) */
.gsch-blades { transform-box:fill-box; transform-origin:center; }
.gsch-louver .gsch-slat { transform-box:fill-box; transform-origin:center; transition:transform .9s ease;
    transform:scaleY(calc(1 - .8 * var(--gs-louver, 0))); }

/* state-driven animation (classes toggled on the svg root) */
.gsch-svg.gsch-running .gsch-blades { animation:gsch-spin var(--gs-fan-dur, .8s) linear infinite; }
.gsch-svg.gsch-running .gsch-glow { opacity:.45; animation:gsch-pulse 2.6s ease-in-out infinite; }
.gsch-svg.gsch-running .gsch-gasflow { opacity:.85; animation:gsch-flow 1.1s linear infinite; }
.gsch-svg.gsch-running .gsch-puff { animation:gsch-puff 2.4s ease-out infinite; }
.gsch-svg.gsch-running .gsch-puff.p2 { animation-delay:.8s; }
.gsch-svg.gsch-running .gsch-puff.p3 { animation-delay:1.6s; }
.gsch-svg.gsch-running .gsch-flap { animation:gsch-flap 1.3s ease-in-out infinite; }
.gsch-svg.gsch-energized .gsch-bus, .gsch-svg.gsch-energized .gsch-load { stroke:var(--gs-good); stroke-dasharray:8 6; animation:gsch-busflow .5s linear infinite; }
.gsch-svg.gsch-energized .gsch-arrow { fill:var(--gs-good); }
.gsch-svg.gsch-closed .gsch-bridge { transform:rotate(0); }
.gsch-svg.gsch-closed .gsch-contact { fill:var(--gs-good); }

@keyframes gsch-spin { to { transform:rotate(360deg); } }
@keyframes gsch-pulse { 50% { opacity:.22; } }
@keyframes gsch-flow { to { stroke-dashoffset:-22; } }
@keyframes gsch-busflow { to { stroke-dashoffset:-14; } }
@keyframes gsch-puff { 0% { opacity:.5; transform:translateY(0) scale(.6); } 100% { opacity:0; transform:translateY(-34px) scale(1.5); } }
@keyframes gsch-flap { 0%,100% { transform:rotate(0deg); } 50% { transform:rotate(-17deg); } }
@keyframes gsch-wpulse { 50% { transform:scale(1.14); } }

/* warning marker — OUTER group is positioned via the SVG transform attribute (JS);
   the pulse lives on the INNER group so its scale() can't clobber the translate. */
/* Nothing on the diagram is meant to be selected — without this every SVG <text>
   (labels, plate readouts, the warning "!") gives a text-caret cursor on hover. */
.gsch-svg { cursor:default; }
.gsch-svg text { user-select:none; -webkit-user-select:none; }
.gsch-warn { cursor:help; }   /* it has a tooltip, so say so */
.gsch-warn { opacity:0; transition:opacity .3s; }
.gsch-warn.show { opacity:1; }
/* count badge when several faults share one component */
.gsch-warn-nbg { fill:#12191b; stroke:var(--gs-warn); stroke-width:1.6; }
.gsch-warn.crit .gsch-warn-nbg { stroke:var(--gs-crit); }
.gsch-warn-n { fill:var(--gs-warn); font:700 10px var(--gs-sans); }
.gsch-warn.crit .gsch-warn-n { fill:var(--gs-crit); }
.gsch-warn-in { transform-box:fill-box; transform-origin:center; }
.gsch-warn.show .gsch-warn-in { animation:gsch-wpulse 1s ease-in-out infinite; }
.gsch-warn .gsch-tri { fill:var(--gs-warn); stroke:#1a1205; stroke-width:1.4; }
.gsch-warn.crit .gsch-tri { fill:var(--gs-crit); }
.gsch-warn .gsch-bang { fill:#1a1205; font:800 15px var(--gs-sans); }

@media (prefers-reduced-motion:reduce) {
    .gsch-svg.gsch-running .gsch-blades, .gsch-svg.gsch-running .gsch-glow,
    .gsch-svg.gsch-running .gsch-gasflow, .gsch-svg.gsch-running .gsch-puff,
    .gsch-svg.gsch-running .gsch-flap,
    .gsch-svg.gsch-energized .gsch-bus, .gsch-svg.gsch-energized .gsch-load,
    .gsch-warn.show .gsch-warn-in { animation:none; }
}

/* Reorder-generators icon in gen group headers */
.gen-reorder-icon {
    color: #888;
    cursor: pointer;
    margin-left: 2em;
    flex-shrink: 0;
    transition: color 0.2s ease;
}
.gen-reorder-icon:hover {
    color: #4a9eff;
}

/* OOS wrench on gen column headers + the gen rows inside the OOS modal */
.oos-wrench {
    font-size: 0.62em;
    color: #888;
    cursor: pointer;
    margin-left: 10px;
    vertical-align: middle;
    transition: color 0.2s ease;
    white-space: nowrap;
}
.oos-wrench:hover {
    color: #4a9eff;
}
.oos-gen-row {
    padding: 9px 10px;
    border: 1px solid #3a3a3a;
    border-radius: 5px;
    margin-bottom: 6px;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.oos-gen-row:hover {
    border-color: #4a9eff;
    background: #222;
}

/* Modals */
.modal {
    display: none;
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    animation: fadeIn 0.2s ease;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.modal-content {
    position: relative;
    background-color: #1a1a1a;
    margin: 1% auto;
    padding: 20px;
    border: 1px solid #3a3a3a;
    border-radius: 12px;
    width: 98%;
    max-width: none;
    height: 95vh;
    overflow: auto;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}

.close {
    color: #aaa;
    position: absolute;
    top: 15px;
    right: 20px;
    font-size: 28px;
    font-weight: bold;
    cursor: pointer;
    transition: color 0.2s ease;
    z-index: 1001;
}

.close:hover {
    color: #fff;
}

/* Loading spinner */
.loading {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 2px solid #3a3a3a;
    border-radius: 50%;
    border-top-color: #00ff00;
    animation: spin 1s ease-in-out infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Custom scrollbar styling for modals */
.modal-content, #genModal .modal-content, #oosGensModal > div {
    scrollbar-width: thin !important;
    scrollbar-color: #666 transparent !important;
}

/* Special sections in summary column */
#oosGenerators, #spareGenerators {
    overflow-wrap: break-word;
    word-wrap: break-word;
}

#oosGenerators div, #spareGenerators div, #powerControlLog div, #minerMonitorLog div {
    margin-bottom: 2px;
    line-height: 1.3;
}

/* Log entry styling */
.log-entry {
    padding: 2px 4px;
    margin-bottom: 2px;
    font-size: 11px;
    line-height: 1.3;
}

.log-count-wake {
    color: #00ff00;
    font-weight: bold;
}

.log-count-sleep {
    color: #ff6666;
    font-weight: bold;
}

.log-count-reboot {
    color: #ffff00;
    font-weight: bold;
}

.log-count-fix-pools {
    color: #aa66ff;
    font-weight: bold;
}

.log-count-fix-power {
    color: #33cccc;
    font-weight: bold;
}

/* Media queries for mobile responsiveness */
@media (max-width: 768px) {
    .container {
        flex-wrap: wrap;
        height: auto;
    }
    
    .column-peps,
    .column-gens,
    .column-notes {
        flex: 0 0 100% !important;
        width: 100% !important;
        margin-bottom: 20px;
    }
    
    /* Mobile column order: Summary, then Pods, then Gens
       (Site Overview is outside .container so it stays on top) */
    .column-notes { order: 1; }
    .column-peps  { order: 2; }
    .column-gens  { order: 3; }

    /* Sites section adjustments for mobile */
    .sites-section {
        margin: 10px;
        padding: 15px;
    }
    
    .sites-grid {
        gap: 10px;
    }
    
    /* Header adjustments */
    .header h1 {
        font-size: 24px;
    }
    
    /* Reduce column padding on mobile */
    .column {
        padding: 15px;
    }

    /* Gen detail modal: stack the info column above the charts so the modal
       is readable in portrait (no landscape rotation needed). */
    .gen-modal-cols {
        flex-direction: column;
        gap: 16px;
    }
    .gen-modal-col-left {
        max-height: none;
        overflow-y: visible;
    }
    /* Let the whole modal scroll as one and use the full phone width. */
    .modal-content {
        width: 96vw;
        padding: 14px;
    }
}

/* Starlink WAN status icon (green = up, red = down) */
.starlink-dish {
    font-size: 11px;
    margin-right: 3px;
    vertical-align: middle;
}
.starlink-dish.up { color: #00ff00; }
.starlink-dish.down { color: #ff4444; }

/* Live generator-telemetry icon (green = gen is streaming live data) */
.live-data-icon {
    font-size: 10px;
    margin-left: 5px;
    color: #00ff00;
    vertical-align: middle;
    flex-shrink: 0;
}

/* Our gen-warning triangle (threshold/status-bit warnings, not the DSE rollup) */
.gen-warning-icon {
    font-size: 11px;
    margin-left: 6px;
    vertical-align: middle;
    flex-shrink: 0;
}
.gen-warning-icon.warn { color: #ffb300; }
.gen-warning-icon.critical {
    color: #ff4444;
    animation: gen-warn-pulse 1.6s ease-in-out infinite;
}
@keyframes gen-warn-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}
/* Why a gen is down, on its own full-width line under the gen number — aligned
   with the notes line beneath it so the two read as one indented block.
   Red because it is the reason, not a note; 12px because the row above is the
   headline. Full text, never ellipsised: it used to live in the right-hand cell
   where it was both the widest element and the elastic one, so it was quietly
   paying for every chip we added. */
/* .device-item is a WRAPPING FLEX container, so a child that does not claim the
   full basis shares its line with the next one -- which is how the reason and the
   note ended up side by side. The kW bar already declares flex:0 0 100% for this
   reason; these two now say it explicitly rather than relying on being the only
   auto-width item on the row, which is what quietly broke the moment a second one
   appeared. padding rather than margin, so the indent lives INSIDE the 100% basis
   and cannot overflow the row. */
.gen-down-reason,
.gen-note-line {
    flex: 0 0 100%;
    width: 100%;
    box-sizing: border-box;
    padding-left: 20px;
    line-height: 1.3;
}
.gen-down-reason { color: #ff4444; font-size: 12px; }
.gen-note-line   { color: #888; }
.device-item.service-disabled .gen-down-reason { color: #c06060; }

/* Capacity-hold badge. Amber, and deliberately NOT the hazard striping used for
   a tech being on a gen -- that one means a PERSON could get hurt; this one means
   a machine is being nursed. Sized for this page's rows; mobile.css sizes the
   same class for its own. */
.gen-hold-badge {
    display: inline-block;
    padding: 1px 5px;
    border-radius: 2px;
    font-size: 9px;
    font-weight: bold;
    white-space: nowrap;
    flex-shrink: 0;
    background: rgba(255, 170, 0, 0.16);
    color: #ffc14d;
    border: 1px solid rgba(255, 170, 0, 0.45);
}

/* The gen row now spaces its children with flex `gap`, so the per-chip
   margin-left would double up to 12px and, worse, is applied inconsistently
   (.pm-pill has it, the icons have it, the power cell never did -- which is why
   a row carrying Swap + PM ended up with its tags pressed against the battery
   voltage). Scoped to .device-item so gen_manager.py and mesa_gen_tracker.py,
   which use these same chip classes in non-flex rows, keep their own spacing. */
.device-item .gen-status-badge,
.device-item .pm-pill,
.device-item .gen-servicing-badge,
.device-item .gen-hold-badge,
.device-item .gen-muted-icon,
.device-item .gen-hold-icon,
.device-item .gen-warning-icon { margin-left: 0; }

.gen-hold-icon {
    font-size: 11px;
    margin-left: 6px;
    vertical-align: middle;
    flex-shrink: 0;
    color: #4a9eff;   /* blue — matches the EMS/sleep family */
}
/* Ignored/muted alarm on a gen (known-bad sensor). Grey bell-slash = intentionally
   silenced — this signal won't warn or arm a capacity hold on this gen. */
.gen-muted-icon {
    font-size: 11px;
    margin-left: 6px;
    vertical-align: middle;
    flex-shrink: 0;
    color: #888;
}
/* Gen filed under a different site in Mesa than in master_config. Purple keeps it
   clearly apart from the health icons (amber/red warning, blue hold, grey mute) —
   this is a bookkeeping problem, not a generator fault. */
.gen-misplaced-icon {
    font-size: 11px;
    margin-left: 6px;
    vertical-align: middle;
    flex-shrink: 0;
    color: #aa44ff;
}

/* Per-gen kW bar: full-width thin track on its own row under the gen's data + notes.
   Filled left→right on a FIXED 0..trip scale so fills line up across a group. The
   background is a blue→green→orange gradient with stops at fixed absolute kW positions
   (see genKwBar() in status_core.js); .gkb-cover dims the unfilled right portion so the
   bright fill ends at the gen's kW, in its zone color. Fills stay comparable regardless
   of gen tags/notes because the bar is always the full row width. */
.gen-kw-bar {
    position: relative;
    flex: 0 0 100%;
    width: 100%;
    height: 3px;
    margin: 4px 0 1px;
    border-radius: 2px;
    overflow: hidden;
}
.gen-kw-bar .gkb-cover {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    /* left set inline to the fill %; everything to the right is muted "empty track" */
    background: rgba(9, 11, 14, 0.82);
}
/* Down gen: inert neutral track — keeps row height consistent, no kW reading implied. */
.gen-kw-bar-empty {
    background: #23262b;
}
/* Down gen WITH telemetry: battery bar. Same track mechanic as .gen-kw-bar but a
   touch taller with a soft glow so it reads as a distinct (battery, not kW) gauge.
   Fill color (violet/amber/red) is set inline by genBatteryBar(); length = charge. */
.gen-batt-bar {
    height: 4px;
    box-shadow: 0 0 4px rgba(150, 90, 240, 0.35);
}
/* Stale-Mesa battery: last-known value only (feed frozen) — no glow, dimmed, so it
   reads as "we don't have current battery for this gen" rather than a live reading. */
.gen-batt-bar-stale {
    box-shadow: none;
    opacity: 0.55;
}

/* Small muted unit suffix on gen readouts (PSI / kW) — keeps meaning without eating
   the horizontal space a full-size repeated unit does on every row. */
.gen-unit {
    font-size: 0.72em;
    font-weight: 400;
    opacity: 0.55;
    margin-left: 1px;
}

/* ===== Site Issues panel (replaces Miner Control box) ===== */
#siteIssuesPanel { font-size: 13px; }
.si-panel details { margin: 0; }
.si-site { border-bottom: 1px solid #222; }
.si-site > summary { list-style: none; cursor: pointer; display: flex;
    justify-content: space-between; align-items: center; padding: 5px 2px; font-weight: 600; }
.si-site > summary::-webkit-details-marker { display: none; }
.si-site > summary:hover { color: #4a9eff; }
.si-cnt { font-size: 12px; padding: 0 8px; border-radius: 10px; background: #3a2a12; color: #ffb454; font-weight: 400; }
.si-cat { margin: 2px 0 3px 10px; }
.si-cat > summary, .si-node > summary { list-style: none; cursor: pointer; padding: 2px 3px; }
.si-cat > summary::-webkit-details-marker, .si-node > summary::-webkit-details-marker { display: none; }
.si-cat > summary:before, .si-node > summary:before { content: '\25B8  '; color: #557055; }
.si-cat[open] > summary:before, .si-node[open] > summary:before { content: '\25BE  '; }
.si-cat > summary { color: #cfe3cf; font-weight: 600; font-size: 12px; }
.si-ccnt { color: #ffb454; font-weight: 400; }
.si-node { margin-left: 14px; }
.si-node > summary { color: #bcd; font-size: 12px; }
.si-ncnt { color: #888; font-size: 11px; }
.si-leaf { padding: 2px 0 0 26px; color: #cdd; }
.si-line { padding: 1px 0 1px 38px; font-size: 12px; color: #9fb0a0; }
.si-net { color: #ff9a9a; padding-left: 10px; }
.si-clear { color: #7fbf7f; font-size: 12px; padding: 6px 2px 2px; }
.si-muted { color: #666; font-size: 11px; padding: 2px; }
.si-allclear { color: #7fbf7f; padding: 8px 2px; }
.si-error { color: #ff9a9a; font-size: 12px; padding: 6px 2px; }

/* ---------------------------------------------------------------------------
   Capacity Utilization table (power modal) — collapsible site > group > gen.
   Grid rather than <table> so the three nesting levels can share one column
   ruler while each level keeps its own row styling.
   --------------------------------------------------------------------------- */
.cap-table {
    border: 1px solid #3a3a3a;
    border-radius: 6px;
    overflow: hidden;
    font-size: 12px;
}

.cap-row {
    display: grid;
    grid-template-columns: minmax(150px, 2fr) 60px 80px 80px 110px 90px 90px minmax(110px, 1fr);
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border-bottom: 1px solid #2a2a2a;
}

.cap-row.cap-head {
    background: #222;
    color: #888;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.cap-row.cap-site {
    background: #262626;
    color: #fff;
    font-weight: bold;
    cursor: pointer;
}

.cap-row.cap-site:hover { background: #2e2e2e; }

.cap-row.cap-group {
    background: #1f1f1f;
    color: #ddd;
    cursor: pointer;
}

.cap-row.cap-group:hover { background: #272727; }

.cap-row.cap-gen { background: #1a1a1a; }

.cap-num { text-align: right; white-space: nowrap; }
.cap-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cap-why  { text-align: left; overflow: hidden; white-space: nowrap; }

.cap-caret {
    font-size: 9px;
    color: #666;
    transition: transform 0.15s ease;
    display: inline-block;
    width: 10px;
}

/* Collapsed by default — .open is toggled by powerModal.toggleCapRow() */
.cap-children { display: none; }
.cap-children.open { display: block; }

@media (max-width: 768px) {
    /* Drop capacity/used/miners columns on narrow screens; load + headroom + why
       are the three that actually drive a decision. */
    .cap-row { grid-template-columns: minmax(110px, 2fr) 46px 70px 78px minmax(90px, 1fr); }
    .cap-row > :nth-child(4),
    .cap-row > :nth-child(5),
    .cap-row > :nth-child(7) { display: none; }
}

/* --- Maintenance (PM) chips -------------------------------------------------
   Rounded, outlined pills — deliberately a different SHAPE and weight from the
   solid .gen-status-badge tags they sit beside. An amber "Inspect" badge and an
   amber PM chip were the same size, shape and colour, so the two read as the same
   kind of thing. Same palette as the Mesa Gen Tracker, so a chip means the same
   thing on whichever page you're looking at. */
.pm-pill {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 9px;
    font-size: 9px;
    font-weight: bold;
    margin-left: 6px;
    white-space: nowrap;
    border: 1px solid;
    vertical-align: middle;
}
.pm-pill.overdue  { color: #ff8a8a; border-color: rgba(255,68,68,.55);   background: rgba(255,68,68,.13); }
.pm-pill.due      { color: #ffb27a; border-color: rgba(255,102,0,.55);   background: rgba(255,102,0,.13); }
.pm-pill.upcoming { color: #ffd48a; border-color: rgba(255,170,0,.45);   background: rgba(255,170,0,.10); }
.pm-pill.ok       { color: #93a3b3; border-color: rgba(147,163,179,.32); background: rgba(147,163,179,.07); }
.pm-pill.nodata   { color: #8a8a8a; border-color: rgba(138,138,138,.32); background: rgba(138,138,138,.07); }
/* Booked: colour answers WHEN THEY'RE COMING, not how overdue it is. Once a week
   is booked the interval state stops mattering — someone is handling it — so
   urgency colours are reserved for gens with nobody scheduled. */
.pm-pill.booked   { color: #7fbcff; border-color: rgba(74,158,255,.45);  background: rgba(74,158,255,.10); }
.pm-pill.booked-now   { color: #c9a6ff; border-color: rgba(170,68,255,.50);  background: rgba(170,68,255,.13); }
.pm-pill.booked-next  { color: #7fbcff; border-color: rgba(74,158,255,.45);  background: rgba(74,158,255,.10); }
.pm-pill.booked-later { color: #93a3b3; border-color: rgba(147,163,179,.32); background: rgba(147,163,179,.07); }
.pm-pill.done     { color: #7fd6a0; border-color: rgba(0,170,68,.45);    background: rgba(0,170,68,.10); }

/* ---------------------------------------------------------------------------
   Generator quick-search (header box + row states)

   Highlight-in-place rather than filter: the columns keep their shape so the
   hit's site and group stay visible, which is usually what you actually wanted
   to know. Group headers deliberately stay bright for the same reason.
   Driven by StatusCore.setGenSearch() / buildGeneratorItem().
   --------------------------------------------------------------------------- */
/* The header is a flex row, and the right-hand cluster's width changes all day:
   the connection chip alone swings between "Live" and "Reconnecting... (7/10)",
   and scanner/watcher/services do the same. With the default flex-shrink the whole
   cluster compressed and re-wrapped on every status flip, which moved the search box
   and read as it resizing. Pin the cluster and let the TITLE absorb the shrink
   instead -- it has slack, and nothing in it moves when it gives up space. */
.header > div:first-child { min-width: 0; }
.data-rate-info { flex-shrink: 0; }

.gen-search-wrap {
    display: inline-block;
    flex: none;
    margin-right: 18px;
    vertical-align: middle;
}

/* Positioning context for the magnifier and the clear button. Deliberately the
   input's own box rather than .gen-search-wrap: the wrapper also holds the match
   count, so anchoring to it would put the clear button ~62px right of the input. */
.gen-search-field {
    position: relative;
    display: inline-block;
    vertical-align: middle;
}

.gen-search-icon {
    position: absolute;
    left: 11px;
    top: 50%;
    transform: translateY(-50%);
    color: #666;
    font-size: 12px;
    pointer-events: none;
}

/* Sits ON the header, which composites to #161616 (body #0a0a0a under the
   rgba(255,255,255,0.05) header wash). So the box is specified the same way the
   header is -- as a translucent wash rather than a flat hex -- and lands just
   under it at #121212: present, but not a separate object pasted on top. The old
   flat #0f0f0f with a #2a2a2a border drew an edge nearly 3x the header's
   brightness, which is what made it look stuck there.

   34px tall is free: .data-rate-info already reserves a 40px line box, and the
   2.2em title next to it is taller still, so the header does not grow. */
.gen-search {
    background: rgba(0, 0, 0, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 6px;
    color: #ddd;
    font-size: 14px;
    line-height: normal;
    height: 34px;
    padding: 0 26px 0 30px;   /* leaves room for the magnifier and the × */
    /* Fixed on all axes and flex-inert, so no layout context anywhere up the tree
       can resize it. Only the border colour reacts to focus. */
    width: 200px;
    min-width: 200px;
    max-width: 200px;
    flex: none;
    outline: none;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

/* Lifts slightly on interaction so it reads as live without changing size. */
.gen-search:hover { background: rgba(0, 0, 0, 0.10); }

.gen-search:focus {
    border-color: #00ff00;
    background: rgba(0, 0, 0, 0.30);
}

/* Shown only when there is something to clear -- a permanent x next to an empty
   box is just clutter. tabindex="-1" in the markup keeps it out of the tab order
   so tabbing off the input still lands where you expect. */
.gen-search-clear {
    position: absolute;
    right: 7px;
    top: 50%;
    transform: translateY(-50%);
    display: none;
    width: 17px;
    height: 17px;
    padding: 0;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #777;
    font-size: 16px;
    line-height: 15px;
    text-align: center;
    cursor: pointer;
}

.gen-search-field.has-query .gen-search-clear { display: block; }

.gen-search-clear:hover { color: #fff; background: #2a2a2a; }

.gen-search::placeholder { color: #555; }

/* Reserved slot too -- this one sits directly right of the input, so "1 match"
   -> "12 matches" -> "3 off-page" would otherwise shove the box on every keystroke. */
.gen-search-count {
    display: inline-block;
    min-width: 62px;       /* fits "12 matches" / "3 off-page" at 12px */
    margin-left: 8px;
    font-size: 12px;
    color: #888;
}

/* No visible hits. Orange rather than red: an off-page gen is a real answer
   (it's parked in Out of Service), not an error. */
.gen-search-count.none { color: #ff6600; }

/* Non-matching rows recede. .device-item already carries a 0.2s transition, so
   this fades rather than snaps. */
.device-item.gen-miss { opacity: 0.18; }

/* The hit. opacity is restated because a service-disabled row is held at 0.5 —
   a disabled gen is exactly the kind you go looking for, so it must not stay
   greyed while it is the match. */
.device-item.gen-hit {
    opacity: 1;
    background: #12240f;
    box-shadow: inset 3px 0 0 #00ff00;
}

.device-item.gen-hit:hover { background: #1b3316; }

/* ---------------------------------------------------------------------------
   Fixed slots for the header's variable-width readouts.

   The right-hand cluster is right-anchored, so ANY width change inside it moves
   everything to its left -- including the search box. Only the fields that change
   during NORMAL operation get a reserved slot; see the note below on why the status
   chips deliberately do not. The data rate is the one that mattered: it redraws
   every second and swings "-- KB/s" -> "3.1 KB/s" -> "48.7 KB/s", which is what
   made the box visibly jitter.

   min-width rather than width throughout: if one of these estimates is short the
   field just grows, which is the behaviour we already had. It can never clip.
   --------------------------------------------------------------------------- */
#dataRate {
    display: inline-block;
    min-width: 78px;       /* fits "1023.9 KB/s", the widest updateDataRate() emits */
    text-align: right;     /* digits grow leftward inside the slot, so the unit sits still */
}

/* Connection / Scanner / Watcher / Services deliberately get NO reserved slot.
   They are right-anchored like everything else here, so their degraded strings
   ("Scanner (3 dead)", "Reconnect 4/10") do widen the cluster and nudge the search
   box. Reserving for that worst case was tried and reverted: it left ~85px of dead
   air between every chip in the normal all-green state, which is what you look at
   99% of the time, to buy stillness during events you actively want to notice
   anyway. The data rate is the one that had to be fixed because it redraws every
   second; these change only on a real state transition. */
